tm1npm 1.5.3 → 2.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/lib/index.d.ts +1 -1
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/services/ApplicationService.d.ts +19 -3
  5. package/lib/services/ApplicationService.d.ts.map +1 -1
  6. package/lib/services/ApplicationService.js +232 -6
  7. package/lib/services/AsyncOperationService.d.ts +8 -1
  8. package/lib/services/AsyncOperationService.d.ts.map +1 -1
  9. package/lib/services/AsyncOperationService.js +69 -26
  10. package/lib/services/ElementService.d.ts +67 -1
  11. package/lib/services/ElementService.d.ts.map +1 -1
  12. package/lib/services/ElementService.js +214 -0
  13. package/lib/services/FileService.d.ts.map +1 -1
  14. package/lib/services/HierarchyService.d.ts +26 -0
  15. package/lib/services/HierarchyService.d.ts.map +1 -1
  16. package/lib/services/HierarchyService.js +306 -0
  17. package/lib/services/ProcessService.d.ts +40 -22
  18. package/lib/services/ProcessService.d.ts.map +1 -1
  19. package/lib/services/ProcessService.js +118 -111
  20. package/lib/services/RestService.d.ts +213 -25
  21. package/lib/services/RestService.d.ts.map +1 -1
  22. package/lib/services/RestService.js +841 -263
  23. package/lib/services/SubsetService.d.ts +2 -0
  24. package/lib/services/SubsetService.d.ts.map +1 -1
  25. package/lib/services/SubsetService.js +33 -0
  26. package/lib/services/TM1Service.d.ts +44 -1
  27. package/lib/services/TM1Service.d.ts.map +1 -1
  28. package/lib/services/TM1Service.js +96 -4
  29. package/lib/services/index.d.ts +1 -1
  30. package/lib/services/index.d.ts.map +1 -1
  31. package/lib/tests/100PercentParityCheck.test.js +23 -6
  32. package/lib/tests/applicationService.issue38.test.d.ts +5 -0
  33. package/lib/tests/applicationService.issue38.test.d.ts.map +1 -0
  34. package/lib/tests/applicationService.issue38.test.js +237 -0
  35. package/lib/tests/asyncOperationService.test.js +51 -45
  36. package/lib/tests/bugfix28.test.js +12 -4
  37. package/lib/tests/elementService.issue37.test.d.ts +5 -0
  38. package/lib/tests/elementService.issue37.test.d.ts.map +1 -0
  39. package/lib/tests/elementService.issue37.test.js +413 -0
  40. package/lib/tests/elementService.issue38.test.d.ts +5 -0
  41. package/lib/tests/elementService.issue38.test.d.ts.map +1 -0
  42. package/lib/tests/elementService.issue38.test.js +79 -0
  43. package/lib/tests/hierarchyService.issue38.test.d.ts +5 -0
  44. package/lib/tests/hierarchyService.issue38.test.d.ts.map +1 -0
  45. package/lib/tests/hierarchyService.issue38.test.js +460 -0
  46. package/lib/tests/processService.comprehensive.test.js +9 -9
  47. package/lib/tests/processService.test.js +234 -0
  48. package/lib/tests/restService.test.d.ts +0 -4
  49. package/lib/tests/restService.test.d.ts.map +1 -1
  50. package/lib/tests/restService.test.js +1558 -143
  51. package/lib/tests/subsetService.issue38.test.d.ts +5 -0
  52. package/lib/tests/subsetService.issue38.test.d.ts.map +1 -0
  53. package/lib/tests/subsetService.issue38.test.js +113 -0
  54. package/lib/tests/tm1Service.test.js +80 -8
  55. package/package.json +1 -1
  56. package/src/index.ts +1 -1
  57. package/src/services/ApplicationService.ts +282 -10
  58. package/src/services/AsyncOperationService.ts +76 -29
  59. package/src/services/ElementService.ts +322 -1
  60. package/src/services/FileService.ts +3 -3
  61. package/src/services/HierarchyService.ts +419 -1
  62. package/src/services/ProcessService.ts +185 -142
  63. package/src/services/RestService.ts +1021 -267
  64. package/src/services/SubsetService.ts +48 -0
  65. package/src/services/TM1Service.ts +127 -6
  66. package/src/services/index.ts +1 -1
  67. package/src/tests/100PercentParityCheck.test.ts +29 -8
  68. package/src/tests/applicationService.issue38.test.ts +293 -0
  69. package/src/tests/asyncOperationService.test.ts +52 -48
  70. package/src/tests/bugfix28.test.ts +12 -4
  71. package/src/tests/elementService.issue37.test.ts +571 -0
  72. package/src/tests/elementService.issue38.test.ts +103 -0
  73. package/src/tests/hierarchyService.issue38.test.ts +599 -0
  74. package/src/tests/processService.comprehensive.test.ts +10 -10
  75. package/src/tests/processService.test.ts +295 -3
  76. package/src/tests/restService.test.ts +1844 -139
  77. package/src/tests/subsetService.issue38.test.ts +182 -0
  78. package/src/tests/tm1Service.test.ts +95 -11
