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

106 lines
3.2 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 (
"testing"
"time"
"github.com/break/junhong_cmp_fiber/pkg/asynctask"
)
func TestCompletedTaskUsesCountsForPartialAndAllItemFailures(t *testing.T) {
t.Parallel()
now := time.Date(2026, 7, 23, 10, 0, 0, 0, time.UTC)
cases := []struct {
name string
success int
failed int
}{
{name: "部分成功", success: 7, failed: 3},
{name: "业务项全部失败", success: 0, failed: 10},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
projection, err := asynctask.NewTerminalProjection(asynctask.TerminalResult{
TaskID: "task-1", Status: asynctask.StatusCompleted,
TotalCount: 10, SuccessCount: tc.success, FailedCount: tc.failed,
StartedAt: &now, CompletedAt: &now, UpdatedAt: now,
})
if err != nil {
t.Fatalf("构造任务投影失败:%v", err)
}
if projection.Status != asynctask.StatusCompleted || projection.StatusName != "已完成" {
t.Fatalf("业务项处理到终点必须为已完成:%+v", projection)
}
if projection.Progress != 100 {
t.Fatalf("终态进度必须为 100得到%d", projection.Progress)
}
})
}
}
func TestTerminalProjectionRejectsBrokenCountInvariant(t *testing.T) {
t.Parallel()
_, err := asynctask.NewTerminalProjection(asynctask.TerminalResult{
TaskID: "task-2", Status: asynctask.StatusCompleted,
TotalCount: 10, SuccessCount: 5, FailedCount: 4,
UpdatedAt: time.Now(),
})
if err == nil {
t.Fatal("终态计数不守恒时应拒绝构造公开投影")
}
}
func TestStructuredPayloadRejectsBytes(t *testing.T) {
t.Parallel()
if err := asynctask.ValidatePayload([]byte(`{"task_id":1}`)); err == nil {
t.Fatal("预序列化 []byte 载荷必须被拒绝")
}
if err := asynctask.ValidatePayload(struct {
TaskID uint `json:"task_id"`
}{TaskID: 1}); err != nil {
t.Fatalf("结构化载荷应通过校验:%v", err)
}
}
func TestClaimRecoversOnlyExpiredProcessingTask(t *testing.T) {
t.Parallel()
now := time.Date(2026, 7, 23, 10, 0, 0, 0, time.UTC)
activeExpiry := now.Add(time.Minute)
active, err := asynctask.Claim(asynctask.LeaseState{
Status: asynctask.StatusProcessing, LeaseOwner: "worker-a", LeaseExpiresAt: &activeExpiry,
}, "worker-b", now, time.Minute)
if err != nil {
t.Fatalf("检查有效租约失败:%v", err)
}
if active.Claimed {
t.Fatal("其他 Worker 不得抢占有效租约")
}
expiredAt := now.Add(-time.Second)
recovered, err := asynctask.Claim(asynctask.LeaseState{
Status: asynctask.StatusProcessing, LeaseOwner: "worker-a", LeaseExpiresAt: &expiredAt,
}, "worker-b", now, time.Minute)
if err != nil {
t.Fatalf("恢复过期任务失败:%v", err)
}
if !recovered.Claimed || recovered.State.LeaseOwner != "worker-b" {
t.Fatalf("过期任务应由新 Worker 恢复:%+v", recovered)
}
}
func TestTerminalTaskCannotBeClaimedOrCancelledAgain(t *testing.T) {
t.Parallel()
result, err := asynctask.Claim(asynctask.LeaseState{Status: asynctask.StatusCompleted}, "worker-a", time.Now(), time.Minute)
if err != nil {
t.Fatalf("检查终态任务失败:%v", err)
}
if result.Claimed || asynctask.CanCancel(asynctask.StatusCompleted) {
t.Fatal("终态任务不得被重复消费或取消")
}
}