113 lines
6.0 KiB
PHP
113 lines
6.0 KiB
PHP
<?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'));
|
||
}
|
||
}
|