Something went wrong. Try again.
A lexicon-driven AppView for ATProto.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372import { test, expect } from "@playwright/test"import pg from "pg"import { loginAsTestAdmin } from "./auth-helper"
const TEST_TYPE_NSID = "com.example.testspace"const TEST_SKEY = "e2e-test-space"const DB_URL = "postgres://happyview:happyview@localhost:5434/happyview_test"
async function enableSpacesFeature(): Promise<void> { const client = new pg.Client(DB_URL) await client.connect() try { const now = new Date().toISOString() await client.query( `INSERT INTO happyview_instance_settings (key, value, updated_at) VALUES ('feature.spaces_enabled', 'true', $1) ON CONFLICT (key) DO UPDATE SET value = 'true', updated_at = $1`, [now], ) } finally { await client.end() }}
test.describe("Spaces API", () => { let createdSpaceUri: string | null = null
test.beforeEach(async ({ page }) => { await enableSpacesFeature() await loginAsTestAdmin(page) })
test.afterEach(async ({ page }) => { if (!createdSpaceUri) return await page.request.post("/xrpc/com.atproto.simplespace.deleteSpace", { data: { space: createdSpaceUri }, }) createdSpaceUri = null })
test("create space and verify it appears in listSpaces", async ({ page }) => { const createResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY, displayName: "E2E Test Space", mintPolicy: "member-list", }, }, )
if (!createResp.ok()) { const errBody = await createResp.text() throw new Error(`createSpace failed (${createResp.status()}): ${errBody}`) } const createBody = await createResp.json() expect(createBody).toHaveProperty("uri") expect(createBody.uri).toMatch(/^at:\/\/.+\/space\//) createdSpaceUri = createBody.uri
const listResp = await page.request.get( "/xrpc/com.atproto.space.listSpaces", ) expect(listResp.ok()).toBe(true) const listBody = await listResp.json() expect(listBody).toHaveProperty("spaces")
const found = listBody.spaces.some( (s: { uri: string }) => s.uri === createdSpaceUri, ) expect(found).toBe(true) })
test("getSpace returns the created space", async ({ page }) => { const createResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY + "-get", displayName: "GetSpace Test", }, }, ) expect(createResp.ok()).toBe(true) const { uri } = await createResp.json() createdSpaceUri = uri
const getResp = await page.request.get("/xrpc/com.atproto.space.getSpace", { params: { space: uri }, }) expect(getResp.ok()).toBe(true) const getBody = await getResp.json() expect(getBody.space.display_name).toBe("GetSpace Test") expect(getBody.space.mint_policy).toBe("member-list") })
test("create duplicate space returns conflict", async ({ page }) => { const createResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY + "-dup", }, }, ) expect(createResp.ok()).toBe(true) createdSpaceUri = (await createResp.json()).uri
const dupResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY + "-dup", }, }, ) expect(dupResp.status()).toBe(409) })
test("updateSpace changes display name", async ({ page }) => { const createResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY + "-update", displayName: "Before Update", }, }, ) if (!createResp.ok()) { throw new Error( `createSpace failed (${createResp.status()}): ${await createResp.text()}`, ) } const { uri } = await createResp.json() createdSpaceUri = uri
const updateResp = await page.request.post( "/xrpc/com.atproto.simplespace.updateSpace", { data: { space: uri, displayName: "After Update" } }, ) expect(updateResp.ok()).toBe(true) const updateBody = await updateResp.json() expect(updateBody.space.display_name).toBe("After Update") })
test("deleteSpace removes the space", async ({ page }) => { const createResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY + "-delete", }, }, ) if (!createResp.ok()) { throw new Error( `createSpace failed (${createResp.status()}): ${await createResp.text()}`, ) } const { uri } = await createResp.json()
const deleteResp = await page.request.post( "/xrpc/com.atproto.simplespace.deleteSpace", { data: { space: uri } }, ) expect(deleteResp.ok()).toBe(true)
const getResp = await page.request.get( "/xrpc/com.atproto.space.getSpace", { params: { space: uri } }, ) expect(getResp.status()).toBe(404) })
test("addMember returns 201", async ({ page }) => { const createResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY + "-add-member", }, }, ) if (!createResp.ok()) { throw new Error( `createSpace failed (${createResp.status()}): ${await createResp.text()}`, ) } const { uri } = await createResp.json() createdSpaceUri = uri
const addResp = await page.request.post( "/xrpc/com.atproto.simplespace.addMember", { data: { space: uri, did: "did:plc:test-member", access: "read" } }, ) expect(addResp.status()).toBe(201) const addBody = await addResp.json() expect(addBody.member.did).toBe("did:plc:test-member") })
test("removeMember removes a previously added member", async ({ page }) => { const createResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY + "-remove-member", }, }, ) if (!createResp.ok()) { throw new Error( `createSpace failed (${createResp.status()}): ${await createResp.text()}`, ) } const { uri } = await createResp.json() createdSpaceUri = uri
await page.request.post("/xrpc/com.atproto.simplespace.addMember", { data: { space: uri, did: "did:plc:test-member-rm", access: "read" }, })
const removeResp = await page.request.post( "/xrpc/com.atproto.simplespace.removeMember", { data: { space: uri, did: "did:plc:test-member-rm" } }, ) expect(removeResp.ok()).toBe(true) })
test("listMembers includes added member", async ({ page }) => { const createResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY + "-list-members", }, }, ) if (!createResp.ok()) { throw new Error( `createSpace failed (${createResp.status()}): ${await createResp.text()}`, ) } const { uri } = await createResp.json() createdSpaceUri = uri
await page.request.post("/xrpc/com.atproto.simplespace.addMember", { data: { space: uri, did: "did:plc:test-member-list", access: "write" }, })
const listResp = await page.request.get( "/xrpc/com.atproto.simplespace.listMembers", { params: { space: uri } }, ) expect(listResp.ok()).toBe(true) const listBody = await listResp.json() expect(listBody.members).toBeInstanceOf(Array) const found = listBody.members.some( (m: { did: string }) => m.did === "did:plc:test-member-list", ) expect(found).toBe(true) })
test("getConfig returns space configuration", async ({ page }) => { const createResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY + "-get-config", mintPolicy: "member-list", }, }, ) if (!createResp.ok()) { throw new Error( `createSpace failed (${createResp.status()}): ${await createResp.text()}`, ) } const { uri } = await createResp.json() createdSpaceUri = uri
const configResp = await page.request.get( "/xrpc/com.atproto.simplespace.getConfig", { params: { space: uri } }, ) expect(configResp.ok()).toBe(true) const configBody = await configResp.json() expect(configBody.mintPolicy).toBe("member-list") })
test("updateConfig changes mint policy", async ({ page }) => { const createResp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: TEST_TYPE_NSID, skey: TEST_SKEY + "-update-config", mintPolicy: "member-list", }, }, ) if (!createResp.ok()) { throw new Error( `createSpace failed (${createResp.status()}): ${await createResp.text()}`, ) } const { uri } = await createResp.json() createdSpaceUri = uri
const updateResp = await page.request.post( "/xrpc/com.atproto.simplespace.updateConfig", { data: { space: uri, mintPolicy: "public" } }, ) expect(updateResp.ok()).toBe(true)
const configResp = await page.request.get( "/xrpc/com.atproto.simplespace.getConfig", { params: { space: uri } }, ) expect(configResp.ok()).toBe(true) const configBody = await configResp.json() expect(configBody.mintPolicy).toBe("public") })})
async function disableSpacesFeature(): Promise<void> { const client = new pg.Client(DB_URL) await client.connect() try { await client.query( `DELETE FROM happyview_instance_settings WHERE key = 'feature.spaces_enabled'`, ) } finally { await client.end() }}
test.describe("Spaces Feature Flag", () => { test.beforeEach(async ({ page }) => { await disableSpacesFeature() await loginAsTestAdmin(page) })
test("spaces endpoints return 404 when feature is disabled", async ({ page, }) => { const resp = await page.request.post( "/xrpc/com.atproto.simplespace.createSpace", { data: { type: "com.example.test", skey: "flag-test", }, }, ) expect(resp.status()).toBe(404) const body = await resp.json() expect(body.error).toBe("FeatureDisabled") })})