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.
- package/CHANGELOG.md +89 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/services/ApplicationService.d.ts +19 -3
- package/lib/services/ApplicationService.d.ts.map +1 -1
- package/lib/services/ApplicationService.js +232 -6
- package/lib/services/AsyncOperationService.d.ts +8 -1
- package/lib/services/AsyncOperationService.d.ts.map +1 -1
- package/lib/services/AsyncOperationService.js +69 -26
- package/lib/services/ElementService.d.ts +67 -1
- package/lib/services/ElementService.d.ts.map +1 -1
- package/lib/services/ElementService.js +214 -0
- package/lib/services/FileService.d.ts.map +1 -1
- package/lib/services/HierarchyService.d.ts +26 -0
- package/lib/services/HierarchyService.d.ts.map +1 -1
- package/lib/services/HierarchyService.js +306 -0
- package/lib/services/ProcessService.d.ts +40 -22
- package/lib/services/ProcessService.d.ts.map +1 -1
- package/lib/services/ProcessService.js +118 -111
- package/lib/services/RestService.d.ts +213 -25
- package/lib/services/RestService.d.ts.map +1 -1
- package/lib/services/RestService.js +841 -263
- package/lib/services/SubsetService.d.ts +2 -0
- package/lib/services/SubsetService.d.ts.map +1 -1
- package/lib/services/SubsetService.js +33 -0
- package/lib/services/TM1Service.d.ts +44 -1
- package/lib/services/TM1Service.d.ts.map +1 -1
- package/lib/services/TM1Service.js +96 -4
- package/lib/services/index.d.ts +1 -1
- package/lib/services/index.d.ts.map +1 -1
- package/lib/tests/100PercentParityCheck.test.js +23 -6
- package/lib/tests/applicationService.issue38.test.d.ts +5 -0
- package/lib/tests/applicationService.issue38.test.d.ts.map +1 -0
- package/lib/tests/applicationService.issue38.test.js +237 -0
- package/lib/tests/asyncOperationService.test.js +51 -45
- package/lib/tests/bugfix28.test.js +12 -4
- package/lib/tests/elementService.issue37.test.d.ts +5 -0
- package/lib/tests/elementService.issue37.test.d.ts.map +1 -0
- package/lib/tests/elementService.issue37.test.js +413 -0
- package/lib/tests/elementService.issue38.test.d.ts +5 -0
- package/lib/tests/elementService.issue38.test.d.ts.map +1 -0
- package/lib/tests/elementService.issue38.test.js +79 -0
- package/lib/tests/hierarchyService.issue38.test.d.ts +5 -0
- package/lib/tests/hierarchyService.issue38.test.d.ts.map +1 -0
- package/lib/tests/hierarchyService.issue38.test.js +460 -0
- package/lib/tests/processService.comprehensive.test.js +9 -9
- package/lib/tests/processService.test.js +234 -0
- package/lib/tests/restService.test.d.ts +0 -4
- package/lib/tests/restService.test.d.ts.map +1 -1
- package/lib/tests/restService.test.js +1558 -143
- package/lib/tests/subsetService.issue38.test.d.ts +5 -0
- package/lib/tests/subsetService.issue38.test.d.ts.map +1 -0
- package/lib/tests/subsetService.issue38.test.js +113 -0
- package/lib/tests/tm1Service.test.js +80 -8
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/services/ApplicationService.ts +282 -10
- package/src/services/AsyncOperationService.ts +76 -29
- package/src/services/ElementService.ts +322 -1
- package/src/services/FileService.ts +3 -3
- package/src/services/HierarchyService.ts +419 -1
- package/src/services/ProcessService.ts +185 -142
- package/src/services/RestService.ts +1021 -267
- package/src/services/SubsetService.ts +48 -0
- package/src/services/TM1Service.ts +127 -6
- package/src/services/index.ts +1 -1
- package/src/tests/100PercentParityCheck.test.ts +29 -8
- package/src/tests/applicationService.issue38.test.ts +293 -0
- package/src/tests/asyncOperationService.test.ts +52 -48
- package/src/tests/bugfix28.test.ts +12 -4
- package/src/tests/elementService.issue37.test.ts +571 -0
- package/src/tests/elementService.issue38.test.ts +103 -0
- package/src/tests/hierarchyService.issue38.test.ts +599 -0
- package/src/tests/processService.comprehensive.test.ts +10 -10
- package/src/tests/processService.test.ts +295 -3
- package/src/tests/restService.test.ts +1844 -139
- package/src/tests/subsetService.issue38.test.ts +182 -0
- 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
|
-
|
|
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(
|
|
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).
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
});
|