A minimal AT Protocol Personal Data Server written in JavaScript.
at main 152 lines 4.4 kB view raw
1// test/helpers/node-server.js 2import { mkdirSync, rmSync } from 'node:fs'; 3import { createServer } from '@pds/node'; 4import { LexiconResolver } from '@pds/lexicon-resolver'; 5import { defineLexicon } from '@bigmoves/lexicon'; 6 7const TEST_DATA_DIR = './test-data'; 8 9// Minimal app.bsky.feed.post schema for e2e validation testing 10const postSchema = defineLexicon({ 11 lexicon: 1, 12 id: 'app.bsky.feed.post', 13 defs: { 14 main: { 15 type: 'record', 16 key: 'tid', 17 record: { 18 type: 'object', 19 required: ['text', 'createdAt'], 20 properties: { 21 text: { type: 'string', maxLength: 3000 }, 22 createdAt: { type: 'string', format: 'datetime' }, 23 }, 24 }, 25 }, 26 }, 27}); 28const TEST_PORT = 3000; 29const USE_LOCAL_INFRA = process.env.USE_LOCAL_INFRA !== 'false'; 30const USE_S3 = process.env.BLOB_STORAGE === 's3'; 31 32const MINIO_BUCKET = 'pds-blobs'; 33 34/** 35 * Create blob adapter using MinIO client 36 * @returns {Promise<import('@pds/core/ports').BlobPort>} 37 */ 38async function createMinioBlobs() { 39 const { Client } = await import('minio'); 40 41 const client = new Client({ 42 endPoint: 'localhost', 43 port: 9000, 44 useSSL: false, 45 accessKey: 'minioadmin', 46 secretKey: 'minioadmin', 47 }); 48 49 // Ensure bucket exists 50 const exists = await client.bucketExists(MINIO_BUCKET); 51 if (!exists) { 52 await client.makeBucket(MINIO_BUCKET); 53 console.log('Created MinIO bucket:', MINIO_BUCKET); 54 } 55 56 return { 57 async get(did, cid) { 58 try { 59 const objectName = `${did}/${cid}`; 60 const stat = await client.statObject(MINIO_BUCKET, objectName); 61 const stream = await client.getObject(MINIO_BUCKET, objectName); 62 63 // Collect stream into buffer 64 const chunks = []; 65 for await (const chunk of stream) { 66 chunks.push(chunk); 67 } 68 const data = new Uint8Array(Buffer.concat(chunks)); 69 const mimeType = stat.metaData?.mime_type || 'application/octet-stream'; 70 71 return { data, mimeType }; 72 } catch (e) { 73 if (e.code === 'NotFound') return null; 74 throw e; 75 } 76 }, 77 78 async put(did, cid, data, mimeType) { 79 const objectName = `${did}/${cid}`; 80 await client.putObject( 81 MINIO_BUCKET, 82 objectName, 83 Buffer.from(data), 84 data.length, 85 { 86 'Content-Type': mimeType, 87 mime_type: mimeType, 88 }, 89 ); 90 }, 91 92 async delete(did, cid) { 93 const objectName = `${did}/${cid}`; 94 await client.removeObject(MINIO_BUCKET, objectName); 95 }, 96 }; 97} 98 99/** 100 * Start Node.js PDS server for e2e tests 101 * Cleans test data directory for fresh state each run 102 * @param {Object} [options] 103 * @param {import('@pds/core/ports').LexiconResolverPort} [options.lexiconResolver] - Lexicon resolver for record validation 104 * @returns {Promise<{close: () => Promise<void>}>} Server instance with close() method 105 */ 106export async function startNodeServer(options = {}) { 107 // Fresh data each run 108 rmSync(TEST_DATA_DIR, { recursive: true, force: true }); 109 mkdirSync(TEST_DATA_DIR, { recursive: true }); 110 111 // Configure blobs adapter 112 const blobs = USE_S3 ? await createMinioBlobs() : undefined; 113 if (USE_S3) { 114 console.log('Using S3 blob storage (MinIO)'); 115 } 116 117 // Create default lexicon resolver with bsky post schema for validation testing 118 const lexiconResolver = 119 options.lexiconResolver ?? new LexiconResolver({ schemas: [postSchema] }); 120 121 const server = await createServer({ 122 port: TEST_PORT, 123 dbPath: `${TEST_DATA_DIR}/pds.db`, 124 blobsDir: `${TEST_DATA_DIR}/blobs`, 125 blobs, 126 jwtSecret: 'test-secret-for-e2e', 127 // Use HTTPS hostname (via Caddy proxy) when docker infra is enabled 128 hostname: USE_LOCAL_INFRA 129 ? 'host.docker.internal:3443' 130 : `localhost:${TEST_PORT}`, 131 password: 'test-password', 132 // Use local relay when docker infrastructure is available 133 relayUrl: USE_LOCAL_INFRA ? 'http://localhost:2470' : undefined, 134 // Keep appview pointing to production (for proxy tests) 135 appviewUrl: 'https://api.bsky.app', 136 appviewDid: 'did:web:api.bsky.app', 137 lexiconResolver, 138 }); 139 140 await server.listen(); 141 return server; 142} 143 144/** 145 * Stop Node.js PDS server 146 * @param {{close: () => Promise<void>}} server - Server instance from startNodeServer 147 */ 148export async function stopNodeServer(server) { 149 await server.close(); 150} 151 152export { USE_LOCAL_INFRA, TEST_PORT, USE_S3 };