CaiWuXiTong/tests/Feature/Console/SupplierBookkeepingShippingImporterTest.php

113 lines
6.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Tests\Feature\Console;
use Illuminate\Support\Facades\DB;
use Modules\EcommerceFinance\Services\SupplierBookkeepingShippingImporter as Importer;
use Tests\TestCase;
class SupplierBookkeepingShippingImporterTest extends TestCase
{
private array $files = [];
protected function setUp(): void
{
parent::setUp();
if (!\Illuminate\Support\Facades\Schema::connection('business_mirror')->hasTable(Importer::MONTHS)) {
(require base_path('database/migrations/2026_11_07_100000_create_supplier_bookkeeping_shipping_mirror.php'))->up();
}
}
protected function tearDown(): void
{
foreach ($this->files as $file) @unlink($file);
parent::tearDown();
}
private function input(int $count = 2, array $patch = []): array
{
$file = tempnam(sys_get_temp_dir(), 'sb-shipping-');
$manifest = $file . '.json';
$this->files = array_merge($this->files, [$file, $manifest]);
$out = fopen('compress.zlib://' . $file, 'wb');
fputcsv($out, Importer::HEADERS);
for ($i = 2; $i < $count+2; $i++) {
fputcsv($out, [$i, 'EXP-'.$i, 'OLD-'.$i, 'ORDER-'.$i.',MERGED-'.$i, '', 'SKU-A', '2', $i === 2 ? '' : '8.50', '2026/10/1 1:02:03', '', '']);
}
fclose($out);
$meta = array_replace(['schema_version' => 1, 'source_month' => '2026-10', 'source_filename' => 'source.xlsx',
'source_sha256' => str_repeat('a',64), 'data_sha256' => hash_file('sha256', $file), 'row_count' => $count, 'freight_column' => '运费'], $patch);
file_put_contents($manifest, json_encode($meta, JSON_THROW_ON_ERROR));
return [$file, $manifest];
}
public function test_streams_exact_references_preserves_missing_freight_and_is_idempotent(): void
{
[$file, $manifest] = $this->input();
$importer = app(Importer::class);
$this->assertSame('validated', $importer->import($file,$manifest,true)['status']);
$db = DB::connection('business_mirror');
$this->assertSame(0, $db->table(Importer::MONTHS)->count());
$this->assertSame('succeeded', $importer->import($file,$manifest)['status']);
$this->assertSame(2, $db->table(Importer::ROWS)->count());
$this->assertSame(8, $db->table(Importer::REFS)->count());
$this->assertSame('', $db->table(Importer::ROWS)->where('source_row',2)->value('freight'));
$this->assertSame('2026-10-01 01:02:03', $db->table(Importer::ROWS)->where('source_row',2)->value('ship_time'));
$this->assertSame('unchanged', $importer->import($file,$manifest)['status']);
$this->assertSame(2, $db->table(Importer::ROWS)->count());
}
public function test_failed_replacement_retains_active_generation_and_resumes_last_committed_batch(): void
{
$importer = app(Importer::class);
$importer->import(...$this->input());
$db = DB::connection('business_mirror');
$active = $db->table(Importer::MONTHS)->value('active_generation');
[$file,$manifest] = $this->input(501);
$inserts = 0; $interrupted = false;
$db->listen(function ($event) use (&$inserts, &$interrupted): void {
if (str_starts_with($event->sql, 'insert into ') && str_contains($event->sql, Importer::ROWS) && ++$inserts === 2 && !$interrupted) {
$interrupted = true;
throw new \RuntimeException('simulated interruption');
}
});
try { $importer->import($file,$manifest); $this->fail('Expected interruption'); }
catch (\RuntimeException $e) { $this->assertSame('simulated interruption', $e->getMessage()); }
$state = $db->table(Importer::MONTHS)->first();
$this->assertSame($active, $state->active_generation);
$this->assertSame(500, (int) $state->checkpoint);
$result = $importer->import($file,$manifest);
$this->assertSame(500, $result['resumed_from']);
$state = $db->table(Importer::MONTHS)->first();
$this->assertNotSame($active, $state->active_generation);
$this->assertSame(501, $db->table(Importer::ROWS)->where('generation',$state->active_generation)->count());
}
public function test_indexes_clean_references_while_retaining_original_source_values(): void
{
[$file, $manifest] = $this->input(1);
$out = fopen('compress.zlib://' . $file, 'wb');
fputcsv($out, Importer::HEADERS);
fputcsv($out, [2, " '@0001234567890123456789 ", '@OLD-1’', " '@ORDER-1,@ORDER-2’", '', 'SKU-A+B', '2', '8.50', '2026/10/1 1:02:03', '', '']);
fclose($out);
$meta = json_decode(file_get_contents($manifest), true, 512, JSON_THROW_ON_ERROR);
$meta['data_sha256'] = hash_file('sha256', $file);
file_put_contents($manifest, json_encode($meta, JSON_THROW_ON_ERROR));
$this->assertSame('succeeded', app(Importer::class)->import($file, $manifest)['status']);
$db = DB::connection('business_mirror');
$this->assertSame(['0001234567890123456789', 'OLD-1'], $db->table(Importer::REFS)->where('kind', 'tracking')->orderBy('reference')->pluck('reference')->all());
$this->assertSame(['ORDER-1', 'ORDER-2'], $db->table(Importer::REFS)->where('kind', 'order')->orderBy('reference')->pluck('reference')->all());
$this->assertSame(" '@0001234567890123456789 ", $db->table(Importer::ROWS)->value('express_no'));
$this->assertSame('SKU-A+B', $db->table(Importer::ROWS)->value('barcode'));
}
public function test_rejects_wrong_column_hash_month_and_incomplete_row_count(): void
{
foreach ([['freight_column' => '结算运费'], ['data_sha256' => str_repeat('b',64)], ['source_month' => '2026-09'], ['row_count' => 3]] as $patch) {
try { app(Importer::class)->import(...$this->input(2,$patch)); $this->fail('Expected invalid source'); }
catch (\RuntimeException $e) { $this->assertNotSame('', $e->getMessage()); }
}
$this->assertNull(DB::connection('business_mirror')->table(Importer::MONTHS)->value('active_generation'));
}
}