'sqlite','database.connections.sqlite.database'=>':memory:']);DB::purge('sqlite'); require_once base_path('cleanup-imported-supply.php'); $this->backupDirectory=sys_get_temp_dir().'/supply-cleanup-test-'.bin2hex(random_bytes(6)); Schema::create('supply_demands',function(Blueprint $t){$t->id();$t->integer('is_proxy');$t->string('import_key')->nullable();$t->string('content');$t->integer('user_id')->default(0);$t->integer('status')->default(1);$t->timestamps();$t->softDeletes();}); Schema::create('dialogues',function(Blueprint $t){$t->id();$t->integer('supply_demand_id');}); Schema::create('messages',function(Blueprint $t){$t->id();$t->integer('supply_demand_id');$t->integer('dialogue_id');$t->string('content');$t->softDeletes();}); foreach ([[1,1,'import-a',null],[2,1,'import-b','2026-10-20 00:00:00'],[3,1,null,null],[4,0,'self-published',null]] as [$id,$proxy,$key,$deleted]) DB::table('supply_demands')->insert(['id'=>$id,'is_proxy'=>$proxy,'import_key'=>$key,'content'=>'原始内容'.$id,'user_id'=>123,'deleted_at'=>$deleted]); } protected function tearDown(): void { if(is_dir($this->backupDirectory)){foreach(glob($this->backupDirectory.'/*') as $f)unlink($f);rmdir($this->backupDirectory);} DB::disconnect('sqlite');parent::tearDown(); } public function test_check_is_read_only_and_execute_backs_up_before_clearing_only_imports() { $before=DB::table('supply_demands')->orderBy('id')->get(); $check=\ImportedSupplyCleanup::run(false,$this->backupDirectory); $this->assertSame(2,$check['imported_demands']);$this->assertSame(0,$check['cleaned']);$this->assertFalse(is_dir($this->backupDirectory)); $this->assertEquals($before,DB::table('supply_demands')->orderBy('id')->get()); $done=\ImportedSupplyCleanup::run(true,$this->backupDirectory); $this->assertSame(2,$done['cleaned']);$this->assertSame(hash_file('sha256',$done['backup']['file']),$done['backup']['sha256']); $backup=json_decode(file_get_contents($done['backup']['file']),true); $this->assertSame('import-a',$backup['supply_demands'][0]['import_key']);$this->assertNull($backup['supply_demands'][0]['deleted_at']); $this->assertSame(0600,fileperms($done['backup']['file']) & 0777); $this->assertSame(0,DB::table('supply_demands')->whereIn('id',[1,2])->whereNotNull('import_key')->count()); $this->assertSame(2,DB::table('supply_demands')->whereIn('id',[1,2])->whereNotNull('deleted_at')->count()); $this->assertSame('2026-10-20 00:00:00',DB::table('supply_demands')->where('id',2)->value('deleted_at')); foreach([3,4] as $id)$this->assertEquals($before->firstWhere('id',$id),DB::table('supply_demands')->where('id',$id)->first()); $this->assertSame(123,DB::table('supply_demands')->where('id',1)->value('user_id')); $this->assertSame(0,\ImportedSupplyCleanup::run(true,$this->backupDirectory)['cleaned']); } public function test_any_private_message_including_deleted_or_dialogue_only_stops_cleanup_after_backup() { DB::table('dialogues')->insert(['id'=>10,'supply_demand_id'=>1]); DB::table('messages')->insert(['id'=>1,'supply_demand_id'=>0,'dialogue_id'=>10,'content'=>'历史消息','deleted_at'=>'2026-10-20 00:00:00']); $before=DB::table('supply_demands')->get(); $report=\ImportedSupplyCleanup::run(true,$this->backupDirectory); $this->assertSame(1,$report['messages']);$this->assertSame(0,$report['cleaned']); $this->assertFileExists($report['backup']['file']); $backup=json_decode(file_get_contents($report['backup']['file']),true); $this->assertSame('历史消息',$backup['messages'][0]['content']); $this->assertEquals($before,DB::table('supply_demands')->get()); $this->assertSame(1,DB::table('messages')->count());$this->assertSame(1,DB::table('dialogues')->count()); } public function test_backup_failure_prevents_business_changes() { $before=DB::table('supply_demands')->get(); try { \ImportedSupplyCleanup::run(true,'/dev/null/unwritable');$this->fail('Expected backup failure'); } catch(\Throwable $e){$this->assertStringNotContainsString('Expected backup failure',$e->getMessage());} $this->assertEquals($before,DB::table('supply_demands')->get()); } }