197 lines
9.0 KiB
PHP
197 lines
9.0 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Modules\EcommerceFinance\Repositories\ThirdPartyWarehouseStorageUpstreamRepository;
|
|
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
|
|
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class ThirdPartyWarehouseStorageUpstreamRepositoryTest extends TestCase
|
|
{
|
|
public function test_header_queries_cover_all_warehouses_with_a_bounded_id_window(): void
|
|
{
|
|
$query = new RecordingThirdPartyWarehouseQuery();
|
|
$query->scalarResult = 987654;
|
|
$repository = $this->repository($query);
|
|
|
|
$this->assertSame(987654, $repository->headerHighWatermark());
|
|
$repository->headerRange(100, 200);
|
|
|
|
$this->assertStringContainsString('MAX([Id])', $query->scalarCalls[0]['sql']);
|
|
$this->assertStringContainsString('[dbo].[jst_inoutWater]', $query->scalarCalls[0]['sql']);
|
|
$this->assertStringNotContainsString('[wms_co_id] = ?', $query->scalarCalls[0]['sql']);
|
|
$this->assertSame([], $query->scalarCalls[0]['bindings']);
|
|
$this->assertStringContainsString('[Id] > ?', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('[Id] <= ?', $query->selectCalls[0]['sql']);
|
|
$this->assertStringNotContainsString('[wms_co_id] = ?', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('h.[wh_id]', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('[io_date]', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('AS occurred_at', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('NOT EXISTS', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('newer.[io_id] = h.[io_id]', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('newer.[Id] > h.[Id]', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('ORDER BY h.[Id]', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('h.[io_date] >= ?', $query->selectCalls[0]['sql']);
|
|
$this->assertSame([100, 200, '2026-07-01'], $query->selectCalls[0]['bindings']);
|
|
}
|
|
|
|
public function test_items_are_parameterized_and_split_into_bounded_io_id_batches(): void
|
|
{
|
|
$query = new RecordingThirdPartyWarehouseQuery();
|
|
$query->selectResults = [[
|
|
['source_id' => 2, 'io_id' => 2],
|
|
], [
|
|
['source_id' => 1, 'io_id' => 501],
|
|
]];
|
|
$repository = $this->repository($query);
|
|
|
|
$rows = $repository->itemsForIoIds(range(1, 501));
|
|
|
|
$this->assertCount(2, $query->selectCalls);
|
|
$this->assertCount(500, $query->selectCalls[0]['bindings']);
|
|
$this->assertCount(1, $query->selectCalls[1]['bindings']);
|
|
$this->assertStringContainsString('[dbo].[jst_inoutWaterItem]', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('[qty_type]', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('[io_id] IN (', $query->selectCalls[0]['sql']);
|
|
$this->assertStringNotContainsString('[io_id] IN (1,2', $query->selectCalls[0]['sql']);
|
|
$this->assertSame([1, 2], array_column($rows, 'source_id'));
|
|
}
|
|
|
|
public function test_incremental_headers_use_a_bounded_modified_id_cursor_without_a_warehouse_filter(): void
|
|
{
|
|
$query = new RecordingThirdPartyWarehouseQuery();
|
|
$repository = $this->repository($query);
|
|
|
|
$repository->modifiedHeadersBatch(
|
|
'2026-09-09 03:20:00.000',
|
|
123,
|
|
'2026-09-11 03:20:00.000',
|
|
99999,
|
|
);
|
|
|
|
$call = $query->selectCalls[0];
|
|
$this->assertStringContainsString('SELECT TOP (5000)', $call['sql']);
|
|
$this->assertStringContainsString('[modified] >= ?', $call['sql']);
|
|
$this->assertStringContainsString('[modified] < ?', $call['sql']);
|
|
$this->assertStringContainsString('[modified] = ? AND [Id] > ?', $call['sql']);
|
|
$this->assertStringContainsString('ORDER BY [modified], [Id]', $call['sql']);
|
|
$this->assertStringNotContainsString('[wms_co_id] = ?', $call['sql']);
|
|
$this->assertSame([
|
|
'2026-09-09 03:20:00.000',
|
|
'2026-09-11 03:20:00.000',
|
|
'2026-09-09 03:20:00.000',
|
|
'2026-09-09 03:20:00.000',
|
|
123,
|
|
], $call['bindings']);
|
|
}
|
|
|
|
public function test_changed_documents_reload_only_the_latest_header_in_bounded_batches(): void
|
|
{
|
|
$query = new RecordingThirdPartyWarehouseQuery();
|
|
$query->selectResults = [[
|
|
['source_id' => 2, 'io_id' => 2],
|
|
], [
|
|
['source_id' => 1, 'io_id' => 501],
|
|
]];
|
|
$repository = $this->repository($query);
|
|
|
|
$rows = $repository->latestHeadersForIoIds(range(1, 501), '2026-09-11 03:20:00.000');
|
|
|
|
$this->assertCount(2, $query->selectCalls);
|
|
$this->assertCount(502, $query->selectCalls[0]['bindings']);
|
|
$this->assertCount(3, $query->selectCalls[1]['bindings']);
|
|
$this->assertStringContainsString('MAX([Id]) AS latest_id', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('[io_id] IN (', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('[modified] < ?', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('ON latest.[latest_id] = h.[Id] WHERE h.[io_date] >= ?', $query->selectCalls[0]['sql']);
|
|
$this->assertStringNotContainsString('[modified] < ? AND [io_date] >= ?', $query->selectCalls[0]['sql']);
|
|
$this->assertStringNotContainsString('[wms_co_id] = ?', $query->selectCalls[0]['sql']);
|
|
$this->assertSame('2026-09-11 03:20:00.000', $query->selectCalls[0]['bindings'][500]);
|
|
$this->assertSame('2026-07-01', $query->selectCalls[0]['bindings'][501]);
|
|
$this->assertSame([1, 2], array_column($rows, 'source_id'));
|
|
|
|
$repository->latestHeadersForIoIds([], '2026-09-11 03:20:00.000');
|
|
$this->assertCount(2, $query->selectCalls);
|
|
}
|
|
|
|
public function test_inventory_uses_all_warehouse_keyset_pagination_and_a_bounded_limit(): void
|
|
{
|
|
$query = new RecordingThirdPartyWarehouseQuery();
|
|
$query->scalarResult = 191818;
|
|
$repository = $this->repository($query);
|
|
|
|
$this->assertSame(191818, $repository->inventoryHighWatermark());
|
|
$repository->inventoryBatch(12350, 191818, 99999);
|
|
|
|
$this->assertStringContainsString('[dbo].[jst_inventoryInfo]', $query->scalarCalls[0]['sql']);
|
|
$this->assertStringNotContainsString('[wms_co_id] = ?', $query->scalarCalls[0]['sql']);
|
|
$this->assertSame([], $query->scalarCalls[0]['bindings']);
|
|
$this->assertStringContainsString('SELECT TOP (5000)', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('[dbo].[jst_inventoryInfo]', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('[Id] > ?', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('[Id] <= ?', $query->selectCalls[0]['sql']);
|
|
$this->assertStringNotContainsString('[wms_co_id] = ?', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('ORDER BY [Id]', $query->selectCalls[0]['sql']);
|
|
$this->assertStringContainsString('AS source_modified_at', $query->selectCalls[0]['sql']);
|
|
foreach (['return_qty', 'in_qty', 'defective_qty', 'customize_qty_1', 'customize_qty_2', 'customize_qty_3'] as $column) {
|
|
$this->assertStringContainsString("[{$column}]", $query->selectCalls[0]['sql']);
|
|
}
|
|
$this->assertSame([12350, 191818], $query->selectCalls[0]['bindings']);
|
|
|
|
$repository->inventoryBatch(191818, 191818, 2000);
|
|
$this->assertCount(1, $query->selectCalls);
|
|
}
|
|
|
|
private function repository(UpstreamReadOnlyQuery $query): ThirdPartyWarehouseStorageUpstreamRepository
|
|
{
|
|
$factory = new class($query) extends UpstreamReadOnlyQueryFactory {
|
|
public function __construct(private readonly UpstreamReadOnlyQuery $query)
|
|
{
|
|
}
|
|
|
|
public function forSource(string $source): UpstreamReadOnlyQuery
|
|
{
|
|
TestCase::assertSame('jiansun', $source);
|
|
|
|
return $this->query;
|
|
}
|
|
};
|
|
|
|
return new ThirdPartyWarehouseStorageUpstreamRepository($factory);
|
|
}
|
|
}
|
|
|
|
class RecordingThirdPartyWarehouseQuery extends UpstreamReadOnlyQuery
|
|
{
|
|
public mixed $scalarResult = 0;
|
|
|
|
/** @var list<list<array<string,mixed>>> */
|
|
public array $selectResults = [];
|
|
|
|
/** @var list<array{sql:string,bindings:array}> */
|
|
public array $selectCalls = [];
|
|
|
|
/** @var list<array{sql:string,bindings:array}> */
|
|
public array $scalarCalls = [];
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct('jiansun');
|
|
}
|
|
|
|
public function select(string $sql, array $bindings = []): array
|
|
{
|
|
$this->selectCalls[] = compact('sql', 'bindings');
|
|
|
|
return array_shift($this->selectResults) ?? [];
|
|
}
|
|
|
|
public function scalar(string $sql, array $bindings = []): mixed
|
|
{
|
|
$this->scalarCalls[] = compact('sql', 'bindings');
|
|
|
|
return $this->scalarResult;
|
|
}
|
|
}
|