true, 'upstream_mirrors.product_purchase_prices' => [ 'batch_size' => 1000, 'max_batches_per_run' => 5, 'full_reconcile_hours' => 168, 'reconcile_weekday' => 0, 'reconcile_start_hour' => 2, 'reconcile_window_hours' => 6, ], ]); } protected function tearDown(): void { Carbon::setTestNow(); parent::tearDown(); } public function test_it_runs_incremental_sync_after_initialization(): void { Carbon::setTestNow('2026-08-21 10:00:00'); $mirror = Mockery::mock(ProductPurchasePriceMirrorRepository::class); $mirror->shouldReceive('state')->once()->andReturn([ 'status' => 'succeeded', 'run_mode' => 'incremental', 'initial_completed_at' => '2026-08-14 10:00:00', 'last_full_succeeded_at' => '2026-08-17 02:00:00', 'projection_version' => ProductPurchasePriceMirrorRepository::CURRENT_PROJECTION_VERSION, ]); $synchronizer = Mockery::mock(ProductPurchasePriceMirrorSynchronizer::class); $synchronizer->shouldReceive('sync') ->once() ->with(false, null, null) ->andReturn([ 'status' => 'succeeded', 'mode' => 'incremental', 'batches' => 1, 'rows' => 12, ]); $this->app->instance(ProductPurchasePriceMirrorRepository::class, $mirror); $this->app->instance(ProductPurchasePriceMirrorSynchronizer::class, $synchronizer); $this->artisan('business-mirror:sync-product-purchase-prices') ->expectsOutputToContain('采购单价增量同步:完成') ->assertSuccessful(); } public function test_it_does_not_start_the_first_full_sync_automatically(): void { Carbon::setTestNow('2026-08-23 02:30:00'); $mirror = Mockery::mock(ProductPurchasePriceMirrorRepository::class); $mirror->shouldReceive('state')->once()->andReturn(null); $synchronizer = Mockery::mock(ProductPurchasePriceMirrorSynchronizer::class); $synchronizer->shouldReceive('sync') ->once() ->with(false, null, null) ->andReturn([ 'status' => 'requires_full_sync', 'mode' => 'incremental', 'batches' => 0, 'rows' => 0, ]); $this->app->instance(ProductPurchasePriceMirrorRepository::class, $mirror); $this->app->instance(ProductPurchasePriceMirrorSynchronizer::class, $synchronizer); $this->artisan('business-mirror:sync-product-purchase-prices') ->expectsOutputToContain('完成备份后显式使用 --full') ->assertSuccessful(); } }