tm1npm 1.2.0 → 1.3.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,377 @@
1
+ /**
2
+ * Unit tests for DebuggerService (Issue #13)
3
+ * Tests debug session management, variable inspection, and process analysis
4
+ */
5
+
6
+ import { DebuggerService } from '../services/DebuggerService';
7
+ import { ProcessService } from '../services/ProcessService';
8
+ import { RestService } from '../services/RestService';
9
+ import { AxiosResponse } from 'axios';
10
+
11
+ // Mock RestService
12
+ jest.mock('../services/RestService');
13
+
14
+ // Helper to create mock AxiosResponse
15
+ const createMockResponse = <T>(data: T): AxiosResponse<T> => ({
16
+ data,
17
+ status: 200,
18
+ statusText: 'OK',
19
+ headers: {},
20
+ config: {} as any
21
+ });
22
+
23
+ describe('DebuggerService and Enhanced Process Debugging (Issue #13)', () => {
24
+ let debuggerService: DebuggerService;
25
+ let processService: ProcessService;
26
+ let mockRestService: jest.Mocked<RestService>;
27
+
28
+ beforeEach(() => {
29
+ mockRestService = new RestService({
30
+ address: 'localhost',
31
+ port: 8001,
32
+ user: 'admin',
33
+ password: 'apple'
34
+ }) as jest.Mocked<RestService>;
35
+
36
+ debuggerService = new DebuggerService(mockRestService);
37
+ processService = new ProcessService(mockRestService);
38
+ });
39
+
40
+ afterEach(() => {
41
+ jest.clearAllMocks();
42
+ });
43
+
44
+ describe('DebuggerService - Session Management', () => {
45
+ describe('createDebugSession', () => {
46
+ it('should create a debug session successfully', async () => {
47
+ mockRestService.post.mockResolvedValue(createMockResponse({
48
+ ID: 'debug-session-123'
49
+ }));
50
+
51
+ const sessionId = await debuggerService.createDebugSession('MyProcess');
52
+
53
+ expect(sessionId).toBeDefined();
54
+ expect(mockRestService.post).toHaveBeenCalledWith(
55
+ "/Processes('MyProcess')/tm1.BeginDebug",
56
+ {}
57
+ );
58
+ });
59
+
60
+ it('should track the created session', async () => {
61
+ mockRestService.post.mockResolvedValue(createMockResponse({
62
+ ID: 'debug-session-456'
63
+ }));
64
+
65
+ const sessionId = await debuggerService.createDebugSession('TestProcess');
66
+ const sessionInfo = debuggerService.getSessionInfo(sessionId);
67
+
68
+ expect(sessionInfo).toBeDefined();
69
+ expect(sessionInfo?.processName).toBe('TestProcess');
70
+ expect(sessionInfo?.status).toBe('Paused');
71
+ });
72
+ });
73
+
74
+ describe('terminateDebugSession', () => {
75
+ it('should terminate a debug session', async () => {
76
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({ ID: 'session-789' }));
77
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}));
78
+
79
+ const sessionId = await debuggerService.createDebugSession('MyProcess');
80
+ await debuggerService.terminateDebugSession(sessionId);
81
+
82
+ expect(mockRestService.post).toHaveBeenCalledWith(
83
+ "/Processes('MyProcess')/tm1.EndDebug",
84
+ {}
85
+ );
86
+
87
+ const sessionInfo = debuggerService.getSessionInfo(sessionId);
88
+ expect(sessionInfo).toBeUndefined();
89
+ });
90
+
91
+ it('should throw error if session not found', async () => {
92
+ await expect(
93
+ debuggerService.terminateDebugSession('non-existent')
94
+ ).rejects.toThrow('Debug session non-existent not found');
95
+ });
96
+ });
97
+
98
+ describe('listActiveSessions', () => {
99
+ it('should list all active debug sessions', async () => {
100
+ mockRestService.post
101
+ .mockResolvedValueOnce(createMockResponse({ ID: 'session-1' }))
102
+ .mockResolvedValueOnce(createMockResponse({ ID: 'session-2' }));
103
+
104
+ await debuggerService.createDebugSession('Process1');
105
+ await debuggerService.createDebugSession('Process2');
106
+
107
+ const sessions = debuggerService.listActiveSessions();
108
+
109
+ expect(sessions).toHaveLength(2);
110
+ expect(sessions.some(s => s.processName === 'Process1')).toBe(true);
111
+ expect(sessions.some(s => s.processName === 'Process2')).toBe(true);
112
+ });
113
+ });
114
+ });
115
+
116
+ describe('DebuggerService - Debug Control', () => {
117
+ let sessionId: string;
118
+
119
+ beforeEach(async () => {
120
+ mockRestService.post.mockResolvedValue(createMockResponse({ ID: 'test-session' }));
121
+ sessionId = await debuggerService.createDebugSession('MyProcess');
122
+ });
123
+
124
+ describe('stepInto', () => {
125
+ it('should step into next line', async () => {
126
+ mockRestService.post.mockResolvedValue(createMockResponse({}));
127
+
128
+ await debuggerService.stepInto(sessionId);
129
+
130
+ expect(mockRestService.post).toHaveBeenCalledWith(
131
+ "/Processes('MyProcess')/tm1.DebugStepIn",
132
+ {}
133
+ );
134
+ });
135
+
136
+ it('should throw error if session not found', async () => {
137
+ await expect(
138
+ debuggerService.stepInto('invalid-session')
139
+ ).rejects.toThrow('Debug session invalid-session not found');
140
+ });
141
+ });
142
+
143
+ describe('stepOver', () => {
144
+ it('should step over next line', async () => {
145
+ mockRestService.post.mockResolvedValue(createMockResponse({}));
146
+
147
+ await debuggerService.stepOver(sessionId);
148
+
149
+ expect(mockRestService.post).toHaveBeenCalledWith(
150
+ "/Processes('MyProcess')/tm1.DebugStepOver",
151
+ {}
152
+ );
153
+ });
154
+ });
155
+
156
+ describe('stepOut', () => {
157
+ it('should step out of current function', async () => {
158
+ mockRestService.post.mockResolvedValue(createMockResponse({}));
159
+
160
+ await debuggerService.stepOut(sessionId);
161
+
162
+ expect(mockRestService.post).toHaveBeenCalledWith(
163
+ "/Processes('MyProcess')/tm1.DebugStepOut",
164
+ {}
165
+ );
166
+ });
167
+ });
168
+
169
+ describe('continueExecution', () => {
170
+ it('should continue execution', async () => {
171
+ mockRestService.post.mockResolvedValue(createMockResponse({}));
172
+
173
+ await debuggerService.continueExecution(sessionId);
174
+
175
+ expect(mockRestService.post).toHaveBeenCalledWith(
176
+ "/Processes('MyProcess')/tm1.DebugContinue",
177
+ {}
178
+ );
179
+
180
+ const sessionInfo = debuggerService.getSessionInfo(sessionId);
181
+ expect(sessionInfo?.status).toBe('Running');
182
+ });
183
+ });
184
+ });
185
+
186
+ describe('DebuggerService - Variable Inspection', () => {
187
+ let sessionId: string;
188
+
189
+ beforeEach(async () => {
190
+ mockRestService.post.mockResolvedValue(createMockResponse({ ID: 'test-session' }));
191
+ sessionId = await debuggerService.createDebugSession('MyProcess');
192
+ });
193
+
194
+ describe('getProcessVariables', () => {
195
+ it('should get all process variables', async () => {
196
+ mockRestService.get.mockResolvedValue(createMockResponse({
197
+ value: [
198
+ { Name: 'vCounter', Value: 10, Type: 'Numeric', Scope: 'Local' },
199
+ { Name: 'vName', Value: 'Test', Type: 'String', Scope: 'Local' },
200
+ { Name: 'pYear', Value: 2024, Type: 'Numeric', Scope: 'Parameter' }
201
+ ]
202
+ }));
203
+
204
+ const variables = await debuggerService.getProcessVariables(sessionId);
205
+
206
+ expect(variables).toHaveLength(3);
207
+ expect(variables[0]).toMatchObject({
208
+ name: 'vCounter',
209
+ value: 10,
210
+ type: 'Numeric',
211
+ scope: 'Local'
212
+ });
213
+ });
214
+ });
215
+
216
+ describe('setProcessVariable', () => {
217
+ it('should set a process variable value', async () => {
218
+ mockRestService.patch.mockResolvedValue(createMockResponse({}));
219
+
220
+ await debuggerService.setProcessVariable(sessionId, 'vCounter', 20);
221
+
222
+ expect(mockRestService.patch).toHaveBeenCalledWith(
223
+ "/Processes('MyProcess')/Variables('vCounter')",
224
+ { Value: 20 }
225
+ );
226
+ });
227
+ });
228
+
229
+ describe('evaluateExpression', () => {
230
+ it('should evaluate a TM1 expression', async () => {
231
+ mockRestService.post.mockResolvedValue(createMockResponse({
232
+ Result: 30
233
+ }));
234
+
235
+ const result = await debuggerService.evaluateExpression(
236
+ sessionId,
237
+ 'vCounter + 10'
238
+ );
239
+
240
+ expect(result).toBe(30);
241
+ expect(mockRestService.post).toHaveBeenCalledWith(
242
+ "/Processes('MyProcess')/tm1.Evaluate",
243
+ { Expression: 'vCounter + 10' }
244
+ );
245
+ });
246
+ });
247
+
248
+ describe('getCallStack', () => {
249
+ it('should get the call stack', async () => {
250
+ mockRestService.get.mockResolvedValue(createMockResponse({
251
+ value: [
252
+ { ProcedureName: 'Main', LineNumber: 10 },
253
+ { ProcedureName: 'Prolog', LineNumber: 5 }
254
+ ]
255
+ }));
256
+
257
+ const callStack = await debuggerService.getCallStack(sessionId);
258
+
259
+ expect(callStack).toHaveLength(2);
260
+ expect(callStack[0].procedureName).toBe('Main');
261
+ expect(callStack[0].lineNumber).toBe(10);
262
+ });
263
+
264
+ it('should return basic info if call stack API not available', async () => {
265
+ mockRestService.get.mockRejectedValue({ response: { status: 404 } });
266
+
267
+ const callStack = await debuggerService.getCallStack(sessionId);
268
+
269
+ expect(callStack).toHaveLength(1);
270
+ expect(callStack[0].procedureName).toBe('MyProcess');
271
+ });
272
+ });
273
+ });
274
+
275
+ describe('ProcessService - Enhanced Debugging', () => {
276
+ describe('analyzeProcessDependencies', () => {
277
+ it('should analyze process dependencies', async () => {
278
+ mockRestService.get.mockResolvedValue(createMockResponse({
279
+ Name: 'ImportData',
280
+ PrologProcedure: `
281
+ IF(CubeExists('Sales') = 0);
282
+ CubeCreate('Sales', 'Year', 'Month', 'Value');
283
+ ENDIF;
284
+ ExecuteProcess('SetupDimensions');
285
+ `,
286
+ DataProcedure: `
287
+ vValue = CellGetN('Budget', '2024', 'Jan');
288
+ CellPutN(vValue, 'Sales', '2024', 'Jan');
289
+ `,
290
+ EpilogProcedure: '',
291
+ MetadataProcedure: '',
292
+ Parameters: [],
293
+ Variables: []
294
+ }));
295
+
296
+ const deps = await processService.analyzeProcessDependencies('ImportData');
297
+
298
+ expect(deps.cubes).toContain('Sales');
299
+ expect(deps.cubes).toContain('Budget');
300
+ expect(deps.processes).toContain('SetupDimensions');
301
+ });
302
+ });
303
+
304
+ describe('validateProcessSyntax', () => {
305
+ it('should return valid for correct syntax', async () => {
306
+ mockRestService.get.mockResolvedValue(createMockResponse({
307
+ Name: 'ValidProcess',
308
+ PrologProcedure: 'vCounter = 1;',
309
+ DataProcedure: '',
310
+ EpilogProcedure: '',
311
+ MetadataProcedure: '',
312
+ Parameters: [],
313
+ Variables: []
314
+ }));
315
+
316
+ mockRestService.post.mockResolvedValue(createMockResponse({}));
317
+
318
+ const result = await processService.validateProcessSyntax('ValidProcess');
319
+
320
+ expect(result.isValid).toBe(true);
321
+ expect(result.errors).toHaveLength(0);
322
+ });
323
+
324
+ it('should return errors for invalid syntax', async () => {
325
+ mockRestService.get.mockResolvedValue(createMockResponse({
326
+ Name: 'InvalidProcess',
327
+ PrologProcedure: 'INVALID SYNTAX;',
328
+ DataProcedure: '',
329
+ EpilogProcedure: '',
330
+ MetadataProcedure: '',
331
+ Parameters: [],
332
+ Variables: []
333
+ }));
334
+
335
+ mockRestService.post.mockRejectedValue({
336
+ response: {
337
+ status: 400,
338
+ data: {
339
+ error: {
340
+ message: 'Syntax error on line 1'
341
+ }
342
+ }
343
+ }
344
+ });
345
+
346
+ const result = await processService.validateProcessSyntax('InvalidProcess');
347
+
348
+ expect(result.isValid).toBe(false);
349
+ expect(result.errors.length).toBeGreaterThan(0);
350
+ });
351
+ });
352
+
353
+ describe('getProcessExecutionPlan', () => {
354
+ it('should analyze process execution plan', async () => {
355
+ mockRestService.get.mockResolvedValue(createMockResponse({
356
+ Name: 'ComplexProcess',
357
+ PrologProcedure: 'A'.repeat(600),
358
+ DataProcedure: 'B'.repeat(800),
359
+ EpilogProcedure: 'C'.repeat(400),
360
+ MetadataProcedure: '',
361
+ Parameters: [{ Name: 'pYear', Type: 'Numeric' }],
362
+ Variables: [{ Name: 'vCounter', Type: 'Numeric' }]
363
+ }));
364
+
365
+ const plan = await processService.getProcessExecutionPlan('ComplexProcess');
366
+
367
+ expect(plan.processName).toBe('ComplexProcess');
368
+ expect(plan.hasParameters).toBe(true);
369
+ expect(plan.parameterCount).toBe(1);
370
+ expect(plan.hasVariables).toBe(true);
371
+ expect(plan.variableCount).toBe(1);
372
+ expect(plan.procedures.hasPrologProcedure).toBe(true);
373
+ expect(plan.estimatedComplexity).toBe('Medium');
374
+ });
375
+ });
376
+ });
377
+ });