package authgate import ( "context" "database/sql" "net/http" "net/http/httptest" "strings" "testing" "atcr.io/pkg/appview/db" "atcr.io/pkg/atproto" "atcr.io/pkg/auth" ) // newTestDB returns an in-memory libsql DB with the full appview schema // applied. Tears down on test completion. func newTestDB(t *testing.T) *sql.DB { t.Helper() testDB, err := db.InitDB(":memory:", db.LibsqlConfig{}) if err != nil { t.Fatalf("InitDB: %v", err) } t.Cleanup(func() { _ = testDB.Close() }) return testDB } // seedUser upserts a users row and optionally sets default_hold_did. // Pass "" for defaultHold to leave it NULL. func seedUser(t *testing.T, d *sql.DB, did, handle, defaultHold string) { t.Helper() if err := db.UpsertUser(d, &db.User{DID: did, Handle: handle, PDSEndpoint: "https://pds.example/" + did}); err != nil { t.Fatalf("UpsertUser(%s): %v", did, err) } if defaultHold != "" { if err := db.UpdateUserDefaultHold(d, did, defaultHold); err != nil { t.Fatalf("UpdateUserDefaultHold(%s, %s): %v", did, defaultHold, err) } } } // seedCaptain inserts a single hold_captain_records row. func seedCaptain(t *testing.T, d *sql.DB, holdDID, ownerDID string) { t.Helper() if err := db.BatchUpsertCaptainRecords(d, []db.HoldCaptainRecord{ {HoldDID: holdDID, OwnerDID: ownerDID, Public: false, AllowAllCrew: false}, }); err != nil { t.Fatalf("BatchUpsertCaptainRecords(%s, %s): %v", holdDID, ownerDID, err) } } // seedCrewMember inserts a single hold_crew_members row with the given // permissions JSON (pass "" to leave permissions NULL — note that the // underlying schema may coerce empty strings; pass `"[]"` for an empty // permissions array). func seedCrewMember(t *testing.T, d *sql.DB, holdDID, memberDID, permsJSON string) { t.Helper() if err := db.BatchUpsertCrewMembers(d, []db.CrewMember{ {HoldDID: holdDID, MemberDID: memberDID, Rkey: "rkey-" + memberDID, Role: "crew", Permissions: permsJSON}, }); err != nil { t.Fatalf("BatchUpsertCrewMembers(%s, %s): %v", holdDID, memberDID, err) } } // quotaServerResult captures HTTP traffic the server saw, for assertions. type quotaServerResult struct { server *httptest.Server holdDID string // did:web:127.0.0.1%3APORT form hits int lastURL string } // quotaServer spins up an httptest.Server that responds to every request // with the given status + body, records hit count + last URL, and returns // both the server URL and the did:web:HOST form that resolves to it under // atproto.SetTestMode(true). func quotaServer(t *testing.T, status int, body string) *quotaServerResult { t.Helper() res := "aServerResult{} res.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { res.hits++ res.lastURL = r.URL.String() w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _, _ = w.Write([]byte(body)) })) t.Cleanup(res.server.Close) // httptest.Server.URL has the form "http://127.0.0.1:PORT". The // did:web equivalent percent-encodes the colon; didWebToURL reverses // the encoding and re-derives the http://host:port form, which is // what we need for atproto.SetTestMode(true) to route requests. host := strings.TrimPrefix(res.server.URL, "http://") res.holdDID = "did:web:" + strings.Replace(host, ":", "%3A", 1) return res } // httpClient returns the server's client, which trusts its TLS cert (n/a // here since httptest.NewServer is HTTP) and routes to the loopback. func (r *quotaServerResult) httpClient() *http.Client { return r.server.Client() } // fakeHoldAuthorizer is a no-op auth.HoldAuthorizer stub. The Authorize // orchestration tests don't exercise the reconciliation closure (the // closure is nil for our purposes because we don't supply a refresher // and don't go through ResolveIdentity), so we don't need atomic // counters — just zero-value returns. type fakeHoldAuthorizer struct{} func (fakeHoldAuthorizer) CheckReadAccess(_ context.Context, _, _ string) (bool, error) { return true, nil } func (fakeHoldAuthorizer) CheckWriteAccess(_ context.Context, _, _ string) (bool, error) { return true, nil } func (fakeHoldAuthorizer) GetCaptainRecord(_ context.Context, _ string) (*atproto.CaptainRecord, error) { return nil, nil } func (fakeHoldAuthorizer) IsCrewMember(_ context.Context, _, _ string) (bool, error) { return false, nil } func (fakeHoldAuthorizer) ClearCrewDenial(_ context.Context, _, _ string) error { return nil } func (fakeHoldAuthorizer) IsCachedCrewMember(_ context.Context, _, _ string) (bool, error) { return false, nil } func (fakeHoldAuthorizer) RecordCrewApproval(_ context.Context, _, _ string) error { return nil } var _ auth.HoldAuthorizer = fakeHoldAuthorizer{}