diff --git a/backend/internal/admin/server_test.go b/backend/internal/admin/server_test.go index a4ec095..933dc82 100644 --- a/backend/internal/admin/server_test.go +++ b/backend/internal/admin/server_test.go @@ -60,6 +60,7 @@ func TestScertAdminSystemdUnitKeepsManualOnlyPolicy(t *testing.T) { func TestControllerActivatesAndStopsResetPath(t *testing.T) { cfg := config.Default(t.TempDir()) + cfg.Paths.Data = t.TempDir() cfg.SetupReady = true cfg.Web.Hostname = "scert.example.test" cfg.Server.Port = 8443 @@ -111,6 +112,7 @@ func TestExpiredLeaseIsInactive(t *testing.T) { func TestResetCodeCanOnlyBeConsumedOnce(t *testing.T) { cfg := config.Default(t.TempDir()) + cfg.Paths.Data = t.TempDir() cfg.SetupReady = true cfg.Web.Hostname = "scert.example.test" controller := New(cfg) @@ -137,6 +139,7 @@ func TestResetCodeCanOnlyBeConsumedOnce(t *testing.T) { func TestControllerWritesAndDestroysProtectedCodeFile(t *testing.T) { cfg := config.Default(t.TempDir()) + cfg.Paths.Data = t.TempDir() cfg.SetupReady = true cfg.Web.Hostname = "scert.example.test" codePath := filepath.Join(t.TempDir(), "runtime", "reset-code") @@ -172,6 +175,7 @@ func TestControllerWritesAndDestroysProtectedCodeFile(t *testing.T) { func TestConsumeDestroysCodeFile(t *testing.T) { cfg := config.Default(t.TempDir()) + cfg.Paths.Data = t.TempDir() cfg.SetupReady = true cfg.Web.Hostname = "scert.example.test" codePath := filepath.Join(t.TempDir(), "runtime", "reset-code") @@ -217,6 +221,7 @@ func TestExpiredLeaseDestroysCodeFile(t *testing.T) { func TestControllerDoesNotOverwriteExistingCodeFile(t *testing.T) { cfg := config.Default(t.TempDir()) + cfg.Paths.Data = t.TempDir() cfg.SetupReady = true cfg.Web.Hostname = "scert.example.test" codePath := filepath.Join(t.TempDir(), "runtime", "reset-code") diff --git a/backend/internal/certificate/service_test.go b/backend/internal/certificate/service_test.go index 80baf99..ad83ed7 100644 --- a/backend/internal/certificate/service_test.go +++ b/backend/internal/certificate/service_test.go @@ -326,6 +326,7 @@ func TestServiceRestoreVersionRollsBackFilesWhenStateUpdateFails(t *testing.T) { func certificateTestConfig(root string) config.Config { cfg := config.Default(root) + cfg.Paths.Config = filepath.Join(root, "conf") cfg.Paths.Certificate = filepath.Join(root, "ssl") cfg.Paths.Backup = filepath.Join(root, "backups") cfg.DNS.Provider = "cloudflare" diff --git a/backend/internal/database/credentials_test.go b/backend/internal/database/credentials_test.go index f05c4f4..7344325 100644 --- a/backend/internal/database/credentials_test.go +++ b/backend/internal/database/credentials_test.go @@ -87,6 +87,7 @@ func TestValidateConfigKeepsExternalDatabaseUsernameOutOfPersistedDSN(t *testing func TestVerifyWritableSQLiteCleansTemporaryTable(t *testing.T) { cfg := config.Default(t.TempDir()).Database + cfg.DSN = filepath.Join(t.TempDir(), "verification.db") if err := VerifyWritable(t.Context(), cfg); err != nil { t.Fatalf("SQLite 可写验证失败:%v", err) } diff --git a/backend/internal/database/review_runtime_test.go b/backend/internal/database/review_runtime_test.go index 765a54f..acfb75c 100644 --- a/backend/internal/database/review_runtime_test.go +++ b/backend/internal/database/review_runtime_test.go @@ -2,6 +2,7 @@ package database import ( "context" + "path/filepath" "strings" "testing" @@ -10,6 +11,7 @@ import ( func TestDatabasePingAndSQLitePragmas(t *testing.T) { cfg := config.Default(t.TempDir()).Database + cfg.DSN = filepath.Join(t.TempDir(), "runtime.db") db, err := Open(context.Background(), cfg) if err != nil { t.Fatalf("打开 SQLite 失败:%v", err) diff --git a/backend/internal/database/sqlite_close_test.go b/backend/internal/database/sqlite_close_test.go index 48a5c10..64814f3 100644 --- a/backend/internal/database/sqlite_close_test.go +++ b/backend/internal/database/sqlite_close_test.go @@ -22,6 +22,7 @@ func TestSQLiteCanceledQueriesReleaseFiles(t *testing.T) { } t.Run(name, func(t *testing.T) { cfg := config.Default(t.TempDir()).Database + cfg.DSN = filepath.Join(t.TempDir(), "close.db") cfg.MaxOpenConns, cfg.MaxIdleConns = 1, 1 db, err := Open(t.Context(), cfg) if err != nil { diff --git a/backend/internal/database/sqlite_concurrency_test.go b/backend/internal/database/sqlite_concurrency_test.go index 357e257..16ba763 100644 --- a/backend/internal/database/sqlite_concurrency_test.go +++ b/backend/internal/database/sqlite_concurrency_test.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "path/filepath" "sync" "testing" "time" @@ -15,6 +16,7 @@ import ( func newSQLiteConcurrencyPools(t *testing.T) (*sql.DB, *sql.DB) { t.Helper() cfg := config.Default(t.TempDir()).Database + cfg.DSN = filepath.Join(t.TempDir(), "concurrency.db") first := openSQLiteTestPool(t, cfg) second := openSQLiteTestPool(t, cfg) if _, err := first.ExecContext(t.Context(), "CREATE TABLE lock_probe (id INTEGER PRIMARY KEY, value INTEGER NOT NULL); INSERT INTO lock_probe VALUES (1, 0)"); err != nil { diff --git a/backend/internal/database/sqlite_deadline_test.go b/backend/internal/database/sqlite_deadline_test.go index 998a474..70723b0 100644 --- a/backend/internal/database/sqlite_deadline_test.go +++ b/backend/internal/database/sqlite_deadline_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "path/filepath" "testing" "time" @@ -98,6 +99,7 @@ func TestSQLiteDeadlineAcrossExecutionPaths(t *testing.T) { func TestSQLitePoolWaitHonorsContext(t *testing.T) { cfg := config.Default(t.TempDir()).Database + cfg.DSN = filepath.Join(t.TempDir(), "pool-wait.db") cfg.MaxOpenConns = 1 cfg.MaxIdleConns = 1 db := openSQLiteTestPool(t, cfg) @@ -146,6 +148,7 @@ func TestSQLiteDeadlinePreservesQueryMetadata(t *testing.T) { func TestSQLiteOpenDeadlineWhileChangingJournalMode(t *testing.T) { cfg := config.Default(t.TempDir()).Database + cfg.DSN = filepath.Join(t.TempDir(), "journal-mode.db") if err := ensureSQLiteDir(cfg.DSN); err != nil { t.Fatal(err) } diff --git a/backend/internal/database/sqlite_pool_test.go b/backend/internal/database/sqlite_pool_test.go index c7b0181..2a88b41 100644 --- a/backend/internal/database/sqlite_pool_test.go +++ b/backend/internal/database/sqlite_pool_test.go @@ -3,6 +3,7 @@ package database import ( "context" "database/sql" + "path/filepath" "testing" "git.pcux.cn/xuans/Scert/backend/internal/config" @@ -44,6 +45,7 @@ func TestSQLitePoolConfiguresEveryConnection(t *testing.T) { for _, name := range []string{"parallel_connections", "no_idle_connections", "replacement_connections", "uri_options"} { t.Run(name, func(t *testing.T) { cfg := config.Default(t.TempDir()).Database + cfg.DSN = filepath.Join(t.TempDir(), "pool.db") cfg.MaxOpenConns = 4 cfg.MaxIdleConns = 4 if name == "no_idle_connections" { diff --git a/backend/internal/database/sqlite_process_test.go b/backend/internal/database/sqlite_process_test.go index 4cea049..17b30fb 100644 --- a/backend/internal/database/sqlite_process_test.go +++ b/backend/internal/database/sqlite_process_test.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "os/exec" + "path/filepath" "strings" "sync" "testing" @@ -29,6 +30,7 @@ func TestSQLiteProcessConcurrency(t *testing.T) { return } cfg := config.Default(t.TempDir()).Database + cfg.DSN = filepath.Join(t.TempDir(), "process.db") db := openSQLiteTestPool(t, cfg) if _, err := db.ExecContext(t.Context(), "CREATE TABLE lock_probe (id INTEGER PRIMARY KEY, value INTEGER NOT NULL); INSERT INTO lock_probe VALUES (1, 0)"); err != nil { t.Fatal(err) diff --git a/backend/internal/database/sqlite_wal_integrity_test.go b/backend/internal/database/sqlite_wal_integrity_test.go index c1e4ac0..47a9f5b 100644 --- a/backend/internal/database/sqlite_wal_integrity_test.go +++ b/backend/internal/database/sqlite_wal_integrity_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "path/filepath" "runtime" "sync" "testing" @@ -15,6 +16,7 @@ import ( // 覆盖真实连接间的写入与检查点竞争,不将概率性压力测试当成上游缺陷的确定性复现。 func TestSQLiteWALCheckpointIntegrity(t *testing.T) { cfg := config.Default(t.TempDir()).Database + cfg.DSN = filepath.Join(t.TempDir(), "checkpoint.db") cfg.MaxOpenConns, cfg.MaxIdleConns = 3, 3 db := openSQLiteTestPool(t, cfg) ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) diff --git a/doc/Linux生产流程测试记录.md b/doc/Linux生产流程测试记录.md index 48d760f..fc95a4b 100644 --- a/doc/Linux生产流程测试记录.md +++ b/doc/Linux生产流程测试记录.md @@ -1,5 +1,16 @@ # Linux 生产流程测试记录 +## 2026年10月2日13:59 测试隔离修复与完整检出回归 + +- [x] 十个测试文件共二十一行调整:重置状态文件、敏感配置及 SQLite DSN 均进入临时目录;共享连接池和子进程仍复用同一个测试数据库。不改产品行为、不提权写正式目录。 +- [x] `/var/tmp/scert-recovery-full-20261002-1344/full-backend.log` 保留归档复测失败:三个原路径污染包已通过,仅发布边界测试因没有 Git 元数据失败;同目录 `full-vet.log` 退出零。 +- [x] `/var/tmp/scert-recovery-checkout-20261002-1348/source` 为完整 Git 检出 3b25449,叠加十个测试文件修复;nobody 执行 `go test ./... -count=1`,输出 `CHECKOUT_FULL_EXIT=0`,`full-backend.log` 记录三十一个通过包。未跳过发布边界检查。 +- [x] 13:55 双机监听核查:Scert 均仅监听 127.0.0.1:8443,Nginx 占用 80/443,Scert 与 Nginx 均 active;未改公网端口或共存业务。 +- [!] 第一台 `/var/tmp/scert-recovery-race-20261002-1345/race.log` 的竞争检测未完成:依赖下载连接持续 SYN-SENT,13:58 中断后退出 130,不能记为通过。 +- [x] 缓存归档三端 SHA256 一致,使用独立离线代理及新 GOPATH 执行 `go test -p 2 -race ./internal/deployment -run 'TestRecovery|TestJournal' -count=20`;保留 go.sum 校验,13:58:59 返回 `OFFLINE_RACE_EXIT=0`,`race-offline.log` 记录通过。仅覆盖恢复与事务用例,不声称全后端竞争检测通过。 +- [x] 完整 Git 检出目录重新执行全后端 go vet,日志为同目录 `full-vet.log`,返回 `CHECKOUT_VET_EXIT=0`。 +- [~] 本批修复未正式构建部署,线上仍为此前 1150c39 对应版本;未清业务库,未显式开启外部数据库用例,不扩大为三数据库生产验收。 + ## 2026年10月2日13:41 上传归属与扩大回归 - [x] 同一隔离目录的 `upload-baseline.log`:带事务日志的上传碰撞保护和未知临时文件恢复保护两个场景均失败,退出码 1,确认原实现会误删。 diff --git a/doc/Scert整改任务执行清单.md b/doc/Scert整改任务执行清单.md index 08fc6f6..6db11c4 100644 --- a/doc/Scert整改任务执行清单.md +++ b/doc/Scert整改任务执行清单.md @@ -1,5 +1,17 @@ # Scert 整改任务执行清单 +## 2026年10月2日13:59 当前任务入口 + +需求确认:不允许额外公网端口、未知归属文件误删或绕过安全校验;测试失败必须保留证据,不能降低验收门槛。 + +- [x] T2-G:十个测试文件共二十一行调整,将重置状态、敏感配置与 SQLite 数据库隔离到临时目录;不修改产品行为,不写正式安装目录。 +- [x] 完整 Git 检出叠加测试隔离修复后,原生 Linux 全后端测试退出零;源码归档缺 Git 元数据造成的发布边界失败已定位,未跳过检查。 +- [x] T2-D:第一台竞争检测因依赖下载连接停留在 SYN-SENT,保留原日志并中断,退出 130;使用三端摘要一致的缓存归档复测,恢复及事务场景竞争检测重复二十次通过,退出零。 +- [x] 完整 Git 检出上的全后端 go vet 通过,输出 CHECKOUT_VET_EXIT=0。 +- [x] 双机现场监听核查:Scert 均为 127.0.0.1:8443,Nginx 提供 80/443;服务均运行中,未新增公网端口。 +- [ ] T2-F:测试隔离修改提交后,从干净源码正式构建,再进行部署与业务实机恢复复审。 +- [~] P0-10 及整体生产门禁仍未通过;本轮不是清库首次流程,也不等于三数据库生产验收。 + ## 2026年10月2日13:41 当前任务入口 - [x] T2-E:已复现上传碰撞误删;修复为独占创建确认、摘要落库与整组临时文件预检,碰撞、变更及旧日志缺归属证据均保留现场。 diff --git a/doc/Scert整改合并优化清单.md b/doc/Scert整改合并优化清单.md index 2aaf84f..857edff 100644 --- a/doc/Scert整改合并优化清单.md +++ b/doc/Scert整改合并优化清单.md @@ -1,5 +1,15 @@ # Scert 整改、合并与优化清单 +## 2026年10月2日13:59 全后端隔离回归进度 + +- [x] 已修复 admin、certificate、database 十个测试文件的正式路径污染,保持产品默认配置及测试语义不变。 +- [x] 完整 Git 检出上的原生 Linux 全后端测试通过,退出零;归档目录缺 Git 元数据导致的发布边界失败保留,不跳过源码检查。 +- [x] 双机实时确认 Scert 仅监听 127.0.0.1:8443,Nginx 负责 80/443;未开放额外公网端口。 +- [x] 竞争检测:第一台原任务阻塞于外部依赖下载,保留日志并中断;改用摘要一致的缓存归档和新依赖目录复测,未关闭依赖校验,恢复及事务场景重复二十次通过,退出零。 +- [x] 完整 Git 检出上的全后端 go vet 通过,退出零。 +- [ ] 本批代码正式干净构建、双机部署与业务实机负向恢复复审。 +- [~] P0-10 保持进行中,整体生产验收未通过;线上尚未部署本批修复。 + ## 2026年10月2日13:41 上传归属安全整改 - [x] 已复现并修复带事务日志时误删上传碰撞文件、恢复时误删身份已变更临时文件的问题;旧实现两个新增场景均失败。