~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
107 lines · 3.7 KB Raw History
/**
 * 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);
  });
});