feat: add durable relative task delays and outage recovery regression
This commit is contained in:
@@ -393,4 +393,66 @@ func TestIntegrationTaskAPI(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
t.Run("relative_delay_uses_database_acceptance_and_original_identity", func(t *testing.T) {
|
||||
delayed := input
|
||||
delayed.DelayMS = 5000
|
||||
key := "delayed-submit"
|
||||
first := must("POST", "/v1/tasks", a.Token, key, marshal(delayed), 201)
|
||||
var v service.SubmitResult
|
||||
if err := json.Unmarshal(first.data, &v); err != nil || !v.Task.AvailableAt.Equal(v.Task.CreatedAt.Add(5*time.Second)) {
|
||||
t.Fatal("delay not anchored to persisted database acceptance", err)
|
||||
}
|
||||
const n = 8
|
||||
delayedBody := marshal(delayed)
|
||||
results := make(chan apiReply, n)
|
||||
errs := make(chan error, n)
|
||||
for range n {
|
||||
go func() {
|
||||
out, err := call("POST", "/v1/tasks", a.Token, key, delayedBody)
|
||||
results <- out
|
||||
errs <- err
|
||||
}()
|
||||
}
|
||||
for range n {
|
||||
out, err := <-results, <-errs
|
||||
var duplicate service.SubmitResult
|
||||
if err != nil || out.status != 200 || json.Unmarshal(out.data, &duplicate) != nil || !duplicate.Deduplicated || duplicate.Task.ID != v.Task.ID || !duplicate.Task.AvailableAt.Equal(v.Task.AvailableAt) || !duplicate.Task.DeadlineAt.Equal(v.Task.DeadlineAt) {
|
||||
t.Fatal("repeated delay reset execution time or identity", err)
|
||||
}
|
||||
}
|
||||
changed := delayed
|
||||
changed.DelayMS++
|
||||
must("POST", "/v1/tasks", a.Token, key, marshal(changed), 409)
|
||||
changed = delayed
|
||||
changed.DelayMS = 0
|
||||
changed.AvailableAt = &v.Task.AvailableAt
|
||||
must("POST", "/v1/tasks", a.Token, key, marshal(changed), 409)
|
||||
changed.DelayMS = delayed.DelayMS
|
||||
must("POST", "/v1/tasks", a.Token, "both-timing-modes", marshal(changed), 400)
|
||||
changed = delayed
|
||||
tooEarly := v.Task.CreatedAt.Add(time.Second)
|
||||
changed.StartDeadlineAt = &tooEarly
|
||||
must("POST", "/v1/tasks", a.Token, "delay-after-start-deadline", marshal(changed), 400)
|
||||
changed = delayed
|
||||
changed.DelayMS = 86400000
|
||||
must("POST", "/v1/tasks", a.Token, "delay-at-total-deadline", marshal(changed), 400)
|
||||
if count("pulse_tasks", "namespace_id=? AND id=?", a.NamespaceID, v.Task.ID) != 1 || count("orbit_events", "namespace_id=? AND aggregate_id=?", a.NamespaceID, v.Task.ID) != 1 {
|
||||
t.Fatal("delayed duplicate emitted another task or event")
|
||||
}
|
||||
// 省略与显式零保持原有即刻/绝对时间请求语义,不破坏升级前的摘要。
|
||||
zero := bytes.Clone(bytes.TrimSuffix(raw, []byte("}")))
|
||||
zero = append(zero, []byte(`,"delay_ms":0}`)...)
|
||||
must("POST", "/v1/tasks", a.Token, "stable-submit", zero, 200)
|
||||
batch := service.BatchSubmitInput{Items: []service.BatchSubmitItem{{IdempotencyKey: key, Task: delayed}, {IdempotencyKey: "delay-batch-new", Task: delayed}}}
|
||||
batchReply := must("POST", "/v1/tasks:batch", a.Token, "", marshal(batch), 200)
|
||||
var batchOut service.BatchSubmitResult
|
||||
if json.Unmarshal(batchReply.data, &batchOut) != nil || len(batchOut.Items) != 2 || batchOut.Items[0].Receipt == nil || batchOut.Items[1].Receipt == nil || !batchOut.Items[0].Receipt.Deduplicated || batchOut.Items[0].Receipt.TaskID != v.Task.ID {
|
||||
t.Fatal("batch did not reuse relative-delay identity")
|
||||
}
|
||||
fresh := must("GET", "/v1/tasks/"+string(batchOut.Items[1].Receipt.TaskID), a.Token, "", nil, 200)
|
||||
var freshTask service.TaskView
|
||||
if json.Unmarshal(fresh.data, &freshTask) != nil || !freshTask.AvailableAt.Equal(freshTask.CreatedAt.Add(5*time.Second)) {
|
||||
t.Fatal("batch delay did not use its own acceptance time")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user