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

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
142 lines · 4.8 KB Raw History
import { describe, expect, it } from 'vitest';
import { parseWorkflow, refMatches, workflowRunsOn } from '../src/workflow.js';

const minimal = JSON.stringify({ steps: [{ run: 'true' }] });

describe('parseWorkflow', () => {
  it('reads name, steps, refs and env', () => {
    const workflow = parseWorkflow(
      JSON.stringify({
        name: 'checks',
        steps: [
          { name: 'install', run: 'pnpm install' },
          { name: 'test', run: 'npm test' },
        ],
        refs: ['refs/heads/main'],
        env: { NODE_ENV: 'test' },
      }),
    );
    expect(workflow.name).toBe('checks');
    expect(workflow.steps).toEqual([
      { name: 'install', run: 'pnpm install' },
      { name: 'test', run: 'npm test' },
    ]);
    expect(workflow.refs).toEqual(['refs/heads/main']);
    expect(workflow.env).toEqual({ NODE_ENV: 'test' });
  });

  it('defaults the name, the refs, the env and the secrets', () => {
    const workflow = parseWorkflow(minimal);
    expect(workflow.name).toBe('ci');
    expect(workflow.refs).toEqual(['refs/heads/*']);
    expect(workflow.env).toEqual({});
    expect(workflow.secrets).toEqual([]);
  });

  it('reads the declared secret names', () => {
    const workflow = parseWorkflow(
      JSON.stringify({
        steps: [{ run: 'true' }],
        secrets: ['NPM_TOKEN', 'SITE_TOKEN'],
      }),
    );
    expect(workflow.secrets).toEqual(['NPM_TOKEN', 'SITE_TOKEN']);
  });

  it('numbers an unnamed step from one', () => {
    expect(parseWorkflow(minimal).steps[0].name).toBe('step 1');
  });

  it('reads YAML, keeping a block scalar as written', () => {
    const workflow = parseWorkflow(
      [
        '# the shell a step runs',
        'name: release',
        'refs:',
        '  - refs/tags/v*',
        'env:',
        '  PDS_URL: "https://pds.example.com"',
        'secrets: [NPM_TOKEN]',
        'steps:',
        '  - name: guard',
        '    run: |',
        '      set -e',
        '      if [ -z "${NPM_TOKEN:-}" ]; then',
        '        echo "no token"',
        '        exit 1',
        '      fi',
      ].join('\n'),
    );
    expect(workflow.name).toBe('release');
    expect(workflow.refs).toEqual(['refs/tags/v*']);
    expect(workflow.env).toEqual({ PDS_URL: 'https://pds.example.com' });
    expect(workflow.secrets).toEqual(['NPM_TOKEN']);
    // The newlines are the point: a step is a script, not one escaped line.
    expect(workflow.steps[0].run).toBe(
      'set -e\nif [ -z "${NPM_TOKEN:-}" ]; then\n  echo "no token"\n  exit 1\nfi\n',
    );
  });

  it('still reads JSON, which YAML 1.2 subsumes', () => {
    const workflow = parseWorkflow(minimal);
    expect(workflow.steps).toEqual([{ name: 'step 1', run: 'true' }]);
  });

  it('rejects malformed definitions', () => {
    expect(() => parseWorkflow('steps: [')).toThrow(/cannot parse/);
    expect(() => parseWorkflow('[]')).toThrow(/must be a mapping/);
    expect(() => parseWorkflow('{}')).toThrow(/steps must be/);
    expect(() => parseWorkflow('{"steps":[]}')).toThrow(/steps must be/);
    expect(() => parseWorkflow('{"steps":["true"]}')).toThrow(
      /step 0 must be an object/,
    );
    expect(() => parseWorkflow('{"steps":[{}]}')).toThrow(/step 0 run/);
    expect(() => parseWorkflow('{"steps":[{"run":"true"}],"refs":[]}')).toThrow(
      /refs must be/,
    );
    expect(() =>
      parseWorkflow('{"steps":[{"run":"true"}],"env":{"A":1}}'),
    ).toThrow(/env\.A/);
    expect(() => parseWorkflow('{"steps":[{"run":"true"}],"env":[]}')).toThrow(
      /env must be an object/,
    );
    expect(() =>
      parseWorkflow('{"steps":[{"run":"true"}],"secrets":{}}'),
    ).toThrow(/secrets must be an array/);
    expect(() =>
      parseWorkflow('{"steps":[{"run":"true"}],"secrets":[1]}'),
    ).toThrow(/secrets entry 0/);
  });
});

describe('refMatches', () => {
  it('matches an exact ref', () => {
    expect(refMatches('refs/heads/main', 'refs/heads/main')).toBe(true);
    expect(refMatches('refs/heads/main', 'refs/heads/other')).toBe(false);
  });

  it('treats a trailing star as a prefix', () => {
    expect(refMatches('refs/heads/*', 'refs/heads/feature/a')).toBe(true);
    expect(refMatches('refs/heads/*', 'refs/tags/v1')).toBe(false);
  });
});

describe('workflowRunsOn', () => {
  it('runs when any pattern matches', () => {
    const workflow = parseWorkflow(
      JSON.stringify({
        steps: [{ run: 'true' }],
        refs: ['refs/tags/*', 'refs/heads/main'],
      }),
    );
    expect(workflowRunsOn(workflow, 'refs/heads/main')).toBe(true);
    expect(workflowRunsOn(workflow, 'refs/tags/v1')).toBe(true);
    expect(workflowRunsOn(workflow, 'refs/heads/topic')).toBe(false);
  });

  it('runs on any branch by default', () => {
    const workflow = parseWorkflow(minimal);
    expect(workflowRunsOn(workflow, 'refs/heads/topic')).toBe(true);
    expect(workflowRunsOn(workflow, 'refs/tags/v1')).toBe(false);
  });
});