// Preferences handler tests - auth, read-only, and the array shape import { describe, expect, it } from 'vitest'; import { createPreferencesHandlers } from '../packages/core/src/handlers/xrpc-preferences.js'; const DID = 'did:plc:preferencestestaccount'; const FULL = { did: DID, scope: 'com.atproto.access' }; /** @param {{readOnly?: boolean, stored?: unknown[]}} [options] */ function createPreferences({ readOnly = false, stored = [] } = {}) { let preferences = stored; const handlers = createPreferencesHandlers({ actorStorage: /** @type {any} */ ({ async getPreferences() { return preferences; }, async setPreferences(/** @type {unknown[]} */ next) { preferences = next; }, }), readOnly, readOnlyError: () => Response.json( { error: 'AuthenticationRequired', message: 'This PDS is read-only' }, { status: 401 }, ), }); return { handlers, read: () => preferences }; } const GET = '/xrpc/app.bsky.actor.getPreferences'; const PUT = '/xrpc/app.bsky.actor.putPreferences'; /** * @param {ReturnType} handlers * @param {string} path * @param {{body?: Object, auth?: {did: string, scope: string}|null}} [options] */ async function call(handlers, path, { body, auth = null } = {}) { const route = handlers.routes[path]; const url = new URL(`https://pds.example.com${path}`); /** @type {RequestInit} */ const init = { method: body ? 'POST' : 'GET' }; if (body) init.body = JSON.stringify(body); const handler = /** @type {(request: Request, url: URL, auth: unknown) => Promise} */ ( route.handler ); return handler(new Request(url, init), url, auth); } describe('getPreferences', () => { it('answers with what is stored', async () => { const { handlers } = createPreferences({ stored: [{ $type: 'app.bsky.actor.defs#adultContentPref' }], }); const body = await (await call(handlers, GET, { auth: FULL })).json(); expect(body.preferences).toHaveLength(1); }); it('needs a session', async () => { const { handlers } = createPreferences(); const response = await call(handlers, GET); expect(response.status).toBe(401); expect((await response.json()).error).toBe('AuthenticationRequired'); }); }); describe('putPreferences', () => { it('replaces the stored array', async () => { const { handlers, read } = createPreferences({ stored: ['old'] }); const response = await call(handlers, PUT, { auth: FULL, body: { preferences: ['new'] }, }); expect(response.status).toBe(200); expect(await response.json()).toEqual({}); expect(read()).toEqual(['new']); }); it('refuses a read-only server', async () => { const { handlers, read } = createPreferences({ readOnly: true, stored: ['old'], }); const response = await call(handlers, PUT, { auth: FULL, body: { preferences: ['new'] }, }); expect(response.status).toBe(401); expect(read()).toEqual(['old']); }); it('needs a session', async () => { const { handlers } = createPreferences(); const response = await call(handlers, PUT, { body: { preferences: [] }, }); expect(response.status).toBe(401); }); it('refuses anything that is not an array', async () => { const { handlers, read } = createPreferences({ stored: ['old'] }); const response = await call(handlers, PUT, { auth: FULL, body: { preferences: { nope: true } }, }); expect(response.status).toBe(400); expect((await response.json()).message).toMatch(/must be an array/); expect(read()).toEqual(['old']); }); });