CaiWuXiTong/tests/Unit/Deployment/BusinessMirrorScheduleTest.php
yueyang 7b8a5e1214 feat: 增加OA进行中流程按需刷新 (#785)
页面进入时仅异步刷新当前页审批中的流程,并自动载入最新镜像。
2026-10-30 15:04:43 +08:00

260 lines
15 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,
);
$this->assertStringContainsString(
"cat > /etc/cron.d/caiwu-ding-process-details <<'EOF'\n"
. "CRON_TZ=Asia/Shanghai\n"
. '2-59/5 * * * * root CAIWU_BATCH_LOCK_WAIT=900 /usr/bin/flock -n /run/caiwu-ding-process-details.lock '
. "/bin/sh /opt/caiwuxitong/run-finance-batch.sh business-mirror:sync-ding-process-details --quiet >/dev/null 2>&1\n"
. "# 页面打开时只提交当前页审批中的流程;有请求时才启动定向刷新批处理。\n"
. '* * * * * root CAIWU_BATCH_LOCK_WAIT=900 /usr/bin/flock -n /run/caiwu-ding-process-refresh.lock '
. "/bin/sh /opt/caiwuxitong/run-finance-batch.sh business-mirror:process-ding-refresh-requests --quiet >/dev/null 2>&1\n"
. 'EOF',
$contents,
$script,
);
$this->assertStringContainsString(
'chmod 644 /etc/cron.d/caiwu-ding-process-details',
$contents,
$script,
);
}
}
public function test_on_demand_refresh_skips_batch_container_when_no_request_is_pending(): void
{
$contents = file_get_contents(dirname(__DIR__, 3) . '/deploy/run-finance-batch.sh');
$this->assertIsString($contents);
$this->assertStringContainsString(
"if [ \"\$1\" = 'business-mirror:process-ding-refresh-requests' ]; then",
$contents,
);
$this->assertStringContainsString(
"find \"\$storage_root/app/ding-process-refresh\" -maxdepth 1 -type f -name '*.pending'",
$contents,
);
$this->assertStringContainsString(
"pending_request=\"\$(find \"\$storage_root/app/ding-process-refresh\" -maxdepth 1 -type f -name '*.pending' -print -quit)\"",
$contents,
);
}
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);
}
}