feat: add durable business task outbox example and fault recovery

This commit is contained in:
2026-09-17 23:13:48 +08:00
parent 2b8213a4f7
commit 522fe2ef3e
27 changed files with 2195 additions and 7 deletions

View File

@@ -0,0 +1,63 @@
package outbox
import (
"bytes"
"context"
"encoding/json"
"errors"
"testing"
"time"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
)
func testID(t *testing.T) string {
t.Helper()
id, err := orbit.NewEventMessageID()
if err != nil {
t.Fatal(err)
}
return id
}
func testRequest(queue string) orbit.SubmitTaskRequest {
return orbit.SubmitTaskRequest{QueueID: queue, Handler: "object.prepare", HandlerVersion: "v1", PayloadVersion: "v1", Payload: json.RawMessage(`{"sequence":9007199254740993,"ref":"objects/test"}`), TaskTimeoutMS: 60000}
}
func TestFrozenDocumentAndFailureClassification(t *testing.T) {
s := &Store{namespace: testID(t), queue: testID(t)}
in := testRequest(s.queue)
in.TaskTimeoutMS = 0
raw, err := s.document(in)
if err != nil {
t.Fatal(err)
}
var frozen orbit.SubmitTaskRequest
if json.Unmarshal(raw, &frozen) != nil || frozen.TaskTimeoutMS != 86400000 || frozen.Retry.MaxAttempts != 1 || frozen.Retry.AttemptTimeoutMS != 30000 || !bytes.Contains(frozen.Payload, []byte("9007199254740993")) {
t.Fatal("frozen defaults or precision changed")
}
in.QueueID = testID(t)
if _, err = s.document(in); !errors.Is(err, ErrInvalid) {
t.Fatal("cross queue request frozen")
}
in = testRequest(s.queue)
in.BusinessKey = "operation-\xff"
if _, err = s.document(in); !errors.Is(err, ErrInvalid) {
t.Fatal("invalid UTF-8 was silently rewritten")
}
for _, test := range []struct {
err error
state, code string
}{
{context.DeadlineExceeded, "PENDING", "UNCONFIRMED"},
{&orbit.APIError{StatusCode: 503, Code: "UNAVAILABLE"}, "PENDING", "UNCONFIRMED"},
{&orbit.APIError{StatusCode: 429, RetryAfter: 30 * time.Second}, "PENDING", "BACKPRESSURE"},
{&orbit.APIError{StatusCode: 403}, "REVIEW", "AUTHORIZATION"},
{&orbit.APIError{StatusCode: 409, Code: "IDEMPOTENCY_CONFLICT"}, "REVIEW", "IDEMPOTENCY_CONFLICT"},
{&orbit.HTTPError{StatusCode: 302}, "REVIEW", "HTTP_REJECTED"},
{&orbit.HTTPError{StatusCode: 502}, "PENDING", "UNCONFIRMED"},
} {
state, code, delay := classify(test.err, 100)
if state != test.state || code != test.code || delay < time.Second || delay > time.Hour {
t.Fatal(state, code, delay)
}
}
}