namespace BuildFlow.Architecture.Tests; using System.Text.RegularExpressions; using Xunit; /// /// 后端优化闭环 SOP 的可执行合同测试,防止脚本退化为不可复现或会污染开发环境的手工步骤。 /// public sealed class BackendOptimizationSopTests { private static readonly string[] OrderedStages = [ "01-preflight", "02-static-guards", "03-restore-build", "04-unit-tests", "05-start-infrastructure", "06-database-migrations", "07-integration-tests", "08-recovery-tests", "09-performance-tests", "10-full-solution", ]; [Fact] public void ScriptShouldFailFastAndCleanOnlyItsComposeProject() { var script = ReadRequiredFile("scripts", "backend-optimization-sop.sh"); Assert.Contains("#!/usr/bin/env bash", script, StringComparison.Ordinal); Assert.Contains("set -euo pipefail", script, StringComparison.Ordinal); Assert.Matches(new Regex(@"trap\s+cleanup\s+EXIT", RegexOptions.CultureInvariant), script); Assert.Contains( "docker compose -p \"$COMPOSE_PROJECT\" -f \"$COMPOSE_FILE\" down -v", script, StringComparison.Ordinal); Assert.DoesNotContain("docker compose down", script, StringComparison.Ordinal); } [Fact] public void ComposeShouldProvideFourPinnedRandomPortServices() { var compose = ReadRequiredFile("scripts", "backend-optimization-compose.yml"); Assert.Contains("postgres:", compose, StringComparison.Ordinal); Assert.Contains("pgvector/pgvector:0.8.0-pg17", compose, StringComparison.Ordinal); Assert.Contains("redis:", compose, StringComparison.Ordinal); Assert.Contains("redis:7-alpine", compose, StringComparison.Ordinal); Assert.Contains("mongo:", compose, StringComparison.Ordinal); Assert.Contains("mongo:7", compose, StringComparison.Ordinal); Assert.Contains("minio:", compose, StringComparison.Ordinal); Assert.Contains("minio/minio:RELEASE.2025-04-22T22-12-26Z", compose, StringComparison.Ordinal); Assert.Contains("127.0.0.1::5432", compose, StringComparison.Ordinal); Assert.Contains("127.0.0.1::6379", compose, StringComparison.Ordinal); Assert.Contains("127.0.0.1::27017", compose, StringComparison.Ordinal); Assert.Contains("127.0.0.1::9000", compose, StringComparison.Ordinal); } [Fact] public void ScriptShouldArchiveOrderedStageCommandsTestsAndMetrics() { var script = ReadRequiredFile("scripts", "backend-optimization-sop.sh"); var previousIndex = -1; foreach (var stage in OrderedStages) { var currentIndex = script.IndexOf($"run_stage '{stage}'", StringComparison.Ordinal); Assert.True(currentIndex > previousIndex, $"SOP 阶段缺失或顺序错误: {stage}"); previousIndex = currentIndex; } Assert.Contains("dotnet restore \"$SOLUTION\"", script, StringComparison.Ordinal); Assert.Contains( "dotnet build \"$SOLUTION\" --no-restore -warnaserror", script, StringComparison.Ordinal); Assert.Contains("--logger \"trx;LogFileName=", script, StringComparison.Ordinal); Assert.Contains("--results-directory \"$TRX_DIR\"", script, StringComparison.Ordinal); Assert.Contains("assert_no_skipped_tests", script, StringComparison.Ordinal); Assert.Contains("collect_sop_metrics", script, StringComparison.Ordinal); Assert.Contains("\"$LOG_DIR\" \"$TRX_DIR\"", script, StringComparison.Ordinal); Assert.Contains("SOP_METRIC", script, StringComparison.Ordinal); Assert.Contains("summary.md", script, StringComparison.Ordinal); Assert.Contains("date -u +%Y%m%dT%H%M%SZ", script, StringComparison.Ordinal); } [Fact] public void ScriptShouldRunFinalDebtScansWithoutDeveloperSecretsOrDatabases() { var script = ReadRequiredFile("scripts", "backend-optimization-sop.sh"); Assert.Contains("EndpointConstructorsShouldNotDependOnDbContexts", script, StringComparison.Ordinal); Assert.Contains("HostShouldNotContainInProcessBridges", script, StringComparison.Ordinal); Assert.Contains("BusinessExceptionsShouldSpecifySemanticErrorCodes", script, StringComparison.Ordinal); Assert.Contains("SourceFilesShouldContainAtMostOneTopLevelClassRecordOrEnum", script, StringComparison.Ordinal); Assert.Contains("Password\\s*=", script, StringComparison.Ordinal); Assert.Contains("DateTime\\.Now", script, StringComparison.Ordinal); Assert.Contains("Task\\.Run", script, StringComparison.Ordinal); Assert.DoesNotContain("appsettings.Secrets.json", script, StringComparison.Ordinal); Assert.DoesNotContain("rag123", script, StringComparison.Ordinal); Assert.DoesNotContain("Database=workflow;", script, StringComparison.Ordinal); Assert.DoesNotContain("Database=im_system;", script, StringComparison.Ordinal); } [Fact] public void OperatorGuideShouldDocumentInvocationIsolationAndEvidenceRules() { var guide = ReadRequiredFile("docs", "backend-optimization-sop.md"); Assert.Contains("scripts/backend-optimization-sop.sh", guide, StringComparison.Ordinal); Assert.Contains("BACKEND_SOP_ARTIFACT_ROOT", guide, StringComparison.Ordinal); Assert.Contains("随机端口", guide, StringComparison.Ordinal); Assert.Contains("独立 Compose 项目", guide, StringComparison.Ordinal); Assert.Contains("TRX", guide, StringComparison.Ordinal); Assert.Contains("SOP_METRIC", guide, StringComparison.Ordinal); Assert.Contains("summary.md", guide, StringComparison.Ordinal); Assert.Contains("skipped", guide, StringComparison.OrdinalIgnoreCase); Assert.Contains("失败", guide, StringComparison.Ordinal); } private static string ReadRequiredFile(params string[] pathParts) { var repoRoot = FindRepoRoot(); var path = Path.Combine([repoRoot.FullName, .. pathParts]); Assert.True(System.IO.File.Exists(path), $"SOP 合同文件不存在: {Path.GetRelativePath(repoRoot.FullName, path)}"); return System.IO.File.ReadAllText(path); } private static DirectoryInfo FindRepoRoot() { var current = new DirectoryInfo(AppContext.BaseDirectory); while (current is not null) { if (System.IO.File.Exists(Path.Combine(current.FullName, "BuildFlow.slnx"))) { return current; } current = current.Parent; } throw new InvalidOperationException("无法定位 BuildFlow.slnx,SOP 合同测试无法确定仓库边界。"); } }