true]); $registry = Mockery::mock(BusinessMirrorDatasetRegistry::class); $sync = Mockery::mock(BusinessMirrorSynchronizer::class); foreach (self::DATASETS as $key) { $registry->shouldReceive('scheduled')->once()->with($key)->andReturn(true); $sync->shouldReceive('sync')->once()->with($key, true, null, 0)->ordered() ->andReturn(['status' => 'succeeded', 'batches' => 3, 'rows' => 1200]); } $this->app->instance(BusinessMirrorDatasetRegistry::class, $registry); $this->app->instance(BusinessMirrorSynchronizer::class, $sync); $this->artisan('business-mirror:sync-supplier-management')->assertSuccessful(); } public function test_one_failure_does_not_prevent_other_supplier_data_from_refreshing(): void { config(['upstream_mirrors.refresh_enabled' => true]); $registry = Mockery::mock(BusinessMirrorDatasetRegistry::class); $sync = Mockery::mock(BusinessMirrorSynchronizer::class); foreach (self::DATASETS as $index => $key) { $registry->shouldReceive('scheduled')->once()->with($key)->andReturn(true); $expectation = $sync->shouldReceive('sync')->once()->with($key, true, null, 0)->ordered(); if ($index === 0) $expectation->andThrow(new RuntimeException('upstream unavailable')); else $expectation->andReturn(['status' => 'succeeded', 'batches' => 1, 'rows' => 5]); } $this->app->instance(BusinessMirrorDatasetRegistry::class, $registry); $this->app->instance(BusinessMirrorSynchronizer::class, $sync); $this->artisan('business-mirror:sync-supplier-management') ->expectsOutputToContain('同步失败')->assertExitCode(1); } public function test_local_refresh_disabled_never_reads_upstream(): void { config(['upstream_mirrors.refresh_enabled' => false]); $registry = Mockery::mock(BusinessMirrorDatasetRegistry::class); $registry->shouldNotReceive('scheduled'); $sync = Mockery::mock(BusinessMirrorSynchronizer::class); $sync->shouldNotReceive('sync'); $this->app->instance(BusinessMirrorDatasetRegistry::class, $registry); $this->app->instance(BusinessMirrorSynchronizer::class, $sync); $this->artisan('business-mirror:sync-supplier-management')->assertSuccessful(); } public function test_disabled_supplier_datasets_are_not_synced(): void { config(['upstream_mirrors.refresh_enabled' => true]); $registry = Mockery::mock(BusinessMirrorDatasetRegistry::class); $registry->shouldReceive('scheduled')->times(4)->andReturn(false); $sync = Mockery::mock(BusinessMirrorSynchronizer::class); $sync->shouldNotReceive('sync'); $this->app->instance(BusinessMirrorDatasetRegistry::class, $registry); $this->app->instance(BusinessMirrorSynchronizer::class, $sync); $this->artisan('business-mirror:sync-supplier-management')->assertSuccessful(); } public function test_schedule_runs_at_0815_shanghai_even_when_app_timezone_is_utc(): void { config(['app.installed' => true, 'app.schedule_time' => '09:00', 'app.timezone' => 'UTC']); $schedule = new Schedule('UTC'); (new \ReflectionMethod(\App\Console\Kernel::class, 'schedule'))->invoke($this->app->make(Kernel::class), $schedule); $events = collect($schedule->events()) ->filter(fn ($event) => str_contains($event->command ?? '', 'business-mirror:sync-supplier-management')); $this->assertCount(1, $events); $event = $events->first(); try { foreach (['00:14' => false, '00:15' => true, '00:16' => false] as $time => $due) { Carbon::setTestNow(Carbon::parse('2026-10-12 ' . $time . ':00', 'UTC')); $this->assertSame($due, $event->isDue($this->app), $time); } } finally { Carbon::setTestNow(); } } }