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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
383 lines · 10.8 KB Raw History
/**
 * Reducing a runner's check listing to this repository's runs, naming the log
 * blob a finished check carries, and reading that blob as text.
 */

import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
  checkLogUrl,
  checkLook,
  duration,
  groupBySha,
  latestBySha,
  latestPerWorkflow,
  loadRefChecks,
  readLog,
  runDuration,
  summarize,
  summaryWords,
  workflowNames,
  workflowOf,
} from '../src/lib/checks.js';
import { account } from '../src/lib/git.js';

describe('latestBySha', () => {
  it('keys the newest run of each workflow by its commit', () => {
    const sha = 'a'.repeat(40);
    const map = latestBySha([
      {
        sha,
        workflow: 'ci',
        status: 'success',
        startedAt: '2026-01-02T00:00:00Z',
      },
      {
        sha,
        workflow: 'ci',
        status: 'failure',
        startedAt: '2026-01-01T00:00:00Z',
      },
      {
        sha: 'b'.repeat(40),
        status: 'failure',
        startedAt: '2026-01-03T00:00:00Z',
      },
    ]);
    expect(map.size).toBe(2);
    expect(map.get(sha)?.map((run) => run.status)).toEqual(['success']);
    expect(map.get('b'.repeat(40))?.map((run) => run.status)).toEqual([
      'failure',
    ]);
  });

  it('keeps one entry per workflow, so no workflow hides another', () => {
    const sha = 'a'.repeat(40);
    const map = latestBySha([
      {
        sha,
        workflow: 'publish',
        status: 'success',
        startedAt: '2026-01-02T00:00:00Z',
      },
      {
        sha,
        workflow: 'ci',
        status: 'failure',
        startedAt: '2026-01-01T00:00:00Z',
      },
    ]);
    expect(map.get(sha)?.map(workflowOf)).toEqual(['publish', 'ci']);
  });
});

describe('groupBySha', () => {
  const run = (sha, workflow, startedAt) => ({
    sha,
    workflow,
    status: 'success',
    startedAt,
  });

  it('gathers each commit runs, newest commit first', () => {
    const groups = groupBySha([
      run('b', 'ci', '2026-01-03T00:00:00Z'),
      run('a', 'ci', '2026-01-02T00:00:00Z'),
      run('b', 'lint', '2026-01-01T00:00:00Z'),
    ]);
    expect(groups.map((group) => group.sha)).toEqual(['b', 'a']);
    expect(groups[0].runs.map(workflowOf)).toEqual(['ci', 'lint']);
  });

  it('keeps a re-run beside the run it repeated', () => {
    const groups = groupBySha([
      run('a', 'ci', '2026-01-02T00:00:00Z'),
      run('a', 'ci', '2026-01-01T00:00:00Z'),
    ]);
    expect(groups).toHaveLength(1);
    expect(groups[0].runs).toHaveLength(2);
  });
});

describe('latestPerWorkflow', () => {
  it('keeps the newest run of each workflow', () => {
    const latest = latestPerWorkflow([
      { sha: 'a', workflow: 'ci', status: 'success', startedAt: 'b' },
      { sha: 'a', workflow: 'ci', status: 'failure', startedAt: 'a' },
      { sha: 'a', workflow: 'lint', status: 'success', startedAt: 'a' },
    ]);
    expect(latest.map((run) => [workflowOf(run), run.status])).toEqual([
      ['ci', 'success'],
      ['lint', 'success'],
    ]);
  });
});

