CaiWuXiTong/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php

221 lines
11 KiB
PHP

<?php
namespace Tests\Unit\EcommerceFinance;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Modules\EcommerceFinance\Repositories\GenericBusinessMirrorRepository;
use Modules\EcommerceFinance\Repositories\StoreReceivingAlipayBillMirrorRepository;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use RuntimeException;
use Tests\TestCase;
class StoreReceivingAlipayBillMirrorRepositoryTest extends TestCase
{
private StoreReceivingAlipayBillMirrorRepository $repository;
protected function setUp(): void
{
parent::setUp();
config([
'database.connections.business_mirror' => [
'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '',
],
'upstream_mirrors.connection' => 'business_mirror',
'upstream_mirrors.datasets' => [
StoreReceivingAlipayBillMirrorRepository::DATASET => [
'source_key' => 'operations_replica', 'schema' => 'rpt',
'object' => 'jst_originalBillInfo', 'target' => 'ops_jst_original_bill_info',
'keys' => ['Id'],
],
StoreReceivingAlipayBillMirrorRepository::SHOP_DATASET => [
'source_key' => 'operations_replica', 'schema' => 'rpt',
'object' => 'jst_shopInfo', 'target' => 'ops_jst_shop_info', 'keys' => ['Id'],
],
],
]);
DB::purge('business_mirror');
$schema = Schema::connection('business_mirror');
$schema->create('business_mirror_table_states', function (Blueprint $table): void {
$table->string('dataset_key')->primary();
$table->dateTime('initial_completed_at')->nullable();
$table->string('status')->nullable();
$table->dateTime('last_succeeded_at')->nullable();
$table->unsignedBigInteger('mirror_row_count')->nullable();
});
$schema->create('ops_jst_original_bill_info', function (Blueprint $table): void {
$table->integer('Id');
$table->integer('shop_id')->nullable();
foreach (['channel', 'primary_key', 'fee_name', 'inout_type', 'p_auto_id', 'shop_i_id', 'shop_sku_id', 'sku_id', 'so_id', 'combine_type'] as $column) {
$table->string($column)->nullable();
}
$table->dateTime('fee_time')->nullable();
$table->dateTime('createTime')->nullable();
$table->dateTime('updateTime')->nullable();
$table->bigInteger('auto_id')->nullable();
$table->decimal('amount', 20, 6)->nullable();
});
$schema->create('ops_jst_shop_info', function (Blueprint $table): void {
$table->integer('Id');
$table->integer('shop_id')->nullable();
$table->string('shop_name')->nullable();
});
$this->repository = new StoreReceivingAlipayBillMirrorRepository(
new BusinessMirrorDatasetRegistry(), new GenericBusinessMirrorRepository(app('db')),
);
}
public function test_it_reads_operations_replica_rows_and_maps_shop_and_flow_fields(): void
{
$this->markReady();
DB::connection('business_mirror')->table('ops_jst_shop_info')->insert([
['Id' => 10, 'shop_id' => 7, 'shop_name' => '天猫-经营店'],
['Id' => 11, 'shop_id' => 8, 'shop_name' => '淘宝-其他店'],
]);
DB::connection('business_mirror')->table('ops_jst_original_bill_info')->insert([
$this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25),
$this->row(2, 7, '2026-08-02 09:00:00', '-1', 20.10),
$this->row(3, 8, '2026-08-03 09:00:00', '1', 88),
]);
$result = $this->repository->paginate(
['store_name' => '天猫-经营店', 'start' => '2026-08-01', 'end' => '2026-08-31'],
'occurred_at', 'desc', 1, 10,
);
$this->assertSame(2, $result['total']);
$this->assertSame('2', $result['data'][0]['record_code']);
$this->assertSame('天猫-经营店', $result['data'][0]['store_name']);
$this->assertSame('支出', $result['data'][0]['business_type']);
$this->assertSame('CommonBillTGCPay', $result['data'][0]['alipay_account']);
$this->assertSame(100.25, $result['summary']['income']);
$this->assertSame(20.10, $result['summary']['expense']);
$this->assertSame(80.15, $result['summary']['balance']);
$this->assertSame(2, $result['summary']['count']);
}
public function test_it_rejects_an_unready_mirror_instead_of_querying_upstream_from_http(): void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('store_receiving_alipay_bill_mirror_not_ready');
$this->repository->paginate([], 'occurred_at', 'desc', 1, 10);
}
public function test_export_uses_the_same_filters_and_mapping_as_the_page(): void
{
$this->markReady();
DB::connection('business_mirror')->table('ops_jst_shop_info')->insert([
'Id' => 10, 'shop_id' => 7, 'shop_name' => '天猫-经营店',
]);
DB::connection('business_mirror')->table('ops_jst_original_bill_info')->insert([
$this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25),
$this->row(2, 7, '2026-08-02 09:00:00', '-1', 20.10),
]);
$rows = iterator_to_array($this->repository->exportRows([
'business_type' => '支出', 'alipay_account' => 'CommonBill', 'keyword' => 'SKU-2',
]));
$this->assertCount(1, $rows);
$this->assertSame('2', $rows[0]['record_code']);
$this->assertSame(20.10, $rows[0]['expense']);
}
public function test_common_filters_use_the_existing_daily_summary_acceleration(): void
{
$this->markReady();
DB::connection('business_mirror')->table('ops_jst_shop_info')->insert([
'Id' => 10, 'shop_id' => 7, 'shop_name' => '天猫-经营店',
]);
DB::connection('business_mirror')->table('ops_jst_original_bill_info')->insert(
$this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25),
);
Schema::connection('business_mirror')->create('store_report_import_states', function (Blueprint $table): void {
$table->string('dataset_key')->primary();
$table->string('active_generation')->nullable();
$table->dateTime('initial_completed_at')->nullable();
});
Schema::connection('business_mirror')->create('store_report_daily_rows', function (Blueprint $table): void {
$table->string('generation');
$table->string('store_name');
$table->date('occurred_on');
$table->decimal('income', 20, 4);
$table->decimal('expense', 20, 4);
});
DB::connection('business_mirror')->table('store_report_import_states')->insert([
'dataset_key' => 'store-report.daily-sales', 'active_generation' => 'daily-v1',
'initial_completed_at' => '2026-09-01 09:00:00',
]);
DB::connection('business_mirror')->table('store_report_daily_rows')->insert([
'generation' => 'daily-v1', 'store_name' => '天猫-经营店', 'occurred_on' => '2026-08-01',
'income' => 100.25, 'expense' => 20.10,
]);
$result = $this->repository->paginate([
'store_name' => '天猫-经营店', 'start' => '2026-08-01', 'end' => '2026-08-01',
], 'occurred_at', 'desc', 1, 10);
$this->assertSame(['income' => 100.25, 'expense' => 20.10, 'balance' => 80.15, 'count' => 1], $result['summary']);
}
public function test_unfiltered_pages_reuse_exact_completed_count_and_observe_the_next_sync(): void
{
$this->markReady();
$db = DB::connection('business_mirror');
$db->table('ops_jst_original_bill_info')->insert([
$this->row(1, 7, '2026-08-01 09:00:00', '1', 10),
$this->row(2, 7, '2026-08-02 09:00:00', '-1', 5),
]);
$state = $db->table('business_mirror_table_states')->where('dataset_key', StoreReceivingAlipayBillMirrorRepository::DATASET);
$state->update(['mirror_row_count' => 2]);
$db->enableQueryLog();
$first = $this->repository->paginate([], 'occurred_at', 'desc', 1, 1);
$second = $this->repository->paginate(['store_name' => '', 'start' => null], 'occurred_at', 'desc', 2, 1);
$this->assertSame(2, $first['total']);
$this->assertSame(2, $second['total']);
$this->assertNotSame($first['data'][0]['id'], $second['data'][0]['id']);
$this->assertCount(0, array_filter($db->getQueryLog(), fn ($q) => str_contains(strtolower($q['query']), 'count(*)')));
$db->disableQueryLog();
$db->table('ops_jst_original_bill_info')->insert($this->row(3, 7, '2026-08-03 09:00:00', '1', 4));
$state->update(['mirror_row_count' => 3, 'last_succeeded_at' => '2026-09-08 09:00:00']);
$this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']);
$this->assertSame(1, $this->repository->paginate(['business_type' => '支出'], 'occurred_at', 'desc', 1, 1)['total']);
$state->update(['status' => 'running', 'mirror_row_count' => 2]);
$this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']);
$state->update(['status' => 'failed']);
$this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']);
$state->update(['status' => 'succeeded', 'mirror_row_count' => null]);
$this->assertSame(3, $this->repository->paginate([], 'occurred_at', 'desc', 1, 1)['total']);
}
private function markReady(): void
{
foreach ([StoreReceivingAlipayBillMirrorRepository::DATASET, StoreReceivingAlipayBillMirrorRepository::SHOP_DATASET] as $dataset) {
DB::connection('business_mirror')->table('business_mirror_table_states')->insert([
'dataset_key' => $dataset,
'initial_completed_at' => '2026-09-07 09:00:00',
'status' => 'succeeded',
'last_succeeded_at' => '2026-09-07 09:00:00',
]);
}
}
private function row(int $id, int $shopId, string $time, string $direction, float $amount): array
{
return [
'Id' => $id, 'shop_id' => $shopId, 'channel' => 'CommonBillTGCPay',
'primary_key' => 'KEY-' . $id, 'fee_name' => '交易佣金', 'inout_type' => $direction,
'p_auto_id' => 'P-' . $id, 'shop_i_id' => 'STYLE-' . $id,
'shop_sku_id' => 'SHOP-SKU-' . $id, 'sku_id' => 'SKU-' . $id,
'so_id' => 'ORDER-' . $id, 'combine_type' => 'double',
'fee_time' => $time, 'createTime' => '2026-08-04 09:00:00',
'updateTime' => '2026-08-04 10:00:00', 'auto_id' => 1000 + $id, 'amount' => $amount,
];
}
}