Something went wrong. Try again.
A lexicon-driven AppView for ATProto.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181import { test, expect } from "@playwright/test"import { loginAsTestAdmin } from "./auth-helper"
const PDS_URL = "http://localhost:3100"const PDS_PASSWORD = "Test-password-e2e-123"
const TEST_NSID = "test.e2e.linkedoauth.note"const TEST_LEXICON = { lexicon: 1, id: TEST_NSID, defs: { main: { type: "record", key: "tid", record: { type: "object", properties: { text: { type: "string" } } }, }, },}
async function createPdsAccount(): Promise<{ did: string; handle: string }> { const suffix = Date.now().toString(36) const handle = `linkedrepo-${suffix}.test` const resp = await fetch(`${PDS_URL}/xrpc/com.atproto.server.createAccount`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: `linkedrepo-${suffix}@example.com`, handle, password: PDS_PASSWORD, }), }) if (!resp.ok) { throw new Error(`PDS createAccount failed (${resp.status}): ${await resp.text()}`) } const data = (await resp.json()) as { did: string; handle?: string } return { did: data.did, handle: data.handle ?? handle }}
/** * The one path nothing else covers: an invite link followed all the way through * a REAL PDS authorization. Every other test either calls `flow::complete` * directly or asserts an expected failure, which is exactly how a missing * `atproto` scope in the authorization request survived twelve reviews — the * first thing a real user does had never been exercised. */test.describe("Linked Repos — real OAuth authorization", () => { let account: { did: string; handle: string } let grantId: string let inviteToken: string
test.beforeAll(async ({ browser }) => { account = await createPdsAccount()
const page = await browser.newPage() await loginAsTestAdmin(page)
const lex = await page.request.post("/admin/lexicons", { data: { lexicon_json: TEST_LEXICON, backfill: false, target_collection: TEST_NSID, }, }) if (!lex.ok() && !(await lex.text()).includes("already exists")) { throw new Error(`lexicon seed failed: ${lex.status()}`) }
const created = await page.request.post("/admin/linked-repos", { data: { reason: "e2e real-oauth grant", scopes: `repo:${TEST_NSID}?action=create`, }, }) expect(created.ok(), `create grant: ${await created.text()}`).toBeTruthy() grantId = (await created.json()).id
const invited = await page.request.post( `/admin/linked-repos/${grantId}/invite`, { data: {} }, ) expect(invited.ok()).toBeTruthy() inviteToken = (await invited.json()).invite_url.split("token=")[1]
await page.close() })
test.afterAll(async ({ browser }) => { if (!grantId) return const page = await browser.newPage() await loginAsTestAdmin(page) await page.request.delete(`/admin/linked-repos/${grantId}`) await page.close() })
test("an invite link authorizes a real repo end to end", async ({ page }) => { // Deliberately unauthenticated: this is a stranger following a link. await page.goto(`/auth/linked-repo/start?token=${inviteToken}`) await expect(page).toHaveURL(/\/link\/start\/?\?token=/)
// The landing page must say what is being asked before asking for anything. await expect(page.getByText(TEST_NSID).first()).toBeVisible({ timeout: 15000, })
// The DID, not the handle: `.test` domains cannot be resolved from inside // the Docker network, and `resolve_identifier` accepts either. The same // constraint is documented in setup-attach-account.spec.ts. await page.getByPlaceholder("you.bsky.social").fill(account.did) await page.getByRole("button", { name: /continue/i }).click()
// -> PDS login await page.waitForURL(/pds\.localhost/, { timeout: 30000 }) await page.locator("#username").fill(account.handle) await page.locator("#password").fill(PDS_PASSWORD) await page.locator("button[type='submit']").click()
// The PDS may show a consent screen or redirect straight back. const outcome = await Promise.race([ page .waitForURL(/127\.0\.0\.1:3200/, { timeout: 30000 }) .then(() => "callback" as const), page .locator("text=/authorize/i") .waitFor({ timeout: 10000 }) .then(() => "consent" as const) .catch(() => null), ]) if (outcome === "consent") { await page.locator("button", { hasText: /authorize/i }).last().click() await page.waitForURL(/127\.0\.0\.1:3200/, { timeout: 20000 }) }
// The invitee lands on the PUBLIC result page — never the admin dashboard, // which they cannot view. await page.waitForURL(/\/link\/result/, { timeout: 20000 }) const url = new URL(page.url()) expect( url.searchParams.get("status"), `expected success, got ${page.url()}`, ).toBe("success") // This grant was created open (no handle), so the callback reports the DID // it bound rather than a handle. await expect(page.getByText(account.did).first()).toBeVisible() })
test("the grant is now active and bound to the authorized DID", async ({ browser, }) => { const page = await browser.newPage() await loginAsTestAdmin(page)
const resp = await page.request.get("/admin/linked-repos") const { linked_repos } = await resp.json() const grant = linked_repos.find((g: { id: string }) => g.id === grantId)
expect(grant, "the grant should still exist").toBeTruthy() expect(grant.status).toBe("active") expect(grant.did).toBe(account.did) expect(grant.last_error).toBeNull()
await page.close() })
test("linking retires the invite", async ({ browser }) => { const page = await browser.newPage() await loginAsTestAdmin(page)
const resp = await page.request.get( `/admin/linked-repos/${grantId}/invites`, ) expect(resp.ok()).toBeTruthy() const { invites } = await resp.json() expect( invites, "a completed link must leave no outstanding invites", ).toHaveLength(0)
await page.close() })})