Files
junhong_cmp_fiber/internal/infrastructure/asynctask/store_integration_test.go
break 17782d5f8e
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
实现七月迭代公共技术基础
2026-07-23 17:52:48 +09:00

131 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}