elsabro 6.0.0 → 7.0.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,274 @@
1
+ 'use strict';
2
+
3
+ const { describe, it } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const { CallbackProtocol, SUPPORT_AGENTS, PADDING_ORDER } = require('../src/callbacks');
6
+
7
+ // ---------- requiresTeam ----------
8
+
9
+ describe('CallbackProtocol: requiresTeam', () => {
10
+ const protocol = new CallbackProtocol();
11
+
12
+ it('returns false for 0 branches', () => {
13
+ assert.equal(protocol.requiresTeam([]), false);
14
+ });
15
+
16
+ it('returns false for 1 branch', () => {
17
+ assert.equal(protocol.requiresTeam([
18
+ { id: 'a', agent: 'x', config: { model: 'opus' } }
19
+ ]), false);
20
+ });
21
+
22
+ it('returns true for 2+ opus branches', () => {
23
+ assert.equal(protocol.requiresTeam([
24
+ { id: 'a', agent: 'x', config: { model: 'opus' } },
25
+ { id: 'b', agent: 'y', config: { model: 'opus' } }
26
+ ]), true);
27
+ });
28
+
29
+ it('returns true for mixed haiku/opus branches', () => {
30
+ assert.equal(protocol.requiresTeam([
31
+ { id: 'a', agent: 'x', config: { model: 'haiku' } },
32
+ { id: 'b', agent: 'y', config: { model: 'opus' } }
33
+ ]), true);
34
+ });
35
+
36
+ it('returns false for 2+ all-haiku branches', () => {
37
+ assert.equal(protocol.requiresTeam([
38
+ { id: 'a', agent: 'x', config: { model: 'haiku' } },
39
+ { id: 'b', agent: 'y', config: { model: 'haiku' } },
40
+ { id: 'c', agent: 'z', config: { model: 'haiku' } }
41
+ ]), false);
42
+ });
43
+
44
+ it('returns true for branches with no config (defaults to non-haiku)', () => {
45
+ assert.equal(protocol.requiresTeam([
46
+ { id: 'a', agent: 'x' },
47
+ { id: 'b', agent: 'y' }
48
+ ]), true);
49
+ });
50
+ });
51
+
52
+ // ---------- generateTeamName ----------
53
+
54
+ describe('CallbackProtocol: generateTeamName', () => {
55
+ const protocol = new CallbackProtocol();
56
+
57
+ it('generates from nodeId alone', () => {
58
+ const name = protocol.generateTeamName('parallel_implementation');
59
+ assert.equal(name, 'elsabro-parallel-implementation');
60
+ });
61
+
62
+ it('generates from nodeId + task slug', () => {
63
+ const name = protocol.generateTeamName('parallel_review', 'auth-feature');
64
+ assert.equal(name, 'elsabro-parallel-review-auth-feature');
65
+ });
66
+
67
+ it('truncates long names to 38 chars', () => {
68
+ const name = protocol.generateTeamName('very_long_node_name_that_goes_on', 'and-a-very-long-task-slug');
69
+ assert.ok(name.length <= 38, `Name "${name}" exceeds 38 chars`);
70
+ assert.ok(name.startsWith('elsabro-'));
71
+ });
72
+ });
73
+
74
+ // ---------- composeTeam ----------
75
+
76
+ describe('CallbackProtocol: composeTeam', () => {
77
+ const protocol = new CallbackProtocol();
78
+
79
+ it('pads 2 branches to 5 members', () => {
80
+ const branches = [
81
+ { id: 'impl', agent: 'elsabro-executor', config: { model: 'opus' } },
82
+ { id: 'test', agent: 'elsabro-qa', config: { model: 'opus' } }
83
+ ];
84
+ const members = protocol.composeTeam(branches, 'implementation');
85
+ assert.equal(members.length, 5);
86
+ assert.equal(members[0].agent, 'elsabro-executor');
87
+ assert.equal(members[1].agent, 'elsabro-qa');
88
+ });
89
+
90
+ it('pads 3 branches to 5 members', () => {
91
+ const branches = [
92
+ { id: 'a', agent: 'agent-a' },
93
+ { id: 'b', agent: 'agent-b' },
94
+ { id: 'c', agent: 'agent-c' }
95
+ ];
96
+ const members = protocol.composeTeam(branches, 'default');
97
+ assert.equal(members.length, 5);
98
+ });
99
+
100
+ it('does not pad when already 5+ branches', () => {
101
+ const branches = Array.from({ length: 6 }, (_, i) => ({
102
+ id: `b${i}`, agent: `agent-${i}`, config: { model: 'opus' }
103
+ }));
104
+ const members = protocol.composeTeam(branches);
105
+ assert.equal(members.length, 6);
106
+ });
107
+
108
+ it('does not duplicate agents already in branches', () => {
109
+ const branches = [
110
+ { id: 'impl', agent: 'elsabro-executor' },
111
+ { id: 'qa', agent: 'elsabro-qa' }
112
+ ];
113
+ const members = protocol.composeTeam(branches, 'implementation');
114
+ const agents = members.map(m => m.agent);
115
+ const unique = new Set(agents);
116
+ assert.equal(unique.size, agents.length, 'Duplicate agents found');
117
+ });
118
+
119
+ it('uses context-specific padding order', () => {
120
+ // For 'review' context, first padding agent should be verifier (since it's not in branches)
121
+ const branches = [
122
+ { id: 'a', agent: 'agent-a' },
123
+ { id: 'b', agent: 'agent-b' }
124
+ ];
125
+
126
+ const reviewMembers = protocol.composeTeam(branches, 'review');
127
+ // First padded member (index 2) should be verifier for review context
128
+ assert.equal(reviewMembers[2].agent, 'elsabro-verifier');
129
+ // Second padded member (index 3) should be qa for review context
130
+ assert.equal(reviewMembers[3].agent, 'elsabro-qa');
131
+
132
+ const fixMembers = protocol.composeTeam(branches, 'fix');
133
+ assert.equal(fixMembers[2].agent, 'elsabro-verifier');
134
+ assert.equal(fixMembers[3].agent, 'elsabro-qa');
135
+ });
136
+ });
137
+
138
+ // ---------- buildParallelInstruction ----------
139
+
140
+ describe('CallbackProtocol: buildParallelInstruction', () => {
141
+ it('returns useAgentTeams=true for opus branches with 5+ team members', () => {
142
+ const protocol = new CallbackProtocol();
143
+ const branches = [
144
+ { id: 'impl', agent: 'elsabro-executor', config: { model: 'opus' }, inputs: {} },
145
+ { id: 'test', agent: 'elsabro-qa', config: { model: 'opus' }, inputs: {} }
146
+ ];
147
+ const instr = protocol.buildParallelInstruction('parallel_impl', branches, { nodeContext: 'implementation' });
148
+
149
+ assert.equal(instr.type, 'parallel');
150
+ assert.equal(instr.useAgentTeams, true);
151
+ assert.ok(instr.team);
152
+ assert.ok(instr.team.members.length >= 5);
153
+ assert.ok(instr.team.name.startsWith('elsabro-'));
154
+ });
155
+
156
+ it('returns useAgentTeams=false for haiku branches with no team object', () => {
157
+ const protocol = new CallbackProtocol();
158
+ const branches = [
159
+ { id: 'a', agent: 'Explore', config: { model: 'haiku' } },
160
+ { id: 'b', agent: 'Plan', config: { model: 'haiku' } }
161
+ ];
162
+ const instr = protocol.buildParallelInstruction('standard_analyze', branches);
163
+
164
+ assert.equal(instr.useAgentTeams, false);
165
+ assert.equal(instr.team, undefined);
166
+ });
167
+
168
+ it('team name follows convention', () => {
169
+ const protocol = new CallbackProtocol();
170
+ const branches = [
171
+ { id: 'a', agent: 'x', config: { model: 'opus' } },
172
+ { id: 'b', agent: 'y', config: { model: 'opus' } }
173
+ ];
174
+ const instr = protocol.buildParallelInstruction('fix_issues', branches, { taskSlug: 'auth bug' });
175
+ assert.ok(instr.team.name.startsWith('elsabro-'));
176
+ assert.ok(instr.team.name.length <= 38);
177
+ });
178
+
179
+ it('sets _activeTeam and _teamMembers', () => {
180
+ const protocol = new CallbackProtocol();
181
+ const branches = [
182
+ { id: 'a', agent: 'x', config: { model: 'opus' } },
183
+ { id: 'b', agent: 'y', config: { model: 'opus' } }
184
+ ];
185
+ protocol.buildParallelInstruction('test_node', branches);
186
+
187
+ assert.ok(protocol.hasActiveTeam());
188
+ assert.ok(protocol.getActiveTeam().startsWith('elsabro-'));
189
+ assert.ok(protocol.getTeamMembers().length >= 5);
190
+ });
191
+ });
192
+
193
+ // ---------- getLog + getTeamMembers ----------
194
+
195
+ describe('CallbackProtocol: logging and state', () => {
196
+ it('records team_parallel log with member count', () => {
197
+ const protocol = new CallbackProtocol();
198
+ const branches = [
199
+ { id: 'a', agent: 'x', config: { model: 'opus' } },
200
+ { id: 'b', agent: 'y', config: { model: 'opus' } }
201
+ ];
202
+ protocol.buildParallelInstruction('node1', branches);
203
+
204
+ const log = protocol.getLog();
205
+ assert.equal(log.length, 1);
206
+ assert.equal(log[0].action, 'team_parallel');
207
+ assert.ok(log[0].memberCount >= 5);
208
+ });
209
+
210
+ it('getTeamMembers returns current roster', () => {
211
+ const protocol = new CallbackProtocol();
212
+ const branches = [
213
+ { id: 'a', agent: 'x', config: { model: 'opus' } },
214
+ { id: 'b', agent: 'y', config: { model: 'opus' } }
215
+ ];
216
+ protocol.buildParallelInstruction('node1', branches);
217
+
218
+ const members = protocol.getTeamMembers();
219
+ assert.ok(members.length >= 5);
220
+ assert.equal(members[0].agent, 'x');
221
+ assert.equal(members[1].agent, 'y');
222
+ });
223
+ });
224
+
225
+ // ---------- Other instruction builders ----------
226
+
227
+ describe('CallbackProtocol: instruction builders', () => {
228
+ const protocol = new CallbackProtocol();
229
+
230
+ it('buildAgentInstruction has correct type and fields', () => {
231
+ const instr = protocol.buildAgentInstruction('node1', 'elsabro-executor', { model: 'opus' }, { task: 'foo' });
232
+ assert.equal(instr.type, 'agent');
233
+ assert.equal(instr.nodeId, 'node1');
234
+ assert.equal(instr.agent, 'elsabro-executor');
235
+ assert.equal(instr.model, 'opus');
236
+ assert.deepEqual(instr.inputs, { task: 'foo' });
237
+ });
238
+
239
+ it('buildInterruptInstruction has correct type and fields', () => {
240
+ const instr = protocol.buildInterruptInstruction('int1', { title: 'Test' }, { approve: 'next' }, 'reason');
241
+ assert.equal(instr.type, 'interrupt');
242
+ assert.equal(instr.nodeId, 'int1');
243
+ assert.deepEqual(instr.display, { title: 'Test' });
244
+ assert.deepEqual(instr.routes, { approve: 'next' });
245
+ assert.equal(instr.reason, 'reason');
246
+ });
247
+
248
+ it('buildSequenceInstruction has correct type and fields', () => {
249
+ const steps = [{ action: 'bash', command: 'echo hi' }];
250
+ const instr = protocol.buildSequenceInstruction('seq1', steps);
251
+ assert.equal(instr.type, 'sequence');
252
+ assert.equal(instr.nodeId, 'seq1');
253
+ assert.equal(instr.steps.length, 1);
254
+ });
255
+ });
256
+
257
+ // ---------- clearActiveTeam ----------
258
+
259
+ describe('CallbackProtocol: clearActiveTeam', () => {
260
+ it('resets active team state', () => {
261
+ const protocol = new CallbackProtocol();
262
+ const branches = [
263
+ { id: 'a', agent: 'x', config: { model: 'opus' } },
264
+ { id: 'b', agent: 'y', config: { model: 'opus' } }
265
+ ];
266
+ protocol.buildParallelInstruction('node1', branches);
267
+ assert.ok(protocol.hasActiveTeam());
268
+
269
+ protocol.clearActiveTeam();
270
+ assert.equal(protocol.hasActiveTeam(), false);
271
+ assert.equal(protocol.getActiveTeam(), null);
272
+ assert.equal(protocol.getTeamMembers().length, 0);
273
+ });
274
+ });
@@ -0,0 +1,208 @@
1
+ 'use strict';
2
+
3
+ const { describe, it, beforeEach, afterEach } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const { main, parseArgs, flowIdFromPath, isAutoResolvable, detectNodeContext } = require('../src/cli');
9
+
10
+ const FLOW_PATH = path.resolve(__dirname, '../../flows/development-flow.json');
11
+
12
+ // Use a temp dir for checkpoints to avoid polluting project
13
+ let tmpDir;
14
+ let origCwd;
15
+
16
+ beforeEach(() => {
17
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'elsabro-cli-test-'));
18
+ origCwd = process.cwd();
19
+ process.chdir(tmpDir);
20
+ });
21
+
22
+ afterEach(() => {
23
+ process.chdir(origCwd);
24
+ fs.rmSync(tmpDir, { recursive: true, force: true });
25
+ });
26
+
27
+ // ---------- Helpers ----------
28
+
29
+ describe('CLI: helpers', () => {
30
+ it('parseArgs extracts command and flags', () => {
31
+ const { command, args } = parseArgs(['node', 'cli.js', 'validate', '--flow', 'test.json']);
32
+ assert.equal(command, 'validate');
33
+ assert.equal(args.flow, 'test.json');
34
+ });
35
+
36
+ it('flowIdFromPath generates valid id', () => {
37
+ assert.equal(flowIdFromPath('flows/development-flow.json'), 'development-flow');
38
+ });
39
+
40
+ it('detectNodeContext classifies node names', () => {
41
+ assert.equal(detectNodeContext('parallel_implementation'), 'implementation');
42
+ assert.equal(detectNodeContext('parallel_review'), 'review');
43
+ assert.equal(detectNodeContext('fix_issues'), 'fix');
44
+ assert.equal(detectNodeContext('some_node'), 'default');
45
+ });
46
+ });
47
+
48
+ // ---------- validate ----------
49
+
50
+ describe('CLI: validate', () => {
51
+ it('reports valid flow with 42 nodes', async () => {
52
+ const result = await main(['node', 'cli.js', 'validate', '--flow', FLOW_PATH]);
53
+ assert.equal(result.valid, true);
54
+ assert.equal(result.nodeCount, 42);
55
+ assert.ok(result.parallelNodes.length >= 4);
56
+ });
57
+
58
+ it('parallel_implementation shows 5 team members', async () => {
59
+ const result = await main(['node', 'cli.js', 'validate', '--flow', FLOW_PATH]);
60
+ const implNode = result.parallelNodes.find(n => n.nodeId === 'parallel_implementation');
61
+ assert.ok(implNode, 'parallel_implementation not found');
62
+ assert.equal(implNode.useAgentTeams, true);
63
+ assert.equal(implNode.team.memberCount, 5);
64
+ });
65
+ });
66
+
67
+ // ---------- init + step + complete ----------
68
+
69
+ describe('CLI: init + step + complete', () => {
70
+ it('init creates checkpoint and returns entryNode', async () => {
71
+ const result = await main(['node', 'cli.js', 'init', '--flow', FLOW_PATH, '--task', 'test task', '--profile', 'default']);
72
+ assert.equal(result.initialized, true);
73
+ assert.equal(result.entryNode, 'start');
74
+ assert.equal(result.task, 'test task');
75
+ });
76
+
77
+ it('step advances past entry to first actionable node', async () => {
78
+ // Init first
79
+ await main(['node', 'cli.js', 'init', '--flow', FLOW_PATH, '--task', 'build auth', '--profile', 'default']);
80
+
81
+ // Step
82
+ const result = await main(['node', 'cli.js', 'step', '--flow', FLOW_PATH]);
83
+ assert.ok(!result.error, `Step errored: ${result.error}`);
84
+ // Should have advanced past entry + condition/router to an actionable node
85
+ assert.ok(result.instruction || result.finished, 'Expected instruction or finished');
86
+ });
87
+
88
+ it('complete accepts result and advances', async () => {
89
+ await main(['node', 'cli.js', 'init', '--flow', FLOW_PATH, '--task', 'test', '--profile', 'default']);
90
+ const stepResult = await main(['node', 'cli.js', 'step', '--flow', FLOW_PATH]);
91
+
92
+ if (stepResult.instruction) {
93
+ const completeResult = await main([
94
+ 'node', 'cli.js', 'complete', '--flow', FLOW_PATH,
95
+ '--result', JSON.stringify({ output: 'test result' })
96
+ ]);
97
+ assert.equal(completeResult.completed, true);
98
+ assert.ok(completeResult.nodeId);
99
+ }
100
+ });
101
+
102
+ it('subsequent step reaches next node', async () => {
103
+ await main(['node', 'cli.js', 'init', '--flow', FLOW_PATH, '--task', 'test', '--profile', 'default']);
104
+ const step1 = await main(['node', 'cli.js', 'step', '--flow', FLOW_PATH]);
105
+
106
+ if (step1.instruction) {
107
+ await main([
108
+ 'node', 'cli.js', 'complete', '--flow', FLOW_PATH,
109
+ '--result', JSON.stringify({ output: 'done' })
110
+ ]);
111
+
112
+ const step2 = await main(['node', 'cli.js', 'step', '--flow', FLOW_PATH]);
113
+ assert.ok(step2.instruction || step2.finished, 'Expected instruction or finished');
114
+ // Should be a different node than step1 (or finished)
115
+ if (step2.instruction && step1.instruction) {
116
+ // They could be the same node if flow loops, but at least it advanced
117
+ assert.ok(step2.nodeId);
118
+ }
119
+ }
120
+ });
121
+ });
122
+
123
+ // ---------- status ----------
124
+
125
+ describe('CLI: status', () => {
126
+ it('shows active session with nodesVisited', async () => {
127
+ await main(['node', 'cli.js', 'init', '--flow', FLOW_PATH, '--task', 'test', '--profile', 'default']);
128
+ await main(['node', 'cli.js', 'step', '--flow', FLOW_PATH]);
129
+
130
+ const result = await main(['node', 'cli.js', 'status', '--flow', FLOW_PATH]);
131
+ assert.equal(result.active, true);
132
+ assert.ok(Array.isArray(result.nodesVisited));
133
+ });
134
+ });
135
+
136
+ // ---------- dry-run ----------
137
+
138
+ describe('CLI: dry-run', () => {
139
+ it('completes a run and reports nodes visited', async () => {
140
+ const result = await main([
141
+ 'node', 'cli.js', 'dry-run', '--flow', FLOW_PATH, '--task', 'test', '--profile', 'default'
142
+ ]);
143
+ assert.ok(!result.error, `Dry-run errored: ${result.error}`);
144
+ assert.equal(result.success, true);
145
+ assert.ok(result.nodesVisited.length > 5, 'Expected more than 5 nodes visited');
146
+ });
147
+
148
+ it('reports teamActions with 5+ members for non-haiku parallels', async () => {
149
+ const result = await main([
150
+ 'node', 'cli.js', 'dry-run', '--flow', FLOW_PATH, '--task', 'test', '--profile', 'default'
151
+ ]);
152
+ // Should have team actions for parallel_implementation, fix_issues, parallel_review
153
+ if (result.teamActions && result.teamActions.length > 0) {
154
+ for (const ta of result.teamActions) {
155
+ assert.ok(ta.memberCount >= 5, `Team ${ta.teamName} has ${ta.memberCount} members, expected >= 5`);
156
+ }
157
+ }
158
+ });
159
+
160
+ it('visits profile-specific path nodes based on profile', async () => {
161
+ const result = await main([
162
+ 'node', 'cli.js', 'dry-run', '--flow', FLOW_PATH, '--task', 'quick job', '--profile', 'yolo'
163
+ ]);
164
+ // Yolo profile should visit yolo-related nodes
165
+ assert.ok(result.nodesVisited.length > 3, 'Expected nodes to be visited');
166
+ });
167
+ });
168
+
169
+ // ---------- Edge cases ----------
170
+
171
+ describe('CLI: edge cases', () => {
172
+ it('step with no checkpoint returns error', async () => {
173
+ const result = await main(['node', 'cli.js', 'step', '--flow', FLOW_PATH]);
174
+ assert.ok(result.error);
175
+ assert.ok(result.error.includes('No checkpoint'));
176
+ });
177
+
178
+ it('complete with no checkpoint returns error', async () => {
179
+ const result = await main([
180
+ 'node', 'cli.js', 'complete', '--flow', FLOW_PATH,
181
+ '--result', '{"ok": true}'
182
+ ]);
183
+ assert.ok(result.error);
184
+ assert.ok(result.error.includes('No checkpoint'));
185
+ });
186
+
187
+ it('double-complete returns error instead of corrupting state', async () => {
188
+ // init + step to reach an actionable node
189
+ await main(['node', 'cli.js', 'init', '--flow', FLOW_PATH, '--task', 'test', '--profile', 'default']);
190
+ const stepResult = await main(['node', 'cli.js', 'step', '--flow', FLOW_PATH]);
191
+ assert.ok(stepResult.instruction, 'Expected actionable instruction');
192
+
193
+ // First complete — should succeed
194
+ const first = await main([
195
+ 'node', 'cli.js', 'complete', '--flow', FLOW_PATH,
196
+ '--result', '{"output": "done"}'
197
+ ]);
198
+ assert.ok(first.completed, 'First complete should succeed');
199
+
200
+ // Second complete on same node — should be rejected
201
+ const second = await main([
202
+ 'node', 'cli.js', 'complete', '--flow', FLOW_PATH,
203
+ '--result', '{"output": "done again"}'
204
+ ]);
205
+ assert.ok(second.error, 'Double-complete should return error');
206
+ assert.ok(second.error.includes('already completed'), 'Error should mention already completed');
207
+ });
208
+ });
@@ -7,7 +7,9 @@ const {
7
7
  executeParallel,
8
8
  executeInterrupt,
9
9
  executeTeam,
10
- NotImplementedError
10
+ checkRuntimeStatus,
11
+ NotImplementedError,
12
+ DeprecatedNodeError
11
13
  } = require('../src/executors');
