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); }); });