chadtmiller.com/pds.js
An atproto PDS in JavaScript, for Cloudflare Workers and Node
0
0
// 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<typeof createPreferencesHandlers>} 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<Response>} */ (
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']);
});
});