cli4ai 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Tests for routine-engine.ts
3
+ */
4
+
5
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
6
+ import { mkdtempSync, rmSync, writeFileSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { tmpdir } from 'os';
9
+ import {
10
+ dryRunRoutine,
11
+ loadRoutineDefinition,
12
+ runRoutine,
13
+ RoutineParseError,
14
+ RoutineTemplateError,
15
+ RoutineValidationError,
16
+ type RoutineDefinition
17
+ } from './routine-engine.js';
18
+
19
+ describe('routine-engine', () => {
20
+ let tempDir: string;
21
+
22
+ beforeEach(() => {
23
+ tempDir = mkdtempSync(join(tmpdir(), 'cli4ai-routine-engine-test-'));
24
+ });
25
+
26
+ afterEach(() => {
27
+ rmSync(tempDir, { recursive: true, force: true });
28
+ });
29
+
30
+ test('loadRoutineDefinition throws RoutineParseError on invalid JSON', () => {
31
+ const path = join(tempDir, 'bad.routine.json');
32
+ writeFileSync(path, '{not json');
33
+ expect(() => loadRoutineDefinition(path)).toThrow(RoutineParseError);
34
+ });
35
+
36
+ test('loadRoutineDefinition throws RoutineValidationError on duplicate step ids', () => {
37
+ const path = join(tempDir, 'dup.routine.json');
38
+ writeFileSync(
39
+ path,
40
+ JSON.stringify({
41
+ version: 1,
42
+ name: 'dup',
43
+ steps: [
44
+ { id: 'a', type: 'set', vars: {} },
45
+ { id: 'a', type: 'set', vars: {} }
46
+ ]
47
+ })
48
+ );
49
+ expect(() => loadRoutineDefinition(path)).toThrow(RoutineValidationError);
50
+ });
51
+
52
+ test('runRoutine executes exec+set steps and renders object result templates', async () => {
53
+ const def: RoutineDefinition = {
54
+ version: 1,
55
+ name: 'demo',
56
+ vars: {
57
+ lang: { default: 'rust' }
58
+ },
59
+ steps: [
60
+ {
61
+ id: 'hello',
62
+ type: 'exec',
63
+ cmd: 'bash',
64
+ args: ['-lc', "echo '{\"x\":1,\"lang\":\"{{vars.lang}}\"}'"],
65
+ capture: 'json'
66
+ },
67
+ {
68
+ id: 'setvars',
69
+ type: 'set',
70
+ vars: {
71
+ lang2: '{{steps.hello.json.lang}}',
72
+ x: '{{steps.hello.json.x}}'
73
+ }
74
+ },
75
+ {
76
+ id: 'echo',
77
+ type: 'exec',
78
+ cmd: 'bash',
79
+ args: ['-lc', "echo '{{vars.lang2}} {{vars.x}}'"],
80
+ capture: 'text'
81
+ }
82
+ ],
83
+ result: {
84
+ lang: '{{vars.lang2}}',
85
+ x: '{{vars.x}}',
86
+ raw: '{{steps.echo.stdout}}'
87
+ }
88
+ };
89
+
90
+ const summary = await runRoutine(def, { lang: 'typescript' }, tempDir);
91
+ expect(summary.status).toBe('success');
92
+ expect(summary.exitCode).toBe(0);
93
+ expect(summary.result).toEqual({
94
+ lang: 'typescript',
95
+ x: 1,
96
+ raw: 'typescript 1\n'
97
+ });
98
+ });
99
+
100
+ test('runRoutine fails fast on missing template path', async () => {
101
+ const def: RoutineDefinition = {
102
+ version: 1,
103
+ name: 'bad-template',
104
+ steps: [
105
+ {
106
+ id: 'set',
107
+ type: 'set',
108
+ vars: {
109
+ x: '{{steps.nope.json.0}}'
110
+ }
111
+ }
112
+ ]
113
+ };
114
+
115
+ await expect(runRoutine(def, {}, tempDir)).rejects.toThrow(RoutineTemplateError);
116
+ });
117
+
118
+ test('dryRunRoutine is lenient for missing step paths', async () => {
119
+ const def: RoutineDefinition = {
120
+ version: 1,
121
+ name: 'dry',
122
+ steps: [
123
+ {
124
+ id: 'set',
125
+ type: 'set',
126
+ vars: {
127
+ x: '{{steps.nope.json.0}}'
128
+ }
129
+ }
130
+ ],
131
+ result: { x: '{{vars.x}}' }
132
+ };
133
+
134
+ const plan = await dryRunRoutine(def, {}, tempDir);
135
+ expect(plan.vars.x).toBe('{{steps.nope.json.0}}');
136
+ expect(plan.result).toEqual({ x: '{{steps.nope.json.0}}' });
137
+ });
138
+ });
139
+