124 lines
5.5 KiB
PHP
124 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Illuminate\Support\Carbon;
|
|
use Modules\EcommerceFinance\Repositories\JushuitanCostPriceHistoryMirrorRepository;
|
|
use Modules\EcommerceFinance\Services\JushuitanCostPriceHistorySynchronizer;
|
|
use Modules\EcommerceFinance\Services\JushuitanOpenApiClient;
|
|
use RuntimeException;
|
|
use Tests\TestCase;
|
|
|
|
class JushuitanCostPriceHistorySynchronizerTest extends TestCase
|
|
{
|
|
protected function tearDown(): void
|
|
{
|
|
Carbon::setTestNow();
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_incremental_sync_waits_for_completed_initial_sync(): void
|
|
{
|
|
$upstream = $this->createMock(JushuitanOpenApiClient::class);
|
|
$upstream->expects($this->never())->method('getCostPriceOperationHistory');
|
|
$mirror = $this->createMock(JushuitanCostPriceHistoryMirrorRepository::class);
|
|
$mirror->method('available')->willReturn(true);
|
|
$mirror->method('state')->willReturn(null);
|
|
|
|
$result = (new JushuitanCostPriceHistorySynchronizer($upstream, $mirror))->sync();
|
|
|
|
$this->assertSame('requires_full_sync', $result['status']);
|
|
}
|
|
|
|
public function test_full_sync_uses_stable_time_windows_and_page_cursor(): void
|
|
{
|
|
Carbon::setTestNow('2026-08-15 00:00:00');
|
|
config()->set('services.jushuitan.cost_price_history_start', '2026-08-01 00:00:00');
|
|
config()->set('services.jushuitan.cost_price_history_window_days', 30);
|
|
$responses = [[
|
|
'records' => [['operation_id' => '10', 'sku_id' => 'SKU-10']],
|
|
'source_rows' => 2,
|
|
'current_page' => 1,
|
|
'has_next' => true,
|
|
], [
|
|
'records' => [['operation_id' => '20', 'sku_id' => 'SKU-20']],
|
|
'source_rows' => 1,
|
|
'current_page' => 2,
|
|
'has_next' => false,
|
|
], [
|
|
'records' => [],
|
|
'source_rows' => 1,
|
|
'current_page' => 1,
|
|
'has_next' => false,
|
|
]];
|
|
$apiCalls = [];
|
|
$upstream = $this->createMock(JushuitanOpenApiClient::class);
|
|
$upstream->expects($this->exactly(3))->method('getCostPriceOperationHistory')
|
|
->willReturnCallback(function (Carbon $start, Carbon $end, int $page, int $pageSize) use (&$responses, &$apiCalls): array {
|
|
$apiCalls[] = [$start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'), $page, $pageSize];
|
|
|
|
return array_shift($responses);
|
|
});
|
|
|
|
$storedPages = [];
|
|
$mirror = $this->createMock(JushuitanCostPriceHistoryMirrorRepository::class);
|
|
$mirror->method('available')->willReturn(true);
|
|
$mirror->method('state')->willReturn(null);
|
|
$mirror->expects($this->once())->method('startRun')
|
|
->with('full', $this->isType('string'), $this->isInstanceOf(Carbon::class), $this->isInstanceOf(Carbon::class), false)
|
|
->willReturn([
|
|
'cursor_window_started_at' => '2026-08-01 00:00:00',
|
|
'cursor_page' => 1,
|
|
'last_processed_source_rows' => 0,
|
|
'last_upserted_rows' => 0,
|
|
]);
|
|
$mirror->expects($this->exactly(3))->method('storePage')
|
|
->willReturnCallback(function (...$arguments) use (&$storedPages): void {
|
|
$storedPages[] = $arguments;
|
|
});
|
|
$mirror->expects($this->once())->method('complete')
|
|
->with('full', $this->isType('string'), $this->isInstanceOf(Carbon::class), 4, 2);
|
|
|
|
$result = (new JushuitanCostPriceHistorySynchronizer($upstream, $mirror))->sync(true, 2000, 0);
|
|
|
|
$this->assertSame([
|
|
['2026-08-01 00:00:00', '2026-08-08 00:00:00', 1, 50],
|
|
['2026-08-01 00:00:00', '2026-08-08 00:00:00', 2, 50],
|
|
['2026-08-08 00:00:00', '2026-08-15 00:00:00', 1, 50],
|
|
], $apiCalls);
|
|
$this->assertSame('2026-08-01 00:00:00', $storedPages[0][1]->format('Y-m-d H:i:s'));
|
|
$this->assertSame(2, $storedPages[0][2]);
|
|
$this->assertSame('2026-08-08 00:00:00', $storedPages[1][1]->format('Y-m-d H:i:s'));
|
|
$this->assertSame(1, $storedPages[1][2]);
|
|
$this->assertSame('succeeded', $result['status']);
|
|
$this->assertSame(4, $result['source_rows']);
|
|
$this->assertSame(2, $result['rows']);
|
|
}
|
|
|
|
public function test_api_failure_marks_run_failed_without_completing_or_storing_a_page(): void
|
|
{
|
|
Carbon::setTestNow('2026-09-14 00:00:00');
|
|
$upstream = $this->createMock(JushuitanOpenApiClient::class);
|
|
$upstream->expects($this->once())->method('getCostPriceOperationHistory')
|
|
->willThrowException(new RuntimeException('upstream_unavailable'));
|
|
$mirror = $this->createMock(JushuitanCostPriceHistoryMirrorRepository::class);
|
|
$mirror->method('available')->willReturn(true);
|
|
$mirror->method('state')->willReturn([
|
|
'initial_completed_at' => '2026-09-13 00:00:00',
|
|
'last_succeeded_at' => '2026-09-13 00:00:00',
|
|
]);
|
|
$mirror->expects($this->once())->method('startRun')->willReturn([
|
|
'cursor_window_started_at' => '2026-09-11 00:00:00',
|
|
'cursor_page' => 1,
|
|
'last_processed_source_rows' => 0,
|
|
'last_upserted_rows' => 0,
|
|
]);
|
|
$mirror->expects($this->never())->method('storePage');
|
|
$mirror->expects($this->never())->method('complete');
|
|
$mirror->expects($this->once())->method('fail')->with('upstream_unavailable');
|
|
|
|
$this->expectExceptionMessage('upstream_unavailable');
|
|
(new JushuitanCostPriceHistorySynchronizer($upstream, $mirror))->sync();
|
|
}
|
|
}
|