@squiz/dxp-cli-next 5.33.0-develop.2 → 5.33.0-develop.4
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/README.md +1 -0
- package/lib/migration/batch-get/batch-get.d.ts +3 -0
- package/lib/migration/batch-get/batch-get.js +45 -0
- package/lib/migration/batch-get/batch-get.spec.js +182 -0
- package/lib/migration/batch-next/batch-next.d.ts +3 -0
- package/lib/migration/batch-next/batch-next.js +60 -0
- package/lib/migration/batch-next/batch-next.spec.d.ts +1 -0
- package/lib/migration/batch-next/batch-next.spec.js +251 -0
- package/lib/migration/batch-revert/batch-revert.d.ts +3 -0
- package/lib/migration/batch-revert/batch-revert.js +45 -0
- package/lib/migration/batch-revert/batch-revert.spec.d.ts +1 -0
- package/lib/migration/batch-revert/batch-revert.spec.js +197 -0
- package/lib/migration/create/create.js +27 -7
- package/lib/migration/create/create.spec.js +48 -20
- package/lib/migration/get/get.js +3 -3
- package/lib/migration/get/get.spec.js +8 -19
- package/lib/migration/index.js +9 -1
- package/lib/migration/list/list.spec.js +7 -6
- package/lib/migration/mark-deployed/mark-deployed.d.ts +3 -0
- package/lib/migration/mark-deployed/mark-deployed.js +55 -0
- package/lib/migration/mark-deployed/mark-deployed.spec.d.ts +1 -0
- package/lib/migration/mark-deployed/mark-deployed.spec.js +217 -0
- package/lib/migration/next/next.spec.js +6 -5
- package/lib/migration/pre/pre.js +3 -3
- package/lib/migration/pre/pre.spec.js +8 -8
- package/lib/migration/revert/revert.js +1 -1
- package/lib/migration/revert/revert.spec.js +1 -1
- package/lib/migration/settings/settings.spec.js +2 -2
- package/lib/migration/types/common.types.d.ts +33 -2
- package/lib/migration/types/common.types.js +17 -0
- package/lib/migration/types/createMigration.types.d.ts +41 -3
- package/lib/migration/types/getMigration.types.d.ts +5 -1
- package/lib/migration/types/index.d.ts +1 -0
- package/lib/migration/types/index.js +1 -0
- package/lib/migration/types/markDeployed.types.d.ts +20 -0
- package/lib/migration/types/markDeployed.types.js +2 -0
- package/lib/migration/types/nextStage.types.d.ts +22 -1
- package/lib/migration/types/revert.types.d.ts +12 -0
- package/lib/migration/utils/common.d.ts +17 -3
- package/lib/migration/utils/common.js +56 -24
- package/lib/migration/utils/common.spec.js +73 -2
- package/lib/migration/utils/createMigration.d.ts +4 -21
- package/lib/migration/utils/createMigration.js +23 -32
- package/lib/migration/utils/createMigration.spec.js +19 -18
- package/lib/migration/utils/getMigration.d.ts +2 -1
- package/lib/migration/utils/getMigration.js +23 -18
- package/lib/migration/utils/getMigration.spec.js +146 -7
- package/lib/migration/utils/index.d.ts +1 -0
- package/lib/migration/utils/index.js +1 -0
- package/lib/migration/utils/listMigrations.spec.js +7 -6
- package/lib/migration/utils/loadAssetIdsFromFile.d.ts +9 -0
- package/lib/migration/utils/loadAssetIdsFromFile.js +40 -0
- package/lib/migration/utils/loadAssetIdsFromFile.spec.d.ts +1 -0
- package/lib/migration/utils/{loadCctIdsFromFile.spec.js → loadAssetIdsFromFile.spec.js} +13 -13
- package/lib/migration/utils/loadStageOptionsFromFile.d.ts +2 -1
- package/lib/migration/utils/markComponentsDeployed.d.ts +2 -0
- package/lib/migration/utils/markComponentsDeployed.js +37 -0
- package/lib/migration/utils/markComponentsDeployed.spec.d.ts +1 -0
- package/lib/migration/utils/markComponentsDeployed.spec.js +222 -0
- package/lib/migration/utils/nextStage.d.ts +2 -1
- package/lib/migration/utils/nextStage.js +32 -22
- package/lib/migration/utils/nextStage.spec.js +179 -7
- package/lib/migration/utils/options.d.ts +4 -1
- package/lib/migration/utils/options.js +32 -2
- package/lib/migration/utils/revertMigration.d.ts +2 -1
- package/lib/migration/utils/revertMigration.js +21 -1
- package/lib/migration/utils/revertMigration.spec.js +115 -0
- package/lib/page/utils/definitions.js +1 -1
- package/lib/page/utils/definitions.spec.js +19 -8
- package/package.json +1 -1
- package/lib/migration/utils/loadCctIdsFromFile.d.ts +0 -1
- package/lib/migration/utils/loadCctIdsFromFile.js +0 -32
- /package/lib/migration/{utils/loadCctIdsFromFile.spec.d.ts → batch-get/batch-get.spec.d.ts} +0 -0
|
@@ -91,13 +91,16 @@ describe('getMigration', () => {
|
|
|
91
91
|
expect(result.assetId).toBe('asset-456');
|
|
92
92
|
expect(mockApiServiceInstance.client.get).toHaveBeenCalledWith('https://custom.migration.url/migrations/migration-123/assets/asset-456', expect.any(Object));
|
|
93
93
|
}));
|
|
94
|
-
it('handles
|
|
95
|
-
|
|
94
|
+
it('handles API errors (4xx/5xx status codes)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
95
|
+
// In production, Axios throws AxiosError for 4xx/5xx status codes
|
|
96
|
+
const axiosError = new Error('Request failed with status code 404');
|
|
97
|
+
axiosError.isAxiosError = true;
|
|
98
|
+
axiosError.response = {
|
|
96
99
|
status: 404,
|
|
97
|
-
data: {},
|
|
100
|
+
data: { message: 'Not Found' },
|
|
98
101
|
};
|
|
99
|
-
mockApiServiceInstance.client.get.
|
|
100
|
-
yield expect((0, getMigration_1.getMigration)(mockOptions)).rejects.toThrow('
|
|
102
|
+
mockApiServiceInstance.client.get.mockRejectedValue(axiosError);
|
|
103
|
+
yield expect((0, getMigration_1.getMigration)(mockOptions)).rejects.toThrow('Request failed with status code 404');
|
|
101
104
|
}));
|
|
102
105
|
it('handles missing response data', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
106
|
const mockResponse = {
|
|
@@ -112,9 +115,10 @@ describe('getMigration', () => {
|
|
|
112
115
|
mockApiServiceInstance.client.get.mockRejectedValue(error);
|
|
113
116
|
yield expect((0, getMigration_1.getMigration)(mockOptions)).rejects.toThrow('Network error');
|
|
114
117
|
}));
|
|
115
|
-
it('
|
|
118
|
+
it('propagates rejected values as-is', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
119
|
+
// Non-Error values are propagated without wrapping
|
|
116
120
|
mockApiServiceInstance.client.get.mockRejectedValue('Unknown error');
|
|
117
|
-
yield expect((0, getMigration_1.getMigration)(mockOptions)).rejects.
|
|
121
|
+
yield expect((0, getMigration_1.getMigration)(mockOptions)).rejects.toBe('Unknown error');
|
|
118
122
|
}));
|
|
119
123
|
it('works without optional fields in response', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
120
124
|
const mockResponse = {
|
|
@@ -154,3 +158,138 @@ describe('getMigration', () => {
|
|
|
154
158
|
expect(mockApiServiceInstance.client.get).toHaveBeenCalledWith('https://example.com/__dxp/service/aiapps/migration/migrations/migration-123/assets/asset-456', expect.any(Object));
|
|
155
159
|
}));
|
|
156
160
|
});
|
|
161
|
+
describe('batchGetMigration', () => {
|
|
162
|
+
let mockApiServiceInstance;
|
|
163
|
+
let mockOptions;
|
|
164
|
+
const mockApiService = ApiService_1.ApiService;
|
|
165
|
+
const mockFetchApplicationConfig = ApplicationConfig_1.fetchApplicationConfig;
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
mockApiServiceInstance = {
|
|
168
|
+
client: {
|
|
169
|
+
get: jest.fn(),
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
mockApiService.mockImplementation(() => mockApiServiceInstance);
|
|
173
|
+
mockOptions = {
|
|
174
|
+
migrationId: 'migration-123',
|
|
175
|
+
tenant: 'test-tenant',
|
|
176
|
+
};
|
|
177
|
+
mockFetchApplicationConfig.mockResolvedValue({
|
|
178
|
+
tenant: 'test-tenant',
|
|
179
|
+
baseUrl: 'https://example.com',
|
|
180
|
+
region: 'au',
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
afterEach(() => {
|
|
184
|
+
jest.clearAllMocks();
|
|
185
|
+
});
|
|
186
|
+
it('gets batch migration summary successfully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
187
|
+
const mockResponse = {
|
|
188
|
+
status: 200,
|
|
189
|
+
data: {
|
|
190
|
+
migrationId: 'migration-123',
|
|
191
|
+
created: 1704067200000,
|
|
192
|
+
expiry: 1704153600000,
|
|
193
|
+
preview: {
|
|
194
|
+
complete: { assetIds: ['asset-1', 'asset-2'] },
|
|
195
|
+
running: { assetIds: ['asset-3'] },
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
mockApiServiceInstance.client.get.mockResolvedValue(mockResponse);
|
|
200
|
+
const result = yield (0, getMigration_1.batchGetMigration)(mockOptions);
|
|
201
|
+
expect(result).toEqual({
|
|
202
|
+
migrationId: 'migration-123',
|
|
203
|
+
created: 1704067200000,
|
|
204
|
+
expiry: 1704153600000,
|
|
205
|
+
preview: {
|
|
206
|
+
complete: { assetIds: ['asset-1', 'asset-2'] },
|
|
207
|
+
running: { assetIds: ['asset-3'] },
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
expect(mockApiServiceInstance.client.get).toHaveBeenCalledWith('https://example.com/__dxp/service/aiapps/migration/migrations/migration-123', {
|
|
211
|
+
headers: {
|
|
212
|
+
'Content-Type': 'application/json',
|
|
213
|
+
'x-dxp-tenant': 'test-tenant',
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}));
|
|
217
|
+
it('gets batch migration with override URL', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
218
|
+
const optionsWithOverride = Object.assign(Object.assign({}, mockOptions), { overrideUrl: 'https://custom.migration.url' });
|
|
219
|
+
const mockResponse = {
|
|
220
|
+
status: 200,
|
|
221
|
+
data: {
|
|
222
|
+
migrationId: 'migration-123',
|
|
223
|
+
created: 1704067200000,
|
|
224
|
+
expiry: 1704153600000,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
mockApiServiceInstance.client.get.mockResolvedValue(mockResponse);
|
|
228
|
+
const result = yield (0, getMigration_1.batchGetMigration)(optionsWithOverride);
|
|
229
|
+
expect(result.migrationId).toBe('migration-123');
|
|
230
|
+
expect(mockApiServiceInstance.client.get).toHaveBeenCalledWith('https://custom.migration.url/migrations/migration-123', expect.any(Object));
|
|
231
|
+
}));
|
|
232
|
+
it('handles API errors (4xx/5xx status codes)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
233
|
+
const axiosError = new Error('Request failed with status code 404');
|
|
234
|
+
axiosError.isAxiosError = true;
|
|
235
|
+
axiosError.response = {
|
|
236
|
+
status: 404,
|
|
237
|
+
data: { message: 'Not Found' },
|
|
238
|
+
};
|
|
239
|
+
mockApiServiceInstance.client.get.mockRejectedValue(axiosError);
|
|
240
|
+
yield expect((0, getMigration_1.batchGetMigration)(mockOptions)).rejects.toThrow('Request failed with status code 404');
|
|
241
|
+
}));
|
|
242
|
+
it('handles missing response data', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
243
|
+
const mockResponse = {
|
|
244
|
+
status: 200,
|
|
245
|
+
data: null,
|
|
246
|
+
};
|
|
247
|
+
mockApiServiceInstance.client.get.mockResolvedValue(mockResponse);
|
|
248
|
+
yield expect((0, getMigration_1.batchGetMigration)(mockOptions)).rejects.toThrow('No data returned from migration service');
|
|
249
|
+
}));
|
|
250
|
+
it('handles API service errors', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
251
|
+
const error = new Error('Network error');
|
|
252
|
+
mockApiServiceInstance.client.get.mockRejectedValue(error);
|
|
253
|
+
yield expect((0, getMigration_1.batchGetMigration)(mockOptions)).rejects.toThrow('Network error');
|
|
254
|
+
}));
|
|
255
|
+
it('propagates rejected values as-is', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
256
|
+
mockApiServiceInstance.client.get.mockRejectedValue('Unknown error');
|
|
257
|
+
yield expect((0, getMigration_1.batchGetMigration)(mockOptions)).rejects.toBe('Unknown error');
|
|
258
|
+
}));
|
|
259
|
+
it('constructs correct URL path without assetId', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
260
|
+
const mockResponse = {
|
|
261
|
+
status: 200,
|
|
262
|
+
data: {
|
|
263
|
+
migrationId: 'migration-123',
|
|
264
|
+
created: 1704067200000,
|
|
265
|
+
expiry: 1704153600000,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
mockApiServiceInstance.client.get.mockResolvedValue(mockResponse);
|
|
269
|
+
yield (0, getMigration_1.batchGetMigration)(mockOptions);
|
|
270
|
+
expect(mockApiServiceInstance.client.get).toHaveBeenCalledWith('https://example.com/__dxp/service/aiapps/migration/migrations/migration-123', expect.any(Object));
|
|
271
|
+
}));
|
|
272
|
+
it('handles response with multiple stages', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
273
|
+
const mockResponse = {
|
|
274
|
+
status: 200,
|
|
275
|
+
data: {
|
|
276
|
+
migrationId: 'migration-123',
|
|
277
|
+
created: 1704067200000,
|
|
278
|
+
expiry: 1704153600000,
|
|
279
|
+
'export-to-json': {
|
|
280
|
+
complete: { assetIds: ['asset-1'] },
|
|
281
|
+
},
|
|
282
|
+
preview: {
|
|
283
|
+
running: { assetIds: ['asset-2'] },
|
|
284
|
+
failed: { assetIds: ['asset-3'] },
|
|
285
|
+
},
|
|
286
|
+
morph: {
|
|
287
|
+
'awaiting-confirmation': { assetIds: ['asset-4'] },
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
mockApiServiceInstance.client.get.mockResolvedValue(mockResponse);
|
|
292
|
+
const result = yield (0, getMigration_1.batchGetMigration)(mockOptions);
|
|
293
|
+
expect(result).toEqual(mockResponse.data);
|
|
294
|
+
}));
|
|
295
|
+
});
|
|
@@ -23,3 +23,4 @@ __exportStar(require("./setMigrationSettings"), exports);
|
|
|
23
23
|
__exportStar(require("./revertMigration"), exports);
|
|
24
24
|
__exportStar(require("./loadStageOptionsFromFile"), exports);
|
|
25
25
|
__exportStar(require("./promptForMorphConfirmation"), exports);
|
|
26
|
+
__exportStar(require("./markComponentsDeployed"), exports);
|
|
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const mockClient = { get: jest.fn() };
|
|
13
13
|
const ApplicationConfig_1 = require("../../ApplicationConfig");
|
|
14
|
+
const types_1 = require("../types");
|
|
14
15
|
const listMigrations_1 = require("./listMigrations");
|
|
15
16
|
jest.mock('path');
|
|
16
17
|
jest.mock('child_process');
|
|
@@ -31,24 +32,24 @@ describe('listMigration', () => {
|
|
|
31
32
|
{
|
|
32
33
|
migrationId: 'migration-1',
|
|
33
34
|
assetId: 'asset-1',
|
|
34
|
-
stage:
|
|
35
|
-
status:
|
|
35
|
+
stage: types_1.MigrationStage.morph,
|
|
36
|
+
status: types_1.MigrationStatus.complete,
|
|
36
37
|
created: 100,
|
|
37
38
|
updated: 200,
|
|
38
39
|
},
|
|
39
40
|
{
|
|
40
41
|
migrationId: 'migration-2',
|
|
41
42
|
assetId: 'asset-2',
|
|
42
|
-
stage:
|
|
43
|
-
status:
|
|
43
|
+
stage: types_1.MigrationStage.morph,
|
|
44
|
+
status: types_1.MigrationStatus.complete,
|
|
44
45
|
created: 100,
|
|
45
46
|
updated: 200,
|
|
46
47
|
},
|
|
47
48
|
{
|
|
48
49
|
migrationId: 'migration-3',
|
|
49
50
|
assetId: 'asset-3',
|
|
50
|
-
stage:
|
|
51
|
-
status:
|
|
51
|
+
stage: types_1.MigrationStage.morph,
|
|
52
|
+
status: types_1.MigrationStatus.complete,
|
|
52
53
|
created: 100,
|
|
53
54
|
updated: 200,
|
|
54
55
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads the asset/cct IDs from a file.
|
|
3
|
+
* Asset Ids can be used for bulk migrations.
|
|
4
|
+
* CCT Ids can be used for pre-migrations or to skip CCTs in migrations.
|
|
5
|
+
* @param filePath - The path to the file containing the asset IDs.
|
|
6
|
+
* @param assetType - The type of asset to load the IDs for - assets or ccts. Defaults to ccts.
|
|
7
|
+
* @returns The asset/cct IDs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const loadAssetIdsFromFile: (filePath?: string, assetType?: 'asset' | 'cct') => string[] | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadAssetIdsFromFile = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
/**
|
|
9
|
+
* Loads the asset/cct IDs from a file.
|
|
10
|
+
* Asset Ids can be used for bulk migrations.
|
|
11
|
+
* CCT Ids can be used for pre-migrations or to skip CCTs in migrations.
|
|
12
|
+
* @param filePath - The path to the file containing the asset IDs.
|
|
13
|
+
* @param assetType - The type of asset to load the IDs for - assets or ccts. Defaults to ccts.
|
|
14
|
+
* @returns The asset/cct IDs.
|
|
15
|
+
*/
|
|
16
|
+
const loadAssetIdsFromFile = (filePath, assetType = 'cct') => {
|
|
17
|
+
if (!filePath) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
22
|
+
throw new Error(`${assetType.toUpperCase()} IDs file not found: ${filePath}`);
|
|
23
|
+
}
|
|
24
|
+
const fileContent = fs_1.default.readFileSync(filePath, 'utf8');
|
|
25
|
+
const assetIds = JSON.parse(fileContent);
|
|
26
|
+
if (!Array.isArray(assetIds) ||
|
|
27
|
+
assetIds.some(id => typeof id !== 'string' || !id.trim()) ||
|
|
28
|
+
assetIds.length === 0) {
|
|
29
|
+
throw new Error(`${assetType.toUpperCase()} IDs file must contain a valid array of strings representing ${assetType.toUpperCase()} IDs with at least one ${assetType.toUpperCase()} ID`);
|
|
30
|
+
}
|
|
31
|
+
return assetIds;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error instanceof Error) {
|
|
35
|
+
throw new Error(`Failed to load ${assetType.toUpperCase()} IDs from file: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`Failed to load ${assetType.toUpperCase()} IDs from file: ${error}`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.loadAssetIdsFromFile = loadAssetIdsFromFile;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -4,15 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const fs_1 = __importDefault(require("fs"));
|
|
7
|
-
const
|
|
7
|
+
const loadAssetIdsFromFile_1 = require("./loadAssetIdsFromFile");
|
|
8
8
|
jest.mock('fs');
|
|
9
9
|
const mockFs = fs_1.default;
|
|
10
|
-
describe('
|
|
10
|
+
describe('loadAssetIdsFromFile', () => {
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
jest.clearAllMocks();
|
|
13
13
|
});
|
|
14
14
|
it('should return undefined when no file path is provided', () => {
|
|
15
|
-
const result = (0,
|
|
15
|
+
const result = (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)();
|
|
16
16
|
expect(result).toBeUndefined();
|
|
17
17
|
});
|
|
18
18
|
it('should load and parse valid JSON file with CCT IDs', () => {
|
|
@@ -21,7 +21,7 @@ describe('loadCctIdsFromFile', () => {
|
|
|
21
21
|
const expectedResult = ['cct-id-1', 'cct-id-2', 'cct-id-3'];
|
|
22
22
|
mockFs.existsSync.mockReturnValue(true);
|
|
23
23
|
mockFs.readFileSync.mockReturnValue(mockJsonContent);
|
|
24
|
-
const result = (0,
|
|
24
|
+
const result = (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)(mockFilePath);
|
|
25
25
|
expect(mockFs.existsSync).toHaveBeenCalledWith(mockFilePath);
|
|
26
26
|
expect(mockFs.readFileSync).toHaveBeenCalledWith(mockFilePath, 'utf8');
|
|
27
27
|
expect(result).toEqual(expectedResult);
|
|
@@ -29,63 +29,63 @@ describe('loadCctIdsFromFile', () => {
|
|
|
29
29
|
it('should throw error when file does not exist', () => {
|
|
30
30
|
const mockFilePath = '/test/path/nonexistent.json';
|
|
31
31
|
mockFs.existsSync.mockReturnValue(false);
|
|
32
|
-
expect(() => (0,
|
|
32
|
+
expect(() => (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)(mockFilePath)).toThrow(`Failed to load CCT IDs from file: CCT IDs file not found: ${mockFilePath}`);
|
|
33
33
|
});
|
|
34
34
|
it('should throw error when JSON is invalid', () => {
|
|
35
35
|
const mockFilePath = '/test/path/invalid.json';
|
|
36
36
|
const invalidJson = '["invalid": json}';
|
|
37
37
|
mockFs.existsSync.mockReturnValue(true);
|
|
38
38
|
mockFs.readFileSync.mockReturnValue(invalidJson);
|
|
39
|
-
expect(() => (0,
|
|
39
|
+
expect(() => (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)(mockFilePath)).toThrow('Failed to load CCT IDs from file:');
|
|
40
40
|
});
|
|
41
41
|
it('should throw error when JSON is not an array', () => {
|
|
42
42
|
const mockFilePath = '/test/path/object.json';
|
|
43
43
|
const objectJson = '{"id": "cct-id-1"}';
|
|
44
44
|
mockFs.existsSync.mockReturnValue(true);
|
|
45
45
|
mockFs.readFileSync.mockReturnValue(objectJson);
|
|
46
|
-
expect(() => (0,
|
|
46
|
+
expect(() => (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
|
|
47
47
|
});
|
|
48
48
|
it('should throw error when array is empty', () => {
|
|
49
49
|
const mockFilePath = '/test/path/empty.json';
|
|
50
50
|
const emptyArrayJson = '[]';
|
|
51
51
|
mockFs.existsSync.mockReturnValue(true);
|
|
52
52
|
mockFs.readFileSync.mockReturnValue(emptyArrayJson);
|
|
53
|
-
expect(() => (0,
|
|
53
|
+
expect(() => (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
|
|
54
54
|
});
|
|
55
55
|
it('should throw error when array contains non-string values', () => {
|
|
56
56
|
const mockFilePath = '/test/path/mixed.json';
|
|
57
57
|
const mixedArrayJson = '["cct-id-1", 123, "cct-id-3"]';
|
|
58
58
|
mockFs.existsSync.mockReturnValue(true);
|
|
59
59
|
mockFs.readFileSync.mockReturnValue(mixedArrayJson);
|
|
60
|
-
expect(() => (0,
|
|
60
|
+
expect(() => (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
|
|
61
61
|
});
|
|
62
62
|
it('should throw error when array contains empty strings', () => {
|
|
63
63
|
const mockFilePath = '/test/path/empty-strings.json';
|
|
64
64
|
const emptyStringsJson = '["cct-id-1", "", "cct-id-3"]';
|
|
65
65
|
mockFs.existsSync.mockReturnValue(true);
|
|
66
66
|
mockFs.readFileSync.mockReturnValue(emptyStringsJson);
|
|
67
|
-
expect(() => (0,
|
|
67
|
+
expect(() => (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
|
|
68
68
|
});
|
|
69
69
|
it('should throw error when array contains whitespace-only strings', () => {
|
|
70
70
|
const mockFilePath = '/test/path/whitespace.json';
|
|
71
71
|
const whitespaceJson = '["cct-id-1", " ", "cct-id-3"]';
|
|
72
72
|
mockFs.existsSync.mockReturnValue(true);
|
|
73
73
|
mockFs.readFileSync.mockReturnValue(whitespaceJson);
|
|
74
|
-
expect(() => (0,
|
|
74
|
+
expect(() => (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
|
|
75
75
|
});
|
|
76
76
|
it('should throw error when JSON is null', () => {
|
|
77
77
|
const mockFilePath = '/test/path/null.json';
|
|
78
78
|
const nullJson = 'null';
|
|
79
79
|
mockFs.existsSync.mockReturnValue(true);
|
|
80
80
|
mockFs.readFileSync.mockReturnValue(nullJson);
|
|
81
|
-
expect(() => (0,
|
|
81
|
+
expect(() => (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
|
|
82
82
|
});
|
|
83
83
|
it('should handle single CCT ID in array', () => {
|
|
84
84
|
const mockFilePath = '/test/path/single.json';
|
|
85
85
|
const singleIdJson = '["cct-id-only"]';
|
|
86
86
|
mockFs.existsSync.mockReturnValue(true);
|
|
87
87
|
mockFs.readFileSync.mockReturnValue(singleIdJson);
|
|
88
|
-
const result = (0,
|
|
88
|
+
const result = (0, loadAssetIdsFromFile_1.loadAssetIdsFromFile)(mockFilePath);
|
|
89
89
|
expect(result).toEqual(['cct-id-only']);
|
|
90
90
|
});
|
|
91
91
|
});
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { StageOptions } from '../types';
|
|
2
|
+
export declare const loadStageOptionsFromFile: (filePath?: string) => StageOptions | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.markComponentsDeployed = void 0;
|
|
13
|
+
const ApiService_1 = require("../../ApiService");
|
|
14
|
+
const _1 = require(".");
|
|
15
|
+
function markComponentsDeployed(options) {
|
|
16
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
17
|
+
const apiService = new ApiService_1.ApiService({
|
|
18
|
+
validateStatus: _1.validateAxiosStatus,
|
|
19
|
+
});
|
|
20
|
+
const migrationUrl = yield (0, _1.buildMigrationUrl)(options.tenant, options.overrideUrl);
|
|
21
|
+
const payload = {
|
|
22
|
+
cctIds: options.cctIds,
|
|
23
|
+
};
|
|
24
|
+
const response = yield apiService.client.post(`${migrationUrl}/components/deployments`, payload, {
|
|
25
|
+
headers: yield (0, _1.getMigrationHeaders)(options.tenant),
|
|
26
|
+
});
|
|
27
|
+
const { success, failed } = response.data || {};
|
|
28
|
+
if (!success && !failed) {
|
|
29
|
+
throw new Error('Invalid response format from mark deployed');
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
success: success || [],
|
|
33
|
+
failed: failed || [],
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
exports.markComponentsDeployed = markComponentsDeployed;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
const ApiService_1 = require("../../ApiService");
|
|
36
|
+
const markComponentsDeployed_1 = require("./markComponentsDeployed");
|
|
37
|
+
const utils = __importStar(require("."));
|
|
38
|
+
jest.mock('../../ApiService');
|
|
39
|
+
jest.mock('.');
|
|
40
|
+
const mockApiService = ApiService_1.ApiService;
|
|
41
|
+
const mockUtils = utils;
|
|
42
|
+
describe('markComponentsDeployed', () => {
|
|
43
|
+
let mockApiServiceInstance;
|
|
44
|
+
let mockOptions;
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
mockApiServiceInstance = {
|
|
47
|
+
client: {
|
|
48
|
+
post: jest.fn(),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
mockApiService.mockImplementation(() => mockApiServiceInstance);
|
|
52
|
+
mockOptions = {
|
|
53
|
+
cctIds: ['component-1', 'component-2', 'component-3'],
|
|
54
|
+
tenant: 'test-tenant',
|
|
55
|
+
};
|
|
56
|
+
mockUtils.buildMigrationUrl.mockResolvedValue('https://example.com/__dxp/service/aiapps/migration/migrations');
|
|
57
|
+
mockUtils.getMigrationHeaders.mockResolvedValue({
|
|
58
|
+
'Content-Type': 'application/json',
|
|
59
|
+
'x-dxp-tenant': 'test-tenant',
|
|
60
|
+
});
|
|
61
|
+
mockUtils.validateAxiosStatus.mockImplementation(status => status >= 200 && status < 300);
|
|
62
|
+
});
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
jest.clearAllMocks();
|
|
65
|
+
});
|
|
66
|
+
it('marks components as deployed successfully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
+
const mockResponse = {
|
|
68
|
+
status: 200,
|
|
69
|
+
data: {
|
|
70
|
+
success: ['component-1', 'component-2', 'component-3'],
|
|
71
|
+
failed: [],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
75
|
+
const result = yield (0, markComponentsDeployed_1.markComponentsDeployed)(mockOptions);
|
|
76
|
+
expect(result).toEqual({
|
|
77
|
+
success: ['component-1', 'component-2', 'component-3'],
|
|
78
|
+
failed: [],
|
|
79
|
+
});
|
|
80
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith('https://example.com/__dxp/service/aiapps/migration/migrations/components/deployments', { cctIds: ['component-1', 'component-2', 'component-3'] }, {
|
|
81
|
+
headers: {
|
|
82
|
+
'Content-Type': 'application/json',
|
|
83
|
+
'x-dxp-tenant': 'test-tenant',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}));
|
|
87
|
+
it('handles mixed success and failure response', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
|
+
const mockResponse = {
|
|
89
|
+
status: 200,
|
|
90
|
+
data: {
|
|
91
|
+
success: ['component-1'],
|
|
92
|
+
failed: [
|
|
93
|
+
{ cctId: 'component-2', error: 'Component not found' },
|
|
94
|
+
{ cctId: 'component-3', error: 'Already deployed' },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
99
|
+
const result = yield (0, markComponentsDeployed_1.markComponentsDeployed)(mockOptions);
|
|
100
|
+
expect(result).toEqual({
|
|
101
|
+
success: ['component-1'],
|
|
102
|
+
failed: [
|
|
103
|
+
{ cctId: 'component-2', error: 'Component not found' },
|
|
104
|
+
{ cctId: 'component-3', error: 'Already deployed' },
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
}));
|
|
108
|
+
it('handles API errors (4xx/5xx status codes)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
109
|
+
const axiosError = new Error('Request failed with status code 400');
|
|
110
|
+
axiosError.isAxiosError = true;
|
|
111
|
+
axiosError.response = {
|
|
112
|
+
status: 400,
|
|
113
|
+
data: { message: 'Bad Request' },
|
|
114
|
+
};
|
|
115
|
+
mockApiServiceInstance.client.post.mockRejectedValue(axiosError);
|
|
116
|
+
yield expect((0, markComponentsDeployed_1.markComponentsDeployed)(mockOptions)).rejects.toThrow('Request failed with status code 400');
|
|
117
|
+
}));
|
|
118
|
+
it('handles missing success and failed in response', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
119
|
+
const mockResponse = {
|
|
120
|
+
status: 200,
|
|
121
|
+
data: {},
|
|
122
|
+
};
|
|
123
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
124
|
+
yield expect((0, markComponentsDeployed_1.markComponentsDeployed)(mockOptions)).rejects.toThrow('Invalid response format from mark deployed');
|
|
125
|
+
}));
|
|
126
|
+
it('handles API service errors', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
127
|
+
const error = new Error('Network error');
|
|
128
|
+
mockApiServiceInstance.client.post.mockRejectedValue(error);
|
|
129
|
+
yield expect((0, markComponentsDeployed_1.markComponentsDeployed)(mockOptions)).rejects.toThrow('Network error');
|
|
130
|
+
}));
|
|
131
|
+
it('propagates rejected values as-is', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
132
|
+
mockApiServiceInstance.client.post.mockRejectedValue('Unknown error');
|
|
133
|
+
yield expect((0, markComponentsDeployed_1.markComponentsDeployed)(mockOptions)).rejects.toBe('Unknown error');
|
|
134
|
+
}));
|
|
135
|
+
it('uses override URL when provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
136
|
+
const optionsWithOverride = Object.assign(Object.assign({}, mockOptions), { overrideUrl: 'https://custom.migration.url' });
|
|
137
|
+
mockUtils.buildMigrationUrl.mockResolvedValue('https://custom.migration.url/migrations');
|
|
138
|
+
const mockResponse = {
|
|
139
|
+
status: 200,
|
|
140
|
+
data: {
|
|
141
|
+
success: ['component-1'],
|
|
142
|
+
failed: [],
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
146
|
+
yield (0, markComponentsDeployed_1.markComponentsDeployed)(optionsWithOverride);
|
|
147
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith('https://custom.migration.url/migrations/components/deployments', expect.any(Object), expect.any(Object));
|
|
148
|
+
}));
|
|
149
|
+
it('constructs correct URL path', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
150
|
+
const mockResponse = {
|
|
151
|
+
status: 200,
|
|
152
|
+
data: {
|
|
153
|
+
success: [],
|
|
154
|
+
failed: [],
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
158
|
+
yield (0, markComponentsDeployed_1.markComponentsDeployed)(mockOptions);
|
|
159
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith('https://example.com/__dxp/service/aiapps/migration/migrations/components/deployments', expect.any(Object), expect.any(Object));
|
|
160
|
+
}));
|
|
161
|
+
it('uses correct headers', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
162
|
+
const mockResponse = {
|
|
163
|
+
status: 200,
|
|
164
|
+
data: {
|
|
165
|
+
success: [],
|
|
166
|
+
failed: [],
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
170
|
+
yield (0, markComponentsDeployed_1.markComponentsDeployed)(mockOptions);
|
|
171
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith(expect.any(String), expect.any(Object), {
|
|
172
|
+
headers: {
|
|
173
|
+
'Content-Type': 'application/json',
|
|
174
|
+
'x-dxp-tenant': 'test-tenant',
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}));
|
|
178
|
+
it('sends cctIds in payload', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
179
|
+
const mockResponse = {
|
|
180
|
+
status: 200,
|
|
181
|
+
data: {
|
|
182
|
+
success: [],
|
|
183
|
+
failed: [],
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
187
|
+
yield (0, markComponentsDeployed_1.markComponentsDeployed)(mockOptions);
|
|
188
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith(expect.any(String), { cctIds: ['component-1', 'component-2', 'component-3'] }, expect.any(Object));
|
|
189
|
+
}));
|
|
190
|
+
it('works with a single cctId', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
191
|
+
const singleComponentOptions = Object.assign(Object.assign({}, mockOptions), { cctIds: ['single-component'] });
|
|
192
|
+
const mockResponse = {
|
|
193
|
+
status: 200,
|
|
194
|
+
data: {
|
|
195
|
+
success: ['single-component'],
|
|
196
|
+
failed: [],
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
200
|
+
const result = yield (0, markComponentsDeployed_1.markComponentsDeployed)(singleComponentOptions);
|
|
201
|
+
expect(result).toEqual({
|
|
202
|
+
success: ['single-component'],
|
|
203
|
+
failed: [],
|
|
204
|
+
});
|
|
205
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith(expect.any(String), { cctIds: ['single-component'] }, expect.any(Object));
|
|
206
|
+
}));
|
|
207
|
+
it('returns empty arrays when response has empty success and failed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
208
|
+
const mockResponse = {
|
|
209
|
+
status: 200,
|
|
210
|
+
data: {
|
|
211
|
+
success: [],
|
|
212
|
+
failed: [],
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
216
|
+
const result = yield (0, markComponentsDeployed_1.markComponentsDeployed)(mockOptions);
|
|
217
|
+
expect(result).toEqual({
|
|
218
|
+
success: [],
|
|
219
|
+
failed: [],
|
|
220
|
+
});
|
|
221
|
+
}));
|
|
222
|
+
});
|