Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

workflow-parser-ext.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

workflow-parser-ext.test.tsBlame200 lines · 1 contributor
f3e1873Claude1/**
2 * Unit tests for src/lib/workflow-parser-ext.ts (Agent 3, Sprint 1).
3 *
4 * FIXME (Agent 3): At the time this test file was authored the
5 * `workflow-parser-ext.ts` module had not yet landed on disk. The tests
6 * below are written to the documented contract, but the whole suite guards
7 * the import via a dynamic `require` so that `bun test` does not fail
8 * cold-start if Agent 3 is still in flight. Once the module exists, these
9 * tests will begin running automatically — no edit required.
10 *
11 * Contract under test:
12 * parseExtended(yaml: string):
13 * | { ok: true; workflow: ExtendedWorkflow }
14 * | { ok: false; error: string }
15 *
16 * Extended workflow adds (vs base ParsedWorkflow):
17 * - dispatchInputs (from on.workflow_dispatch.inputs)
18 * - job.needs (string[], normalised from scalar-or-array)
19 * - job.strategy.matrix.axes
20 * - step.if (raw expression string)
21 * - step.uses + step.with
22 * - warnings[] for malformed extension fields that don't kill the parse
23 */
24
25import { describe, it, expect } from "bun:test";
26
27// Guarded dynamic import — if Agent 3's module isn't present yet we skip.
28let parseExtended: ((yaml: string) => any) | null = null;
29try {
30 // eslint-disable-next-line @typescript-eslint/no-var-requires
31 const mod = require("../lib/workflow-parser-ext");
32 parseExtended = mod.parseExtended ?? null;
33} catch {
34 parseExtended = null;
35}
36
37const d = parseExtended ? describe : describe.skip;
38
39d("workflow-parser-ext — parseExtended", () => {
40 it("parses a bare workflow with no extension fields populated", () => {
41 const res = parseExtended!(`name: bare
42on: [push]
43jobs:
44 build:
45 runs-on: default
46 steps:
47 - run: echo hi
48`);
49 expect(res.ok).toBe(true);
50 if (!res.ok) return;
51 // Base fields still present.
52 expect(res.workflow.name).toBe("bare");
53 expect(Array.isArray(res.workflow.on)).toBe(true);
54 expect(res.workflow.jobs.length).toBe(1);
55 // Extension fields should be absent / empty.
56 expect(res.workflow.dispatchInputs).toBeFalsy();
57 const job = res.workflow.jobs[0];
58 expect(job.needs === undefined || (Array.isArray(job.needs) && job.needs.length === 0)).toBe(true);
59 expect(job.strategy === undefined || job.strategy === null).toBe(true);
60 });
61
62 it("captures workflow_dispatch inputs with a choice type", () => {
63 const res = parseExtended!(`name: dispatch
64on:
65 workflow_dispatch:
66 inputs:
67 environment:
68 type: choice
69 options: [staging, production]
70jobs:
71 deploy:
72 steps:
73 - run: echo deploy
74`);
75 expect(res.ok).toBe(true);
76 if (!res.ok) return;
77 expect(res.workflow.on).toContain("workflow_dispatch");
78 expect(res.workflow.dispatchInputs).toBeDefined();
79 // dispatchInputs is typically keyed by input name.
80 const inputs = res.workflow.dispatchInputs!;
81 expect(inputs.environment).toBeDefined();
82 expect(inputs.environment.type).toBe("choice");
83 expect(inputs.environment.options).toContain("staging");
84 expect(inputs.environment.options).toContain("production");
85 });
86
87 it("normalises a scalar `needs:` into a single-element array", () => {
88 const res = parseExtended!(`name: needs-scalar
89on: [push]
90jobs:
91 build:
92 steps:
93 - run: echo build
94 deploy:
95 needs: build
96 steps:
97 - run: echo deploy
98`);
99 expect(res.ok).toBe(true);
100 if (!res.ok) return;
101 const deploy = res.workflow.jobs.find((j: any) => j.name === "deploy");
102 expect(deploy).toBeDefined();
103 expect(deploy.needs).toEqual(["build"]);
104 });
105
106 it("passes through an array `needs:` unchanged", () => {
107 const res = parseExtended!(`name: needs-array
108on: [push]
109jobs:
110 a:
111 steps:
112 - run: echo a
113 b:
114 steps:
115 - run: echo b
116 deploy:
117 needs: [a, b]
118 steps:
119 - run: echo deploy
120`);
121 expect(res.ok).toBe(true);
122 if (!res.ok) return;
123 const deploy = res.workflow.jobs.find((j: any) => j.name === "deploy");
124 expect(deploy.needs).toEqual(["a", "b"]);
125 });
126
127 it("captures job.strategy.matrix.axes from a matrix block", () => {
128 const res = parseExtended!(`name: matrix
129on: [push]
130jobs:
131 test:
132 strategy:
133 matrix:
134 node: [16, 18]
135 os: [ubuntu]
136 steps:
137 - run: bun test
138`);
139 expect(res.ok).toBe(true);
140 if (!res.ok) return;
141 const job = res.workflow.jobs[0];
142 expect(job.strategy).toBeDefined();
143 expect(job.strategy.matrix).toBeDefined();
144 expect(job.strategy.matrix.axes).toBeDefined();
145 expect(job.strategy.matrix.axes.node).toEqual([16, 18]);
146 expect(job.strategy.matrix.axes.os).toEqual(["ubuntu"]);
147 });
148
149 it("captures step-level `if:` as the raw expression string", () => {
150 const res = parseExtended!(`name: iffy
151on: [push]
152jobs:
153 test:
154 steps:
155 - if: success()
156 run: echo only-on-success
157`);
158 expect(res.ok).toBe(true);
159 if (!res.ok) return;
160 const step = res.workflow.jobs[0].steps[0];
161 expect(step.if).toBe("success()");
162 });
163
164 it("captures step `uses:` and `with:` blocks verbatim", () => {
165 const res = parseExtended!(`name: uses
166on: [push]
167jobs:
168 scan:
169 steps:
170 - uses: gluecron/gatetest@v1
171 with:
172 url: 'x'
173`);
174 expect(res.ok).toBe(true);
175 if (!res.ok) return;
176 const step = res.workflow.jobs[0].steps[0];
177 expect(step.uses).toBe("gluecron/gatetest@v1");
178 expect(step.with).toBeDefined();
179 expect(step.with.url).toBe("x");
180 });
181
182 it("emits warnings[] when an extension field is malformed but base parse succeeds", () => {
183 // Matrix whose axes value is a scalar (not an array) — an extension
184 // error. Base workflow must still parse.
185 const res = parseExtended!(`name: malformed
186on: [push]
187jobs:
188 test:
189 strategy:
190 matrix:
191 node: not-an-array
192 steps:
193 - run: echo hi
194`);
195 expect(res.ok).toBe(true);
196 if (!res.ok) return;
197 expect(Array.isArray(res.workflow.warnings)).toBe(true);
198 expect(res.workflow.warnings.length).toBeGreaterThan(0);
199 });
200});