deepdebug-local-agent 1.0.21 → 1.0.23

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,101 @@
1
+ // test/adapters/registry.test.js
2
+ // Contract tests for the Phase 2 adapter registry — Plan 02-12 Wave 3.
3
+ // Validates loadAdapters() construction, allowed-stack enforcement, and
4
+ // stopAll() lifecycle/timeout behaviour (AC-4, AC-11).
5
+
6
+ import { test, describe } from 'node:test';
7
+ import assert from 'node:assert/strict';
8
+ import {
9
+ loadAdapters,
10
+ stopAll,
11
+ ALLOWED_STACK_LIST,
12
+ } from '../../src/adapters/index.js';
13
+
14
+ describe('adapter registry', () => {
15
+ test('loadAdapters({legacyStacks:[]}) returns []', () => {
16
+ assert.deepEqual(loadAdapters({ legacyStacks: [] }), []);
17
+ });
18
+
19
+ test('loadAdapters({}) returns [] (missing legacyStacks treated as empty)', () => {
20
+ assert.deepEqual(loadAdapters({}), []);
21
+ });
22
+
23
+ test('loadAdapters({legacyStacks:["sap"]}) returns 1 SapLogAdapter, unstarted', () => {
24
+ const adapters = loadAdapters({ legacyStacks: ['sap'] });
25
+ assert.equal(adapters.length, 1);
26
+ assert.equal(adapters[0].constructor.name, 'SapLogAdapter');
27
+ assert.equal(adapters[0].isRunning, false);
28
+ });
29
+
30
+ test('loadAdapters with all 5 stacks returns 5 unstarted adapters', () => {
31
+ const adapters = loadAdapters({ legacyStacks: ALLOWED_STACK_LIST.slice() });
32
+ assert.equal(adapters.length, 5);
33
+ const ctorNames = adapters.map((a) => a.constructor.name).sort();
34
+ assert.deepEqual(
35
+ ctorNames,
36
+ [
37
+ 'As400LogAdapter',
38
+ 'CobolLogAdapter',
39
+ 'IbmMqLogAdapter',
40
+ 'OracleFormsLogAdapter',
41
+ 'SapLogAdapter',
42
+ ],
43
+ );
44
+ for (const a of adapters) assert.equal(a.isRunning, false);
45
+ });
46
+
47
+ test('loadAdapters({legacyStacks:["unknown"]}) throws with stack name', () => {
48
+ assert.throws(
49
+ () => loadAdapters({ legacyStacks: ['unknown'] }),
50
+ /unknown/,
51
+ );
52
+ });
53
+
54
+ test('loadAdapters propagates per-stack config to constructor', () => {
55
+ const adapters = loadAdapters({
56
+ legacyStacks: ['sap'],
57
+ sap: { sapDir: '/tmp/test-sap', instanceNumber: '42', interval: 1234 },
58
+ });
59
+ assert.equal(adapters[0].sapDir, '/tmp/test-sap');
60
+ assert.equal(adapters[0].instanceNumber, '42');
61
+ assert.equal(adapters[0].interval, 1234);
62
+ });
63
+
64
+ test('stopAll([]) resolves silently', async () => {
65
+ await stopAll([]);
66
+ });
67
+
68
+ test('stopAll(undefined) resolves silently', async () => {
69
+ await stopAll(undefined);
70
+ });
71
+
72
+ test('stopAll calls stop() on each adapter', async () => {
73
+ let stopped = 0;
74
+ const fake = { stop: async () => { stopped++; } };
75
+ await stopAll([fake, fake, fake]);
76
+ assert.equal(stopped, 3);
77
+ });
78
+
79
+ test('stopAll respects timeout — slow stop does not block forever', async () => {
80
+ const slow = { stop: () => new Promise(() => { /* never resolve */ }) };
81
+ const before = Date.now();
82
+ await stopAll([slow], 200);
83
+ const elapsed = Date.now() - before;
84
+ assert.ok(elapsed < 1000, `stopAll should return within timeout; took ${elapsed}ms`);
85
+ });
86
+
87
+ test('stopAll swallows per-adapter stop() errors (never throws)', async () => {
88
+ const bad = { stop: async () => { throw new Error('boom'); } };
89
+ const good = { stop: async () => { /* ok */ } };
90
+ // Should resolve without rejecting.
91
+ await stopAll([bad, good, bad]);
92
+ });
93
+
94
+ test('ALLOWED_STACK_LIST is the canonical 5-stack list, frozen', () => {
95
+ assert.deepEqual(
96
+ [...ALLOWED_STACK_LIST].sort(),
97
+ ['as400', 'cobol', 'ibmmq', 'oracle-forms', 'sap'],
98
+ );
99
+ assert.equal(Object.isFrozen(ALLOWED_STACK_LIST), true);
100
+ });
101
+ });
@@ -0,0 +1,142 @@
1
+ import { test, describe, before, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { writeFileSync, appendFileSync, truncateSync, mkdirSync, rmSync } from 'node:fs';
4
+ import { mkdtempSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { setTimeout as sleep } from 'node:timers/promises';
8
+ import { SapLogAdapter } from '../../../src/adapters/sap/index.js';
9
+ import { SAP_DEV_W_REGEX, SAP_SLOG_REGEX } from '../../../src/adapters/sap/sap-parser.js';
10
+
11
+ function tempSapTree() {
12
+ const root = mkdtempSync(join(tmpdir(), 'sap-test-'));
13
+ const workDir = join(root, 'D00', 'work');
14
+ const logDir = join(root, 'D00', 'log');
15
+ mkdirSync(workDir, { recursive: true });
16
+ mkdirSync(logDir, { recursive: true });
17
+ return { root, workDir, logDir };
18
+ }
19
+
20
+ async function awaitEvents(adapter, n, timeoutMs = 12000) {
21
+ const events = [];
22
+ return new Promise((resolve, reject) => {
23
+ const onErr = (e) => { events.push(e); if (events.length >= n) { adapter.off('error', onErr); resolve(events); } };
24
+ adapter.on('error', onErr);
25
+ setTimeout(() => { adapter.off('error', onErr); resolve(events); }, timeoutMs);
26
+ });
27
+ }
28
+
29
+ describe('SapLogAdapter', () => {
30
+ test('TAIL mode default — pre-existing content not emitted (AC-9)', async () => {
31
+ const { root, workDir } = tempSapTree();
32
+ const f = join(workDir, 'dev_w0');
33
+ writeFileSync(f, [
34
+ 'trc file: "dev_w0"',
35
+ 'M *** ERROR => Pre-existing CONVT_NO_NUMBER',
36
+ ].join('\n') + '\n');
37
+
38
+ const adapter = new SapLogAdapter({ sapDir: root, instanceNumber: '00', interval: 200 });
39
+ await adapter.start();
40
+ const events = await awaitEvents(adapter, 1, 1500);
41
+ await adapter.stop();
42
+ assert.equal(events.length, 0, 'no events should fire for pre-existing content');
43
+ rmSync(root, { recursive: true, force: true });
44
+ });
45
+
46
+ test('emits 6-key event on appended dev_w error line (AC-2)', async () => {
47
+ const { root, workDir } = tempSapTree();
48
+ const f = join(workDir, 'dev_w0');
49
+ writeFileSync(f, 'trc file: "dev_w0"\n');
50
+
51
+ const adapter = new SapLogAdapter({ sapDir: root, instanceNumber: '00', interval: 200 });
52
+ await adapter.start();
53
+ await sleep(400);
54
+ appendFileSync(f, 'M *** ERROR => Runtime error CONVT_NO_NUMBER\n');
55
+ const events = await awaitEvents(adapter, 1, 5000);
56
+ await adapter.stop();
57
+
58
+ assert.equal(events.length, 1);
59
+ const e = events[0];
60
+ assert.equal(Object.keys(e).sort().join(','), 'adapter,errorCode,file,message,source,timestamp');
61
+ assert.equal(e.adapter, 'sap');
62
+ assert.equal(e.errorCode, 'CONVT_NO_NUMBER');
63
+ assert.equal(e.file, f);
64
+ assert.match(e.message, /CONVT_NO_NUMBER/);
65
+ assert.equal(e.source, workDir);
66
+ assert.match(e.timestamp, /^\d{4}-\d{2}-\d{2}T/);
67
+ rmSync(root, { recursive: true, force: true });
68
+ });
69
+
70
+ test('emits event on SLOG line', async () => {
71
+ const { root, logDir } = tempSapTree();
72
+ const f = join(logDir, 'SLOG00');
73
+ writeFileSync(f, '| 2024-01-15 09:00:00 | I | 00 | System log opened |\n');
74
+
75
+ const adapter = new SapLogAdapter({ sapDir: root, instanceNumber: '00', interval: 200 });
76
+ await adapter.start();
77
+ await sleep(400);
78
+ appendFileSync(f, '| 2024-01-15 09:12:44 | E | 04 | Database error in select on ZSALES_ORDER, sy-subrc = 4 |\n');
79
+ const events = await awaitEvents(adapter, 1, 5000);
80
+ await adapter.stop();
81
+ assert.equal(events.length, 1);
82
+ assert.equal(events[0].adapter, 'sap');
83
+ rmSync(root, { recursive: true, force: true });
84
+ });
85
+
86
+ test('start/stop/start lifecycle idempotent (AC-1)', async () => {
87
+ const { root, workDir } = tempSapTree();
88
+ writeFileSync(join(workDir, 'dev_w0'), 'trc\n');
89
+ const adapter = new SapLogAdapter({ sapDir: root, instanceNumber: '00', interval: 200 });
90
+ await adapter.start();
91
+ await adapter.start(); // idempotent
92
+ await adapter.stop();
93
+ await adapter.stop(); // idempotent
94
+ await adapter.start();
95
+ await adapter.stop();
96
+ rmSync(root, { recursive: true, force: true });
97
+ });
98
+
99
+ test('rotation: truncate + append → event emitted (AC-10)', async () => {
100
+ const { root, workDir } = tempSapTree();
101
+ const f = join(workDir, 'dev_w0');
102
+ writeFileSync(f, 'trc\nM *** ERROR => OLD_BASELINE\n');
103
+
104
+ const adapter = new SapLogAdapter({ sapDir: root, instanceNumber: '00', interval: 200 });
105
+ await adapter.start();
106
+ await sleep(400);
107
+ truncateSync(f, 0);
108
+ await sleep(400);
109
+ appendFileSync(f, 'M *** ERROR => Runtime error CONVT_NO_NUMBER\n');
110
+ const events = await awaitEvents(adapter, 1, 5000);
111
+ await adapter.stop();
112
+ assert.ok(events.length >= 1, 'rotation should produce at least 1 event');
113
+ rmSync(root, { recursive: true, force: true });
114
+ });
115
+
116
+ test('ENOENT silent — no event, no throw', async () => {
117
+ const adapter = new SapLogAdapter({ sapDir: '/nonexistent/path-' + Date.now(), instanceNumber: '00', interval: 200 });
118
+ await adapter.start();
119
+ await sleep(500);
120
+ await adapter.stop();
121
+ });
122
+
123
+ test('parser regexes exported and callable (T-2-03)', () => {
124
+ assert.ok(SAP_DEV_W_REGEX instanceof RegExp);
125
+ assert.ok(SAP_SLOG_REGEX instanceof RegExp);
126
+ const m = 'M *** ERROR => Runtime error CONVT_NO_NUMBER'.match(SAP_DEV_W_REGEX);
127
+ assert.ok(m, 'dev_w regex should match runtime error line');
128
+ });
129
+
130
+ test('replayOnStart: true emits pre-existing content', async () => {
131
+ const { root, workDir } = tempSapTree();
132
+ const f = join(workDir, 'dev_w0');
133
+ writeFileSync(f, 'M *** ERROR => Runtime error CONVT_NO_NUMBER\n');
134
+
135
+ const adapter = new SapLogAdapter({ sapDir: root, instanceNumber: '00', interval: 200, replayOnStart: true });
136
+ await adapter.start();
137
+ const events = await awaitEvents(adapter, 1, 5000);
138
+ await adapter.stop();
139
+ assert.ok(events.length >= 1);
140
+ rmSync(root, { recursive: true, force: true });
141
+ });
142
+ });
Binary file