114 lines
6.4 KiB
PHP
114 lines
6.4 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\EcommerceFinance\Repositories\GenericBusinessMirrorRepository;
|
|
use Modules\EcommerceFinance\Repositories\JushuitanCostPriceObservationRepository;
|
|
use Modules\EcommerceFinance\Repositories\ProductCatalogMirrorRepository;
|
|
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
|
|
use Modules\EcommerceFinance\Services\JushuitanCostPriceSnapshotDiffer as Differ;
|
|
use Tests\TestCase;
|
|
|
|
class JushuitanCostPriceSnapshotDifferTest extends TestCase
|
|
{
|
|
private Differ $differ;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
config(['database.connections.business_mirror' => ['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => ''],
|
|
'upstream_mirrors.connection' => 'business_mirror', 'cache.default' => 'array']);
|
|
DB::purge('business_mirror');
|
|
Carbon::setTestNow('2026-10-20 12:00:00');
|
|
$schema = Schema::connection('business_mirror');
|
|
$schema->create(Differ::STATES, function (Blueprint $t): void {
|
|
$t->string('dataset_key')->primary();
|
|
foreach (['status', 'source_key', 'source_object', 'target_table', 'run_mode', 'generation', 'last_error_code'] as $c) $t->string($c)->nullable();
|
|
foreach (['cursor', 'high_watermark'] as $c) $t->json($c)->nullable();
|
|
foreach (['window_started_at', 'window_ends_at', 'initial_completed_at', 'last_attempted_at', 'last_succeeded_at'] as $c) $t->dateTime($c, 6)->nullable();
|
|
$t->timestamps();
|
|
});
|
|
$schema->create('ops_jst_sku', function (Blueprint $t): void {
|
|
$t->char('_mirror_key', 64)->primary(); $t->string('sku_type'); $t->string('sku_id');
|
|
$t->decimal('cost_price', 20, 4)->nullable(); $t->dateTime('modified')->nullable(); $t->dateTime('_mirror_synced_at', 6);
|
|
});
|
|
$schema->create(Differ::SNAPSHOTS, function (Blueprint $t): void {
|
|
$t->char('source_key', 64)->primary(); $t->string('sku_id'); $t->decimal('cost_price', 20, 4); $t->dateTime('captured_at', 6);
|
|
});
|
|
$schema->create(Differ::OBSERVATIONS, function (Blueprint $t): void {
|
|
$t->char('id', 64)->primary(); $t->char('source_key', 64); $t->string('sku_id');
|
|
$t->decimal('before_price', 20, 4); $t->decimal('after_price', 20, 4);
|
|
$t->dateTime('previous_observed_at', 6); $t->dateTime('observed_at', 6); $t->dateTime('source_modified_at', 6)->nullable();
|
|
});
|
|
DB::connection('business_mirror')->table(Differ::STATES)->insert([
|
|
'dataset_key' => ProductCatalogMirrorRepository::DATASET_KEY, 'status' => 'succeeded',
|
|
'initial_completed_at' => '2026-10-19 00:00:00', 'last_succeeded_at' => '2026-10-20 11:59:00',
|
|
]);
|
|
$this->differ = new Differ(new GenericBusinessMirrorRepository(app('db')), new BusinessMirrorDatasetRegistry());
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Carbon::setTestNow();
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_baseline_is_resumable_and_does_not_invent_history_then_observes_at_capture_time(): void
|
|
{
|
|
$db = DB::connection('business_mirror');
|
|
$this->product('a', 'SKU-A', 12);
|
|
$this->product('b', 'SKU-B', 20);
|
|
$this->assertSame('running', $this->differ->sync(1, 1)['status']);
|
|
$db->table(Differ::STATES)->where('dataset_key', Differ::DATASET_KEY)->update(['status' => 'failed']);
|
|
$this->assertSame('succeeded', $this->differ->sync(1, 3)['status']);
|
|
$this->assertSame(2, $db->table(Differ::SNAPSHOTS)->count());
|
|
$this->assertSame(0, $db->table(Differ::OBSERVATIONS)->count());
|
|
|
|
Carbon::setTestNow('2026-10-20 12:10:00');
|
|
$db->table('ops_jst_sku')->where('_mirror_key', 'a')->update(['cost_price' => 15.1234, '_mirror_synced_at' => '2026-10-20 12:09:00']);
|
|
$result = $this->differ->sync(10, 2);
|
|
$this->assertSame(1, $result['changes']);
|
|
$event = $db->table(Differ::OBSERVATIONS)->first();
|
|
$this->assertSame('2026-10-20 12:10:00.000000', $event->observed_at);
|
|
$this->assertSame('2026-01-01 00:00:00', $event->source_modified_at);
|
|
$this->assertEquals(12, $event->before_price);
|
|
$this->assertEquals(15.1234, $event->after_price);
|
|
$this->assertSame(0, $this->differ->sync(10, 2)['changes']);
|
|
$this->assertSame(1, $db->table(Differ::OBSERVATIONS)->count());
|
|
$this->assertFalse(Schema::connection('business_mirror')->hasTable('jushuitan_cost_price_history_rows'));
|
|
$this->assertFalse(Schema::connection('business_mirror')->hasTable('jushuitan_cost_price_history_sync_states'));
|
|
|
|
$rows = [(object) ['_mirror_key' => 'a'], (object) ['_mirror_key' => 'b']];
|
|
(new JushuitanCostPriceObservationRepository(new GenericBusinessMirrorRepository(app('db'))))->attach($rows);
|
|
$this->assertCount(1, $rows[0]->jushuitan_cost_price_observations);
|
|
$this->assertCount(0, $rows[1]->jushuitan_cost_price_observations);
|
|
$this->assertStringContainsString('不是真实改价时间', $rows[0]->jushuitan_cost_price_observations[0]['remark']);
|
|
}
|
|
|
|
public function test_new_products_null_prices_and_same_sku_on_different_source_rows_do_not_invent_changes(): void
|
|
{
|
|
$this->product('a', 'SAME', 12);
|
|
$this->product('b', 'SAME', 25);
|
|
$this->product('c', 'NULL', null);
|
|
$this->differ->sync();
|
|
Carbon::setTestNow('2026-10-20 12:10:00');
|
|
$this->product('d', 'NEW', 99, '2026-10-20 12:09:00');
|
|
$this->assertSame(0, $this->differ->sync()['changes']);
|
|
$this->assertSame(3, DB::connection('business_mirror')->table(Differ::SNAPSHOTS)->count());
|
|
DB::connection('business_mirror')->table(Differ::STATES)->where('dataset_key', ProductCatalogMirrorRepository::DATASET_KEY)->update(['status' => 'running']);
|
|
$this->assertSame('source_not_ready', $this->differ->sync()['status']);
|
|
}
|
|
|
|
private function product(string $key, string $sku, ?float $price, string $at = '2026-10-20 11:50:00'): void
|
|
{
|
|
DB::connection('business_mirror')->table('ops_jst_sku')->insert([
|
|
'_mirror_key' => $key, 'sku_id' => $sku, 'sku_type' => 'normal', 'cost_price' => $price,
|
|
'modified' => '2026-01-01 00:00:00', '_mirror_synced_at' => $at,
|
|
]);
|
|
}
|
|
}
|