106 lines
3.8 KiB
Go
106 lines
3.8 KiB
Go
package orbit
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
)
|
|
|
|
func TestRecoveryReceiptAndAuditBoundary(t *testing.T) {
|
|
const task = "11111111111111111111111111111111"
|
|
const child = "22222222222222222222222222222222"
|
|
const audit = "33333333333333333333333333333333"
|
|
var calls atomic.Int32
|
|
var malformed atomic.Bool
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls.Add(1)
|
|
if r.Header.Get("Authorization") != "Bearer example-token" {
|
|
t.Error("missing token")
|
|
}
|
|
var data any
|
|
switch r.URL.Path {
|
|
case "/v1/tasks/" + task + ":review":
|
|
if r.Method != "POST" || r.Header.Get("Idempotency-Key") != "stable-review" {
|
|
t.Error("review identity")
|
|
}
|
|
var in ReviewTaskRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&in); err != nil || in.ExpectedRevision != 9007199254740993 || !in.ExecutionSettled {
|
|
t.Error("lost review fields", err)
|
|
}
|
|
out := ReviewTaskResult{AuditID: audit, Task: Task{ID: task, State: TaskFailed, Revision: 9007199254740994}}
|
|
if malformed.Load() {
|
|
out.Task.EffectUncertain = true
|
|
}
|
|
data = out
|
|
case "/v1/tasks/" + task + ":retry":
|
|
if r.Method != "POST" || r.Header.Get("Idempotency-Key") != "stable-retry" {
|
|
t.Error("retry identity")
|
|
}
|
|
parent := task
|
|
out := RetryTaskResult{AuditID: audit, SourceTaskID: task, Task: Task{ID: child, RetryOf: &parent}}
|
|
if malformed.Load() {
|
|
out.Task.RetryOf = nil
|
|
}
|
|
data = out
|
|
case "/v1/tasks/" + task + "/operations":
|
|
if r.Method != "GET" || r.Header.Get("Idempotency-Key") != "" || r.URL.Query().Get("cursor") != "opaque+cursor/=" {
|
|
t.Error("audit page request")
|
|
}
|
|
data = TaskOperationPage{Items: []TaskOperationSummary{{ID: audit, TaskID: task, Action: "task.review"}}}
|
|
case "/v1/tasks/" + task + "/operations/" + audit:
|
|
data = TaskOperation{TaskOperationSummary: TaskOperationSummary{ID: audit, TaskID: task}, After: json.RawMessage(`{"revision":"9007199254740994"}`)}
|
|
default:
|
|
t.Error("unexpected path", r.URL.Path)
|
|
}
|
|
json.NewEncoder(w).Encode(map[string]any{"request_id": audit, "data": data})
|
|
}))
|
|
defer server.Close()
|
|
c, _ := NewClient(server.URL, Options{Token: "example-token"})
|
|
ctx := context.Background()
|
|
in := ReviewTaskRequest{ExpectedRevision: 9007199254740993, ExecutionSettled: true, Outcome: TaskFailed}
|
|
review, err := c.ReviewTask(ctx, task, "stable-review", in)
|
|
if err != nil || review.Task.Revision != 9007199254740994 {
|
|
t.Fatal(review, err)
|
|
}
|
|
retry, err := c.RetryTask(ctx, task, "stable-retry", RetryTaskRequest{})
|
|
if err != nil || retry.Task.ID != child {
|
|
t.Fatal(retry, err)
|
|
}
|
|
page, err := c.ListTaskOperations(ctx, task, HistoryPageRequest{Cursor: "opaque+cursor/="})
|
|
if err != nil || len(page.Items) != 1 {
|
|
t.Fatal(page, err)
|
|
}
|
|
detail, err := c.GetTaskOperation(ctx, task, audit)
|
|
if err != nil || string(detail.After) != `{"revision":"9007199254740994"}` {
|
|
t.Fatal(detail, err)
|
|
}
|
|
malformed.Store(true)
|
|
if _, err = c.ReviewTask(ctx, task, "stable-review", in); err == nil {
|
|
t.Fatal("inconsistent review accepted")
|
|
}
|
|
if _, err = c.RetryTask(ctx, task, "stable-retry", RetryTaskRequest{}); err == nil {
|
|
t.Fatal("missing retry parent accepted")
|
|
}
|
|
for _, id := range []string{"../tasks", strings.Repeat("A", 32), "id?token=secret"} {
|
|
if _, err = c.ReviewTask(ctx, id, "key", in); err == nil {
|
|
t.Fatal("unsafe path")
|
|
}
|
|
if _, err = c.RetryTask(ctx, id, "key", RetryTaskRequest{}); err == nil {
|
|
t.Fatal("unsafe path")
|
|
}
|
|
if _, err = c.GetTaskOperation(ctx, task, id); err == nil {
|
|
t.Fatal("unsafe audit path")
|
|
}
|
|
}
|
|
if _, err = c.ListTaskOperations(ctx, task, HistoryPageRequest{Limit: 101}); err == nil {
|
|
t.Fatal("unbounded page")
|
|
}
|
|
if calls.Load() != 6 {
|
|
t.Fatal("invalid path reached transport")
|
|
}
|
|
}
|