73 lines
2.9 KiB
Go
73 lines
2.9 KiB
Go
package orbit
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
)
|
|
|
|
func TestTaskClientContractAndNoSilentRetry(t *testing.T) {
|
|
const id = "11111111111111111111111111111111"
|
|
var calls atomic.Int32
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls.Add(1)
|
|
if r.Header.Get("Authorization") != "Bearer example-token" || r.Header.Get("Idempotency-Key") != "stable" {
|
|
t.Error("lost authorization or identity")
|
|
}
|
|
raw, _ := io.ReadAll(r.Body)
|
|
if !strings.Contains(string(raw), "9007199254740993") {
|
|
t.Error("payload lost integer precision")
|
|
}
|
|
if calls.Load() == 1 {
|
|
w.WriteHeader(503)
|
|
_, _ = io.WriteString(w, `{"request_id":"`+id+`","error":{"code":"UNAVAILABLE","message":"retry original identity"}}`)
|
|
return
|
|
}
|
|
_, _ = io.WriteString(w, `{"request_id":"`+id+`","data":{"deduplicated":true,"task":{"id":"`+id+`","revision":"9007199254740993","payload":{"sequence":9007199254740993}}}}`)
|
|
}))
|
|
defer srv.Close()
|
|
client, err := NewClient(srv.URL, Options{Token: "example-token"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
request := SubmitTaskRequest{QueueID: id, Payload: json.RawMessage(`{"sequence":9007199254740993}`)}
|
|
_, err = client.SubmitTask(context.Background(), "stable", request)
|
|
var apiErr *APIError
|
|
var httpErr *HTTPError
|
|
if !errors.As(err, &apiErr) || apiErr.Code != "UNAVAILABLE" || apiErr.RequestID != id || !errors.As(err, &httpErr) || httpErr.StatusCode != 503 || calls.Load() != 1 {
|
|
t.Fatalf("error/retry contract: %v, calls=%d", err, calls.Load())
|
|
}
|
|
out, err := client.SubmitTask(context.Background(), "stable", request)
|
|
if err != nil || !out.Deduplicated || out.Task.Revision != 9007199254740993 || string(out.Task.Payload) != `{"sequence":9007199254740993}` {
|
|
t.Fatalf("response: %+v %v", out, err)
|
|
}
|
|
}
|
|
func TestBusinessClientRedirectAndPathBoundaries(t *testing.T) {
|
|
var reached atomic.Bool
|
|
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reached.Store(true) }))
|
|
defer target.Close()
|
|
source := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, target.URL, 307) }))
|
|
defer source.Close()
|
|
client, _ := NewClient(source.URL, Options{Token: "example-token"})
|
|
_, err := client.GetTask(context.Background(), strings.Repeat("1", 32))
|
|
var failure *HTTPError
|
|
if !errors.As(err, &failure) || failure.StatusCode != 307 || reached.Load() {
|
|
t.Fatalf("redirect leaked request: %v", err)
|
|
}
|
|
for _, id := range []string{"../queues", "abc?token=bad", strings.Repeat("A", 32)} {
|
|
if _, err := client.GetTask(context.Background(), id); err == nil {
|
|
t.Fatal("accepted unsafe ID")
|
|
}
|
|
}
|
|
missing, _ := NewClient(source.URL, Options{})
|
|
if _, err := missing.GetQueue(context.Background(), strings.Repeat("1", 32)); err == nil {
|
|
t.Fatal("missing token allowed")
|
|
}
|
|
}
|