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
@@ -0,0 +1,182 @@
1
+ /**
2
+ * SubsetService Tests — Issue #38 new methods
3
+ */
4
+
5
+ import { SubsetService } from '../services/SubsetService';
6
+ import { RestService } from '../services/RestService';
7
+ import { Subset } from '../objects/Subset';
8
+ import { Element, ElementType } from '../objects/Element';
9
+
10
+ const createMockResponse = (data: any, status: number = 200) => ({
11
+ data,
12
+ status,
13
+ statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
14
+ headers: {},
15
+ config: {} as any
16
+ });
17
+
18
+ describe('SubsetService — Issue #38 new methods', () => {
19
+ let subsetService: SubsetService;
20
+ let mockRestService: jest.Mocked<RestService>;
21
+
22
+ beforeEach(() => {
23
+ mockRestService = {
24
+ get: jest.fn(),
25
+ post: jest.fn(),
26
+ patch: jest.fn(),
27
+ delete: jest.fn(),
28
+ put: jest.fn(),
29
+ config: {} as any,
30
+ rest: {} as any,
31
+ buildBaseUrl: jest.fn(),
32
+ extractErrorMessage: jest.fn()
33
+ } as any;
34
+
35
+ subsetService = new SubsetService(mockRestService);
36
+ });
37
+
38
+ // ===== updateStaticElements =====
39
+
40
+ describe('updateStaticElements', () => {
41
+ test('should PUT to Elements/$ref with correct @odata.id body', async () => {
42
+ mockRestService.put.mockResolvedValue(createMockResponse({}, 200));
43
+
44
+ await subsetService.updateStaticElements(
45
+ 'MySubset',
46
+ 'TestDimension',
47
+ 'TestHierarchy',
48
+ false,
49
+ ['ElemA', 'ElemB']
50
+ );
51
+
52
+ expect(mockRestService.put).toHaveBeenCalledTimes(1);
53
+ const [url, body] = mockRestService.put.mock.calls[0];
54
+ expect(url).toContain("Dimensions('TestDimension')/Hierarchies('TestHierarchy')");
55
+ expect(url).toContain("Subsets('MySubset')/Elements/$ref");
56
+
57
+ const parsed = JSON.parse(body);
58
+ expect(Array.isArray(parsed.value)).toBe(true);
59
+ expect(parsed.value).toHaveLength(2);
60
+ expect(parsed.value[0]['@odata.id']).toContain("Elements('ElemA')");
61
+ expect(parsed.value[1]['@odata.id']).toContain("Elements('ElemB')");
62
+ });
63
+
64
+ test('should accept a Subset object and use its elements', async () => {
65
+ mockRestService.put.mockResolvedValue(createMockResponse({}, 200));
66
+
67
+ const subset = new Subset('MySubset', 'TestDimension', 'TestHierarchy', undefined, undefined, ['X', 'Y', 'Z']);
68
+
69
+ await subsetService.updateStaticElements(subset);
70
+
71
+ expect(mockRestService.put).toHaveBeenCalledTimes(1);
72
+ const [, body] = mockRestService.put.mock.calls[0];
73
+ const parsed = JSON.parse(body);
74
+ expect(parsed.value).toHaveLength(3);
75
+ expect(parsed.value[0]['@odata.id']).toContain("Elements('X')");
76
+ expect(parsed.value[2]['@odata.id']).toContain("Elements('Z')");
77
+ });
78
+
79
+ test('should accept a Subset object with overriding elements array', async () => {
80
+ mockRestService.put.mockResolvedValue(createMockResponse({}, 200));
81
+
82
+ const subset = new Subset('MySubset', 'TestDimension', 'TestHierarchy', undefined, undefined, ['X', 'Y']);
83
+ // Override elements explicitly
84
+ await subsetService.updateStaticElements(subset, undefined, undefined, false, ['Override1']);
85
+
86
+ const [, body] = mockRestService.put.mock.calls[0];
87
+ const parsed = JSON.parse(body);
88
+ expect(parsed.value).toHaveLength(1);
89
+ expect(parsed.value[0]['@odata.id']).toContain("Elements('Override1')");
90
+ });
91
+
92
+ test('should accept Element objects (not just strings)', async () => {
93
+ mockRestService.put.mockResolvedValue(createMockResponse({}, 200));
94
+
95
+ const elem1 = new Element('Alpha', ElementType.NUMERIC);
96
+ const elem2 = new Element('Beta', ElementType.NUMERIC);
97
+
98
+ await subsetService.updateStaticElements(
99
+ 'MySubset',
100
+ 'TestDimension',
101
+ 'TestHierarchy',
102
+ false,
103
+ [elem1, elem2]
104
+ );
105
+
106
+ const [, body] = mockRestService.put.mock.calls[0];
107
+ const parsed = JSON.parse(body);
108
+ expect(parsed.value[0]['@odata.id']).toContain("Elements('Alpha')");
109
+ expect(parsed.value[1]['@odata.id']).toContain("Elements('Beta')");
110
+ });
111
+
112
+ test('should use PrivateSubsets collection when isPrivate=true', async () => {
113
+ mockRestService.put.mockResolvedValue(createMockResponse({}, 200));
114
+
115
+ await subsetService.updateStaticElements(
116
+ 'PrivateSubset',
117
+ 'TestDimension',
118
+ 'TestHierarchy',
119
+ true,
120
+ ['E1']
121
+ );
122
+
123
+ const [url] = mockRestService.put.mock.calls[0];
124
+ expect(url).toContain('PrivateSubsets');
125
+ // PrivateSubsets is the only subsets segment — there must be no plain '/Subsets(' prefix
126
+ expect(url).not.toMatch(/\/Subsets\(/);
127
+ });
128
+
129
+ test('should use public Subsets collection when isPrivate=false', async () => {
130
+ mockRestService.put.mockResolvedValue(createMockResponse({}, 200));
131
+
132
+ await subsetService.updateStaticElements(
133
+ 'PublicSubset',
134
+ 'TestDimension',
135
+ 'TestHierarchy',
136
+ false,
137
+ ['E1']
138
+ );
139
+
140
+ const [url] = mockRestService.put.mock.calls[0];
141
+ expect(url).toContain("Subsets('PublicSubset')");
142
+ expect(url).not.toContain('PrivateSubsets');
143
+ });
144
+
145
+ test('should throw when dimensionName is missing for string argument', async () => {
146
+ await expect(
147
+ subsetService.updateStaticElements('MySubset', undefined, 'TestHierarchy', false, ['E1'])
148
+ ).rejects.toThrow('dimensionName is required when passing subset name as string');
149
+ });
150
+
151
+ test('should default hierarchyName to dimensionName for string argument', async () => {
152
+ mockRestService.put.mockResolvedValue(createMockResponse({}, 200));
153
+
154
+ await subsetService.updateStaticElements(
155
+ 'MySubset',
156
+ 'TestDimension',
157
+ undefined, // no hierarchyName — should default to dimensionName
158
+ false,
159
+ ['E1']
160
+ );
161
+
162
+ const [url] = mockRestService.put.mock.calls[0];
163
+ expect(url).toContain("Hierarchies('TestDimension')");
164
+ });
165
+
166
+ test('should send empty value array when no elements provided', async () => {
167
+ mockRestService.put.mockResolvedValue(createMockResponse({}, 200));
168
+
169
+ await subsetService.updateStaticElements(
170
+ 'EmptySubset',
171
+ 'TestDimension',
172
+ 'TestHierarchy',
173
+ false,
174
+ []
175
+ );
176
+
177
+ const [, body] = mockRestService.put.mock.calls[0];
178
+ const parsed = JSON.parse(body);
179
+ expect(parsed.value).toEqual([]);
180
+ });
181
+ });
182
+ });
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { TM1Service } from '../services/TM1Service';
8
8
  import { RestService, RestServiceConfig } from '../services/RestService';
9
+ import { User, UserType } from '../objects/User';
9
10
 
10
11
  // Mock all service dependencies
11
12
  jest.mock('../services/RestService');
@@ -22,6 +23,19 @@ jest.mock('../services/FileService');
22
23
  jest.mock('../services/SessionService');
23
24
  jest.mock('../services/ServerService');
24
25
  jest.mock('../services/MonitoringService');
26
+ jest.mock('../services/AnnotationService');
27
+ jest.mock('../services/ChoreService');
28
+ jest.mock('../services/GitService');
29
+ jest.mock('../services/ApplicationService');
30
+ jest.mock('../services/SandboxService');
31
+ jest.mock('../services/JobService');
32
+ jest.mock('../services/UserService');
33
+ jest.mock('../services/ThreadService');
34
+ jest.mock('../services/TransactionLogService');
35
+ jest.mock('../services/MessageLogService');
36
+ jest.mock('../services/ConfigurationService');
37
+ jest.mock('../services/AuditLogService');
38
+ jest.mock('../services/LoggerService');
25
39
 
26
40
  describe('TM1Service', () => {
27
41
  let tm1Service: TM1Service;
@@ -61,6 +75,8 @@ describe('TM1Service', () => {
61
75
  setSandbox: jest.fn(),
62
76
  getSandbox: jest.fn().mockReturnValue('test-sandbox'),
63
77
  isLoggedIn: jest.fn().mockReturnValue(true),
78
+ getVersion: jest.fn().mockResolvedValue('12.0.0'),
79
+ version: undefined,
64
80
  } as any;
65
81
 
66
82
  // Mock RestService constructor
@@ -83,6 +99,7 @@ describe('TM1Service', () => {
83
99
  expect(tm1Service.security).toBeDefined();
84
100
  expect(tm1Service.files).toBeDefined();
85
101
  expect(tm1Service.sessions).toBeDefined();
102
+ expect(tm1Service.applications).toBeDefined();
86
103
  });
87
104
 
88
105
  test('should create RestService with provided config', () => {
@@ -145,16 +162,18 @@ describe('TM1Service', () => {
145
162
  });
146
163
 
147
164
  describe('User and Authentication', () => {
148
- test('should get current user with whoami', async () => {
149
- // Mock security service getCurrentUser method
165
+ test('should get current user as User object with whoami', async () => {
166
+ const expectedUser = new User('test-user', ['ADMIN'], 'Test User', undefined, UserType.Admin, true);
150
167
  const mockSecurityService = {
151
- getCurrentUser: jest.fn().mockResolvedValue({ name: 'test-user' })
168
+ getCurrentUser: jest.fn().mockResolvedValue(expectedUser)
152
169
  };
153
170
  (tm1Service.security as any) = mockSecurityService;
154
171
 
155
172
  const result = await tm1Service.whoami();
156
-
157
- expect(result).toBe('test-user');
173
+
174
+ expect(result).toBeInstanceOf(User);
175
+ expect(result.name).toBe('test-user');
176
+ expect(result).toBe(expectedUser);
158
177
  expect(mockSecurityService.getCurrentUser).toHaveBeenCalledTimes(1);
159
178
  });
160
179
 
@@ -175,10 +194,11 @@ describe('TM1Service', () => {
175
194
 
176
195
  test('should re-authenticate successfully', async () => {
177
196
  await tm1Service.reAuthenticate();
178
-
197
+
179
198
  expect(mockRestService.disconnect).toHaveBeenCalledTimes(1);
180
199
  expect(mockRestService.connect).toHaveBeenCalledTimes(1);
181
200
  });
201
+
182
202
  });
183
203
 
184
204
  describe('Metadata and Version', () => {
@@ -191,13 +211,13 @@ describe('TM1Service', () => {
191
211
  expect(mockRestService.get).toHaveBeenCalledWith('/$metadata');
192
212
  });
193
213
 
194
- test('should get TM1 version', async () => {
195
- mockRestService.get.mockResolvedValueOnce(mockResponse({ value: '12.0.0' }));
214
+ test('should get TM1 version via cached RestService.getVersion', async () => {
215
+ mockRestService.getVersion.mockResolvedValueOnce('12.0.0');
196
216
 
197
217
  const result = await tm1Service.getVersion();
198
-
218
+
199
219
  expect(result).toBe('12.0.0');
200
- expect(mockRestService.get).toHaveBeenCalledWith('/Configuration/ProductVersion');
220
+ expect(mockRestService.getVersion).toHaveBeenCalledTimes(1);
201
221
  });
202
222
 
203
223
  test('should handle metadata retrieval errors', async () => {
@@ -209,7 +229,7 @@ describe('TM1Service', () => {
209
229
 
210
230
  test('should handle version retrieval errors', async () => {
211
231
  const versionError = new Error('Version not available');
212
- mockRestService.get.mockRejectedValueOnce(versionError);
232
+ mockRestService.getVersion.mockRejectedValueOnce(versionError);
213
233
 
214
234
  await expect(tm1Service.getVersion()).rejects.toThrow('Version not available');
215
235
  });
@@ -358,4 +378,68 @@ describe('TM1Service', () => {
358
378
  expect(monitoring1).toBe(monitoring2);
359
379
  });
360
380
  });
381
+
382
+ describe('Lazy Services (Issue #82)', () => {
383
+ const lazyServiceNames: Array<keyof TM1Service> = [
384
+ 'annotations',
385
+ 'chores',
386
+ 'git',
387
+ 'sandboxes',
388
+ 'jobs',
389
+ 'users',
390
+ 'threads',
391
+ 'transactionLogs',
392
+ 'messageLogs',
393
+ 'configuration',
394
+ 'auditLogs',
395
+ 'loggers',
396
+ ];
397
+
398
+ test.each(lazyServiceNames)('should lazy-initialize %s service', (serviceName) => {
399
+ const instance = tm1Service[serviceName];
400
+ expect(instance).toBeDefined();
401
+ });
402
+
403
+ test.each(lazyServiceNames)('should cache %s service instance across accesses', (serviceName) => {
404
+ const first = tm1Service[serviceName];
405
+ const second = tm1Service[serviceName];
406
+ expect(second).toBe(first);
407
+ });
408
+ });
409
+
410
+ describe('Version Getter (Issue #82)', () => {
411
+ test('should expose cached version via sync getter', () => {
412
+ Object.defineProperty(mockRestService, 'version', {
413
+ get: () => '11.8.0',
414
+ configurable: true,
415
+ });
416
+
417
+ expect(tm1Service.version).toBe('11.8.0');
418
+ });
419
+
420
+ test('should return undefined when version has not been fetched yet', () => {
421
+ Object.defineProperty(mockRestService, 'version', {
422
+ get: () => undefined,
423
+ configurable: true,
424
+ });
425
+
426
+ expect(tm1Service.version).toBeUndefined();
427
+ });
428
+ });
429
+
430
+ describe('reConnect (Issue #82)', () => {
431
+ test('should call connect without disconnecting (tm1py parity)', async () => {
432
+ await tm1Service.reConnect();
433
+
434
+ expect(mockRestService.connect).toHaveBeenCalledTimes(1);
435
+ expect(mockRestService.disconnect).not.toHaveBeenCalled();
436
+ });
437
+
438
+ test('should propagate errors from connect', async () => {
439
+ mockRestService.connect.mockRejectedValueOnce(new Error('Connect failed'));
440
+
441
+ await expect(tm1Service.reConnect()).rejects.toThrow('Connect failed');
442
+ expect(mockRestService.connect).toHaveBeenCalledTimes(1);
443
+ });
444
+ });
361
445
  });