Files
go-fa-api/request_options_test.go
2026-05-26 20:21:55 +02:00

139 lines
4.8 KiB
Go

package fa
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
)
// TestRequestOverride_PerCallCookiesAndUA exercises the multi-tenant flow:
// one Client built with default creds, two calls in a row each carrying a
// different user's cookies via per-call Options. The server records the
// Cookie + User-Agent headers it saw on each request; the per-call values
// must override the client's, and the client's defaults must come back on
// a call that passes no overrides.
func TestRequestOverride_PerCallCookiesAndUA(t *testing.T) {
type seen struct {
cookie string
userAgent string
}
var (
mu sync.Mutex
hits []seen
)
mux := http.NewServeMux()
mux.HandleFunc("/view/1/", func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
hits = append(hits, seen{cookie: r.Header.Get("Cookie"), userAgent: r.Header.Get("User-Agent")})
mu.Unlock()
w.Header().Set("Content-Type", "text/html")
_, _ = w.Write([]byte(syntheticSubmissionHTML))
})
srv := httptest.NewServer(mux)
defer srv.Close()
// Client built with default ("system") creds plus a default UA.
client := newE2EClient(t, srv)
// Layer the client default cookies in via WithCookies so we can verify
// they appear when no override is passed.
clientWithDefaults := New(
WithHTTPClient(client.http),
WithRateLimit(0, 16),
WithMaxRetries(0),
WithUserAgent("default-ua/1.0"),
WithCookies(Cookies{A: "defaultA", B: "defaultB"}),
)
ctx := context.Background()
// Call 1: no override → client defaults must apply.
if _, err := clientWithDefaults.GetSubmission(ctx, 1); err != nil {
t.Fatalf("call 1: %v", err)
}
// Call 2: per-user override (user-A creds + user-A UA).
if _, err := clientWithDefaults.GetSubmission(ctx, 1,
WithCookies(Cookies{A: "userA_a", B: "userA_b"}),
WithCloudflare(CFCookies{Clearance: "userA_cf"}),
WithUserAgent("userA-browser/1.0"),
); err != nil {
t.Fatalf("call 2: %v", err)
}
// Call 3: a different user.
if _, err := clientWithDefaults.GetSubmission(ctx, 1,
WithCookies(Cookies{A: "userB_a", B: "userB_b"}),
); err != nil {
t.Fatalf("call 3: %v", err)
}
mu.Lock()
defer mu.Unlock()
if len(hits) != 3 {
t.Fatalf("hits = %d; want 3", len(hits))
}
// Call 1: default cookies + default UA.
if !strings.Contains(hits[0].cookie, "a=defaultA") || !strings.Contains(hits[0].cookie, "b=defaultB") {
t.Errorf("call 1 cookie = %q; want default a/b", hits[0].cookie)
}
if hits[0].userAgent != "default-ua/1.0" {
t.Errorf("call 1 UA = %q; want default-ua/1.0", hits[0].userAgent)
}
// Call 2: user-A creds replace the jar's defaults wholesale.
if !strings.Contains(hits[1].cookie, "a=userA_a") || !strings.Contains(hits[1].cookie, "b=userA_b") {
t.Errorf("call 2 cookie = %q; want user-A a/b", hits[1].cookie)
}
if !strings.Contains(hits[1].cookie, "cf_clearance=userA_cf") {
t.Errorf("call 2 cookie missing cf_clearance: %q", hits[1].cookie)
}
if strings.Contains(hits[1].cookie, "defaultA") || strings.Contains(hits[1].cookie, "defaultB") {
t.Errorf("call 2 cookie leaked client defaults: %q", hits[1].cookie)
}
if hits[1].userAgent != "userA-browser/1.0" {
t.Errorf("call 2 UA = %q; want userA-browser/1.0", hits[1].userAgent)
}
// Call 3: user-B cookies override, but UA falls back to client default
// because the override did not touch it.
if !strings.Contains(hits[2].cookie, "a=userB_a") || !strings.Contains(hits[2].cookie, "b=userB_b") {
t.Errorf("call 3 cookie = %q; want user-B a/b", hits[2].cookie)
}
if hits[2].userAgent != "default-ua/1.0" {
t.Errorf("call 3 UA = %q; want default-ua/1.0 (no override)", hits[2].userAgent)
}
}
// TestRequestOverride_NoOverrideMeansNoCtxValue is a cheap sanity check
// that applyRequestOptions short-circuits when nothing request-level
// actually changed (e.g. caller passed only client-only options).
func TestRequestOverride_NoOverrideMeansNoCtxValue(t *testing.T) {
c := New(WithCookies(Cookies{A: "x", B: "y"}))
ctx := context.Background()
// No options at all → same ctx.
if got := c.applyRequestOptions(ctx, nil); got != ctx {
t.Error("nil opts should pass through ctx unchanged")
}
// A client-only option that does not touch request-level fields.
got := c.applyRequestOptions(ctx, []Option{WithMaxRetries(7)})
if got != ctx {
t.Error("client-only option should not attach a request override")
}
// A request-level option that happens to equal the current value.
got = c.applyRequestOptions(ctx, []Option{WithCookies(Cookies{A: "x", B: "y"})})
if got != ctx {
t.Error("override matching client config should not attach")
}
// A real override.
got = c.applyRequestOptions(ctx, []Option{WithCookies(Cookies{A: "z", B: "w"})})
if got == ctx {
t.Error("real override should produce a new ctx")
}
if ov := requestOverrideFrom(got); ov == nil || ov.cookies.A != "z" {
t.Errorf("override ctx missing or wrong: %+v", ov)
}
}