describe('summarize', () => {
  const run = (workflow, status) => ({
    sha: 'a'.repeat(40),
    workflow,
    status,
    startedAt: '2026-01-01T00:00:00Z',
  });

  it('reads a set every workflow passed as passed', () => {
    expect(
      summarize([run('ci', 'success'), run('publish', 'success')]),
    ).toEqual({ status: 'success', passed: 2, total: 2 });
  });

  it('lets one failure decide the whole set', () => {
    expect(
      summarize([run('publish', 'success'), run('ci', 'failure')]),
    ).toEqual({ status: 'failure', passed: 1, total: 2 });
  });

  it('names an errored run rather than calling it failed', () => {
    expect(summarize([run('ci', 'error')]).status).toBe('error');
  });

  it('reads an unfinished set as running', () => {
    expect(
      summarize([run('ci', 'success'), run('publish', 'running')]),
    ).toEqual({ status: 'running', passed: 1, total: 2 });
  });

  it('answers null for a commit no workflow reported on', () => {
    expect(summarize([])).toBe(null);
  });
});

describe('summaryWords', () => {
  const run = (workflow, status) => ({
    sha: 'a'.repeat(40),
    workflow,
    status,
    startedAt: '2026-01-01T00:00:00Z',
  });

  it('names the workflow where there is only one', () => {
    const runs = [run('ci', 'failure')];
    expect(summaryWords(summarize(runs), runs)).toBe('ci failed');
  });

  it('counts the workflows where there are several', () => {
    const runs = [run('ci', 'failure'), run('publish', 'success')];
    expect(summaryWords(summarize(runs), runs)).toBe('1 of 2 workflows passed');
  });

  it('says what an unfinished set is still doing', () => {
    const runs = [run('ci', 'success'), run('publish', 'running')];
    expect(summaryWords(summarize(runs), runs)).toBe(
      '1 of 2 workflows passed, the rest running',
    );
  });
});

describe('workflowOf', () => {
  it('reads a run that names no workflow as ci', () => {
    expect(workflowOf({})).toBe('ci');
    expect(workflowOf({ workflow: 'publish' })).toBe('publish');
  });
});

describe('workflowNames', () => {
  it('names each workflow once, in the order a reader meets it', () => {
    expect(
      workflowNames([
        { workflow: 'publish' },
        { workflow: 'ci' },
        { workflow: 'publish' },
        {},
      ]),
    ).toEqual(['publish', 'ci']);
  });
});

describe('checkLook', () => {
  it('gives each known status its own words', () => {
    expect(checkLook('success').label).toBe('passed');
    expect(checkLook('failure').label).toBe('failed');
    expect(checkLook('error').label).toBe('errored');
    expect(checkLook('running').label).toBe('running');
  });

  it('reads a status this build does not know as running', () => {
    expect(checkLook('cancelled').label).toBe('running');
    expect(checkLook(undefined).label).toBe('running');
  });
});

describe('duration', () => {
  it('climbs to the largest unit that stays readable', () => {
    expect(duration(0)).toBe('0ms');
    expect(duration(940)).toBe('940ms');
    expect(duration(1500)).toBe('2s');
    expect(duration(45_000)).toBe('45s');
    expect(duration(72_000)).toBe('1m 12s');
    expect(duration(3_600_000)).toBe('1h 0m');
    expect(duration(5_460_000)).toBe('1h 31m');
  });
});

describe('runDuration', () => {
  it('measures a finished run', () => {
    expect(
      runDuration({
        sha: 'a',
        status: 'success',
        startedAt: '2026-01-01T00:00:00Z',
        finishedAt: '2026-01-01T00:01:12Z',
      }),
    ).toBe('1m 12s');
  });

  it('answers null for a run that has not finished', () => {
    expect(
      runDuration({
        sha: 'a',
        status: 'running',
        startedAt: '2026-01-01T00:00:00Z',
      }),
    ).toBe(null);
  });

  it('answers null when the timestamps disagree about order', () => {
    expect(
      runDuration({
        sha: 'a',
        status: 'success',
        startedAt: '2026-01-01T00:01:00Z',
        finishedAt: '2026-01-01T00:00:00Z',
      }),
    ).toBe(null);
  });
});

