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,79 @@
1
+ "use strict";
2
+ /**
3
+ * ElementService Tests — Issue #38 new methods
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const ElementService_1 = require("../services/ElementService");
7
+ const ElementAttribute_1 = require("../objects/ElementAttribute");
8
+ const createMockResponse = (data, status = 200) => ({
9
+ data,
10
+ status,
11
+ statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
12
+ headers: {},
13
+ config: {}
14
+ });
15
+ describe('ElementService — Issue #38 new methods', () => {
16
+ let elementService;
17
+ let mockRestService;
18
+ beforeEach(() => {
19
+ mockRestService = {
20
+ get: jest.fn(),
21
+ post: jest.fn(),
22
+ patch: jest.fn(),
23
+ delete: jest.fn(),
24
+ put: jest.fn(),
25
+ config: {},
26
+ rest: {},
27
+ buildBaseUrl: jest.fn(),
28
+ extractErrorMessage: jest.fn()
29
+ };
30
+ elementService = new ElementService_1.ElementService(mockRestService);
31
+ });
32
+ // ===== addElementAttributes =====
33
+ describe('addElementAttributes', () => {
34
+ test('should POST to ElementAttributes endpoint with correct body', async () => {
35
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
36
+ const attrs = [
37
+ new ElementAttribute_1.ElementAttribute('Description', 'String'),
38
+ new ElementAttribute_1.ElementAttribute('Score', 'Numeric')
39
+ ];
40
+ await elementService.addElementAttributes('TestDimension', 'TestHierarchy', attrs);
41
+ expect(mockRestService.post).toHaveBeenCalledTimes(1);
42
+ const [url, body] = mockRestService.post.mock.calls[0];
43
+ expect(url).toContain("Dimensions('TestDimension')/Hierarchies('TestHierarchy')/ElementAttributes");
44
+ const parsed = JSON.parse(body);
45
+ expect(Array.isArray(parsed)).toBe(true);
46
+ expect(parsed).toHaveLength(2);
47
+ expect(parsed[0].Name).toBe('Description');
48
+ expect(parsed[0].Type).toBe('String');
49
+ expect(parsed[1].Name).toBe('Score');
50
+ expect(parsed[1].Type).toBe('Numeric');
51
+ });
52
+ test('should POST an empty array when no attributes provided', async () => {
53
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
54
+ await elementService.addElementAttributes('TestDimension', 'TestHierarchy', []);
55
+ expect(mockRestService.post).toHaveBeenCalledTimes(1);
56
+ const [, body] = mockRestService.post.mock.calls[0];
57
+ const parsed = JSON.parse(body);
58
+ expect(parsed).toEqual([]);
59
+ });
60
+ test('should POST a single attribute', async () => {
61
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
62
+ const attrs = [new ElementAttribute_1.ElementAttribute('Alias', 'Alias')];
63
+ await elementService.addElementAttributes('Dim', 'Hier', attrs);
64
+ const [, body] = mockRestService.post.mock.calls[0];
65
+ const parsed = JSON.parse(body);
66
+ expect(parsed).toHaveLength(1);
67
+ expect(parsed[0].Name).toBe('Alias');
68
+ expect(parsed[0].Type).toBe('Alias');
69
+ });
70
+ test('should include dimension and hierarchy names in the URL', async () => {
71
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
72
+ await elementService.addElementAttributes('MyDimension', 'MyHierarchy', [new ElementAttribute_1.ElementAttribute('Attr1', 'String')]);
73
+ const [url] = mockRestService.post.mock.calls[0];
74
+ expect(url).toContain("MyDimension");
75
+ expect(url).toContain("MyHierarchy");
76
+ });
77
+ });
78
+ });
79
+ //# sourceMappingURL=elementService.issue38.test.js.map
@@ -0,0 +1,5 @@
1
+ /**
2
+ * HierarchyService Tests — Issue #38 new methods
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=hierarchyService.issue38.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hierarchyService.issue38.test.d.ts","sourceRoot":"","sources":["../../src/tests/hierarchyService.issue38.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,460 @@
1
+ "use strict";
2
+ /**
3
+ * HierarchyService Tests — Issue #38 new methods
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const HierarchyService_1 = require("../services/HierarchyService");
7
+ const Hierarchy_1 = require("../objects/Hierarchy");
8
+ const Element_1 = require("../objects/Element");
9
+ const ElementAttribute_1 = require("../objects/ElementAttribute");
10
+ const TM1Exception_1 = require("../exceptions/TM1Exception");
11
+ const DataFrame_1 = require("../utils/DataFrame");
12
+ const createMockResponse = (data, status = 200) => ({
13
+ data,
14
+ status,
15
+ statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
16
+ headers: {},
17
+ config: {}
18
+ });
19
+ describe('HierarchyService — Issue #38 new methods', () => {
20
+ let hierarchyService;
21
+ let mockRestService;
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: {},
30
+ rest: {},
31
+ buildBaseUrl: jest.fn(),
32
+ extractErrorMessage: jest.fn()
33
+ };
34
+ hierarchyService = new HierarchyService_1.HierarchyService(mockRestService);
35
+ });
36
+ // ===== updateOrCreate =====
37
+ describe('updateOrCreate', () => {
38
+ test('should call create when hierarchy does not exist', async () => {
39
+ const hierarchy = new Hierarchy_1.Hierarchy('NewHierarchy', 'TestDimension');
40
+ // exists() does a GET for $select=Name — return list without the hierarchy
41
+ mockRestService.get.mockImplementation(async (url) => {
42
+ if (url.includes('$select=Name')) {
43
+ return createMockResponse({ value: [] });
44
+ }
45
+ // getElementAttributes inside create -> updateElementAttributes
46
+ return createMockResponse({ value: [] });
47
+ });
48
+ mockRestService.post.mockResolvedValue(createMockResponse({ Name: 'NewHierarchy' }, 201));
49
+ const result = await hierarchyService.updateOrCreate(hierarchy);
50
+ expect(mockRestService.post).toHaveBeenCalledTimes(1);
51
+ const postUrl = mockRestService.post.mock.calls[0][0];
52
+ expect(postUrl).toContain("/Hierarchies");
53
+ });
54
+ test('should call update when hierarchy exists', async () => {
55
+ const hierarchy = new Hierarchy_1.Hierarchy('ExistingHierarchy', 'TestDimension');
56
+ // exists() returns the hierarchy in the list
57
+ mockRestService.get.mockImplementation(async (url) => {
58
+ if (url.includes('$select=Name')) {
59
+ return createMockResponse({ value: [{ Name: 'ExistingHierarchy' }] });
60
+ }
61
+ // getElementAttributes inside update -> updateElementAttributes
62
+ return createMockResponse({ value: [] });
63
+ });
64
+ mockRestService.patch.mockResolvedValue(createMockResponse({}, 200));
65
+ const result = await hierarchyService.updateOrCreate(hierarchy);
66
+ expect(mockRestService.patch).toHaveBeenCalledTimes(1);
67
+ const patchUrl = mockRestService.patch.mock.calls[0][0];
68
+ expect(patchUrl).toContain("Hierarchies('ExistingHierarchy')");
69
+ });
70
+ });
71
+ // ===== getHierarchySummary =====
72
+ describe('getHierarchySummary', () => {
73
+ test('should return correct counts from OData response', async () => {
74
+ mockRestService.get.mockResolvedValue(createMockResponse({
75
+ 'Cardinality': 0,
76
+ 'Elements@odata.count': 42,
77
+ 'Edges@odata.count': 30,
78
+ 'ElementAttributes@odata.count': 5,
79
+ 'Members@odata.count': 50,
80
+ 'Levels@odata.count': 3
81
+ }));
82
+ const summary = await hierarchyService.getHierarchySummary('TestDimension', 'TestHierarchy');
83
+ expect(summary.Elements).toBe(42);
84
+ expect(summary.Edges).toBe(30);
85
+ expect(summary.ElementAttributes).toBe(5);
86
+ expect(summary.Members).toBe(50);
87
+ expect(summary.Levels).toBe(3);
88
+ });
89
+ test('should default hierarchyName to dimensionName when not provided', async () => {
90
+ mockRestService.get.mockResolvedValue(createMockResponse({
91
+ 'Elements@odata.count': 10,
92
+ 'Edges@odata.count': 5,
93
+ 'ElementAttributes@odata.count': 2,
94
+ 'Members@odata.count': 10,
95
+ 'Levels@odata.count': 1
96
+ }));
97
+ await hierarchyService.getHierarchySummary('TestDimension');
98
+ const calledUrl = mockRestService.get.mock.calls[0][0];
99
+ expect(calledUrl).toContain("Hierarchies('TestDimension')");
100
+ });
101
+ test('should default missing odata counts to 0', async () => {
102
+ mockRestService.get.mockResolvedValue(createMockResponse({}));
103
+ const summary = await hierarchyService.getHierarchySummary('TestDimension', 'TestHierarchy');
104
+ expect(summary.Elements).toBe(0);
105
+ expect(summary.Edges).toBe(0);
106
+ expect(summary.ElementAttributes).toBe(0);
107
+ expect(summary.Members).toBe(0);
108
+ expect(summary.Levels).toBe(0);
109
+ });
110
+ });
111
+ // ===== getDefaultMember =====
112
+ describe('getDefaultMember', () => {
113
+ test('should return member name from response', async () => {
114
+ mockRestService.get.mockResolvedValue(createMockResponse({ Name: 'All Members' }));
115
+ const result = await hierarchyService.getDefaultMember('TestDimension', 'TestHierarchy');
116
+ expect(result).toBe('All Members');
117
+ const calledUrl = mockRestService.get.mock.calls[0][0];
118
+ expect(calledUrl).toContain('/DefaultMember');
119
+ });
120
+ test('should return null on 404', async () => {
121
+ mockRestService.get.mockRejectedValue(new TM1Exception_1.TM1RestException('Not found', 404, { status: 404 }));
122
+ const result = await hierarchyService.getDefaultMember('TestDimension', 'TestHierarchy');
123
+ expect(result).toBeNull();
124
+ });
125
+ test('should rethrow non-404 errors', async () => {
126
+ mockRestService.get.mockRejectedValue(new TM1Exception_1.TM1RestException('Server error', 500, { status: 500 }));
127
+ await expect(hierarchyService.getDefaultMember('TestDimension', 'TestHierarchy')).rejects.toThrow();
128
+ });
129
+ test('should default hierarchyName to dimensionName', async () => {
130
+ mockRestService.get.mockResolvedValue(createMockResponse({ Name: 'Root' }));
131
+ await hierarchyService.getDefaultMember('TestDimension');
132
+ const calledUrl = mockRestService.get.mock.calls[0][0];
133
+ expect(calledUrl).toContain("Hierarchies('TestDimension')");
134
+ });
135
+ });
136
+ // ===== updateDefaultMember =====
137
+ describe('updateDefaultMember', () => {
138
+ test('should use API approach (PATCH) when version is 12.0.0', async () => {
139
+ mockRestService.version = '12.0.0';
140
+ mockRestService.patch.mockResolvedValue(createMockResponse({}, 200));
141
+ await hierarchyService.updateDefaultMember('TestDimension', 'TestHierarchy', 'RootMember');
142
+ expect(mockRestService.patch).toHaveBeenCalledTimes(1);
143
+ const [url, body] = mockRestService.patch.mock.calls[0];
144
+ expect(url).toContain("Dimensions('TestDimension')/Hierarchies('TestHierarchy')");
145
+ const parsed = JSON.parse(body);
146
+ expect(parsed['DefaultMember@odata.bind']).toContain("Elements('RootMember')");
147
+ });
148
+ test('should use API approach when version is undefined', async () => {
149
+ mockRestService.version = undefined;
150
+ mockRestService.patch.mockResolvedValue(createMockResponse({}, 200));
151
+ await hierarchyService.updateDefaultMember('TestDimension', 'TestHierarchy', 'RootMember');
152
+ expect(mockRestService.patch).toHaveBeenCalledTimes(1);
153
+ });
154
+ test('should use props cube approach for pre-v12 (version 11.8.0)', async () => {
155
+ mockRestService.version = '11.8.0';
156
+ // CellService.writeValue calls getDimensionNamesForWriting (GET) then POST
157
+ mockRestService.get.mockResolvedValue(createMockResponse({
158
+ Dimensions: [
159
+ { Name: '}HierarchyProperties' },
160
+ { Name: '}HierarchyProperties_dim' },
161
+ { Name: 'MEMBER_DEFAULT' }
162
+ ]
163
+ }));
164
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 200));
165
+ await hierarchyService.updateDefaultMember('TestDimension', 'TestHierarchy', 'RootMember');
166
+ expect(mockRestService.post).toHaveBeenCalledTimes(1);
167
+ const [url, body] = mockRestService.post.mock.calls[0];
168
+ // The cube name '}HierarchyProperties' gets URL-encoded as '%7DHierarchyProperties'
169
+ expect(url).toContain('HierarchyProperties');
170
+ const parsed = JSON.parse(body);
171
+ expect(parsed.Cells[0].Value).toBe('RootMember');
172
+ });
173
+ test('should clear default member when memberName is empty string (API approach)', async () => {
174
+ mockRestService.version = '12.0.0';
175
+ mockRestService.patch.mockResolvedValue(createMockResponse({}, 200));
176
+ await hierarchyService.updateDefaultMember('TestDimension', 'TestHierarchy', '');
177
+ expect(mockRestService.patch).toHaveBeenCalledTimes(1);
178
+ const [, body] = mockRestService.patch.mock.calls[0];
179
+ const parsed = JSON.parse(body);
180
+ expect(parsed['DefaultMember@odata.bind']).toBeNull();
181
+ });
182
+ test('should default hierarchyName to dimensionName', async () => {
183
+ mockRestService.version = '12.0.0';
184
+ mockRestService.patch.mockResolvedValue(createMockResponse({}, 200));
185
+ await hierarchyService.updateDefaultMember('TestDimension', undefined, 'Root');
186
+ const [url] = mockRestService.patch.mock.calls[0];
187
+ expect(url).toContain("Hierarchies('TestDimension')");
188
+ });
189
+ });
190
+ // ===== removeEdgesUnderConsolidation =====
191
+ describe('removeEdgesUnderConsolidation', () => {
192
+ test('should remove edges under the specified consolidation element', async () => {
193
+ // Build a hierarchy: Total -> [A, B], A -> [A1, A2]
194
+ const hierarchy = new Hierarchy_1.Hierarchy('TestHierarchy', 'TestDimension');
195
+ hierarchy.addEdge('Total', 'A', 1);
196
+ hierarchy.addEdge('Total', 'B', 1);
197
+ hierarchy.addEdge('A', 'A1', 1);
198
+ hierarchy.addEdge('A', 'A2', 1);
199
+ let getCallCount = 0;
200
+ mockRestService.get.mockImplementation(async (url) => {
201
+ getCallCount++;
202
+ if (getCallCount === 1) {
203
+ // First call is the get(dimensionName, hierarchyName) inside removeEdgesUnderConsolidation
204
+ return createMockResponse({
205
+ Name: 'TestHierarchy',
206
+ Elements: [
207
+ { Name: 'Total', Type: 'Consolidated' },
208
+ { Name: 'A', Type: 'Consolidated' },
209
+ { Name: 'B', Type: 'Numeric' },
210
+ { Name: 'A1', Type: 'Numeric' },
211
+ { Name: 'A2', Type: 'Numeric' }
212
+ ],
213
+ Edges: [
214
+ { ParentName: 'Total', ComponentName: 'A', Weight: 1 },
215
+ { ParentName: 'Total', ComponentName: 'B', Weight: 1 },
216
+ { ParentName: 'A', ComponentName: 'A1', Weight: 1 },
217
+ { ParentName: 'A', ComponentName: 'A2', Weight: 1 }
218
+ ],
219
+ ElementAttributes: [],
220
+ Subsets: []
221
+ });
222
+ }
223
+ // Subsequent calls from updateElementAttributes
224
+ return createMockResponse({ value: [] });
225
+ });
226
+ mockRestService.patch.mockResolvedValue(createMockResponse({}, 200));
227
+ await hierarchyService.removeEdgesUnderConsolidation('TestDimension', 'TestHierarchy', 'A');
228
+ expect(mockRestService.patch).toHaveBeenCalledTimes(1);
229
+ const [, body] = mockRestService.patch.mock.calls[0];
230
+ const parsed = JSON.parse(body);
231
+ // After removing edges under A, A should have no children in the hierarchy
232
+ const remainingEdges = parsed.Edges || [];
233
+ const aEdges = remainingEdges.filter((e) => e.ParentName === 'A');
234
+ expect(aEdges).toHaveLength(0);
235
+ });
236
+ });
237
+ // ===== addEdges =====
238
+ describe('addEdges', () => {
239
+ test('should delegate to ElementService.addEdges', async () => {
240
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
241
+ await hierarchyService.addEdges('TestDimension', 'TestHierarchy', { Parent: { Child: 1 } });
242
+ expect(mockRestService.post).toHaveBeenCalledTimes(1);
243
+ const [url] = mockRestService.post.mock.calls[0];
244
+ expect(url).toContain("Dimensions('TestDimension')/Hierarchies('TestHierarchy')");
245
+ });
246
+ test('should use dimensionName as hierarchyName when hierarchyName is undefined', async () => {
247
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
248
+ await hierarchyService.addEdges('TestDimension', undefined, { P: { C: 1 } });
249
+ const [url] = mockRestService.post.mock.calls[0];
250
+ expect(url).toContain("Hierarchies('TestDimension')");
251
+ });
252
+ });
253
+ // ===== addElements =====
254
+ describe('addElements', () => {
255
+ test('should delegate to ElementService.addElements', async () => {
256
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
257
+ const elements = [new Element_1.Element('Elem1', Element_1.ElementType.NUMERIC)];
258
+ await hierarchyService.addElements('TestDimension', 'TestHierarchy', elements);
259
+ expect(mockRestService.post).toHaveBeenCalledTimes(1);
260
+ const [url] = mockRestService.post.mock.calls[0];
261
+ expect(url).toContain("Dimensions('TestDimension')/Hierarchies('TestHierarchy')/Elements");
262
+ });
263
+ });
264
+ // ===== addElementAttributes =====
265
+ describe('addElementAttributes', () => {
266
+ test('should delegate to ElementService.addElementAttributes', async () => {
267
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
268
+ const attrs = [new ElementAttribute_1.ElementAttribute('Description', 'String')];
269
+ await hierarchyService.addElementAttributes('TestDimension', 'TestHierarchy', attrs);
270
+ expect(mockRestService.post).toHaveBeenCalledTimes(1);
271
+ const [url] = mockRestService.post.mock.calls[0];
272
+ expect(url).toContain("Dimensions('TestDimension')/Hierarchies('TestHierarchy')/ElementAttributes");
273
+ });
274
+ });
275
+ // ===== updateOrCreateHierarchyFromDataframe =====
276
+ describe('updateOrCreateHierarchyFromDataframe', () => {
277
+ // Helper: mock hierarchy response with given elements/edges
278
+ const mockHierarchyResponse = (elements = [], edges = []) => createMockResponse({
279
+ Name: 'TestHierarchy',
280
+ Elements: elements,
281
+ Edges: edges,
282
+ ElementAttributes: [],
283
+ Subsets: [],
284
+ DefaultMember: null
285
+ });
286
+ // Helper: mock "hierarchy exists" check (getAllNames-style)
287
+ const setupExistingHierarchy = (elements = [], edges = []) => {
288
+ // exists() calls GET /Hierarchies?$select=Name
289
+ mockRestService.get.mockImplementation(async (url) => {
290
+ if (url.includes('$select=Name')) {
291
+ return createMockResponse({ value: [{ Name: 'TestHierarchy' }] });
292
+ }
293
+ if (url.includes('$expand=Edges')) {
294
+ return mockHierarchyResponse(elements, edges);
295
+ }
296
+ if (url.includes('ElementAttributes')) {
297
+ return createMockResponse({ value: [] });
298
+ }
299
+ return createMockResponse({});
300
+ });
301
+ };
302
+ const setupNewHierarchy = () => {
303
+ let hierarchyCreated = false;
304
+ mockRestService.get.mockImplementation(async (url) => {
305
+ if (url.includes('$select=Name')) {
306
+ return createMockResponse({ value: [] }); // hierarchy doesn't exist
307
+ }
308
+ if (url.includes('$expand=Edges')) {
309
+ return mockHierarchyResponse(); // empty hierarchy after creation
310
+ }
311
+ if (url.includes('ElementAttributes')) {
312
+ return createMockResponse({ value: [] });
313
+ }
314
+ return createMockResponse({});
315
+ });
316
+ mockRestService.post.mockImplementation(async (url, body) => {
317
+ hierarchyCreated = true;
318
+ return createMockResponse({}, 201);
319
+ });
320
+ mockRestService.patch.mockResolvedValue(createMockResponse({}, 200));
321
+ };
322
+ test('should add new elements from DataFrame to existing hierarchy', async () => {
323
+ setupExistingHierarchy([
324
+ { Name: 'Existing1', Type: 'Numeric' }
325
+ ]);
326
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
327
+ const df = new DataFrame_1.DataFrame([
328
+ ['Existing1', 'Numeric'],
329
+ ['NewElem1', 'String'],
330
+ ['NewElem2', 'Numeric']
331
+ ], { columns: ['Element', 'ElementType'] });
332
+ await hierarchyService.updateOrCreateHierarchyFromDataframe('TestDimension', 'TestHierarchy', df, { elementColumn: 'Element' });
333
+ // Should POST new elements (NewElem1, NewElem2 but not Existing1)
334
+ const postCalls = mockRestService.post.mock.calls;
335
+ const elementPostCall = postCalls.find(([url]) => url.includes('/Elements') && !url.includes('ElementAttributes'));
336
+ expect(elementPostCall).toBeDefined();
337
+ const postedBody = JSON.parse(elementPostCall[1]);
338
+ expect(postedBody).toHaveLength(2);
339
+ expect(postedBody.map((e) => e.Name).sort()).toEqual(['NewElem1', 'NewElem2']);
340
+ });
341
+ test('should throw on duplicate elements when verifyUniqueElements=true', async () => {
342
+ const df = new DataFrame_1.DataFrame([
343
+ ['Elem1', 'Numeric'],
344
+ ['elem1', 'String'], // duplicate (case-insensitive)
345
+ ], { columns: ['Element', 'ElementType'] });
346
+ await expect(hierarchyService.updateOrCreateHierarchyFromDataframe('TestDimension', 'TestHierarchy', df, { elementColumn: 'Element', verifyUniqueElements: true })).rejects.toThrow('Duplicate element');
347
+ });
348
+ test('should create dimension when hierarchy does not exist', async () => {
349
+ setupNewHierarchy();
350
+ const df = new DataFrame_1.DataFrame([
351
+ ['Elem1', 'Numeric']
352
+ ], { columns: ['Element', 'ElementType'] });
353
+ await hierarchyService.updateOrCreateHierarchyFromDataframe('NewDimension', 'NewHierarchy', df, { elementColumn: 'Element' });
354
+ // Should have called POST for dimension creation
355
+ const postCalls = mockRestService.post.mock.calls;
356
+ const dimCreateCall = postCalls.find(([url]) => url.includes('/Dimensions'));
357
+ expect(dimCreateCall).toBeDefined();
358
+ });
359
+ test('should unwind all edges when unwindAll=true', async () => {
360
+ setupExistingHierarchy([{ Name: 'Parent', Type: 'Consolidated' }, { Name: 'Child', Type: 'Numeric' }], [{ ParentName: 'Parent', ComponentName: 'Child', Weight: 1 }]);
361
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
362
+ mockRestService.patch.mockResolvedValue(createMockResponse({}, 200));
363
+ const df = new DataFrame_1.DataFrame([
364
+ ['Child', 'Numeric']
365
+ ], { columns: ['Element', 'ElementType'] });
366
+ await hierarchyService.updateOrCreateHierarchyFromDataframe('TestDimension', 'TestHierarchy', df, { elementColumn: 'Element', unwindAll: true });
367
+ // Should PATCH to remove all edges (empty Edges array)
368
+ const patchCalls = mockRestService.patch.mock.calls;
369
+ const edgePatch = patchCalls.find(([_, body]) => {
370
+ try {
371
+ return JSON.parse(body).Edges !== undefined;
372
+ }
373
+ catch (_a) {
374
+ return false;
375
+ }
376
+ });
377
+ expect(edgePatch).toBeDefined();
378
+ });
379
+ test('should re-throw unexpected errors from bulk edge add', async () => {
380
+ setupExistingHierarchy();
381
+ mockRestService.post.mockImplementation(async (url) => {
382
+ if (url.includes('/Edges')) {
383
+ throw new TM1Exception_1.TM1RestException('Server error', 500, { status: 500 });
384
+ }
385
+ if (url.includes('/Elements')) {
386
+ return createMockResponse({}, 201);
387
+ }
388
+ return createMockResponse({}, 201);
389
+ });
390
+ const df = new DataFrame_1.DataFrame([
391
+ ['Child', 'Numeric', 'Parent'],
392
+ ], { columns: ['Element', 'ElementType', 'Level001'] });
393
+ await expect(hierarchyService.updateOrCreateHierarchyFromDataframe('TestDimension', 'TestHierarchy', df, { elementColumn: 'Element' })).rejects.toThrow('Server error');
394
+ });
395
+ test('should fall back to individual edges on 400 from bulk', async () => {
396
+ setupExistingHierarchy();
397
+ let bulkAttempted = false;
398
+ mockRestService.post.mockImplementation(async (url, body) => {
399
+ if (url.includes('/Edges')) {
400
+ if (!bulkAttempted) {
401
+ bulkAttempted = true;
402
+ throw new TM1Exception_1.TM1RestException('Bad request', 400, { status: 400 });
403
+ }
404
+ // Individual edge adds succeed
405
+ return createMockResponse({}, 201);
406
+ }
407
+ if (url.includes('/Elements')) {
408
+ return createMockResponse({}, 201);
409
+ }
410
+ return createMockResponse({}, 201);
411
+ });
412
+ const df = new DataFrame_1.DataFrame([
413
+ ['Child', 'Numeric', 'Parent'],
414
+ ], { columns: ['Element', 'ElementType', 'Level001'] });
415
+ await hierarchyService.updateOrCreateHierarchyFromDataframe('TestDimension', 'TestHierarchy', df, { elementColumn: 'Element' });
416
+ // Bulk failed, then individual edge was attempted
417
+ const edgeCalls = mockRestService.post.mock.calls.filter(([url]) => url.includes('/Edges'));
418
+ expect(edgeCalls.length).toBeGreaterThan(1); // bulk + individual
419
+ });
420
+ test('should delete orphaned consolidations when requested', async () => {
421
+ // Setup: hierarchy has a Consolidated element "Orphan" with no children
422
+ const getCallCount = { n: 0 };
423
+ mockRestService.get.mockImplementation(async (url) => {
424
+ if (url.includes('$select=Name')) {
425
+ return createMockResponse({ value: [{ Name: 'TestHierarchy' }] });
426
+ }
427
+ if (url.includes('$expand=Edges')) {
428
+ getCallCount.n++;
429
+ // Return hierarchy with orphaned consolidation
430
+ return createMockResponse({
431
+ Name: 'TestHierarchy',
432
+ Elements: [
433
+ { Name: 'Leaf', Type: 'Numeric' },
434
+ { Name: 'Orphan', Type: 'Consolidated' }
435
+ ],
436
+ Edges: [], // No edges — Orphan is orphaned
437
+ ElementAttributes: [],
438
+ Subsets: [],
439
+ DefaultMember: null
440
+ });
441
+ }
442
+ if (url.includes('ElementAttributes')) {
443
+ return createMockResponse({ value: [] });
444
+ }
445
+ return createMockResponse({});
446
+ });
447
+ mockRestService.post.mockResolvedValue(createMockResponse({}, 201));
448
+ mockRestService.delete.mockResolvedValue(createMockResponse({}, 204));
449
+ const df = new DataFrame_1.DataFrame([
450
+ ['Leaf', 'Numeric']
451
+ ], { columns: ['Element', 'ElementType'] });
452
+ await hierarchyService.updateOrCreateHierarchyFromDataframe('TestDimension', 'TestHierarchy', df, { elementColumn: 'Element', deleteOrphanedConsolidations: true });
453
+ // Should DELETE the orphaned consolidation
454
+ const deleteCalls = mockRestService.delete.mock.calls;
455
+ const orphanDelete = deleteCalls.find(([url]) => url.includes("Elements('Orphan')"));
456
+ expect(orphanDelete).toBeDefined();
457
+ });
458
+ });
459
+ });
460
+ //# sourceMappingURL=hierarchyService.issue38.test.js.map
@@ -222,7 +222,7 @@ describe('ProcessService - Comprehensive Tests', () => {
222
222
  mockRestService.post.mockResolvedValue(mockResponse(returnData));
223
223
  const result = await processService.executeWithReturn('TestProcess', {}, 30);
224
224
  expect(result).toEqual(mockResponse(returnData));
225
- expect(mockRestService.post).toHaveBeenCalledWith("/Processes('TestProcess')/tm1.ExecuteWithReturn?$expand=*", "{}", { timeout: 30000 });
225
+ expect(mockRestService.post).toHaveBeenCalledWith("/Processes('TestProcess')/tm1.ExecuteWithReturn?$expand=*", "{}", { timeout: 30 });
226
226
  });
227
227
  test('should execute process with return data extraction', async () => {
228
228
  const returnData = {
@@ -396,20 +396,20 @@ describe('ProcessService - Comprehensive Tests', () => {
396
396
  ]
397
397
  };
398
398
  mockRestService.get.mockResolvedValue(mockResponse(breakpointsData));
399
- const result = await processService.getProcessDebugBreakpoints('debug-123');
399
+ const result = await processService.debugGetBreakpoints('debug-123');
400
400
  expect(result).toHaveLength(2);
401
401
  expect(ProcessDebugBreakpoint_1.ProcessDebugBreakpoint.fromDict).toHaveBeenCalledTimes(2);
402
402
  expect(mockRestService.get).toHaveBeenCalledWith("/ProcessDebugContexts('debug-123')/Breakpoints");
403
403
  });
404
- test('should create process debug breakpoint', async () => {
404
+ test('should add process debug breakpoint (delegates to debugAddBreakpoints)', async () => {
405
405
  mockRestService.post.mockResolvedValue(mockResponse({}));
406
- const result = await processService.createProcessDebugBreakpoint('debug-123', mockProcessDebugBreakpoint);
406
+ const result = await processService.debugAddBreakpoint('debug-123', mockProcessDebugBreakpoint);
407
407
  expect(result).toBeDefined();
408
- expect(mockRestService.post).toHaveBeenCalledWith("/ProcessDebugContexts('debug-123')/Breakpoints", mockProcessDebugBreakpoint.body);
408
+ expect(mockRestService.post).toHaveBeenCalledWith("/ProcessDebugContexts('debug-123')/Breakpoints", JSON.stringify([mockProcessDebugBreakpoint.bodyAsDict]));
409
409
  });
410
410
  test('should delete process debug breakpoint', async () => {
411
411
  mockRestService.delete.mockResolvedValue(mockResponse({}));
412
- const result = await processService.deleteProcessDebugBreakpoint('debug-123', 5);
412
+ const result = await processService.debugRemoveBreakpoint('debug-123', 5);
413
413
  expect(result).toBeDefined();
414
414
  expect(mockRestService.delete).toHaveBeenCalledWith("/ProcessDebugContexts('debug-123')/Breakpoints('5')");
415
415
  });
@@ -654,7 +654,7 @@ describe('ProcessService - Comprehensive Tests', () => {
654
654
  const debugResponse = { ID: 'ctx-003', Breakpoints: [], CallStack: [] };
655
655
  mockRestService.post.mockResolvedValue(mockResponse(debugResponse));
656
656
  await processService.debugProcess('TestProcess', 60);
657
- expect(mockRestService.post).toHaveBeenCalledWith(expect.any(String), expect.any(String), { timeout: 60000 });
657
+ expect(mockRestService.post).toHaveBeenCalledWith(expect.any(String), expect.any(String), { timeout: 60 });
658
658
  });
659
659
  test('debugAddBreakpoints should POST breakpoints array to Breakpoints endpoint', async () => {
660
660
  mockRestService.post.mockResolvedValue(mockResponse({}));
@@ -848,10 +848,10 @@ describe('ProcessService - Comprehensive Tests', () => {
848
848
  mockRestService.get.mockResolvedValue(mockResponse(debugData));
849
849
  mockRestService.delete.mockResolvedValue(mockResponse({}));
850
850
  // Debug workflow: set breakpoint, run debug commands, remove breakpoint
851
- await processService.createProcessDebugBreakpoint('debug-123', mockProcessDebugBreakpoint);
851
+ await processService.debugAddBreakpoint('debug-123', mockProcessDebugBreakpoint);
852
852
  await processService.debugStepOver('debug-123');
853
853
  await processService.debugContinue('debug-123');
854
- await processService.deleteProcessDebugBreakpoint('debug-123', 5);
854
+ await processService.debugRemoveBreakpoint('debug-123', 5);
855
855
  expect(mockRestService.post).toHaveBeenCalledTimes(3);
856
856
  expect(mockRestService.delete).toHaveBeenCalledTimes(1);
857
857
  });