import { describe, expect, it } from 'vitest'; import { runSpaceStorageConformance } from '../../../test/helpers/space-storage-conformance.js'; import { createSpaceStorage } from '../src/space.js'; runSpaceStorageConformance('storage-sqlite', async () => { const Database = (await import('better-sqlite3')).default; return createSpaceStorage(new Database(':memory:')); }); describe('opening a database from before space revisions', () => { it('sequences each recorded writer at its repo rev', async () => { const Database = (await import('better-sqlite3')).default; const db = new Database(':memory:'); db.exec(`CREATE TABLE space_writer ( space TEXT NOT NULL, did TEXT NOT NULL, rev TEXT NOT NULL, hash BLOB NOT NULL, PRIMARY KEY (space, did) )`); db.prepare( 'INSERT INTO space_writer (space, did, rev, hash) VALUES (?, ?, ?, ?)', ).run( 'at://did:plc:a/space/x.y.z/s', 'did:plc:w', '3lkaaaaaaaa22', Buffer.alloc(32), ); const store = createSpaceStorage(db); const { writers, cursor } = await store.listSpaceWriters( 'at://did:plc:a/space/x.y.z/s', null, 10, ); expect(writers).toEqual([ expect.objectContaining({ did: 'did:plc:w', repoRev: '3lkaaaaaaaa22', spaceRev: '3lkaaaaaaaa22', }), ]); expect(cursor).toBe('3lkaaaaaaaa22'); expect(await store.getSpaceRevHead('at://did:plc:a/space/x.y.z/s')).toBe( '3lkaaaaaaaa22', ); }); }); describe('opening a database from before space policies', () => { const A = 'at://did:plc:a/space/x.y.z/members'; const B = 'at://did:plc:a/space/x.y.z/public'; const C = 'at://did:plc:a/space/x.y.z/managed'; const D = 'at://did:plc:b/space/x.y.z/joined'; async function openLegacy() { const Database = (await import('better-sqlite3')).default; const db = new Database(':memory:'); db.exec(`CREATE TABLE space ( uri TEXT PRIMARY KEY, space_did TEXT NOT NULL, space_type TEXT NOT NULL, is_owner INTEGER NOT NULL, read_policy TEXT NOT NULL DEFAULT 'member-list', read_managing_app TEXT, write_policy TEXT NOT NULL DEFAULT 'member-list', write_managing_app TEXT, app_access_type TEXT NOT NULL DEFAULT 'open', app_allowed TEXT NOT NULL DEFAULT '[]', created_at TEXT NOT NULL, deleted_at TEXT ); CREATE TABLE space_member ( space TEXT NOT NULL, did TEXT NOT NULL, can_read INTEGER NOT NULL DEFAULT 1, can_write INTEGER NOT NULL DEFAULT 1, PRIMARY KEY (space, did) );`); const insert = db.prepare( `INSERT INTO space (uri, space_did, space_type, is_owner, read_policy, read_managing_app, write_policy, write_managing_app, app_access_type, app_allowed, created_at) VALUES (?, ?, 'x.y.z', ?, ?, ?, ?, ?, ?, ?, '2026-10-01T00:00:00.000Z')`, ); insert.run( A, 'did:plc:a', 1, 'member-list', null, 'member-list', null, 'open', '[]', ); insert.run( B, 'did:plc:a', 1, 'public', null, 'member-list', null, 'allowList', '["https://app.example/c.json"]', ); insert.run( C, 'did:plc:a', 1, 'member-list', null, 'managing-app', 'did:web:app.example#svc', 'allowList', '["https://app.example/c.json"]', ); insert.run( D, 'did:plc:b', 0, 'member-list', null, 'member-list', null, 'open', '[]', ); const member = db.prepare( 'INSERT INTO space_member (space, did, can_read, can_write) VALUES (?, ?, ?, ?)', ); member.run(A, 'did:plc:m', 1, 0); member.run(C, 'did:plc:m', 1, 1); return { db, store: createSpaceStorage(db) }; } it('makes a memberspace of a member-list space, keeping its members', async () => { const { store } = await openLegacy(); expect((await store.getSpace(A))?.policy).toEqual({ $type: 'com.atproto.memberspace.policy', publicRead: false, publicWrite: false, publishPolicy: false, publishMembers: false, }); expect(await store.getMember(A, 'did:plc:m')).toEqual({ read: true, write: false, }); }); it('carries public access and an app allow list into the memberspace', async () => { const { store } = await openLegacy(); expect((await store.getSpace(B))?.policy).toEqual({ $type: 'com.atproto.memberspace.policy', publicRead: true, publicWrite: false, allowedApps: ['https://app.example/c.json'], publishPolicy: false, publishMembers: false, }); }); it('makes a managedspace of a managing app on either side, dropping its members', async () => { const { store } = await openLegacy(); expect((await store.getSpace(C))?.policy).toEqual({ $type: 'com.atproto.managedspace.policy', managingApp: 'did:web:app.example#svc', publishPolicy: false, }); expect(await store.getMember(C, 'did:plc:m')).toBeNull(); }); it('gives a space another authority governs no policy', async () => { const { store } = await openLegacy(); expect((await store.getSpace(D))?.policy).toBeNull(); }); it('converts once, so a second open changes nothing', async () => { const { db, store } = await openLegacy(); await store.putSpace({ .../** @type {import('@pdsjs/core/ports').SpaceRow} */ ( await store.getSpace(A) ), policy: { $type: 'com.atproto.personalspace.policy' }, }); const again = createSpaceStorage(db); expect((await again.getSpace(A))?.policy).toEqual({ $type: 'com.atproto.personalspace.policy', }); expect(await again.getMember(A, 'did:plc:m')).toEqual({ read: true, write: false, }); }); });