实现七月迭代公共技术基础
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
This commit is contained in:
105
pkg/asynctask/contract_test.go
Normal file
105
pkg/asynctask/contract_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
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("终态任务不得被重复消费或取消")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user