edsger 0.45.0 → 0.46.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.
- package/dist/commands/workflow/executors/phase-executor.js +3 -1
- package/dist/commands/workflow/phase-orchestrator.js +1 -2
- package/dist/phases/app-store-generation/index.js +1 -2
- package/dist/phases/branch-planning/index.js +1 -2
- package/dist/phases/bug-fixing/analyzer.js +1 -2
- package/dist/phases/code-implementation/index.js +1 -2
- package/dist/phases/code-refine/index.js +1 -2
- package/dist/phases/code-review/index.js +1 -2
- package/dist/phases/code-testing/analyzer.js +1 -2
- package/dist/phases/feature-analysis/index.js +1 -2
- package/dist/phases/functional-testing/analyzer.js +1 -2
- package/dist/phases/growth-analysis/index.js +1 -2
- package/dist/phases/pr-execution/index.js +1 -0
- package/dist/phases/pr-splitting/index.js +1 -2
- package/dist/phases/run-sheet/index.js +7 -7
- package/dist/phases/run-sheet/render.js +3 -1
- package/dist/phases/smoke-test/agent.js +2 -4
- package/dist/phases/smoke-test/index.js +11 -6
- package/dist/phases/technical-design/index.js +1 -2
- package/dist/phases/test-cases-analysis/index.js +1 -2
- package/dist/phases/user-stories-analysis/index.js +1 -2
- package/package.json +3 -3
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +3 -9
- package/dist/api/__tests__/app-store.test.d.ts +0 -7
- package/dist/api/__tests__/app-store.test.js +0 -60
- package/dist/api/__tests__/intelligence.test.d.ts +0 -11
- package/dist/api/__tests__/intelligence.test.js +0 -315
- package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
- package/dist/api/features/__tests__/feature-utils.test.js +0 -370
- package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
- package/dist/api/features/__tests__/status-updater.test.js +0 -88
- package/dist/commands/build/__tests__/build.test.d.ts +0 -5
- package/dist/commands/build/__tests__/build.test.js +0 -206
- package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
- package/dist/commands/build/__tests__/detect-project.test.js +0 -160
- package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
- package/dist/commands/build/__tests__/run-build.test.js +0 -433
- package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
- package/dist/commands/intelligence/__tests__/command.test.js +0 -48
- package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
- package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
- package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
- package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
- package/dist/config/__tests__/config.test.d.ts +0 -4
- package/dist/config/__tests__/config.test.js +0 -286
- package/dist/config/__tests__/feature-status.test.d.ts +0 -4
- package/dist/config/__tests__/feature-status.test.js +0 -111
- package/dist/errors/__tests__/index.test.d.ts +0 -4
- package/dist/errors/__tests__/index.test.js +0 -349
- package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
- package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
- package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
- package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
- package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
- package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
- package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
- package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
- package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
- package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
- package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
- package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
- package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
- package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
- package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
- package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
- package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
- package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
- package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
- package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
- package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
- package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
- package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
- package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
- package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
- package/dist/phases/release-sync/__tests__/github.test.d.ts +0 -9
- package/dist/phases/release-sync/__tests__/github.test.js +0 -123
- package/dist/phases/release-sync/__tests__/snapshot.test.d.ts +0 -8
- package/dist/phases/release-sync/__tests__/snapshot.test.js +0 -93
- package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
- package/dist/phases/smoke-test/__tests__/agent.test.js +0 -85
- package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
- package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
- package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
- package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
- package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
- package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
- package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
- package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
- package/dist/workspace/__tests__/workspace-manager.test.js +0 -52
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for workflow state management utilities
|
|
3
|
-
*/
|
|
4
|
-
import assert from 'node:assert';
|
|
5
|
-
import { describe, it } from 'node:test';
|
|
6
|
-
import { calculateStats, createCompletedState, createFailedState, createInitialState, createProcessingState, updateFeatureState, } from '../state-manager.js';
|
|
7
|
-
void describe('State Manager', () => {
|
|
8
|
-
void describe('createInitialState', () => {
|
|
9
|
-
void it('should return an empty Map', () => {
|
|
10
|
-
const state = createInitialState();
|
|
11
|
-
assert.ok(state instanceof Map, 'Should return a Map instance');
|
|
12
|
-
assert.strictEqual(state.size, 0, 'Map should be empty');
|
|
13
|
-
});
|
|
14
|
-
void it('should return a new Map on each call', () => {
|
|
15
|
-
const state1 = createInitialState();
|
|
16
|
-
const state2 = createInitialState();
|
|
17
|
-
assert.notStrictEqual(state1, state2, 'Each call should return a distinct Map');
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
void describe('createProcessingState', () => {
|
|
21
|
-
void it('should create a new processing state with retryCount 1 when no current state', () => {
|
|
22
|
-
const state = createProcessingState('feat-1');
|
|
23
|
-
assert.strictEqual(state.featureId, 'feat-1');
|
|
24
|
-
assert.strictEqual(state.retryCount, 1);
|
|
25
|
-
assert.strictEqual(state.status, 'processing');
|
|
26
|
-
assert.ok(state.lastAttempt instanceof Date, 'lastAttempt should be a Date');
|
|
27
|
-
});
|
|
28
|
-
void it('should increment retryCount when current state is provided', () => {
|
|
29
|
-
const currentState = {
|
|
30
|
-
featureId: 'feat-1',
|
|
31
|
-
retryCount: 2,
|
|
32
|
-
lastAttempt: new Date('2024-01-01'),
|
|
33
|
-
status: 'failed',
|
|
34
|
-
};
|
|
35
|
-
const state = createProcessingState('feat-1', currentState);
|
|
36
|
-
assert.strictEqual(state.retryCount, 3, 'Should increment retryCount by 1');
|
|
37
|
-
assert.strictEqual(state.status, 'processing');
|
|
38
|
-
});
|
|
39
|
-
void it('should set a recent lastAttempt date', () => {
|
|
40
|
-
const before = new Date();
|
|
41
|
-
const state = createProcessingState('feat-1');
|
|
42
|
-
const after = new Date();
|
|
43
|
-
assert.ok(state.lastAttempt.getTime() >= before.getTime(), 'lastAttempt should be at or after the call');
|
|
44
|
-
assert.ok(state.lastAttempt.getTime() <= after.getTime(), 'lastAttempt should be at or before now');
|
|
45
|
-
});
|
|
46
|
-
void it('should increment from retryCount 1 to 2', () => {
|
|
47
|
-
const currentState = {
|
|
48
|
-
featureId: 'feat-1',
|
|
49
|
-
retryCount: 1,
|
|
50
|
-
lastAttempt: new Date('2024-01-01'),
|
|
51
|
-
status: 'completed',
|
|
52
|
-
};
|
|
53
|
-
const state = createProcessingState('feat-1', currentState);
|
|
54
|
-
assert.strictEqual(state.retryCount, 2);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
void describe('createCompletedState', () => {
|
|
58
|
-
void it('should create a completed state with retryCount 1 when no current state', () => {
|
|
59
|
-
const state = createCompletedState('feat-2');
|
|
60
|
-
assert.strictEqual(state.featureId, 'feat-2');
|
|
61
|
-
assert.strictEqual(state.retryCount, 1);
|
|
62
|
-
assert.strictEqual(state.status, 'completed');
|
|
63
|
-
assert.ok(state.lastAttempt instanceof Date);
|
|
64
|
-
});
|
|
65
|
-
void it('should preserve retryCount from current state', () => {
|
|
66
|
-
const currentState = {
|
|
67
|
-
featureId: 'feat-2',
|
|
68
|
-
retryCount: 5,
|
|
69
|
-
lastAttempt: new Date('2024-01-01'),
|
|
70
|
-
status: 'processing',
|
|
71
|
-
};
|
|
72
|
-
const state = createCompletedState('feat-2', currentState);
|
|
73
|
-
assert.strictEqual(state.retryCount, 5, 'Should preserve the existing retryCount');
|
|
74
|
-
assert.strictEqual(state.status, 'completed');
|
|
75
|
-
});
|
|
76
|
-
void it('should not increment retryCount (unlike createProcessingState)', () => {
|
|
77
|
-
const currentState = {
|
|
78
|
-
featureId: 'feat-2',
|
|
79
|
-
retryCount: 3,
|
|
80
|
-
lastAttempt: new Date('2024-01-01'),
|
|
81
|
-
status: 'processing',
|
|
82
|
-
};
|
|
83
|
-
const state = createCompletedState('feat-2', currentState);
|
|
84
|
-
assert.strictEqual(state.retryCount, 3, 'retryCount should stay the same, not increment');
|
|
85
|
-
});
|
|
86
|
-
void it('should update lastAttempt to current time', () => {
|
|
87
|
-
const oldDate = new Date('2024-01-01');
|
|
88
|
-
const currentState = {
|
|
89
|
-
featureId: 'feat-2',
|
|
90
|
-
retryCount: 1,
|
|
91
|
-
lastAttempt: oldDate,
|
|
92
|
-
status: 'processing',
|
|
93
|
-
};
|
|
94
|
-
const state = createCompletedState('feat-2', currentState);
|
|
95
|
-
assert.ok(state.lastAttempt.getTime() > oldDate.getTime(), 'lastAttempt should be newer than the old date');
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
void describe('createFailedState', () => {
|
|
99
|
-
void it('should create a failed state with retryCount 1 when no current state', () => {
|
|
100
|
-
const state = createFailedState('feat-3');
|
|
101
|
-
assert.strictEqual(state.featureId, 'feat-3');
|
|
102
|
-
assert.strictEqual(state.retryCount, 1);
|
|
103
|
-
assert.strictEqual(state.status, 'failed');
|
|
104
|
-
assert.ok(state.lastAttempt instanceof Date);
|
|
105
|
-
});
|
|
106
|
-
void it('should preserve retryCount from current state', () => {
|
|
107
|
-
const currentState = {
|
|
108
|
-
featureId: 'feat-3',
|
|
109
|
-
retryCount: 4,
|
|
110
|
-
lastAttempt: new Date('2024-01-01'),
|
|
111
|
-
status: 'processing',
|
|
112
|
-
};
|
|
113
|
-
const state = createFailedState('feat-3', currentState);
|
|
114
|
-
assert.strictEqual(state.retryCount, 4, 'Should preserve the existing retryCount');
|
|
115
|
-
assert.strictEqual(state.status, 'failed');
|
|
116
|
-
});
|
|
117
|
-
void it('should not increment retryCount (unlike createProcessingState)', () => {
|
|
118
|
-
const currentState = {
|
|
119
|
-
featureId: 'feat-3',
|
|
120
|
-
retryCount: 2,
|
|
121
|
-
lastAttempt: new Date('2024-01-01'),
|
|
122
|
-
status: 'processing',
|
|
123
|
-
};
|
|
124
|
-
const state = createFailedState('feat-3', currentState);
|
|
125
|
-
assert.strictEqual(state.retryCount, 2);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
void describe('updateFeatureState', () => {
|
|
129
|
-
void it('should return a new Map (immutability)', () => {
|
|
130
|
-
const original = new Map();
|
|
131
|
-
const updated = updateFeatureState(original, 'feat-1', () => createProcessingState('feat-1'));
|
|
132
|
-
assert.notStrictEqual(updated, original, 'Should return a different Map instance');
|
|
133
|
-
});
|
|
134
|
-
void it('should not mutate the original Map', () => {
|
|
135
|
-
const original = new Map();
|
|
136
|
-
updateFeatureState(original, 'feat-1', () => createProcessingState('feat-1'));
|
|
137
|
-
assert.strictEqual(original.size, 0, 'Original Map should remain empty');
|
|
138
|
-
});
|
|
139
|
-
void it('should add a new feature to the Map', () => {
|
|
140
|
-
const states = new Map();
|
|
141
|
-
const updated = updateFeatureState(states, 'feat-1', () => createProcessingState('feat-1'));
|
|
142
|
-
assert.strictEqual(updated.size, 1, 'Map should have one entry');
|
|
143
|
-
assert.strictEqual(updated.get('feat-1')?.status, 'processing');
|
|
144
|
-
});
|
|
145
|
-
void it('should update an existing feature in the Map', () => {
|
|
146
|
-
const initial = new Map([
|
|
147
|
-
[
|
|
148
|
-
'feat-1',
|
|
149
|
-
{
|
|
150
|
-
featureId: 'feat-1',
|
|
151
|
-
retryCount: 1,
|
|
152
|
-
lastAttempt: new Date('2024-01-01'),
|
|
153
|
-
status: 'processing',
|
|
154
|
-
},
|
|
155
|
-
],
|
|
156
|
-
]);
|
|
157
|
-
const updated = updateFeatureState(initial, 'feat-1', (current) => createCompletedState('feat-1', current));
|
|
158
|
-
assert.strictEqual(updated.get('feat-1')?.status, 'completed');
|
|
159
|
-
assert.strictEqual(updated.get('feat-1')?.retryCount, 1, 'retryCount should be preserved');
|
|
160
|
-
});
|
|
161
|
-
void it('should pass current state to the update function', () => {
|
|
162
|
-
const existingState = {
|
|
163
|
-
featureId: 'feat-1',
|
|
164
|
-
retryCount: 3,
|
|
165
|
-
lastAttempt: new Date('2024-01-01'),
|
|
166
|
-
status: 'processing',
|
|
167
|
-
};
|
|
168
|
-
const states = new Map([
|
|
169
|
-
['feat-1', existingState],
|
|
170
|
-
]);
|
|
171
|
-
let receivedState;
|
|
172
|
-
updateFeatureState(states, 'feat-1', (current) => {
|
|
173
|
-
receivedState = current;
|
|
174
|
-
return createCompletedState('feat-1', current);
|
|
175
|
-
});
|
|
176
|
-
assert.deepStrictEqual(receivedState, existingState, 'Update function should receive the current state');
|
|
177
|
-
});
|
|
178
|
-
void it('should pass undefined for non-existent feature', () => {
|
|
179
|
-
const states = new Map();
|
|
180
|
-
let receivedState = {
|
|
181
|
-
featureId: 'sentinel',
|
|
182
|
-
retryCount: 999,
|
|
183
|
-
lastAttempt: new Date(),
|
|
184
|
-
status: 'completed',
|
|
185
|
-
};
|
|
186
|
-
updateFeatureState(states, 'feat-new', (current) => {
|
|
187
|
-
receivedState = current;
|
|
188
|
-
return createProcessingState('feat-new', current);
|
|
189
|
-
});
|
|
190
|
-
assert.strictEqual(receivedState, undefined, 'Update function should receive undefined for non-existent feature');
|
|
191
|
-
});
|
|
192
|
-
void it('should preserve other features in the Map', () => {
|
|
193
|
-
const states = new Map([
|
|
194
|
-
[
|
|
195
|
-
'feat-1',
|
|
196
|
-
{
|
|
197
|
-
featureId: 'feat-1',
|
|
198
|
-
retryCount: 1,
|
|
199
|
-
lastAttempt: new Date('2024-01-01'),
|
|
200
|
-
status: 'completed',
|
|
201
|
-
},
|
|
202
|
-
],
|
|
203
|
-
[
|
|
204
|
-
'feat-2',
|
|
205
|
-
{
|
|
206
|
-
featureId: 'feat-2',
|
|
207
|
-
retryCount: 2,
|
|
208
|
-
lastAttempt: new Date('2024-01-02'),
|
|
209
|
-
status: 'processing',
|
|
210
|
-
},
|
|
211
|
-
],
|
|
212
|
-
]);
|
|
213
|
-
const updated = updateFeatureState(states, 'feat-2', (current) => createFailedState('feat-2', current));
|
|
214
|
-
assert.strictEqual(updated.size, 2, 'Map should still have two entries');
|
|
215
|
-
assert.strictEqual(updated.get('feat-1')?.status, 'completed', 'feat-1 should remain unchanged');
|
|
216
|
-
assert.strictEqual(updated.get('feat-2')?.status, 'failed', 'feat-2 should be updated');
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
void describe('calculateStats', () => {
|
|
220
|
-
void it('should return zeros for an empty Map', () => {
|
|
221
|
-
const states = new Map();
|
|
222
|
-
const stats = calculateStats(states, false);
|
|
223
|
-
assert.deepStrictEqual(stats, {
|
|
224
|
-
totalProcessed: 0,
|
|
225
|
-
completed: 0,
|
|
226
|
-
failed: 0,
|
|
227
|
-
processing: 0,
|
|
228
|
-
isRunning: false,
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
void it('should count all completed features', () => {
|
|
232
|
-
const states = new Map([
|
|
233
|
-
[
|
|
234
|
-
'feat-1',
|
|
235
|
-
{
|
|
236
|
-
featureId: 'feat-1',
|
|
237
|
-
retryCount: 1,
|
|
238
|
-
lastAttempt: new Date(),
|
|
239
|
-
status: 'completed',
|
|
240
|
-
},
|
|
241
|
-
],
|
|
242
|
-
[
|
|
243
|
-
'feat-2',
|
|
244
|
-
{
|
|
245
|
-
featureId: 'feat-2',
|
|
246
|
-
retryCount: 1,
|
|
247
|
-
lastAttempt: new Date(),
|
|
248
|
-
status: 'completed',
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
]);
|
|
252
|
-
const stats = calculateStats(states, true);
|
|
253
|
-
assert.strictEqual(stats.totalProcessed, 2);
|
|
254
|
-
assert.strictEqual(stats.completed, 2);
|
|
255
|
-
assert.strictEqual(stats.failed, 0);
|
|
256
|
-
assert.strictEqual(stats.processing, 0);
|
|
257
|
-
assert.strictEqual(stats.isRunning, true);
|
|
258
|
-
});
|
|
259
|
-
void it('should count mixed states correctly', () => {
|
|
260
|
-
const states = new Map([
|
|
261
|
-
[
|
|
262
|
-
'feat-1',
|
|
263
|
-
{
|
|
264
|
-
featureId: 'feat-1',
|
|
265
|
-
retryCount: 1,
|
|
266
|
-
lastAttempt: new Date(),
|
|
267
|
-
status: 'completed',
|
|
268
|
-
},
|
|
269
|
-
],
|
|
270
|
-
[
|
|
271
|
-
'feat-2',
|
|
272
|
-
{
|
|
273
|
-
featureId: 'feat-2',
|
|
274
|
-
retryCount: 2,
|
|
275
|
-
lastAttempt: new Date(),
|
|
276
|
-
status: 'failed',
|
|
277
|
-
},
|
|
278
|
-
],
|
|
279
|
-
[
|
|
280
|
-
'feat-3',
|
|
281
|
-
{
|
|
282
|
-
featureId: 'feat-3',
|
|
283
|
-
retryCount: 1,
|
|
284
|
-
lastAttempt: new Date(),
|
|
285
|
-
status: 'processing',
|
|
286
|
-
},
|
|
287
|
-
],
|
|
288
|
-
[
|
|
289
|
-
'feat-4',
|
|
290
|
-
{
|
|
291
|
-
featureId: 'feat-4',
|
|
292
|
-
retryCount: 1,
|
|
293
|
-
lastAttempt: new Date(),
|
|
294
|
-
status: 'completed',
|
|
295
|
-
},
|
|
296
|
-
],
|
|
297
|
-
]);
|
|
298
|
-
const stats = calculateStats(states, true);
|
|
299
|
-
assert.strictEqual(stats.totalProcessed, 4);
|
|
300
|
-
assert.strictEqual(stats.completed, 2);
|
|
301
|
-
assert.strictEqual(stats.failed, 1);
|
|
302
|
-
assert.strictEqual(stats.processing, 1);
|
|
303
|
-
assert.strictEqual(stats.isRunning, true);
|
|
304
|
-
});
|
|
305
|
-
void it('should reflect isRunning as false when stopped', () => {
|
|
306
|
-
const states = new Map([
|
|
307
|
-
[
|
|
308
|
-
'feat-1',
|
|
309
|
-
{
|
|
310
|
-
featureId: 'feat-1',
|
|
311
|
-
retryCount: 1,
|
|
312
|
-
lastAttempt: new Date(),
|
|
313
|
-
status: 'completed',
|
|
314
|
-
},
|
|
315
|
-
],
|
|
316
|
-
]);
|
|
317
|
-
const stats = calculateStats(states, false);
|
|
318
|
-
assert.strictEqual(stats.isRunning, false);
|
|
319
|
-
});
|
|
320
|
-
void it('should count all failed features', () => {
|
|
321
|
-
const states = new Map([
|
|
322
|
-
[
|
|
323
|
-
'feat-1',
|
|
324
|
-
{
|
|
325
|
-
featureId: 'feat-1',
|
|
326
|
-
retryCount: 3,
|
|
327
|
-
lastAttempt: new Date(),
|
|
328
|
-
status: 'failed',
|
|
329
|
-
},
|
|
330
|
-
],
|
|
331
|
-
[
|
|
332
|
-
'feat-2',
|
|
333
|
-
{
|
|
334
|
-
featureId: 'feat-2',
|
|
335
|
-
retryCount: 1,
|
|
336
|
-
lastAttempt: new Date(),
|
|
337
|
-
status: 'failed',
|
|
338
|
-
},
|
|
339
|
-
],
|
|
340
|
-
]);
|
|
341
|
-
const stats = calculateStats(states, false);
|
|
342
|
-
assert.strictEqual(stats.totalProcessed, 2);
|
|
343
|
-
assert.strictEqual(stats.completed, 0);
|
|
344
|
-
assert.strictEqual(stats.failed, 2);
|
|
345
|
-
assert.strictEqual(stats.processing, 0);
|
|
346
|
-
});
|
|
347
|
-
void it('should count all processing features', () => {
|
|
348
|
-
const states = new Map([
|
|
349
|
-
[
|
|
350
|
-
'feat-1',
|
|
351
|
-
{
|
|
352
|
-
featureId: 'feat-1',
|
|
353
|
-
retryCount: 1,
|
|
354
|
-
lastAttempt: new Date(),
|
|
355
|
-
status: 'processing',
|
|
356
|
-
},
|
|
357
|
-
],
|
|
358
|
-
[
|
|
359
|
-
'feat-2',
|
|
360
|
-
{
|
|
361
|
-
featureId: 'feat-2',
|
|
362
|
-
retryCount: 1,
|
|
363
|
-
lastAttempt: new Date(),
|
|
364
|
-
status: 'processing',
|
|
365
|
-
},
|
|
366
|
-
],
|
|
367
|
-
[
|
|
368
|
-
'feat-3',
|
|
369
|
-
{
|
|
370
|
-
featureId: 'feat-3',
|
|
371
|
-
retryCount: 1,
|
|
372
|
-
lastAttempt: new Date(),
|
|
373
|
-
status: 'processing',
|
|
374
|
-
},
|
|
375
|
-
],
|
|
376
|
-
]);
|
|
377
|
-
const stats = calculateStats(states, true);
|
|
378
|
-
assert.strictEqual(stats.totalProcessed, 3);
|
|
379
|
-
assert.strictEqual(stats.completed, 0);
|
|
380
|
-
assert.strictEqual(stats.failed, 0);
|
|
381
|
-
assert.strictEqual(stats.processing, 3);
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
});
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the config module (loadConfig and validateConfig)
|
|
3
|
-
*/
|
|
4
|
-
import assert from 'node:assert';
|
|
5
|
-
import { mkdtempSync } from 'node:fs';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
import { describe, it } from 'node:test';
|
|
9
|
-
import { loadConfig, validateConfig } from '../../config.js';
|
|
10
|
-
/**
|
|
11
|
-
* Helper to create a valid default-shaped config for validateConfig tests.
|
|
12
|
-
* Individual tests override specific fields to trigger validation errors.
|
|
13
|
-
*/
|
|
14
|
-
function makeValidConfig(overrides = {}) {
|
|
15
|
-
return {
|
|
16
|
-
patterns: ['**/*.ts'],
|
|
17
|
-
exclude: ['**/node_modules/**'],
|
|
18
|
-
severity: 'error',
|
|
19
|
-
maxFiles: 50,
|
|
20
|
-
claude: { timeout: 120000 },
|
|
21
|
-
workflow: {
|
|
22
|
-
maxConcurrency: 3,
|
|
23
|
-
pollInterval: 30000,
|
|
24
|
-
maxRetries: 3,
|
|
25
|
-
retryCooldown: 300000,
|
|
26
|
-
workerTimeout: 1800000,
|
|
27
|
-
maxVerificationIterations: 10,
|
|
28
|
-
},
|
|
29
|
-
...overrides,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
void describe('Config Module', () => {
|
|
33
|
-
// ── loadConfig ──────────────────────────────────────────────────────
|
|
34
|
-
void describe('loadConfig', () => {
|
|
35
|
-
void it('should return defaults when no config file exists', () => {
|
|
36
|
-
// Use a fresh temp directory that has no config files
|
|
37
|
-
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
38
|
-
const originalCwd = process.cwd();
|
|
39
|
-
try {
|
|
40
|
-
process.chdir(tempDir);
|
|
41
|
-
const config = loadConfig();
|
|
42
|
-
assert.ok(Array.isArray(config.patterns), 'patterns should be an array');
|
|
43
|
-
assert.ok(config.patterns.length > 0, 'patterns should not be empty');
|
|
44
|
-
assert.ok(Array.isArray(config.exclude), 'exclude should be an array');
|
|
45
|
-
assert.ok(config.exclude.length > 0, 'exclude should not be empty');
|
|
46
|
-
assert.strictEqual(config.severity, 'error');
|
|
47
|
-
assert.strictEqual(config.maxFiles, 50);
|
|
48
|
-
assert.strictEqual(config.claude.timeout, 120000);
|
|
49
|
-
}
|
|
50
|
-
finally {
|
|
51
|
-
process.chdir(originalCwd);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
void it('should include all default patterns for common languages', () => {
|
|
55
|
-
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
56
|
-
const originalCwd = process.cwd();
|
|
57
|
-
try {
|
|
58
|
-
process.chdir(tempDir);
|
|
59
|
-
const config = loadConfig();
|
|
60
|
-
const expectedPatterns = [
|
|
61
|
-
'**/*.js',
|
|
62
|
-
'**/*.ts',
|
|
63
|
-
'**/*.tsx',
|
|
64
|
-
'**/*.py',
|
|
65
|
-
'**/*.go',
|
|
66
|
-
'**/*.rs',
|
|
67
|
-
'**/*.java',
|
|
68
|
-
];
|
|
69
|
-
for (const pattern of expectedPatterns) {
|
|
70
|
-
assert.ok(config.patterns.includes(pattern), `Default patterns should include ${pattern}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
finally {
|
|
74
|
-
process.chdir(originalCwd);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
void it('should include sensible default excludes', () => {
|
|
78
|
-
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
79
|
-
const originalCwd = process.cwd();
|
|
80
|
-
try {
|
|
81
|
-
process.chdir(tempDir);
|
|
82
|
-
const config = loadConfig();
|
|
83
|
-
assert.ok(config.exclude.includes('**/node_modules/**'), 'Should exclude node_modules');
|
|
84
|
-
assert.ok(config.exclude.includes('**/dist/**'), 'Should exclude dist');
|
|
85
|
-
assert.ok(config.exclude.includes('**/__tests__/**'), 'Should exclude __tests__');
|
|
86
|
-
}
|
|
87
|
-
finally {
|
|
88
|
-
process.chdir(originalCwd);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
void it('should return correct default workflow values', () => {
|
|
92
|
-
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
93
|
-
const originalCwd = process.cwd();
|
|
94
|
-
try {
|
|
95
|
-
process.chdir(tempDir);
|
|
96
|
-
const config = loadConfig();
|
|
97
|
-
assert.strictEqual(config.workflow.maxConcurrency, 3);
|
|
98
|
-
assert.strictEqual(config.workflow.pollInterval, 30000);
|
|
99
|
-
assert.strictEqual(config.workflow.maxRetries, 3);
|
|
100
|
-
assert.strictEqual(config.workflow.retryCooldown, 300000);
|
|
101
|
-
assert.strictEqual(config.workflow.workerTimeout, 1800000);
|
|
102
|
-
assert.strictEqual(config.workflow.maxVerificationIterations, 10);
|
|
103
|
-
}
|
|
104
|
-
finally {
|
|
105
|
-
process.chdir(originalCwd);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
void it('should return a claude object with default timeout', () => {
|
|
109
|
-
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
110
|
-
const originalCwd = process.cwd();
|
|
111
|
-
try {
|
|
112
|
-
process.chdir(tempDir);
|
|
113
|
-
const config = loadConfig();
|
|
114
|
-
assert.ok(typeof config.claude === 'object' && config.claude !== null, 'claude should be an object');
|
|
115
|
-
assert.strictEqual(config.claude.timeout, 120000);
|
|
116
|
-
}
|
|
117
|
-
finally {
|
|
118
|
-
process.chdir(originalCwd);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
// ── validateConfig: valid configs ───────────────────────────────────
|
|
123
|
-
void describe('validateConfig - valid configs', () => {
|
|
124
|
-
void it('should return empty errors for a valid config', () => {
|
|
125
|
-
const config = makeValidConfig();
|
|
126
|
-
const errors = validateConfig(config);
|
|
127
|
-
assert.deepStrictEqual(errors, []);
|
|
128
|
-
});
|
|
129
|
-
void it('should accept severity "warning"', () => {
|
|
130
|
-
const config = makeValidConfig({ severity: 'warning' });
|
|
131
|
-
const errors = validateConfig(config);
|
|
132
|
-
assert.deepStrictEqual(errors, []);
|
|
133
|
-
});
|
|
134
|
-
void it('should accept config without claude.timeout (undefined)', () => {
|
|
135
|
-
const config = makeValidConfig({ claude: {} });
|
|
136
|
-
const errors = validateConfig(config);
|
|
137
|
-
assert.deepStrictEqual(errors, []);
|
|
138
|
-
});
|
|
139
|
-
void it('should accept maxFiles of 1', () => {
|
|
140
|
-
const config = makeValidConfig({ maxFiles: 1 });
|
|
141
|
-
const errors = validateConfig(config);
|
|
142
|
-
assert.deepStrictEqual(errors, []);
|
|
143
|
-
});
|
|
144
|
-
void it('should accept a large maxFiles value', () => {
|
|
145
|
-
const config = makeValidConfig({ maxFiles: 10000 });
|
|
146
|
-
const errors = validateConfig(config);
|
|
147
|
-
assert.deepStrictEqual(errors, []);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
// ── validateConfig: patterns ────────────────────────────────────────
|
|
151
|
-
void describe('validateConfig - patterns validation', () => {
|
|
152
|
-
void it('should return error for empty patterns array', () => {
|
|
153
|
-
const config = makeValidConfig({ patterns: [] });
|
|
154
|
-
const errors = validateConfig(config);
|
|
155
|
-
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
156
|
-
assert.ok(errors.some((e) => e.includes('patterns')), 'Error should mention patterns');
|
|
157
|
-
});
|
|
158
|
-
void it('should return error for non-array patterns', () => {
|
|
159
|
-
const config = makeValidConfig({
|
|
160
|
-
patterns: 'not-an-array',
|
|
161
|
-
});
|
|
162
|
-
const errors = validateConfig(config);
|
|
163
|
-
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
164
|
-
assert.ok(errors.some((e) => e.includes('patterns')), 'Error should mention patterns');
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
// ── validateConfig: exclude ─────────────────────────────────────────
|
|
168
|
-
void describe('validateConfig - exclude validation', () => {
|
|
169
|
-
void it('should return error for non-array exclude', () => {
|
|
170
|
-
const config = makeValidConfig({
|
|
171
|
-
exclude: 'not-an-array',
|
|
172
|
-
});
|
|
173
|
-
const errors = validateConfig(config);
|
|
174
|
-
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
175
|
-
assert.ok(errors.some((e) => e.includes('exclude')), 'Error should mention exclude');
|
|
176
|
-
});
|
|
177
|
-
void it('should accept empty exclude array', () => {
|
|
178
|
-
const config = makeValidConfig({ exclude: [] });
|
|
179
|
-
const errors = validateConfig(config);
|
|
180
|
-
assert.deepStrictEqual(errors, []);
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
// ── validateConfig: severity ────────────────────────────────────────
|
|
184
|
-
void describe('validateConfig - severity validation', () => {
|
|
185
|
-
void it('should return error for invalid severity', () => {
|
|
186
|
-
const config = makeValidConfig({
|
|
187
|
-
severity: 'info',
|
|
188
|
-
});
|
|
189
|
-
const errors = validateConfig(config);
|
|
190
|
-
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
191
|
-
assert.ok(errors.some((e) => e.includes('severity')), 'Error should mention severity');
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
// ── validateConfig: maxFiles ────────────────────────────────────────
|
|
195
|
-
void describe('validateConfig - maxFiles validation', () => {
|
|
196
|
-
void it('should return error for zero maxFiles', () => {
|
|
197
|
-
const config = makeValidConfig({ maxFiles: 0 });
|
|
198
|
-
const errors = validateConfig(config);
|
|
199
|
-
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
200
|
-
assert.ok(errors.some((e) => e.includes('maxFiles')), 'Error should mention maxFiles');
|
|
201
|
-
});
|
|
202
|
-
void it('should return error for negative maxFiles', () => {
|
|
203
|
-
const config = makeValidConfig({ maxFiles: -5 });
|
|
204
|
-
const errors = validateConfig(config);
|
|
205
|
-
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
206
|
-
assert.ok(errors.some((e) => e.includes('maxFiles')), 'Error should mention maxFiles');
|
|
207
|
-
});
|
|
208
|
-
void it('should return error for non-number maxFiles', () => {
|
|
209
|
-
const config = makeValidConfig({
|
|
210
|
-
maxFiles: 'fifty',
|
|
211
|
-
});
|
|
212
|
-
const errors = validateConfig(config);
|
|
213
|
-
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
214
|
-
assert.ok(errors.some((e) => e.includes('maxFiles')), 'Error should mention maxFiles');
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
// ── validateConfig: claude.timeout ──────────────────────────────────
|
|
218
|
-
void describe('validateConfig - claude.timeout validation', () => {
|
|
219
|
-
void it('should return error for negative timeout', () => {
|
|
220
|
-
const config = makeValidConfig({ claude: { timeout: -1000 } });
|
|
221
|
-
const errors = validateConfig(config);
|
|
222
|
-
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
223
|
-
assert.ok(errors.some((e) => e.includes('timeout')), 'Error should mention timeout');
|
|
224
|
-
});
|
|
225
|
-
void it('should return error for zero timeout', () => {
|
|
226
|
-
// timeout of 0 is falsy, so the validation guard skips it
|
|
227
|
-
// This documents the current behavior
|
|
228
|
-
const config = makeValidConfig({ claude: { timeout: 0 } });
|
|
229
|
-
const errors = validateConfig(config);
|
|
230
|
-
// 0 is falsy, so the `config.claude.timeout &&` guard means
|
|
231
|
-
// zero timeout does NOT trigger the validation error
|
|
232
|
-
assert.deepStrictEqual(errors, []);
|
|
233
|
-
});
|
|
234
|
-
void it('should return error for non-number timeout', () => {
|
|
235
|
-
const config = makeValidConfig({
|
|
236
|
-
claude: { timeout: 'slow' },
|
|
237
|
-
});
|
|
238
|
-
const errors = validateConfig(config);
|
|
239
|
-
assert.ok(errors.length > 0, 'Should have at least one error');
|
|
240
|
-
assert.ok(errors.some((e) => e.includes('timeout')), 'Error should mention timeout');
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
// ── validateConfig: multiple errors ─────────────────────────────────
|
|
244
|
-
void describe('validateConfig - multiple errors', () => {
|
|
245
|
-
void it('should collect all validation errors at once', () => {
|
|
246
|
-
const config = {
|
|
247
|
-
patterns: [],
|
|
248
|
-
exclude: 'not-array',
|
|
249
|
-
severity: 'info',
|
|
250
|
-
maxFiles: -1,
|
|
251
|
-
claude: { timeout: -500 },
|
|
252
|
-
workflow: {
|
|
253
|
-
maxConcurrency: 3,
|
|
254
|
-
pollInterval: 30000,
|
|
255
|
-
maxRetries: 3,
|
|
256
|
-
retryCooldown: 300000,
|
|
257
|
-
workerTimeout: 1800000,
|
|
258
|
-
maxVerificationIterations: 10,
|
|
259
|
-
},
|
|
260
|
-
};
|
|
261
|
-
const errors = validateConfig(config);
|
|
262
|
-
assert.strictEqual(errors.length, 5, 'Should report all five errors');
|
|
263
|
-
assert.ok(errors.some((e) => e.includes('patterns')));
|
|
264
|
-
assert.ok(errors.some((e) => e.includes('exclude')));
|
|
265
|
-
assert.ok(errors.some((e) => e.includes('severity')));
|
|
266
|
-
assert.ok(errors.some((e) => e.includes('maxFiles')));
|
|
267
|
-
assert.ok(errors.some((e) => e.includes('timeout')));
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
// ── Default config passes validation ────────────────────────────────
|
|
271
|
-
void describe('loadConfig + validateConfig integration', () => {
|
|
272
|
-
void it('should produce a config from loadConfig that passes validation', () => {
|
|
273
|
-
const tempDir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
|
|
274
|
-
const originalCwd = process.cwd();
|
|
275
|
-
try {
|
|
276
|
-
process.chdir(tempDir);
|
|
277
|
-
const config = loadConfig();
|
|
278
|
-
const errors = validateConfig(config);
|
|
279
|
-
assert.deepStrictEqual(errors, [], 'Default config from loadConfig should pass validation');
|
|
280
|
-
}
|
|
281
|
-
finally {
|
|
282
|
-
process.chdir(originalCwd);
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
});
|