describe('checkLogUrl', () => {
  const runner = { did: 'did:plc:runner', service: 'https://pds.example.com' };

  it('names the log blob on the runner PDS', () => {
    const url = checkLogUrl(runner, {
      sha: 'a'.repeat(40),
      status: 'failure',
      startedAt: '2026-01-01T00:00:00Z',
      logs: { ref: { $link: 'bafylog' } },
    });
    expect(url).toBe(
      'https://pds.example.com/xrpc/com.atproto.sync.getBlob?did=did%3Aplc%3Arunner&cid=bafylog',
    );
  });

  it('answers null for a run with no log yet', () => {
    const check = {
      sha: 'a'.repeat(40),
      status: 'running',
      startedAt: '2026-01-01T00:00:00Z',
    };
    expect(checkLogUrl(runner, check)).toBe(null);
    expect(checkLogUrl(null, { ...check, logs: { ref: { $link: 'x' } } })).toBe(
      null,
    );
  });
});

describe('loadRefChecks', () => {
  const original = globalThis.fetch;
  const originalLocation = globalThis.location;

  beforeEach(() => {
    // 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.
    globalThis.location = new URL('https://site.example/');
    account.pds = 'https://pds.example.com';
  });

  afterEach(() => {
    globalThis.fetch = original;
    globalThis.location = originalLocation;
    account.pds = '';
  });

  /**
   * The one read the ref badge makes: the server's latest check per branch.
   * @param {unknown} answer - what dev.pdsjs.git.listChecks answers
   */
  const serve = (answer) => {
    globalThis.fetch = async (/** @type {string} */ url) => {
      if (String(url).includes('dev.pdsjs.git.listChecks')) {
        return Response.json(answer);
      }
      return new Response('no', { status: 404 });
    };
  };

  it('reads the latest run of each workflow on the ref, and the run each names', async () => {
    serve({
      runner: 'did:plc:runner',
      service: 'https://runner.example.com',
      checks: [
        {
          ref: 'refs/heads/main',
          workflow: 'ci',
          sha: 'a'.repeat(40),
          status: 'success',
          startedAt: '2026-01-01T00:00:00Z',
          check: { uri: 'at://did:plc:runner/dev.pdsjs.git.check/3ltid' },
        },
        {
          ref: 'refs/heads/other',
          workflow: 'ci',
          sha: 'b'.repeat(40),
          status: 'failure',
          startedAt: '2026-01-01T00:00:00Z',
          check: { uri: 'at://did:plc:runner/dev.pdsjs.git.check/3other' },
        },
      ],
    });
    const { runner, runs } = await loadRefChecks('project', 'main');
    expect(runner?.service).toBe('https://runner.example.com');
    expect(runs).toHaveLength(1);
    expect(runs[0].rkey).toBe('3ltid');
    expect(workflowOf(runs[0])).toBe('ci');
  });

  it('reads a repository naming no runner as no runs', async () => {
    serve({ runner: null, checks: [] });
    const { runner, runs } = await loadRefChecks('other', 'main');
    expect(runner).toBeNull();
    expect(runs).toEqual([]);
  });
});

describe('readLog', () => {
  const runner = { did: 'did:plc:runner', service: 'https://pds.example.com' };
  const check = {
    sha: 'a'.repeat(40),
    status: 'failure',
    startedAt: '2026-01-01T00:00:00Z',
    logs: { ref: { $link: 'bafylog' } },
  };
  const original = globalThis.fetch;

  afterEach(() => {
    globalThis.fetch = original;
  });

  it('strips the terminal escapes a coloured step writes', async () => {
    const esc = String.fromCharCode(27);
    globalThis.fetch = async () =>
      new Response(`${esc}[32mok${esc}[0m\ndone\n`);
    expect(await readLog(runner, check)).toBe('ok\ndone\n');
  });

  it('answers empty for a run with no log', async () => {
    expect(await readLog(runner, { ...check, logs: undefined })).toBe('');
  });

  it('throws when the runner will not serve the blob', async () => {
    globalThis.fetch = async () => new Response('gone', { status: 404 });
    await expect(readLog(runner, check)).rejects.toThrow(
      /would not serve this log/,
    );
  });
});