// The drive extension over a host with spaces on: a drive held in a space // reads through the space browser and storage the host carries and writes // through the spaces package, all reached from the account routes. import { createAccountJwt } from '@pdsjs/core/auth'; import { PersonalDataServer } from '@pdsjs/core/pds'; import { createMemorySpaceStorage } from '@pdsjs/spaces/memory-storage'; import { makeSpaceRow } from '@pdsjs/spaces/space-row'; import { describe, expect, it } from 'vitest'; import { createDriveExtension } from '../src/extension.js'; const DID = 'did:plc:driveextension'; const SPACE = `at://${DID}/space/dev.pdsjs.drive.space/photos`; const SECRET = 'drive-extension-secret'; const BASE = 'https://pds.example.com'; /** @param {{ownSpace?: boolean}} [opts] */ async function createServer({ ownSpace = true } = {}) { /** @type {Map} */ const blobData = new Map(); const actorStorage = /** @type {any} */ ({ getDid: async () => DID, getRecord: async () => null, listRecords: async () => ({ records: [], cursor: null }), putBlock: async () => {}, putRecord: async () => {}, putBlob: async () => {}, linkBlobToRecord: async () => {}, deleteRecord: async () => {}, unlinkBlobsFromRecord: async () => {}, }); const blobs = /** @type {any} */ ({ put: async ( /** @type {string} */ _did, /** @type {string} */ cid, /** @type {Uint8Array} */ data, ) => { blobData.set(cid, data); }, get: async (/** @type {string} */ _did, /** @type {string} */ cid) => { const data = blobData.get(cid); return data ? { data, mimeType: 'application/octet-stream' } : null; }, }); const spaceStorage = createMemorySpaceStorage(); // The account's own space: a write into one that does not exist is refused. if (ownSpace) { await spaceStorage.putSpace(makeSpaceRow(SPACE, { isOwner: true })); } return new PersonalDataServer({ actorStorage, sharedStorage: /** @type {any} */ ({}), blobs, jwtSecret: SECRET, hostname: 'pds.example.com', blobUploadLimit: 64, spaceStorage, extensions: [createDriveExtension()], }); } async function cookie() { return `pdsjs_account=${encodeURIComponent(await createAccountJwt(DID, SECRET))}`; } /** * @param {PersonalDataServer} pds * @param {string} path * @param {RequestInit & {json?: unknown}} [init] */ async function call(pds, path, init = {}) { const headers = new Headers(init.headers); headers.set('cookie', await cookie()); if (init.method === 'POST') headers.set('origin', BASE); if (init.json !== undefined) headers.set('content-type', 'application/json'); return pds.fetch( new Request(BASE + path, { ...init, headers, body: init.json !== undefined ? JSON.stringify(init.json) : init.body, }), ); } describe('a drive held in a space', () => { it('makes a drive space through the admin port built over the storage', async () => { const pds = await createServer({ ownSpace: false }); const made = await call(pds, '/account/api/drive/space', { method: 'POST', json: { name: 'work' }, }); expect(made.status).toBe(200); expect(await made.json()).toMatchObject({ key: 'work', name: 'work' }); const listing = await call(pds, '/account/api/drive/files'); expect( (await listing.json()).spaces.map( (/** @type {{name: string}} */ s) => s.name, ), ).toEqual(['work']); }); it('creates a folder, stores a file and reads it back through the space', async () => { const pds = await createServer(); const folder = await call(pds, '/account/api/drive/folder', { method: 'POST', json: { name: 'trips', space: SPACE }, }); expect(folder.status).toBe(200); const listing = await call( pds, `/account/api/drive/files?space=${encodeURIComponent(SPACE)}`, ); expect(listing.status).toBe(200); expect( (await listing.json()).folders.map( (/** @type {{name: string}} */ f) => f.name, ), ).toEqual(['trips']); const upload = await call( pds, `/account/api/drive/upload?name=${encodeURIComponent('note.txt')}&space=${encodeURIComponent(SPACE)}`, { method: 'POST', headers: { 'content-type': 'text/plain' }, body: 'hello from a space', }, ); expect(upload.status).toBe(200); const { rkey } = await upload.json(); const file = await call( pds, `/account/api/drive/file?rkey=${encodeURIComponent(rkey)}&space=${encodeURIComponent(SPACE)}`, ); expect(file.status).toBe(200); expect(await file.text()).toBe('hello from a space'); }); });