/** * Which source answers "who last changed this file": the account's server * where it serves the dev.pdsjs.git reads, the walk in this page where it * does not, or where the server will not open the repository whole. */ import { beforeEach, describe, expect, it, vi } from 'vitest'; /** Fresh module state per test, because the route's standing is remembered. */ async function load() { vi.resetModules(); const git = await import('../src/lib/git.js'); git.account.did = 'did:plc:owner'; git.account.pds = 'https://pds.example.com'; git.account.handle = 'owner.example.com'; return git; } /** @type {string[]} */ let asked = []; /** @param {(url: string) => Response} answer */ function stubFetch(answer) { asked = []; vi.stubGlobal('fetch', async (/** @type {string|URL|Request} */ input) => { const url = String( typeof input === 'object' && 'url' in input ? input.url : input, ); asked.push(url); return answer(url); }); } const json = (body, status = 200) => new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json; charset=utf-8' }, }); const text = (body, status = 404) => new Response(body, { status, headers: { 'Content-Type': 'text/plain' } }); beforeEach(() => { vi.unstubAllGlobals(); // The git service decision probes this page's own origin first; the stubs // below refuse that probe, so the account's PDS stays the service. vi.stubGlobal('location', new URL('https://site.example/')); }); describe('lastCommitOf', () => { const commit = { sha: 'a'.repeat(40), message: 'change it\n' }; const READS = [ 'getTree', 'listFiles', 'getFile', 'getRaw', 'listCommits', 'getCommit', 'getLastCommit', ].map((name) => ({ value: `dev.pdsjs.git.${name}` })); const methodAsks = () => asked.filter((url) => url.includes('getLastCommit')); it('asks the method where describe lists the reads, never the route', async () => { stubFetch((url) => { if (url.includes('community.lexicon.service.describe')) { return json({ methods: READS }); } if (url.includes('dev.pdsjs.git.getLastCommit')) return json({ commit }); return text(''); }); const { lastCommitOf } = await load(); expect(await lastCommitOf('repo', 'main', 'src/index.js')).toEqual(commit); expect(methodAsks()[0]).toBe( 'https://pds.example.com/xrpc/dev.pdsjs.git.getLastCommit' + '?repo=repo&ref=main&path=src%2Findex.js', ); }); // The server downloads a chain without indexes only up to a limit. Past // it, this page reads the repository, and does not ask the server again. it('reads for itself a repository the server will not open whole', async () => { stubFetch((url) => { if (url.includes('community.lexicon.service.describe')) { return json({ methods: READS }); } if (url.includes('dev.pdsjs.git.getLastCommit')) { return json({ error: 'RepositoryTooLarge', message: 'too big' }, 400); } return json({ error: 'RecordNotFound' }, 404); }); const { lastCommitOf } = await load(); // No record is in hand, so the in-page reader answers nothing. expect(await lastCommitOf('repo', 'main', 'a.txt')).toBeNull(); expect(await lastCommitOf('repo', 'main', 'b.txt')).toBeNull(); expect(methodAsks()).toHaveLength(1); }); it('walks in this page where the server serves no reads', async () => { stubFetch(() => text('')); const { lastCommitOf } = await load(); // No record is in hand, so the in-page reader answers nothing, and // nothing asked the server for a commit. expect(await lastCommitOf('repo', 'main', 'a.txt')).toBeNull(); expect(methodAsks()).toHaveLength(0); }); });