mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-05-23 16:31:31 +00:00
107 lines
4.4 KiB
Go
107 lines
4.4 KiB
Go
//go:build integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"testing"
|
|
"time"
|
|
|
|
"atcr.io/internal/testharness"
|
|
"atcr.io/pkg/atproto"
|
|
)
|
|
|
|
// TestAuthTokenBootstrapsLexicons verifies that a user who has never pushed
|
|
// or pulled — and therefore has no sailor profile in their PDS and no crew
|
|
// record in the hold's PDS — gets both written by the time their very first
|
|
// /auth/token call returns.
|
|
//
|
|
// The two writes happen on different code paths:
|
|
// - sailor profile: post-auth callback runs storage.EnsureProfile against
|
|
// the user's PDS (the fake testpds in this test).
|
|
// - hold crew: authgate.Authorize runs storage.EnsureCrewMembership, which
|
|
// mints a service-auth via the user's PDS and POSTs requestCrew to the
|
|
// hold, which writes io.atcr.hold.crew into its embedded PDS.
|
|
//
|
|
// Both run during the same /auth/token request (the post-auth callback is
|
|
// synchronous and the authgate goroutine is awaited before the handler
|
|
// returns), so a 200 from /auth/token is the signal that both records exist.
|
|
func TestAuthTokenBootstrapsLexicons(t *testing.T) {
|
|
h := testharness.New(t)
|
|
|
|
// Use AddStranger rather than AddSailor: a stranger has a PDS identity
|
|
// and a users-table row (so PDS resolution works) but no crew_members
|
|
// row anywhere. That's the closest thing to a brand-new user — no
|
|
// pre-seeded crew membership in either the AppView or the hold's PDS,
|
|
// and no sailor profile record in their PDS.
|
|
sailor := h.AddStranger("newcomer.test")
|
|
|
|
// Pre-condition: no sailor profile in the user's PDS.
|
|
if _, ok := h.PDS.GetRecord(sailor.DID(), atproto.SailorProfileCollection, "self"); ok {
|
|
t.Fatalf("expected no sailor profile before /auth/token, but found one for %s", sailor.DID())
|
|
}
|
|
|
|
// Pre-condition: no crew record for this user in the hold's PDS. The
|
|
// lookup wraps repomgr's "not found" without a sentinel, so any non-nil
|
|
// error here means "absent" — and a successful return is what we want
|
|
// to assert is impossible at this point.
|
|
if _, _, err := h.Hold.PDS.GetCrewMemberByDID(t.Context(), sailor.DID()); err == nil {
|
|
t.Fatalf("expected no crew record before /auth/token, but found one for %s", sailor.DID())
|
|
}
|
|
|
|
// Trigger /auth/token via Basic auth. Docker's login does this same call
|
|
// (no scope param) just to validate creds. The handler caches the access
|
|
// token, runs the post-auth callback (EnsureProfile), and waits on the
|
|
// authgate goroutine (EnsureCrewMembership) before returning.
|
|
tokenURL := h.AppViewURL + "/auth/token?service=" + url.QueryEscape("127.0.0.1")
|
|
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, tokenURL, nil)
|
|
if err != nil {
|
|
t.Fatalf("build token request: %v", err)
|
|
}
|
|
req.SetBasicAuth(sailor.Handle(), sailor.Identity.Password)
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatalf("call /auth/token: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("/auth/token: want 200, got %d", resp.StatusCode)
|
|
}
|
|
|
|
// Post-condition: sailor profile now exists in the user's PDS, anchored
|
|
// to the AppView's default hold. EnsureProfile runs synchronously inside
|
|
// the post-auth callback, so it's done by the time the handler returns.
|
|
raw, ok := h.PDS.GetRecord(sailor.DID(), atproto.SailorProfileCollection, "self")
|
|
if !ok {
|
|
t.Fatalf("expected sailor profile in PDS after /auth/token, found none for %s", sailor.DID())
|
|
}
|
|
var profile atproto.SailorProfileRecord
|
|
if err := json.Unmarshal(raw, &profile); err != nil {
|
|
t.Fatalf("decode sailor profile: %v", err)
|
|
}
|
|
if profile.DefaultHold != h.HoldDID {
|
|
t.Errorf("sailor profile defaultHold = %q, want %q", profile.DefaultHold, h.HoldDID)
|
|
}
|
|
|
|
// Post-condition: hold's embedded PDS now has a crew record for this
|
|
// user. The authgate runs EnsureCrewMembership inside a goroutine that
|
|
// the handler awaits, so this should be visible by the time /auth/token
|
|
// returned. We allow a small poll window to absorb any IPC scheduling
|
|
// jitter (the records-index update on the hold side is its own goroutine
|
|
// behind the repomgr write).
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
var lastErr error
|
|
for time.Now().Before(deadline) {
|
|
if _, _, err := h.Hold.PDS.GetCrewMemberByDID(t.Context(), sailor.DID()); err == nil {
|
|
return
|
|
} else {
|
|
lastErr = err
|
|
}
|
|
time.Sleep(25 * time.Millisecond)
|
|
}
|
|
t.Fatalf("expected hold crew record after /auth/token for %s, last lookup error: %v", sailor.DID(), lastErr)
|
|
}
|