100 lines
4.0 KiB
Go
100 lines
4.0 KiB
Go
package repository
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func seedTaskCompletion(t *testing.T, repo *SQLTaskRepository) TaskCompletion {
|
|
t.Helper()
|
|
started := time.Now().UTC().Add(-time.Minute)
|
|
if err := repo.CreateTask(t.Context(), Task{
|
|
ID: "completion-task", DomainID: "domain-1", TaskType: "certificate_deploy",
|
|
Status: "running", Stage: "deployment", StartedAt: &started,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repo.CreateAttempt(t.Context(), TaskAttempt{
|
|
ID: "completion-attempt", TaskID: "completion-task", AttemptNumber: 1,
|
|
Status: "running", Stage: "deployment", StartedAt: &started,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return TaskCompletion{
|
|
TaskID: "completion-task", AttemptID: "completion-attempt", Status: "success",
|
|
Stage: "deployment", Message: "部署完成", FinishedAt: time.Now().UTC(),
|
|
}
|
|
}
|
|
|
|
func TestTaskCompletionPersistsBothTerminalRecords(t *testing.T) {
|
|
for _, status := range []string{"success", "failed", "cancelled", "partial_success"} {
|
|
t.Run(status, func(t *testing.T) {
|
|
repo, db := newTaskRepositoryTest(t)
|
|
defer db.Close()
|
|
completion := seedTaskCompletion(t, repo)
|
|
completion.Status = status
|
|
if err := repo.FinishTaskAttempt(t.Context(), completion); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
task, err := repo.GetTask(t.Context(), completion.TaskID)
|
|
if err != nil || task.Status != status || task.StartedAt == nil || task.FinishedAt == nil || task.Message != completion.Message {
|
|
t.Fatalf("任务终态未完整保存:%+v %v", task, err)
|
|
}
|
|
attempts, err := repo.ListAttempts(t.Context(), completion.TaskID)
|
|
if err != nil || len(attempts) != 1 || attempts[0].Status != status || attempts[0].FinishedAt == nil || !attempts[0].FinishedAt.Equal(*task.FinishedAt) || attempts[0].Message != task.Message {
|
|
t.Fatalf("任务和尝试终态不一致:%+v %v", attempts, err)
|
|
}
|
|
if err := repo.FinishTaskAttempt(t.Context(), completion); !errors.Is(err, sql.ErrNoRows) {
|
|
t.Fatalf("终态不能被迟到的收尾重复覆盖:%v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTaskCompletionRollsBackWhenAttemptCannotFinish(t *testing.T) {
|
|
for _, failure := range []string{"write_failure", "missing_attempt", "foreign_attempt", "stale_attempt", "invalid_status"} {
|
|
t.Run(failure, func(t *testing.T) {
|
|
repo, db := newTaskRepositoryTest(t)
|
|
defer db.Close()
|
|
completion := seedTaskCompletion(t, repo)
|
|
wantAttemptStatus := "running"
|
|
switch failure {
|
|
case "write_failure":
|
|
if _, err := db.ExecContext(t.Context(), `CREATE TRIGGER reject_attempt_completion BEFORE UPDATE ON task_attempts BEGIN SELECT RAISE(ABORT, 'injected completion failure'); END`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
case "missing_attempt":
|
|
completion.AttemptID = "missing"
|
|
case "foreign_attempt":
|
|
if err := repo.CreateTask(t.Context(), Task{ID: "foreign-task", DomainID: "domain-2", TaskType: "certificate_deploy", Status: "running", Stage: "deployment"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := repo.CreateAttempt(t.Context(), TaskAttempt{ID: "foreign-attempt", TaskID: "foreign-task", AttemptNumber: 1, Status: "running", Stage: "deployment"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
completion.AttemptID = "foreign-attempt"
|
|
case "stale_attempt":
|
|
if err := repo.FinishAttempt(t.Context(), completion.AttemptID, "failed", "此前失败", &completion.FinishedAt); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wantAttemptStatus = "failed"
|
|
case "invalid_status":
|
|
completion.Status = "running"
|
|
}
|
|
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 != wantAttemptStatus {
|
|
t.Fatalf("失败操作修改了尝试:%+v %v", attempts, err)
|
|
}
|
|
})
|
|
}
|
|
}
|