set('database.connections.business_mirror', [ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', 'foreign_key_constraints' => true, ]); config()->set('upstream_mirrors.connection', 'business_mirror'); DB::purge('business_mirror'); $this->createTables(); $this->repository = app(JushuitanCostPriceHistoryMirrorRepository::class); } public function test_it_stores_and_formats_multiple_cost_price_operations_for_the_same_sku(): void { $generation = '11111111-1111-4111-8111-111111111111'; $this->repository->startRun( 'full', $generation, Carbon::parse('2026-06-01'), Carbon::parse('2026-07-01'), false, ); $this->repository->storePage([ [ 'operation_id' => '1', 'sku_id' => '15961g', 'operation_type' => '商品与库存管理导入', 'operation_description' => '采购价:72.2222->65', 'operator_name' => '简尚', 'operated_at' => '2026-06-06 14:32:02.993', 'before_price' => 72.2222, 'after_price' => 65, 'diff_amount' => -7.2222, ], [ 'operation_id' => '2', 'sku_id' => '15961G', 'operation_type' => '编辑', 'operation_description' => '采购价:65->63', 'operator_name' => '测试人', 'operated_at' => '2026-06-07 08:00:00', 'before_price' => 65, 'after_price' => 63, 'diff_amount' => -2, ], ], Carbon::parse('2026-07-01'), 1, $generation, 2, 2); $this->repository->complete('full', $generation, Carbon::parse('2026-07-01'), 2, 2); $records = $this->repository->historyForSkus(['15961G'])->get('15961G'); $history = $this->repository->formatHistory($records); $this->assertCount(2, $history); $this->assertSame(['2', '1'], array_column($history, 'id')); $this->assertSame('聚水潭商品及库存管理', $history[0]['source']); $this->assertSame('测试人', $history[0]['operator']); $this->assertSame('编辑', $history[0]['operation']); $this->assertSame('采购价:65->63', $history[0]['remark']); $this->assertSame('63', (string) $history[0]['after_price']); $this->assertTrue($this->repository->syncStatus()['initialized']); } public function test_incremental_empty_page_and_failed_run_keep_previous_history(): void { DB::connection('business_mirror')->table(JushuitanCostPriceHistoryMirrorRepository::TABLE)->insert([ 'operation_id' => 'existing', 'sku_id' => 'SKU-KEEP', 'sku_id_normalized' => 'SKU-KEEP', 'operation_description' => '采购价:20->18', 'operated_at' => '2026-06-01 00:00:00', 'before_price' => 20, 'after_price' => 18, 'diff_amount' => -2, '_mirror_generation' => 'old-generation', 'row_hash' => hash('sha256', 'existing'), '_mirror_synced_at' => '2026-06-01 00:00:00', ]); DB::connection('business_mirror')->table(JushuitanCostPriceHistoryMirrorRepository::STATE_TABLE)->insert([ 'dataset_key' => JushuitanCostPriceHistoryMirrorRepository::DATASET_KEY, 'status' => 'succeeded', 'run_mode' => 'incremental', 'generation' => '22222222-2222-4222-8222-222222222222', 'window_started_at' => '2026-06-01 00:00:00', 'window_ends_at' => '2026-06-03 00:00:00', 'initial_completed_at' => '2026-06-01 00:00:00', 'last_succeeded_at' => '2026-06-01 00:00:00', ]); $this->repository->storePage( [], Carbon::parse('2026-06-03'), 1, '22222222-2222-4222-8222-222222222222', 0, 0, ); $this->repository->fail('upstream_unavailable'); $this->assertSame(1, DB::connection('business_mirror')->table(JushuitanCostPriceHistoryMirrorRepository::TABLE)->count()); $this->assertSame('failed', $this->repository->state()['status']); } public function test_empty_full_sync_keeps_existing_audit_history(): void { DB::connection('business_mirror')->table(JushuitanCostPriceHistoryMirrorRepository::TABLE)->insert([ 'operation_id' => 'existing', 'sku_id' => 'SKU-KEEP', 'sku_id_normalized' => 'SKU-KEEP', 'operation_description' => '采购价:20->18', 'operated_at' => '2026-06-01 00:00:00', 'before_price' => 20, 'after_price' => 18, 'diff_amount' => -2, '_mirror_generation' => 'old-generation', 'row_hash' => hash('sha256', 'existing'), '_mirror_synced_at' => '2026-06-01 00:00:00', ]); $generation = '33333333-3333-4333-8333-333333333333'; $this->repository->startRun( 'full', $generation, Carbon::parse('2026-06-01'), Carbon::parse('2026-07-01'), false, ); $this->repository->complete( 'full', $generation, Carbon::parse('2026-07-01'), 0, 0, ); $this->assertSame(1, DB::connection('business_mirror')->table(JushuitanCostPriceHistoryMirrorRepository::TABLE)->count()); $this->assertSame('succeeded', $this->repository->state()['status']); } private function createTables(): void { Schema::connection('business_mirror')->create(JushuitanCostPriceHistoryMirrorRepository::STATE_TABLE, function (Blueprint $table): void { $table->string('dataset_key')->primary(); $table->string('status')->default('pending'); $table->string('run_mode')->nullable(); $table->string('generation')->nullable(); $table->dateTime('window_started_at')->nullable(); $table->dateTime('window_ends_at')->nullable(); $table->dateTime('cursor_window_started_at')->nullable(); $table->unsignedInteger('cursor_page')->nullable(); $table->dateTime('initial_completed_at')->nullable(); $table->dateTime('last_full_succeeded_at')->nullable(); $table->dateTime('last_attempted_at')->nullable(); $table->dateTime('last_succeeded_at')->nullable(); $table->unsignedBigInteger('last_processed_source_rows')->default(0); $table->unsignedBigInteger('last_upserted_rows')->default(0); $table->unsignedBigInteger('mirror_row_count')->default(0); $table->string('last_error_code')->nullable(); $table->timestamps(); }); Schema::connection('business_mirror')->create(JushuitanCostPriceHistoryMirrorRepository::TABLE, function (Blueprint $table): void { $table->string('operation_id')->primary(); $table->string('sku_id'); $table->string('sku_id_normalized'); $table->string('i_id')->nullable(); $table->string('product_name')->nullable(); $table->string('properties_value')->nullable(); $table->string('sku_type')->nullable(); $table->string('operation_type')->nullable(); $table->text('operation_description')->nullable(); $table->string('operator_name')->nullable(); $table->dateTime('operated_at')->nullable(); $table->decimal('before_price', 20, 4)->nullable(); $table->decimal('after_price', 20, 4)->nullable(); $table->decimal('diff_amount', 20, 4)->nullable(); $table->string('_mirror_generation')->nullable(); $table->string('row_hash'); $table->dateTime('_mirror_synced_at'); $table->timestamps(); }); } }