Scert/backend/internal/repository/task_report_test.go

124 lines
5.9 KiB
Go

package repository
import (
"strings"
"testing"
"time"
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
)
func completionTestReport() *deployment.Report {
return &deployment.Report{
Total: 2, Success: 1, Failed: 1, Status: "failed", VerificationStatus: "failed", RecoveryStatus: "unconfirmed",
Results: []deployment.Result{
{ServerID: "server-1", Server: "第一台", Status: "success", BackupID: "backup-1"},
{ServerID: "server-2", Server: "第二台", Status: "failed", FailureCode: deployment.FailureRecoveryUnknown, Message: "secret-output-marker"},
},
}
}
func TestTaskCompletionReportPersistsAcrossRepositoryRestart(t *testing.T) {
repo, db := newTaskRepositoryTest(t)
defer db.Close()
completion := seedTaskCompletion(t, repo)
completion.Status = "failed"
completion.Report = completionTestReport()
completion.AuditAction = "certificate_deploy"
if err := repo.FinishTaskAttempt(t.Context(), completion); err != nil {
t.Fatal(err)
}
reader := NewSQLTaskRepository(db)
var auditStatus, scope, projectID, resourceID string
if err := db.QueryRowContext(t.Context(), `SELECT status, scope_type, project_id, resource_id FROM audit_logs WHERE task_id = ?`,
completion.TaskID).Scan(&auditStatus, &scope, &projectID, &resourceID); err != nil || auditStatus != "error" || scope != "project" || projectID != "default" || resourceID != "domain-1" {
t.Fatalf("完成审计与任务状态或归属不一致:%s %s %s %s %v", auditStatus, scope, projectID, resourceID, err)
}
reports, err := reader.ListTaskDeploymentReports(t.Context(), completion.TaskID)
if err != nil || len(reports) != 1 {
t.Fatalf("完整报告未保存:%+v %v", reports, err)
}
stored := reports[0]
if stored.AttemptNumber != 1 || stored.AttemptID != completion.AttemptID || stored.Report.VerificationStatus != "failed" || stored.Report.RecoveryStatus != "unconfirmed" || len(stored.Report.Results) != 2 {
t.Fatalf("重读后验证或恢复证据丢失:%+v", stored)
}
if stored.Report.Results[1].FailureCode != deployment.FailureRecoveryUnknown || strings.Contains(stored.Report.Results[1].Message, "secret-output-marker") {
t.Fatal("报告未保留固定诊断或泄露远端输出")
}
var raw string
if err := db.QueryRowContext(t.Context(), `SELECT report_json FROM task_deployment_reports WHERE task_id = ?`, completion.TaskID).Scan(&raw); err != nil || strings.Contains(raw, "secret-output-marker") {
t.Fatalf("持久化前未脱敏:%v", err)
}
results, err := reader.ListTaskDeploymentResults(t.Context(), completion.TaskID)
if err != nil || len(results) != 2 || results[1].Message != stored.Report.Results[1].Message {
t.Fatalf("逐目标结果与报告不一致:%+v %v", results, err)
}
if completion.Report.Results[1].Message != "secret-output-marker" {
t.Fatal("保存报告不能原地修改调用者结果")
}
if _, err := db.ExecContext(t.Context(), `INSERT INTO users (id, username, password_hash, role, status)
VALUES ('report-admin', 'report-admin', 'test', 'admin', 'active')`); err != nil {
t.Fatal(err)
}
if _, err := db.ExecContext(t.Context(), `INSERT INTO project_members (project_id, user_id, role)
VALUES ('default', 'report-admin', 'project_admin')`); err != nil {
t.Fatal(err)
}
_, retry, err := repo.RetryVisibleTask(t.Context(), "report-admin", completion.TaskID, nil)
if err != nil {
t.Fatal(err)
}
completion.AttemptID = retry.ID
completion.Status = "success"
completion.FinishedAt = time.Now().UTC()
completion.Report = &deployment.Report{Status: "success", VerificationStatus: "success"}
if err := repo.FinishTaskAttempt(t.Context(), completion); err != nil {
t.Fatal(err)
}
reports, err = reader.ListTaskDeploymentReports(t.Context(), completion.TaskID)
if err != nil || len(reports) != 2 || reports[0].Report.RecoveryStatus != "unconfirmed" || reports[1].AttemptNumber != 2 || reports[1].Report.VerificationStatus != "success" {
t.Fatalf("重试覆盖或混用了旧报告:%+v %v", reports, err)
}
if reports[1].Report.Results == nil {
t.Fatal("零目标报告必须返回空数组而非空值,避免前端忽略验证状态")
}
}
func TestTaskCompletionReportFailureRollsBackAllRecords(t *testing.T) {
for _, table := range []string{"task_deployment_reports", "task_deployment_results", "audit_logs"} {
t.Run(table, func(t *testing.T) {
repo, db := newTaskRepositoryTest(t)
defer db.Close()
completion := seedTaskCompletion(t, repo)
completion.Report = completionTestReport()
completion.AuditAction = "certificate_deploy"
if _, err := db.ExecContext(t.Context(), `CREATE TRIGGER reject_report BEFORE INSERT ON `+table+` BEGIN SELECT RAISE(ABORT, 'injected report failure'); END`); err != nil {
t.Fatal(err)
}
if err := repo.FinishTaskAttempt(t.Context(), completion); err == nil {
t.Fatal("报告保存失败必须拒绝提交终态")
}
task, err := repo.GetTask(t.Context(), completion.TaskID)
if err != nil || task.Status != "running" || task.FinishedAt != nil {
t.Fatalf("报告失败但任务被提交:%+v %v", task, err)
}
attempts, err := repo.ListAttempts(t.Context(), completion.TaskID)
if err != nil || len(attempts) != 1 || attempts[0].Status != "running" || attempts[0].FinishedAt != nil {
t.Fatalf("报告失败但尝试被提交:%+v %v", attempts, err)
}
reports, err := repo.ListTaskDeploymentReports(t.Context(), completion.TaskID)
if err != nil || len(reports) != 0 {
t.Fatalf("出现部分提交的报告:%+v %v", reports, err)
}
results, err := repo.ListTaskDeploymentResults(t.Context(), completion.TaskID)
if err != nil || len(results) != 0 {
t.Fatalf("出现部分提交的目标结果:%+v %v", results, err)
}
var auditCount int
if err := db.QueryRowContext(t.Context(), `SELECT COUNT(*) FROM audit_logs WHERE task_id = ?`, completion.TaskID).Scan(&auditCount); err != nil || auditCount != 0 {
t.Fatalf("出现部分提交的完成审计:%d %v", auditCount, err)
}
})
}
}