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,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 @@
|
|
|
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:
|
|
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.
|
|
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
|
|
404
|
+
test('should add process debug breakpoint (delegates to debugAddBreakpoints)', async () => {
|
|
405
405
|
mockRestService.post.mockResolvedValue(mockResponse({}));
|
|
406
|
-
const result = await processService.
|
|
406
|
+
const result = await processService.debugAddBreakpoint('debug-123', mockProcessDebugBreakpoint);
|
|
407
407
|
expect(result).toBeDefined();
|
|
408
|
-
expect(mockRestService.post).toHaveBeenCalledWith("/ProcessDebugContexts('debug-123')/Breakpoints", mockProcessDebugBreakpoint.
|
|
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.
|
|
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:
|
|
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.
|
|
851
|
+
await processService.debugAddBreakpoint('debug-123', mockProcessDebugBreakpoint);
|
|
852
852
|
await processService.debugStepOver('debug-123');
|
|
853
853
|
await processService.debugContinue('debug-123');
|
|
854
|
-
await processService.
|
|
854
|
+
await processService.debugRemoveBreakpoint('debug-123', 5);
|
|
855
855
|
expect(mockRestService.post).toHaveBeenCalledTimes(3);
|
|
856
856
|
expect(mockRestService.delete).toHaveBeenCalledTimes(1);
|
|
857
857
|
});
|