createMock(ProductPriceRepository::class); $upstream->expects($this->never())->method('mirrorBatch'); $mirror = $this->createMock(ProductPriceMirrorRepository::class); $mirror->method('ready')->willReturn(true); $mirror->method('state')->willReturn(null); $result = (new ProductPriceMirrorSynchronizer($upstream, $mirror))->sync(); $this->assertSame('requires_full_sync', $result['status']); } public function test_full_sync_upserts_batches_and_marks_initial_sync_complete(): void { Carbon::setTestNow('2026-08-14 15:00:00'); $rows = [[ 'business_id' => 'B1', 'barcode' => 'SKU1', 'cursor_completed_at' => '2026-08-01T10:00:00.1234567', 'completed_at' => '2026-08-01 10:00:00', ], [ 'business_id' => 'B2', 'barcode' => 'SKU2', 'cursor_completed_at' => '2026-08-01T11:00:00.7654321', 'completed_at' => '2026-08-01 11:00:00', ]]; $upstream = $this->createMock(ProductPriceRepository::class); $upstream->expects($this->exactly(2))->method('mirrorBatch') ->willReturnOnConsecutiveCalls($rows, []); $mirror = $this->createMock(ProductPriceMirrorRepository::class); $mirror->method('ready')->willReturn(true); $mirror->method('state')->willReturn(null); $mirror->expects($this->once())->method('startRun') ->with('full', $this->isInstanceOf(Carbon::class), $this->isInstanceOf(Carbon::class), false) ->willReturn(['last_upserted_rows' => 0]); $mirror->expects($this->once())->method('storeBatch') ->with($rows, [ 'completed_at' => '2026-08-01T11:00:00.7654321', 'business_id' => 'B2', 'barcode' => 'SKU2', ], 2); $mirror->expects($this->once())->method('complete') ->with('full', $this->isInstanceOf(Carbon::class), 2); $result = (new ProductPriceMirrorSynchronizer($upstream, $mirror))->sync(true, 2, 0); $this->assertSame([ 'status' => 'succeeded', 'mode' => 'full', 'batches' => 1, 'rows' => 2, ], $result); } }