实现七月迭代公共技术基础
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s

This commit is contained in:
2026-07-23 17:52:48 +09:00
parent f7c42252c0
commit 17782d5f8e
76 changed files with 5246 additions and 158 deletions

View File

@@ -0,0 +1,130 @@
package asynctask_test
import (
"context"
"testing"
"time"
"gorm.io/gorm"
storepkg "github.com/break/junhong_cmp_fiber/internal/infrastructure/asynctask"
"github.com/break/junhong_cmp_fiber/internal/testutil"
contract "github.com/break/junhong_cmp_fiber/pkg/asynctask"
)
func TestPostgresTaskTransitionsAreConditionalAndRecoverExpiredLease(t *testing.T) {
db := testutil.NewPostgresTransaction(t)
createTaskContractTable(t, db)
store, err := storepkg.NewStore(db, taskDefinition())
if err != nil {
t.Fatalf("创建任务契约 Store 失败:%v", err)
}
now := time.Date(2026, 7, 23, 10, 0, 0, 0, time.UTC)
if err := db.Exec("INSERT INTO test_async_contract_task (id, status, updated_at) VALUES (1, 1, ?)", now).Error; err != nil {
t.Fatalf("准备待处理任务失败:%v", err)
}
claimed, err := store.Claim(context.Background(), 1, "worker-a", now, time.Minute)
if err != nil || !claimed {
t.Fatalf("领取待处理任务失败:%v领取%v", err, claimed)
}
claimed, err = store.Claim(context.Background(), 1, "worker-b", now.Add(30*time.Second), time.Minute)
if err != nil || claimed {
t.Fatalf("不得抢占有效租约:%v领取%v", err, claimed)
}
claimed, err = store.Claim(context.Background(), 1, "worker-b", now.Add(2*time.Minute), time.Minute)
if err != nil || !claimed {
t.Fatalf("过期任务应由新 Worker 恢复:%v领取%v", err, claimed)
}
renewed, err := store.Renew(context.Background(), 1, "worker-a", now.Add(150*time.Second), 2*time.Minute)
if err != nil || renewed {
t.Fatalf("旧租约所有者不得续租:%v续租%v", err, renewed)
}
renewed, err = store.Renew(context.Background(), 1, "worker-b", now.Add(150*time.Second), 2*time.Minute)
if err != nil || !renewed {
t.Fatalf("有效租约所有者续租失败:%v续租%v", err, renewed)
}
claimed, err = store.Claim(context.Background(), 1, "worker-c", now.Add(3*time.Minute), time.Minute)
if err != nil || claimed {
t.Fatalf("续租后不得被其他 Worker 领取:%v领取%v", err, claimed)
}
finished, err := store.Finish(context.Background(), 1, "worker-a", contract.TerminalResult{
TaskID: "1", Status: contract.StatusCompleted, TotalCount: 10, SuccessCount: 7, FailedCount: 3, UpdatedAt: now,
}, now.Add(3*time.Minute))
if err != nil || finished {
t.Fatalf("旧租约所有者不得完成任务:%v完成%v", err, finished)
}
finished, err = store.Finish(context.Background(), 1, "worker-b", contract.TerminalResult{
TaskID: "1", Status: contract.StatusCompleted, TotalCount: 10, SuccessCount: 7, FailedCount: 3, UpdatedAt: now,
}, now.Add(3*time.Minute))
if err != nil || !finished {
t.Fatalf("当前租约所有者完成任务失败:%v完成%v", err, finished)
}
claimed, err = store.Claim(context.Background(), 1, "worker-c", now.Add(4*time.Minute), time.Minute)
if err != nil || claimed {
t.Fatalf("终态重复消费必须无副作用:%v领取%v", err, claimed)
}
cancelled, err := store.Cancel(context.Background(), 1, now.Add(4*time.Minute))
if err != nil || cancelled {
t.Fatalf("终态任务不得再次取消:%v取消%v", err, cancelled)
}
var row struct {
Status int
Total int
Success int
Failed int
}
if err := db.Table("test_async_contract_task").Select("status, total_count AS total, success_count AS success, failed_count AS failed").Where("id = 1").Scan(&row).Error; err != nil {
t.Fatalf("读取任务终态失败:%v", err)
}
if row.Status != contract.StatusCompleted || row.Total != 10 || row.Success != 7 || row.Failed != 3 {
t.Fatalf("任务终态计数错误:%+v", row)
}
}
func TestPostgresTaskContractSupportsWholeFailureAndCancellation(t *testing.T) {
db := testutil.NewPostgresTransaction(t)
createTaskContractTable(t, db)
store, _ := storepkg.NewStore(db, taskDefinition())
now := time.Now().UTC()
if err := db.Exec("INSERT INTO test_async_contract_task (id, status, updated_at) VALUES (2, 1, ?), (3, 1, ?)", now, now).Error; err != nil {
t.Fatalf("准备任务失败:%v", err)
}
claimed, _ := store.Claim(context.Background(), 2, "worker", now, time.Minute)
if !claimed {
t.Fatal("整体失败任务领取失败")
}
finished, err := store.Finish(context.Background(), 2, "worker", contract.TerminalResult{
TaskID: "2", Status: contract.StatusFailed, ErrorCode: "FILE_PARSE_FAILED", ErrorSummary: "文件无法解析", UpdatedAt: now,
}, now)
if err != nil || !finished {
t.Fatalf("整体失败终态更新失败:%v", err)
}
cancelled, err := store.Cancel(context.Background(), 3, now)
if err != nil || !cancelled {
t.Fatalf("待处理任务取消失败:%v", err)
}
}
func taskDefinition() storepkg.Definition {
return storepkg.Definition{
Table: "test_async_contract_task", IDColumn: "id", StatusColumn: "status",
LeaseOwnerColumn: "lease_owner", LeaseExpiresColumn: "lease_expires_at",
TotalColumn: "total_count", SuccessColumn: "success_count", FailedColumn: "failed_count",
ProgressColumn: "progress", ErrorCodeColumn: "error_code", ErrorSummaryColumn: "error_summary",
StartedAtColumn: "started_at", CompletedAtColumn: "completed_at", UpdatedAtColumn: "updated_at",
}
}
func createTaskContractTable(t *testing.T, db *gorm.DB) {
t.Helper()
if err := db.Exec(`CREATE TEMP TABLE test_async_contract_task (
id bigint PRIMARY KEY, status integer NOT NULL, total_count integer NOT NULL DEFAULT 0,
success_count integer NOT NULL DEFAULT 0, failed_count integer NOT NULL DEFAULT 0,
progress integer NOT NULL DEFAULT 0, error_code varchar(100) NOT NULL DEFAULT '',
error_summary varchar(500) NOT NULL DEFAULT '', lease_owner varchar(100), lease_expires_at timestamptz,
started_at timestamptz, completed_at timestamptz, updated_at timestamptz NOT NULL
) ON COMMIT DROP`).Error; err != nil {
t.Fatalf("创建任务契约测试表失败:%v", err)
}
}