CaiWuXiTong/tests/Unit/Deployment/BusinessMirrorScheduleTest.php
92451 1cb04adc7b feat: 店铺台账接入每日余额、预留标准与预警
完成店铺台账每日余额来源、日期筛选、预留标准和连续3天预警。
Co-authored-by: 92451 <924518135@qq.com>
Co-committed-by: 92451 <924518135@qq.com>
2026-10-27 18:13:00 +08:00

224 lines
13 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\Unit\Deployment;
use PHPUnit\Framework\TestCase;
class BusinessMirrorScheduleTest extends TestCase
{
public function test_release_scripts_run_the_product_catalog_before_generic_rotation(): void
{
foreach (['activate-release.sh', 'activate-incremental-release.sh'] as $script) {
$contents = file_get_contents(dirname(__DIR__, 3) . '/deploy/' . $script);
$catalogPosition = strpos($contents, 'business-mirror:sync-product-catalog --quiet');
$purchasePricePosition = strpos($contents, 'business-mirror:sync-product-purchase-prices --quiet');
$storeReportPosition = strpos($contents, 'business-mirror:sync-store-report --max-batches=12 --quiet');
$snapshotPosition = strpos($contents, 'upstream:snapshots:refresh-all --quiet');
$pricePosition = strpos($contents, 'business-mirror:sync-product-prices --quiet');
$genericPosition = strpos($contents, 'business-mirror:sync-tables --quiet');
$this->assertNotFalse($catalogPosition, $script);
$this->assertNotFalse($purchasePricePosition, $script);
$this->assertNotFalse($storeReportPosition, $script);
$this->assertNotFalse($snapshotPosition, $script);
$this->assertNotFalse($pricePosition, $script);
$this->assertNotFalse($genericPosition, $script);
$this->assertLessThan($snapshotPosition, $catalogPosition, $script);
$this->assertLessThan($snapshotPosition, $purchasePricePosition, $script);
$this->assertLessThan($purchasePricePosition, $catalogPosition, $script);
$this->assertLessThan($pricePosition, $catalogPosition, $script);
$this->assertLessThan($genericPosition, $catalogPosition, $script);
$this->assertSame(1, substr_count($contents, 'business-mirror:sync-product-catalog --quiet'), $script);
$this->assertSame(2, substr_count($contents, 'business-mirror:sync-product-purchase-prices --quiet'), $script);
$this->assertSame(1, substr_count($contents, 'business-mirror:sync-store-report'), $script);
$this->assertStringContainsString(
'7,22,37,52 * * * * root /usr/bin/flock -w 600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-store-report --max-batches=12 --quiet',
$contents,
$script,
);
$this->assertStringContainsString(
'45 * * * * root /usr/bin/flock -w 600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan order-data:warm-shop-candidates --quiet',
$contents,
$script,
);
$this->assertStringContainsString(
'40 6 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:follow-upstream-columns --quiet',
$contents,
$script,
);
$this->assertStringContainsString(
'0 7 * * * root /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:archive-store-information --quiet',
$contents,
$script,
);
$this->assertSame(1, substr_count($contents, 'business-mirror:archive-store-information --quiet'), $script);
$minuteLines = array_values(array_filter(
preg_split('/\R/', $contents) ?: [],
static fn (string $line): bool => str_starts_with($line, '* * * * * root'),
));
$minuteLine = $minuteLines[0] ?? null;
$this->assertIsString($minuteLine, $script);
$this->assertStringNotContainsString('business-mirror:sync-store-report', $minuteLine, $script);
$this->assertStringContainsString('docker exec -u www-data caiwu_app', $contents, $script);
$this->assertStringContainsString('/run/caiwu-upstream-sync.lock', $contents, $script);
$this->assertStringContainsString("CRON_TZ=Asia/Shanghai\n", $contents, $script);
$this->assertStringContainsString('0 0 * * * root', $contents, $script);
$this->assertStringContainsString('0 5 * * * root', $contents, $script);
$this->assertStringContainsString('30 5 * * * root', $contents, $script);
$this->assertStringContainsString(
'store-violations:sync --quiet',
$contents,
$script,
);
$this->assertSame(1, substr_count($contents, 'store-violations:sync --quiet'), $script);
$this->assertStringContainsString('store-violations:push-instant --quiet', $contents, $script);
$this->assertStringContainsString('store-violations:push-digest --quiet', $contents, $script);
$this->assertStringContainsString('*/5 9-21 * * * root', $contents, $script);
$this->assertStringContainsString('0 10 * * * root', $contents, $script);
$this->assertSame(1, substr_count($contents, 'store-violations:push-instant --quiet'), $script);
$this->assertSame(1, substr_count($contents, 'store-violations:push-digest --quiet'), $script);
$this->assertStringContainsString(
'15 8 * * * root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:sync-supplier-management --quiet',
$contents,
$script,
);
$this->assertSame(1, substr_count($contents, 'business-mirror:sync-supplier-management --quiet'), $script);
$this->assertStringContainsString(
'business-mirror:sync-tables operations_replica.rpt.jst_Orders --quiet',
$contents,
$script,
);
$this->assertStringContainsString(
'business-mirror:sync-ding-online-daifa-price --quiet',
$contents,
$script,
);
$this->assertStringContainsString(
"30 6 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-overlength --days=7 --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-return --days=7 --quiet'",
$contents,
$script,
);
$this->assertStringContainsString(
"40 6 * * 1 root /usr/bin/flock -w 3600 /run/caiwu-upstream-sync.lock /bin/sh -c '/usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-overlength --days=90 --quiet; /usr/bin/docker exec -u www-data caiwu_app php artisan courier-surcharge:pull-return --days=90 --quiet'",
$contents,
$script,
);
$this->assertSame(2, substr_count($contents, 'courier-surcharge:pull-overlength'), $script);
$this->assertSame(2, substr_count($contents, 'courier-surcharge:pull-return'), $script);
$this->assertStringContainsString(
'business-mirror:sync-third-party-warehouse-storage --max-batches=0 --quiet',
$contents,
$script,
);
$this->assertStringContainsString(
'business-mirror:sync-third-party-warehouse-storage --max-batches=0 --quiet && /usr/bin/docker exec -u www-data caiwu_app php artisan business-mirror:warm-report-caches --quiet',
$contents,
$script,
);
$this->assertSame(1, substr_count($contents, 'business-mirror:warm-report-caches --quiet'), $script);
$this->assertSame(
1,
substr_count($contents, 'business-mirror:sync-third-party-warehouse-storage --max-batches=0 --quiet'),
$script,
);
$this->assertStringContainsString('*/2 * * * * root', $contents, $script);
$this->assertStringContainsString('esign:poll-signatures --quiet', $contents, $script);
$this->assertStringContainsString('/run/caiwu-esign-polling.lock', $contents, $script);
$this->assertSame(1, substr_count($contents, 'esign:poll-signatures --quiet'), $script);
$this->assertSame(1, substr_count($contents, 'finance-ledger:sync-shop-data --balance-only --quiet'), $script);
$this->assertStringContainsString(
'# 财务台账:每天 02:30 同步 AI 库动账并重算流水;10:30 生成前一晚店铺余额快照。',
$contents,
$script,
);
$this->assertStringContainsString(
'30 2 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan finance-ledger:sync-rpt --days=7 --merge --quiet >/dev/null 2>&1',
$contents,
$script,
);
$this->assertStringContainsString(
'30 10 * * * root /usr/bin/flock -w 1800 /run/caiwu-upstream-sync.lock /usr/bin/docker exec -u www-data caiwu_app php artisan finance-ledger:sync-shop-data --balance-only --quiet >/dev/null 2>&1',
$contents,
$script,
);
}
}
public function test_configured_lookup_indexes_avoid_known_long_text_columns(): void
{
$contents = file_get_contents(dirname(__DIR__, 3) . '/config/upstream_mirrors.php');
$this->assertIsString($contents);
$this->assertStringNotContainsString("['o_id', 'outer_oi_id', 'Id']", $contents);
$this->assertStringNotContainsString("['shop_i_id', 'modified']", $contents);
$this->assertStringNotContainsString("['stat_date', 'push_fee', 'item_id']", $contents);
$this->assertStringContainsString("['stat_date', 'push_fee']", $contents);
}
public function test_clickhouse_is_internal_resource_bounded_and_uses_private_runtime_credentials(): void
{
$root = dirname(__DIR__, 3);
$compose = file_get_contents($root . '/compose.jiangling.yml');
$start = strpos($compose, " clickhouse:\n");
$end = strpos($compose, " app:\n", $start);
$service = substr($compose, $start, $end - $start);
$this->assertStringContainsString('clickhouse/clickhouse-server:26.8.2.7-alpine', $service);
$this->assertStringContainsString('mem_limit: 4g', $service);
$this->assertStringContainsString('cpus: "2.00"', $service);
$this->assertStringContainsString('max-size: "20m"', $service);
$this->assertStringNotContainsString('ports:', $service);
$this->assertStringContainsString('./runtime/clickhouse.env', $compose);
foreach (['activate-release.sh', 'activate-incremental-release.sh'] as $script) {
$contents = file_get_contents($root . '/deploy/' . $script);
$this->assertStringContainsString('openssl rand -hex 24', $contents, $script);
$this->assertStringContainsString('chmod 600 "$clickhouse_env"', $contents, $script);
$pull = strpos($contents, 'pull clickhouse');
$migration = strpos($contents, 'if [ "$RUN_MIGRATIONS" = "1" ]');
$this->assertNotFalse($pull, $script);
$this->assertLessThan($migration, $pull, $script);
$this->assertStringContainsString('docker inspect caiwu_clickhouse', $contents, $script);
$this->assertStringContainsString('ClickHouse did not become healthy', $contents, $script);
}
}
public function test_shared_config_database_is_sized_for_reports_and_records_slow_queries(): void
{
$compose = file_get_contents(dirname(__DIR__, 3) . '/compose.jiangling.yml');
$start = strpos($compose, " config_db:\n");
$end = strpos($compose, " clickhouse:\n", $start);
$service = substr($compose, $start, $end - $start);
$this->assertStringContainsString('--innodb-buffer-pool-size=4G', $service);
$this->assertStringContainsString('--slow-query-log=ON', $service);
$this->assertStringContainsString('--long-query-time=5', $service);
$this->assertStringContainsString('mem_limit: 6g', $service);
$this->assertStringContainsString('cpus: "4.00"', $service);
}
public function test_production_supplier_payable_maintenance_uses_the_business_mirror(): void
{
$compose = file_get_contents(dirname(__DIR__, 3) . '/compose.jiangling.yml');
$start = strpos($compose, " app:\n");
$end = strpos($compose, "\nvolumes:\n", $start);
$service = substr($compose, $start, $end - $start);
$this->assertStringContainsString(
'SUPPLIER_PAYABLE_MAINTENANCE_DB_CONNECTION: business_mirror',
$service,
);
$this->assertStringContainsString('JST_DB_CONNECTION: business_mirror', $service);
}
public function test_tunnel_task_accepts_both_missing_task_exception_types(): void
{
$contents = file_get_contents(dirname(__DIR__, 3) . '/scripts/install-upstream-tunnel-task.ps1');
$this->assertIsString($contents);
$this->assertStringContainsString('catch [System.Runtime.InteropServices.COMException]', $contents);
$this->assertStringContainsString('catch [System.IO.FileNotFoundException]', $contents);
$this->assertStringContainsString('$rootFolder.GetTask($TaskName)', $contents);
}
}