139 lines
4.8 KiB
Go
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)
|
|
}
|
|
}
|