12
14
 
13
15
  function makeContext(overrides = {}) {
@@ -298,3 +300,61 @@ describe('executeTeam', () => {
298
300
  assert.equal(result.next, 'done');
299
301
  });
300
302
  });
303
+
304
+ // ============================================================
305
+ // P5: checkRuntimeStatus + DeprecatedNodeError
306
+ // ============================================================
307
+
308
+ describe('checkRuntimeStatus (P5)', () => {
309
+ it('throws NotImplementedError for not_implemented nodes', () => {
310
+ assert.throws(
311
+ () => checkRuntimeStatus({ id: 'test_node', runtime_status: 'not_implemented', gaps: ['test gap'] }),
312
+ (err) => {
313
+ assert.equal(err.name, 'NotImplementedError');
314
+ assert.equal(err.nodeId, 'test_node');
315
+ return true;
316
+ }
317
+ );
318
+ });
319
+
320
+ it('throws DeprecatedNodeError for deprecated nodes', () => {
321
+ assert.throws(
322
+ () => checkRuntimeStatus({ id: 'old_node', runtime_status: 'deprecated', deprecated_reason: 'Use new_node instead' }),
323
+ (err) => {
324
+ assert.equal(err.name, 'DeprecatedNodeError');
325
+ assert.equal(err.nodeId, 'old_node');
326
+ assert.equal(err.reason, 'Use new_node instead');
327
+ assert.ok(err.message.includes('deprecated'));
328
+ return true;
329
+ }
330
+ );
331
+ });
332
+
333
+ it('does not throw for implemented nodes', () => {
334
+ assert.doesNotThrow(
335
+ () => checkRuntimeStatus({ id: 'good_node', runtime_status: 'implemented' })
336
+ );
337
+ });
338
+
339
+ it('uses fallback reason when deprecated_reason is missing', () => {
340
+ assert.throws(
341
+ () => checkRuntimeStatus({ id: 'node_x', runtime_status: 'deprecated' }),
342
+ (err) => {
343
+ assert.equal(err.reason, 'Node is deprecated');
344
+ return true;
345
+ }
346
+ );
347
+ });
348
+ });
349
+
350
+ describe('DeprecatedNodeError properties', () => {
351
+ it('has correct name, nodeId, and reason', () => {
352
+ const err = new DeprecatedNodeError('my_node', 'superseded by v2');
353
+ assert.equal(err.name, 'DeprecatedNodeError');
354
+ assert.equal(err.nodeId, 'my_node');
355
+ assert.equal(err.reason, 'superseded by v2');
356
+ assert.ok(err.message.includes('my_node'));
357
+ assert.ok(err.message.includes('superseded by v2'));
358
+ assert.ok(err instanceof Error);
359
+ });
360
+ });