package orbit import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "sync/atomic" "testing" ) func TestHistoryClientPathsPrecisionAndBounds(t *testing.T) { const task = "11111111111111111111111111111111" const attempt = "22222222222222222222222222222222" const evidence = "33333333333333333333333333333333" var calls atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls.Add(1) if r.Method != "GET" || r.Header.Get("Authorization") != "Bearer example-token" || r.Header.Get("Idempotency-Key") != "" { t.Error("invalid history request headers") } var data any switch r.URL.Path { case "/v1/tasks/" + task + "/attempts": if r.URL.Query().Get("cursor") != "opaque+cursor/=" || r.URL.Query().Get("limit") != "2" { t.Error("cursor encoding changed") } data = AttemptPage{Items: []AttemptSummary{{ID: attempt, TaskID: task, Epoch: 9007199254740993, Revision: 9007199254740995}}} case "/v1/tasks/" + task + "/attempts/" + attempt: data = Attempt{AttemptSummary: AttemptSummary{ID: attempt, TaskID: task}, Result: json.RawMessage(`{"n":9007199254740993}`)} case "/v1/tasks/" + task + "/attempts/" + attempt + "/evidence": data = AttemptEvidencePage{Items: []AttemptEvidenceSummary{{ID: evidence, AttemptID: attempt, TaskID: task, Disposition: "EVIDENCE_ONLY"}}} case "/v1/tasks/" + task + "/attempts/" + attempt + "/evidence/" + evidence: data = AttemptEvidence{AttemptEvidenceSummary: AttemptEvidenceSummary{ID: evidence}, Payload: json.RawMessage(`{"ratio":0.1234567890123456789012345}`)} default: t.Error("unexpected history path") } raw, _ := json.Marshal(map[string]any{"request_id": task, "data": data}) w.Write(raw) })) defer server.Close() c, _ := NewClient(server.URL, Options{Token: "example-token"}) ctx := context.Background() page, err := c.ListTaskAttempts(ctx, task, HistoryPageRequest{Limit: 2, Cursor: "opaque+cursor/="}) if err != nil || page.Items[0].Epoch != 9007199254740993 || page.Items[0].Revision != 9007199254740995 { t.Fatal("attempt integers lost precision") } a, err := c.GetTaskAttempt(ctx, task, attempt) if err != nil || string(a.Result) != `{"n":9007199254740993}` { t.Fatal("attempt result changed") } ep, err := c.ListAttemptEvidence(ctx, task, attempt, HistoryPageRequest{}) if err != nil || len(ep.Items) != 1 || ep.Items[0].Disposition != "EVIDENCE_ONLY" { t.Fatal("evidence classification changed") } e, err := c.GetAttemptEvidence(ctx, task, attempt, evidence) if err != nil || string(e.Payload) != `{"ratio":0.1234567890123456789012345}` { t.Fatal("evidence precision changed") } for _, id := range []string{"../queues", strings.Repeat("A", 32), "id?secret=x"} { if _, err := c.ListTaskAttempts(ctx, id, HistoryPageRequest{}); err == nil { t.Fatal("invalid parent id accepted") } if _, err := c.GetTaskAttempt(ctx, task, id); err == nil { t.Fatal("invalid attempt id accepted") } if _, err := c.ListAttemptEvidence(ctx, task, id, HistoryPageRequest{}); err == nil { t.Fatal("invalid evidence parent accepted") } if _, err := c.GetAttemptEvidence(ctx, task, attempt, id); err == nil { t.Fatal("invalid evidence id accepted") } } for _, page := range []HistoryPageRequest{{Limit: -1}, {Limit: 101}, {Cursor: strings.Repeat("x", 1025)}} { if _, err := c.ListTaskAttempts(ctx, task, page); err == nil { t.Fatal("invalid page accepted") } } if calls.Load() != 4 { t.Fatal("invalid history input reached server") } }