feat(tasks): implement atomic claims and leases

This commit is contained in:
QiuSW
2026-07-26 16:16:36 +08:00
parent 49db5b8305
commit ce875af889
50 changed files with 7589 additions and 207 deletions
@@ -432,6 +432,7 @@ func TestTaskDetailPendingCancelUsesCSRFAndPRG(t *testing.T) {
CreatedAt: time.Date(2026, 7, 26, 3, 4, 5, 0, time.UTC),
UpdatedAt: time.Date(2026, 7, 26, 3, 5, 5, 0, time.UTC),
},
cancelResult: Task{Status: "CANCELED"},
}
router := newTestRouter(t, service)
detail := performRequest(
@@ -508,6 +509,81 @@ func TestTaskDetailDoesNotLeakForbiddenResource(t *testing.T) {
}
}
func TestTaskDetailCancelModeFollowsLifecycleStatus(t *testing.T) {
tests := []struct {
status string
canCancel bool
requiresAck bool
}{
{status: "PENDING", canCancel: true},
{status: "CLAIMED", canCancel: true},
{status: "RUNNING", canCancel: true, requiresAck: true},
{
status: "WAITING_CONFIRMATION",
canCancel: true,
requiresAck: true,
},
{status: "SUCCEEDED"},
{status: "FAILED"},
{status: "CANCELED"},
}
for _, test := range tests {
t.Run(test.status, func(t *testing.T) {
view := taskDetailViewFrom(Task{Status: test.status})
if view.CanCancel != test.canCancel ||
view.CancelRequiresAck != test.requiresAck {
t.Fatalf("task detail view = %+v", view)
}
})
}
if notice := detailNotice("cancel-requested"); !strings.Contains(
notice,
"安全停止",
) {
t.Fatalf("cancel requested notice = %q", notice)
}
}
func TestRunningTaskDetailExplainsCancelAcknowledgement(t *testing.T) {
service := &fakeService{
getResult: Task{
ID: testTaskID,
Title: "运行中任务",
SKU: "RUNNING-SKU",
Quantity: 1,
Status: "RUNNING",
ReferenceAssetID: "00000000-0000-4000-8000-000000000009",
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
},
}
router := newTestRouter(t, service)
response := performRequest(
t,
router,
http.MethodGet,
"/tasks/"+testTaskID,
nil,
"",
)
if response.Code != http.StatusOK {
t.Fatalf("running task detail status = %d", response.Code)
}
body := response.Body.String()
for _, expected := range []string{
"请求安全停止任务?",
"设备确认前任务仍保持当前执行状态",
"确认请求停止",
} {
if !strings.Contains(body, expected) {
t.Fatalf("running detail missing %q", expected)
}
}
if strings.Contains(body, "采购执行员将不能再领取") {
t.Fatal("running detail uses immediate cancellation copy")
}
}
func TestStaticFilesAreEmbeddedAndProtected(t *testing.T) {
router := newTestRouter(t, &fakeService{})
for _, route := range []string{"/static/admin.css", "/static/admin.js"} {
@@ -554,7 +630,7 @@ type fakeService struct {
createCalls int
cancelResult Task
cancelErr error
cancelInput CancelPendingInput
cancelInput CancelTaskInput
}
func (service *fakeService) ListTasks(
@@ -595,9 +671,9 @@ func (service *fakeService) CreateTask(
return service.createResult, service.createErr
}
func (service *fakeService) CancelPending(
func (service *fakeService) CancelTask(
_ context.Context,
input CancelPendingInput,
input CancelTaskInput,
) (Task, error) {
service.cancelInput = input
return service.cancelResult, service.cancelErr