@@ -397,12 +397,304 @@ describe('ProcessService Tests', () => {
397
397
 
398
398
  const processNames = await processService.getAllNames();
399
399
  expect(processNames).toContain('DebugProcess');
400
-
400
+
401
401
  // In a real implementation, this would set debug breakpoints
402
402
  // For now, we just verify the mock interaction
403
403
  expect(mockRestService.get).toHaveBeenCalled();
404
-
404
+
405
405
  console.log('✅ Debug operations handled for existing processes');
406
406
  });
407
+
408
+ test('debugGetBreakpoints should return ProcessDebugBreakpoint array', async () => {
409
+ mockRestService.get.mockResolvedValue(createMockResponse({
410
+ value: [
411
+ {
412
+ '@odata.type': '#ibm.tm1.api.v1.ProcessDebugContextLineBreakpoint',
413
+ ID: 1,
414
+ Enabled: true,
415
+ HitMode: 'BreakAlways',
416
+ HitCount: 0,
417
+ Expression: '',
418
+ ProcessName: 'TestProcess',
419
+ Procedure: 'Prolog',
420
+ LineNumber: 5
421
+ }
422
+ ]
423
+ }));
424
+
425
+ const result = await processService.debugGetBreakpoints('debug-123');
426
+
427
+ expect(result).toHaveLength(1);
428
+ expect(result[0].breakpointId).toBe(1);
429
+ expect(mockRestService.get).toHaveBeenCalledWith("/ProcessDebugContexts('debug-123')/Breakpoints");
430
+ });
431
+
432
+ test('debugAddBreakpoint should delegate to debugAddBreakpoints', async () => {
433
+ mockRestService.post.mockResolvedValue(createMockResponse({}));
434
+
435
+ const { ProcessDebugBreakpoint: BP, BreakPointType: BPType, HitMode: HM } =
436
+ require('../objects/ProcessDebugBreakpoint');
437
+ const bp = new BP(1, BPType.PROCESS_DEBUG_CONTEXT_LINE_BREAK_POINT, true, HM.BREAK_ALWAYS);
438
+
439
+ await processService.debugAddBreakpoint('debug-456', bp);
440
+
441
+ // Should POST a JSON array (delegated to debugAddBreakpoints)
442
+ const postCall = mockRestService.post.mock.calls[0];
443
+ expect(postCall[0]).toBe("/ProcessDebugContexts('debug-456')/Breakpoints");
444
+ const postedBody = JSON.parse(postCall[1]);
445
+ expect(Array.isArray(postedBody)).toBe(true);
446
+ expect(postedBody).toHaveLength(1);
447
+ });
448
+
449
+ test('debugRemoveBreakpoint should DELETE correct URL', async () => {
450
+ mockRestService.delete.mockResolvedValue(createMockResponse({}));
451
+
452
+ await processService.debugRemoveBreakpoint('debug-789', 3);
453
+
454
+ expect(mockRestService.delete).toHaveBeenCalledWith(
455
+ "/ProcessDebugContexts('debug-789')/Breakpoints('3')"
456
+ );
457
+ });
458
+ });
459
+
460
+ describe('Process Async Polling', () => {
461
+ test('pollExecuteWithReturn should return parsed result on success', async () => {
462
+ // retrieve_async_response returns an AxiosResponse; the execute summary
463
+ // lives in .data. Tests that mocked the raw body directly did not
464
+ // reflect production shape and silently passed against the untyped
465
+ // parser signature.
466
+ (mockRestService as any).retrieve_async_response = jest.fn().mockResolvedValue({
467
+ status: 200,
468
+ data: {
469
+ ProcessExecuteStatusCode: 'CompletedSuccessfully',
470
+ ErrorLogFile: null
471
+ }
472
+ });
473
+
474
+ const result = await processService.pollExecuteWithReturn('async-001');
475
+
476
+ expect(result).toEqual([true, 'CompletedSuccessfully', null]);
477
+ expect((mockRestService as any).retrieve_async_response).toHaveBeenCalledWith('async-001');
478
+ });
479
+
480
+ test('pollExecuteWithReturn should return error log file when present', async () => {
481
+ (mockRestService as any).retrieve_async_response = jest.fn().mockResolvedValue({
482
+ status: 200,
483
+ data: {
484
+ ProcessExecuteStatusCode: 'CompletedWithMessages',
485
+ ErrorLogFile: { Filename: 'TM1ProcessError_20240101.log' }
486
+ }
487
+ });
488
+
489
+ const result = await processService.pollExecuteWithReturn('async-002');
490
+
491
+ expect(result).toEqual([false, 'CompletedWithMessages', 'TM1ProcessError_20240101.log']);
492
+ });
493
+
494
+ test('pollExecuteWithReturn should return null for 404 (not ready)', async () => {
495
+ const { TM1RestException } = require('../exceptions/TM1Exception');
496
+ (mockRestService as any).retrieve_async_response = jest.fn().mockRejectedValue(
497
+ new TM1RestException('Not found', 404)
498
+ );
499
+
500
+ const result = await processService.pollExecuteWithReturn('async-003');
501
+
502
+ expect(result).toBeNull();
503
+ });
504
+
505
+ test('pollExecuteWithReturn should return null for 202 (accepted/pending)', async () => {
506
+ // After the #80 refactor retrieve_async_response returns the raw AxiosResponse
507
+ // instead of throwing on 202; the pending path is now signalled by status === 202.
508
+ (mockRestService as any).retrieve_async_response = jest.fn().mockResolvedValue({
509
+ status: 202,
510
+ data: {}
511
+ });
512
+
513
+ const result = await processService.pollExecuteWithReturn('async-004');
514
+
515
+ expect(result).toBeNull();
516
+ });
517
+
518
+ test('pollExecuteWithReturn should throw on unexpected errors', async () => {
519
+ const { TM1RestException } = require('../exceptions/TM1Exception');
520
+ const serverError = new TM1RestException('Internal Server Error', 500);
521
+ (mockRestService as any).retrieve_async_response = jest.fn().mockRejectedValue(serverError);
522
+
523
+ await expect(processService.pollExecuteWithReturn('async-005'))
524
+ .rejects.toThrow('Internal Server Error');
525
+ });
526
+ });
527
+
528
+ describe('Process Compile (unbound)', () => {
529
+ test('compileProcess should POST Process body to /CompileProcess', async () => {
530
+ mockRestService.post.mockResolvedValue(createMockResponse({ value: [] }));
531
+
532
+ const testProcess = new Process('TestProcess', false);
533
+ const result = await processService.compileProcess(testProcess);
534
+
535
+ expect(result).toEqual([]);
536
+ const postCall = mockRestService.post.mock.calls[0];
537
+ expect(postCall[0]).toBe('/CompileProcess');
538
+ const payload = JSON.parse(postCall[1]);
539
+ expect(payload.Process).toBeDefined();
540
+ expect(payload.Process.Name).toBe('TestProcess');
541
+ });
542
+
543
+ test('compileProcess should return syntax errors when present', async () => {
544
+ const errors = [
545
+ { LineNumber: 1, Message: 'Syntax error on line 1' }
546
+ ];
547
+ mockRestService.post.mockResolvedValue(createMockResponse({ value: errors }));
548
+
549
+ const testProcess = new Process('BadProcess', false);
550
+ const result = await processService.compileProcess(testProcess);
551
+
552
+ expect(result).toHaveLength(1);
553
+ expect(result[0].Message).toBe('Syntax error on line 1');
554
+ });
555
+ });
556
+
557
+ describe('evaluateTiExpression', () => {
558
+ test('should evaluate a TI expression and return the result', async () => {
559
+ // Mock compileProcess (no errors)
560
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({ value: [] }));
561
+
562
+ // Mock create (process creation)
563
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}, 201));
564
+
565
+ // Mock debugProcess
566
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({
567
+ ID: 'debug-eval-123',
568
+ CallStack: [],
569
+ Breakpoints: []
570
+ }));
571
+
572
+ // Mock debugAddBreakpoint (via debugAddBreakpoints)
573
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}));
574
+
575
+ // Mock debugContinue (first call - run to breakpoint)
576
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}));
577
+ mockRestService.get.mockResolvedValueOnce(createMockResponse({
578
+ CallStack: [{ Variables: [{ Name: 'sFunc', Value: '2024-01-01' }] }]
579
+ }));
580
+
581
+ // Mock debugGetVariableValues
582
+ mockRestService.get.mockResolvedValueOnce(createMockResponse({
583
+ CallStack: [{ Variables: [{ Name: 'sFunc', Value: '2024-01-01' }] }]
584
+ }));
585
+
586
+ // Mock debugContinue (second call - finish)
587
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}));
588
+ mockRestService.get.mockResolvedValueOnce(createMockResponse({ CallStack: [] }));
589
+
590
+ // Mock delete (cleanup)
591
+ mockRestService.delete.mockResolvedValueOnce(createMockResponse({}, 204));
592
+
593
+ const result = await processService.evaluateTiExpression('NOW;');
594
+
595
+ expect(result).toBe('2024-01-01');
596
+ });
597
+
598
+ test('should strip leading "=" prefix from formula', async () => {
599
+ // Mock compileProcess
600
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({ value: [] }));
601
+ // Mock create
602
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}, 201));
603
+ // Mock debugProcess
604
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({
605
+ ID: 'debug-eq-123',
606
+ CallStack: []
607
+ }));
608
+ // Mock debugAddBreakpoint
609
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}));
610
+ // Mock debugContinue
611
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}));
612
+ mockRestService.get.mockResolvedValueOnce(createMockResponse({
613
+ CallStack: [{ Variables: [{ Name: 'sFunc', Value: '42' }] }]
614
+ }));
615
+ // Mock debugGetVariableValues
616
+ mockRestService.get.mockResolvedValueOnce(createMockResponse({
617
+ CallStack: [{ Variables: [{ Name: 'sFunc', Value: '42' }] }]
618
+ }));
619
+ // Mock debugContinue
620
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}));
621
+ mockRestService.get.mockResolvedValueOnce(createMockResponse({ CallStack: [] }));
622
+ // Mock delete
623
+ mockRestService.delete.mockResolvedValueOnce(createMockResponse({}, 204));
624
+
625
+ // Leading "=" is stripped (Excel-style formula prefix)
626
+ const result = await processService.evaluateTiExpression('= NumberToString(42);');
627
+
628
+ expect(result).toBe('42');
629
+
630
+ // Verify the formula was stripped correctly
631
+ const compileCall = mockRestService.post.mock.calls[0];
632
+ const compilePayload = JSON.parse(compileCall[1]);
633
+ expect(compilePayload.Process.PrologProcedure).toContain('sFunc = NumberToString(42);');
634
+ });
635
+
636
+ test('should not mangle formulas with embedded "=" characters', async () => {
637
+ // Mock compileProcess
638
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({ value: [] }));
639
+ // Mock create
640
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}, 201));
641
+ // Mock debugProcess
642
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({
643
+ ID: 'debug-embed-123',
644
+ CallStack: []
645
+ }));
646
+ // Mock debugAddBreakpoint
647
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}));
648
+ // Mock debugContinue
649
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}));
650
+ mockRestService.get.mockResolvedValueOnce(createMockResponse({
651
+ CallStack: [{ Variables: [{ Name: 'sFunc', Value: '1' }] }]
652
+ }));
653
+ // Mock debugGetVariableValues
654
+ mockRestService.get.mockResolvedValueOnce(createMockResponse({
655
+ CallStack: [{ Variables: [{ Name: 'sFunc', Value: '1' }] }]
656
+ }));
657
+ // Mock debugContinue
658
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}));
659
+ mockRestService.get.mockResolvedValueOnce(createMockResponse({ CallStack: [] }));
660
+ // Mock delete
661
+ mockRestService.delete.mockResolvedValueOnce(createMockResponse({}, 204));
662
+
663
+ // Formula with embedded "=" should NOT be mangled
664
+ const result = await processService.evaluateTiExpression('IF(1=1, "yes", "no");');
665
+
666
+ expect(result).toBe('1');
667
+
668
+ // Verify the full formula is preserved
669
+ const compileCall = mockRestService.post.mock.calls[0];
670
+ const compilePayload = JSON.parse(compileCall[1]);
671
+ expect(compilePayload.Process.PrologProcedure).toContain('sFunc = IF(1=1, "yes", "no");');
672
+ });
673
+
674
+ test('should throw readable error on syntax errors from compileProcess', async () => {
675
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({
676
+ value: [{ LineNumber: 1, Message: 'Unexpected token' }]
677
+ }));
678
+
679
+ await expect(processService.evaluateTiExpression('INVALID_FUNC();'))
680
+ .rejects.toThrow('Line 1: Unexpected token');
681
+ });
682
+
683
+ test('should clean up temp process even on debug failure', async () => {
684
+ // Mock compileProcess (no errors)
685
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({ value: [] }));
686
+ // Mock create
687
+ mockRestService.post.mockResolvedValueOnce(createMockResponse({}, 201));
688
+ // Mock debugProcess - fails
689
+ mockRestService.post.mockRejectedValueOnce(new Error('Debug session failed'));
690
+ // Mock delete (cleanup should still happen)
691
+ mockRestService.delete.mockResolvedValueOnce(createMockResponse({}, 204));
692
+
693
+ await expect(processService.evaluateTiExpression('NOW;'))
694
+ .rejects.toThrow('Debug session failed');
695
+
696
+ // Verify delete was called for cleanup
697
+ expect(mockRestService.delete).toHaveBeenCalled();
698
+ });
407
699
  });
408
- });
700
+ });