All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
106 lines
3.2 KiB
Go
106 lines
3.2 KiB
Go
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("终态任务不得被重复消费或取消")
|
||
}
|
||
}
|