A minimal AT Protocol Personal Data Server written in JavaScript.
atproto pds
42
fork

Configure Feed

Select the types of activity you want to include in your feed.

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 };