// The wire adapter's write half and the server's own routes: record and // blob writes carry the request's token, the identity route answers, and // the static surface falls back to the app shell. import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { createAppViewStore } from '@pdsjs/appview/store'; import { cborDecode, cborEncodeDagCbor } from '@pdsjs/core/repo'; import Database from 'better-sqlite3'; import { afterEach, describe, expect, it } from 'vitest'; import { createForgeAuth } from '../src/auth.js'; import { createRemoteAccount } from '../src/remote.js'; import { createGitHostServer } from '../src/server.js'; const ORIGIN = 'http://git.example.com'; const DID = 'did:plc:forgeowner'; /** @type {Array<{close: () => Promise}>} */ const closers = []; /** @type {string[]} */ const dirs = []; afterEach(async () => { for (const closer of closers.splice(0)) await closer.close().catch(() => {}); for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); }); describe('remote account writes', () => { it('carries the request token on a record write', async () => { /** @type {string[]} */ const seen = []; const account = createRemoteAccount({ service: 'https://pds.example.com', account: DID, getToken: async () => 'the-token', fetch: async (input, init) => { const url = String(input); const headers = /** @type {Record} */ ( init?.headers ?? {} ); seen.push(`${url} ${headers.authorization ?? ''}`); if (url.endsWith('/putRecord')) { const body = JSON.parse(String(init?.body)); return Response.json({ uri: body.record.name }, { status: 200 }); } return Response.json({ did: DID, handle: 'forge.test' }); }, }); await account.putRecord( `at://${DID}/dev.pdsjs.git.repo/proj`, 'dev.pdsjs.git.repo', 'proj', cborEncodeDagCbor({ name: 'proj' }), ); expect(seen).toHaveLength(1); expect(seen[0]).toContain('Bearer the-token'); }); it('refuses a failed record write with the server answer', async () => { const account = createRemoteAccount({ service: 'https://pds.example.com', account: DID, fetch: async () => Response.json({ error: 'IsARepo' }, { status: 422 }), }); await expect( account.putRecord( `at://${DID}/dev.pdsjs.git.repo/proj`, 'dev.pdsjs.git.repo', 'proj', cborEncodeDagCbor({}), ), ).rejects.toThrow(/refused/); }); it('round-trips a record read through the PDS JSON', async () => { const account = createRemoteAccount({ service: 'https://pds.example.com', account: DID, fetch: async (input) => { const url = String(input); if (url.includes('getRecord')) { return Response.json({ cid: 'bafy', value: { name: 'proj' } }); } return Response.json({ did: DID, handle: 'forge.test' }); }, }); const row = await account.getRecord(`at://${DID}/dev.pdsjs.git.repo/proj`); // The port's shape is the CBOR-encoded value, which the write path // decodes again. expect(row?.cid).toBe('bafy'); expect(cborDecode(/** @type {Uint8Array} */ (row?.value))).toEqual({ name: 'proj', }); expect(await account.getRecord('not-a-uri')).toBe(null); }); it('uploads a blob as a form with the token', async () => { /** @type {string[]} */ const seen = []; const account = createRemoteAccount({ service: 'https://pds.example.com', account: DID, getToken: async () => 'blob-token', fetch: async (_input, init) => { const headers = /** @type {Record} */ ( init?.headers ?? {} ); seen.push(String(headers.authorization ?? '')); return Response.json({ blob: { cid: 'bafyblob' } }); }, }); await account.uploadBlob(new Uint8Array([1, 2, 3]), 'application/zip'); expect(seen).toEqual(['Bearer blob-token']); }); it('reads a blob range with the header the caller asked for', async () => { /** @type {string[]} */ const ranges = []; const account = createRemoteAccount({ service: 'https://pds.example.com', account: DID, fetch: async (_input, init) => { const headers = /** @type {Record} */ ( init?.headers ?? {} ); ranges.push(String(headers.range ?? '')); return new Response(new Uint8Array([1, 2, 3]), { headers: { 'content-type': 'application/octet-stream' }, }); }, }); const found = await account.getBlobStream(DID, 'bafyblob', { start: 10, end: 20, }); expect(ranges).toEqual(['bytes=10-20']); expect(found?.body).toBeTruthy(); }); }); describe('git host server routes', () => { it('serves the static app with a shell fallback', async () => { const dir = mkdtempSync(join(tmpdir(), 'git-host-static-')); dirs.push(dir); writeFileSync( join(dir, 'index.html'), 'git-host', ); writeFileSync(join(dir, 'app.js'), 'console.log(1)'); const server = createGitHostServer({ account: createRemoteAccount({ service: 'https://pds.example.com', account: DID, fetch: async () => Response.json({ did: DID, handle: 'forge.test' }), }), store: createAppViewStore(new Database(':memory:')), uiDir: dir, origin: ORIGIN, }); closers.push(server); await server.listen(2524); const base = 'http://127.0.0.1:2524'; const page = await fetch(`${base}/repos`); expect(await page.text()).toContain('git-host'); const script = await fetch(`${base}/app.js`); expect(script.headers.get('content-type')).toContain('text/javascript'); const traversal = await fetch(`${base}/../etc/passwd`); expect([200, 404]).toContain(traversal.status); }); it('answers the blob route from the PDS', async () => { const server = createGitHostServer({ account: createRemoteAccount({ service: 'https://pds.example.com', account: DID, fetch: async (input) => { const url = String(input); if (url.includes('getBlob')) { return new Response(new Uint8Array([9, 9]), { headers: { 'content-type': 'application/octet-stream' }, }); } return Response.json({ did: DID, handle: 'forge.test' }); }, }), store: createAppViewStore(new Database(':memory:')), origin: ORIGIN, }); closers.push(server); await server.listen(2525); const base = 'http://127.0.0.1:2525'; const blob = await fetch(`${base}/.blobs/bafyblob`); expect(blob.status).toBe(206); expect(blob.headers.get('accept-ranges')).toBe('bytes'); expect(new Uint8Array(await blob.arrayBuffer())).toEqual( new Uint8Array([9, 9]), ); const missing = await fetch(`${base}/.blobs/none`); // The PDS answers 404 for an unknown blob and the route passes it on. expect([404, 206]).toContain(missing.status); }); it('issues the login redirect against the account handle', async () => { /** @type {string} */ let parBody = ''; /** @type {typeof fetch} */ const fakeOauth = async (input, init) => { const url = String(input); if (url.includes('par')) { parBody = String(init?.body ?? ''); return Response.json({ request_uri: 'urn:par' }); } if (url.includes('oauth-authorization-server')) { return Response.json({ issuer: 'https://pds.example.com', pushed_authorization_request_endpoint: 'https://pds.example.com/par', authorization_endpoint: 'https://pds.example.com/oauth/authorize', token_endpoint: 'https://pds.example.com/oauth/token', }); } return Response.json({}); }; const server = createGitHostServer({ account: createRemoteAccount({ service: 'https://pds.example.com', account: DID, fetch: async () => Response.json({ did: DID, handle: 'forge.test' }), }), store: createAppViewStore(new Database(':memory:')), auth: createForgeAuth({ origin: ORIGIN, accountDid: DID, service: 'https://pds.example.com', fetch: fakeOauth, }), origin: ORIGIN, }); closers.push(server); await server.listen(2526); const base = 'http://127.0.0.1:2526'; const login = await fetch(`${base}/login?handle=${DID}`, { redirect: 'manual', }); expect(login.status).toBe(302); const location = login.headers.get('location') ?? ''; expect(location).toContain('request_uri=urn%3Apar'); expect(parBody).toContain('client_id='); }); it('describes the xrpc methods from the route table', async () => { const server = createGitHostServer({ account: createRemoteAccount({ service: 'https://pds.example.com', account: DID, fetch: async () => Response.json({ did: DID, handle: 'forge.test' }), }), store: createAppViewStore(new Database(':memory:')), origin: ORIGIN, }); closers.push(server); await server.listen(2527); const base = 'http://127.0.0.1:2527'; const describe = await fetch( `${base}/xrpc/community.lexicon.service.describe`, ); expect(describe.ok).toBe(true); const body = /** @type {{roles: string[], methods: Array<{value: string}>}} */ ( await describe.json() ); expect(body.roles).toEqual(['appview']); const methods = body.methods.map(({ value }) => value); expect(methods).toContain('dev.pdsjs.git.listRepos'); expect(methods).toEqual([...methods].sort()); }); it('passes a requested blob range through to the PDS', async () => { /** @type {string[]} */ const ranges = []; const server = createGitHostServer({ account: createRemoteAccount({ service: 'https://pds.example.com', account: DID, fetch: async (input, init) => { const url = String(input); const headers = /** @type {Record} */ ( init?.headers ?? {} ); if (url.includes('getBlob')) { ranges.push(String(headers.range ?? '')); return new Response(new Uint8Array([1, 2, 3, 4]), { headers: { 'content-type': 'application/octet-stream' }, }); } return Response.json({ did: DID, handle: 'forge.test' }); }, }), store: createAppViewStore(new Database(':memory:')), origin: ORIGIN, }); closers.push(server); await server.listen(2528); const base = 'http://127.0.0.1:2528'; const blob = await fetch(`${base}/.blobs/bafyranged`, { headers: { Range: 'bytes=1-2' }, }); expect(ranges).toEqual(['bytes=1-2']); expect(blob.status).toBe(206); expect(blob.headers.get('content-range')).toBe('bytes 1-2'); }); });