@forge/migrations 0.0.0-experimental-416047f

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 (45) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +22 -0
  3. package/out/__test__/errors.test.d.ts +2 -0
  4. package/out/__test__/errors.test.d.ts.map +1 -0
  5. package/out/__test__/errors.test.js +16 -0
  6. package/out/__test__/migration.test.d.ts +2 -0
  7. package/out/__test__/migration.test.d.ts.map +1 -0
  8. package/out/__test__/migration.test.js +221 -0
  9. package/out/__test__/query-api.test.d.ts +2 -0
  10. package/out/__test__/query-api.test.d.ts.map +1 -0
  11. package/out/__test__/query-api.test.js +144 -0
  12. package/out/index.d.ts +3 -0
  13. package/out/index.d.ts.map +1 -0
  14. package/out/index.js +5 -0
  15. package/out/migration/errors.d.ts +6 -0
  16. package/out/migration/errors.d.ts.map +1 -0
  17. package/out/migration/errors.js +15 -0
  18. package/out/migration/index.d.ts +22 -0
  19. package/out/migration/index.d.ts.map +1 -0
  20. package/out/migration/index.js +25 -0
  21. package/out/migration/migration-adaptor.d.ts +60 -0
  22. package/out/migration/migration-adaptor.d.ts.map +1 -0
  23. package/out/migration/migration-adaptor.js +2 -0
  24. package/out/migration/migration.d.ts +14 -0
  25. package/out/migration/migration.d.ts.map +1 -0
  26. package/out/migration/migration.js +81 -0
  27. package/out/migration/query-api.d.ts +11 -0
  28. package/out/migration/query-api.d.ts.map +1 -0
  29. package/out/migration/query-api.js +22 -0
  30. package/out/migration/utils.d.ts +5 -0
  31. package/out/migration/utils.d.ts.map +1 -0
  32. package/out/migration/utils.js +33 -0
  33. package/package.json +21 -0
  34. package/src/__test__/errors.test.ts +15 -0
  35. package/src/__test__/migration.test.ts +243 -0
  36. package/src/__test__/query-api.test.ts +177 -0
  37. package/src/index.ts +3 -0
  38. package/src/migration/errors.ts +13 -0
  39. package/src/migration/index.ts +33 -0
  40. package/src/migration/migration-adaptor.ts +71 -0
  41. package/src/migration/migration.ts +111 -0
  42. package/src/migration/query-api.ts +24 -0
  43. package/src/migration/utils.ts +31 -0
  44. package/tsconfig.json +13 -0
  45. package/tsconfig.tsbuildinfo +1504 -0
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Migration = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
6
+ const utils_1 = require("./utils");
7
+ class Migration {
8
+ constructor() {
9
+ this.basePath = '/app/migration/forge/v1';
10
+ this.sendProgress = async (transferId, progress) => {
11
+ const result = await utils_1.invokePOSTApi(`${this.basePath}/progress/${transferId}`, progress);
12
+ return utils_1.getResponseBody(result);
13
+ };
14
+ this.getMappingById = async (transferId, namespace, keys) => {
15
+ const result = await utils_1.invokePOSTApi(`${this.basePath}/mapping/${transferId}/find?namespace=${namespace}`, keys);
16
+ return utils_1.getResponseBody(result);
17
+ };
18
+ this.getAppDataList = async (transferId) => {
19
+ const result = await utils_1.invokeGETApi(`${this.basePath}/data/${transferId}/all`);
20
+ return utils_1.getResponseBody(result);
21
+ };
22
+ this.getAppDataPayload = async (s3Key) => {
23
+ const result = await utils_1.invokeGETApi(`${this.basePath}/data/${s3Key}`);
24
+ const responseBody = await utils_1.getResponseBody(result);
25
+ return node_fetch_1.default(responseBody.url);
26
+ };
27
+ this.updateFeedback = async (transferId, feedback) => {
28
+ const result = await utils_1.invokePOSTApi(`${this.basePath}/feedback/${transferId}`, feedback);
29
+ return utils_1.getResponseBody(result);
30
+ };
31
+ this.getMappings = async (transferId, namespace, lastEntity, pageSize) => {
32
+ let queryParams = `namespace=${namespace}`;
33
+ if (pageSize != undefined) {
34
+ queryParams = queryParams + `&pageSize=${pageSize}`;
35
+ }
36
+ if (lastEntity != undefined) {
37
+ queryParams = queryParams + `&lastEntity=${lastEntity}`;
38
+ }
39
+ const result = await utils_1.invokeGETApi(`${this.basePath}/mapping/${transferId}/page?${queryParams}`);
40
+ const mappingResponse = await utils_1.getResponseBody(result);
41
+ let results = [];
42
+ if (mappingResponse.items != null) {
43
+ results = Object.entries(mappingResponse.items).map((item) => {
44
+ return { key: item[0], value: item[1] };
45
+ });
46
+ }
47
+ return {
48
+ results: results,
49
+ nextCursor: mappingResponse.meta.lastEntity != null ? mappingResponse.meta.lastEntity : undefined
50
+ };
51
+ };
52
+ this.getContainers = async (transferId, containerType, lastEntity, pageSize) => {
53
+ let queryParams = `containerType=${containerType}`;
54
+ if (pageSize != undefined) {
55
+ queryParams = queryParams + `&pageSize=${pageSize}`;
56
+ }
57
+ if (lastEntity != undefined) {
58
+ queryParams = queryParams + `&lastEntity=${lastEntity}`;
59
+ }
60
+ const result = await utils_1.invokeGETApi(`${this.basePath}/container/${transferId}/page?${queryParams}`);
61
+ const containerResponse = await utils_1.getResponseBody(result);
62
+ let results = [];
63
+ if (containerResponse.containers != null) {
64
+ results = Object.entries(containerResponse.containers).map((item) => {
65
+ return { key: item[0], value: item[1] };
66
+ });
67
+ }
68
+ return {
69
+ results: results,
70
+ nextCursor: containerResponse.meta.lastEntity != null ? containerResponse.meta.lastEntity : undefined
71
+ };
72
+ };
73
+ this.list = async (options, pageSize) => {
74
+ if (options.namespace != undefined)
75
+ return this.getMappings(options.transferId, options.namespace, options.cursor, pageSize);
76
+ else
77
+ return this.getContainers(options.transferId, options.containerType, options.cursor, pageSize);
78
+ };
79
+ }
80
+ }
81
+ exports.Migration = Migration;
@@ -0,0 +1,11 @@
1
+ import { QueryBuilder, Result, ListOptions, ListResults } from './migration-adaptor';
2
+ import { Migration } from './migration';
3
+ export declare class DefaultQueryBuilder implements QueryBuilder {
4
+ private migration;
5
+ private queryOptions;
6
+ constructor(migration: Pick<Migration, 'list'>, queryOptions: ListOptions);
7
+ cursor(cursor: string): QueryBuilder;
8
+ getOne(): Promise<Result | undefined>;
9
+ getMany(): Promise<ListResults>;
10
+ }
11
+ //# sourceMappingURL=query-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-api.d.ts","sourceRoot":"","sources":["../../src/migration/query-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,qBAAa,mBAAoB,YAAW,YAAY;IAC1C,OAAO,CAAC,SAAS;IAA2B,OAAO,CAAC,YAAY;gBAAxD,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,EAAU,YAAY,EAAE,WAAW;IAEzF,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY;IAO9B,MAAM,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAOrC,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC;CAGtC"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultQueryBuilder = void 0;
4
+ class DefaultQueryBuilder {
5
+ constructor(migration, queryOptions) {
6
+ this.migration = migration;
7
+ this.queryOptions = queryOptions;
8
+ }
9
+ cursor(cursor) {
10
+ return new DefaultQueryBuilder(this.migration, Object.assign(Object.assign({}, this.queryOptions), { cursor }));
11
+ }
12
+ async getOne() {
13
+ const listResults = await this.migration.list(this.queryOptions, 1);
14
+ if (listResults.results && listResults.results.length > 0) {
15
+ return listResults.results[0];
16
+ }
17
+ }
18
+ async getMany() {
19
+ return this.migration.list(this.queryOptions);
20
+ }
21
+ }
22
+ exports.DefaultQueryBuilder = DefaultQueryBuilder;
@@ -0,0 +1,5 @@
1
+ import { APIResponse } from '@forge/api';
2
+ export declare function getResponseBody(response: APIResponse): Promise<any>;
3
+ export declare function invokePOSTApi(url: string, payload: any): Promise<APIResponse>;
4
+ export declare function invokeGETApi(url: string): Promise<APIResponse>;
5
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/migration/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,wBAAsB,eAAe,CAAC,QAAQ,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,CAazE;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAQnF;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAEpE"}
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.invokeGETApi = exports.invokePOSTApi = exports.getResponseBody = void 0;
4
+ const errors_1 = require("./errors");
5
+ async function getResponseBody(response) {
6
+ if (response.status !== 200) {
7
+ throw errors_1.APIError.forStatus(response.status, response.statusText);
8
+ }
9
+ const responseText = await response.text();
10
+ try {
11
+ if (responseText !== '' && responseText !== undefined) {
12
+ return JSON.parse(responseText);
13
+ }
14
+ }
15
+ catch (error) {
16
+ throw errors_1.APIError.forUnexpected(`Response text was not a valid JSON: ${responseText}`);
17
+ }
18
+ }
19
+ exports.getResponseBody = getResponseBody;
20
+ async function invokePOSTApi(url, payload) {
21
+ return global.api.asApp().__requestAtlassian(url, {
22
+ method: 'POST',
23
+ headers: {
24
+ 'content-type': 'application/json'
25
+ },
26
+ body: JSON.stringify(payload)
27
+ });
28
+ }
29
+ exports.invokePOSTApi = invokePOSTApi;
30
+ async function invokeGETApi(url) {
31
+ return global.api.asApp().__requestAtlassian(url);
32
+ }
33
+ exports.invokeGETApi = invokeGETApi;
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@forge/migrations",
3
+ "version": "0.0.0-experimental-416047f",
4
+ "description": "App migration methods for Forge app",
5
+ "main": "out/index.js",
6
+ "scripts": {
7
+ "build": "yarn run clean && yarn run compile",
8
+ "clean": "rm -rf ./out && rm -f tsconfig.tsbuildinfo",
9
+ "compile": "tsc -b -v"
10
+ },
11
+ "author": "Atlassian",
12
+ "license": "UNLICENSED",
13
+ "devDependencies": {
14
+ "@types/node": "^12.12.63",
15
+ "@types/node-fetch": "^2.5.7",
16
+ "node-fetch": "2.6.1"
17
+ },
18
+ "dependencies": {
19
+ "@forge/api": "^2.6.1"
20
+ }
21
+ }
@@ -0,0 +1,15 @@
1
+ import { APIError } from '../migration/errors';
2
+
3
+ describe('APIError', () => {
4
+ describe('forStatus', () => {
5
+ it.each([
6
+ [400, 'Bad request'],
7
+ [401, 'Unauthorized'],
8
+ [403, 'Forbidden'],
9
+ [404, 'Not Found'],
10
+ [500, 'Internal server error']
11
+ ] as const)('should contain the correct error message for a code of %s', (status, message) => {
12
+ expect(APIError.forStatus(status, message).message).toEqual(message);
13
+ });
14
+ });
15
+ });
@@ -0,0 +1,243 @@
1
+ import { APIError } from '../migration/errors';
2
+ import { migration } from '../migration';
3
+ import { Progress } from '../migration/migration-adaptor';
4
+ import { when } from 'jest-when';
5
+ import fetch, { Response } from 'node-fetch';
6
+ import { mocked } from 'ts-jest/utils';
7
+
8
+ let transferId: string;
9
+ let invalidTransferId: string;
10
+ let progress: Progress;
11
+ let keys: string[];
12
+ let invalidKeys: string[];
13
+ let namespace: string;
14
+ let mappingByKeyResponseWithKeys: string;
15
+ let mappingByKeyResponseWithInvalidKeys: string;
16
+ let s3Key: string;
17
+ let invalidS3Key: string;
18
+ let feedbackPayload: object;
19
+ let appDataListResponse: string;
20
+ let appDataPayloadResponse: string;
21
+ jest.mock('node-fetch');
22
+
23
+ describe('migration', () => {
24
+ beforeAll(() => {
25
+ (global as any).api = {
26
+ asApp: jest.fn().mockReturnValue({
27
+ __requestAtlassian: jest.fn()
28
+ })
29
+ };
30
+ transferId = '521ee36a-b673-3dc6-b60b-12460332c200';
31
+ invalidTransferId = '521ee36a-b673-3dc6-b60b-12460332c001';
32
+ });
33
+
34
+ describe('sendMigrationProgress', () => {
35
+ beforeAll(() => {
36
+ progress = { status: 'IN_PROGRESS', percent: 10, message: 'reporting' };
37
+ when((global as any).api.asApp().__requestAtlassian)
38
+ .calledWith(`/app/migration/forge/v1/progress/${transferId}`, {
39
+ method: 'POST',
40
+ headers: {
41
+ 'content-type': 'application/json'
42
+ },
43
+ body: JSON.stringify(progress)
44
+ })
45
+ .mockReturnValue({
46
+ status: 200,
47
+ text: jest.fn().mockImplementationOnce(() => Promise.resolve()),
48
+ json: jest.fn().mockImplementationOnce(() => Promise.resolve())
49
+ })
50
+ .calledWith(`/app/migration/forge/v1/progress/${invalidTransferId}`, {
51
+ method: 'POST',
52
+ headers: {
53
+ 'content-type': 'application/json'
54
+ },
55
+ body: JSON.stringify(progress)
56
+ })
57
+ .mockReturnValue({
58
+ status: 403,
59
+ statusText: 'Forbidden'
60
+ });
61
+ });
62
+
63
+ it('should update progress when sendProgress method called with valid transferId', async () => {
64
+ const returnedValue = migration.sendProgress(transferId, progress);
65
+ await expect(returnedValue).resolves.toBeUndefined();
66
+ });
67
+
68
+ it('should return 403 when sendProgress method called with invalid transferId', async () => {
69
+ const returnedValue = migration.sendProgress(invalidTransferId, progress);
70
+ await expect(returnedValue).rejects.toThrow(APIError.forStatus(403, 'Forbidden'));
71
+ });
72
+ });
73
+
74
+ describe('getMappingById', () => {
75
+ beforeAll(() => {
76
+ keys = ['email/abc@example.com', 'email/admin@example.com'];
77
+ invalidKeys = ['email/abc@example.com', 'email/admin@example'];
78
+ namespace = 'identity:user';
79
+ mappingByKeyResponseWithKeys =
80
+ '{"email/abc@example.com":"5a20a4d7c6bd4a32df3a711","email/admin@example.com":"5a29c4ae20cfc31b0dc0e112"}';
81
+ mappingByKeyResponseWithInvalidKeys = '{"email/abc@example.com":"5a20a4d7c6bd4a32df3a711"}';
82
+
83
+ when((global as any).api.asApp().__requestAtlassian)
84
+ .calledWith(`/app/migration/forge/v1/mapping/${transferId}/find?namespace=${namespace}`, {
85
+ method: 'POST',
86
+ headers: {
87
+ 'content-type': 'application/json'
88
+ },
89
+ body: JSON.stringify(keys)
90
+ })
91
+ .mockReturnValue({
92
+ status: 200,
93
+ text: jest.fn().mockResolvedValue(mappingByKeyResponseWithKeys),
94
+ json: jest.fn().mockResolvedValue(JSON.parse(mappingByKeyResponseWithKeys))
95
+ })
96
+ .calledWith(`/app/migration/forge/v1/mapping/${transferId}/find?namespace=${namespace}`, {
97
+ method: 'POST',
98
+ headers: {
99
+ 'content-type': 'application/json'
100
+ },
101
+ body: JSON.stringify(invalidKeys)
102
+ })
103
+ .mockReturnValue({
104
+ status: 200,
105
+ text: jest.fn().mockResolvedValue(mappingByKeyResponseWithInvalidKeys),
106
+ json: jest.fn().mockResolvedValue(JSON.parse(mappingByKeyResponseWithInvalidKeys))
107
+ })
108
+ .calledWith(`/app/migration/forge/v1/mapping/${invalidTransferId}/find?namespace=${namespace}`, {
109
+ method: 'POST',
110
+ headers: {
111
+ 'content-type': 'application/json'
112
+ },
113
+ body: JSON.stringify(keys)
114
+ })
115
+ .mockReturnValue({
116
+ status: 403,
117
+ statusText: 'Forbidden'
118
+ });
119
+ });
120
+
121
+ it('should return MappingResponse when getMappingById method called with valid transferId', async () => {
122
+ const returnedValue = await migration.getMappingById(transferId, namespace, keys);
123
+ expect(JSON.stringify(returnedValue)).toEqual(mappingByKeyResponseWithKeys);
124
+ });
125
+
126
+ it('should return 403 when getMappingById method called with invalid transferId', async () => {
127
+ const returnedValue = migration.getMappingById(invalidTransferId, namespace, keys);
128
+ await expect(returnedValue).rejects.toThrow(APIError.forStatus(403, 'Forbidden'));
129
+ });
130
+
131
+ it('should return MappingResponse except for invalid keys when getMappingById method called with valid transferId', async () => {
132
+ const returnedValue = await migration.getMappingById(transferId, namespace, invalidKeys);
133
+ expect(JSON.stringify(returnedValue)).toEqual(mappingByKeyResponseWithInvalidKeys);
134
+ });
135
+ });
136
+
137
+ describe('getAppDataList', () => {
138
+ beforeAll(() => {
139
+ appDataListResponse =
140
+ '[{"s3Key":"2ea231e3-ab0d-4236-97f7-26f951df1c11","label":null},{"s3Key":"317a3631-2fef-4940-a418-7096e971eb11","label":null}]';
141
+
142
+ when((global as any).api.asApp().__requestAtlassian)
143
+ .calledWith(`/app/migration/forge/v1/data/${transferId}/all`)
144
+ .mockReturnValue({
145
+ status: 200,
146
+ text: jest.fn().mockResolvedValue(appDataListResponse),
147
+ json: jest.fn().mockResolvedValue(JSON.parse(appDataListResponse))
148
+ })
149
+ .calledWith(`/app/migration/forge/v1/data/${invalidTransferId}/all`)
150
+ .mockReturnValue({
151
+ status: 403,
152
+ statusText: 'Forbidden'
153
+ });
154
+ });
155
+
156
+ it('should return AppDataListResponse when getAppDataList method called with valid transferId', async () => {
157
+ const returnedValue = await migration.getAppDataList(transferId);
158
+ expect(returnedValue).toEqual(JSON.parse(appDataListResponse));
159
+ });
160
+
161
+ it('should return 403 when getAppDataList method called with invalid transferId', async () => {
162
+ const returnedValue = migration.getAppDataList(invalidTransferId);
163
+ await expect(returnedValue).rejects.toThrow(APIError.forStatus(403, 'Forbidden'));
164
+ });
165
+ });
166
+
167
+ describe('getAppDataPayload', () => {
168
+ beforeAll(() => {
169
+ s3Key = '7941b65d-094b-4305-97f2-e2b412c6db0a';
170
+ invalidS3Key = '7941b65d-094b-4305-97f2-e2b412c000';
171
+ appDataPayloadResponse = '{ "url": "https://rps--stg-east--app-migration-service--ams.s3.amazonaws.com/test"}';
172
+
173
+ mocked(fetch).mockImplementation(() =>
174
+ Promise.resolve({
175
+ text: () => Promise.resolve('Sample app data')
176
+ } as Response)
177
+ );
178
+
179
+ when((global as any).api.asApp().__requestAtlassian)
180
+ .calledWith(`/app/migration/forge/v1/data/${s3Key}`)
181
+ .mockReturnValue({
182
+ status: 200,
183
+ text: jest.fn().mockResolvedValue(JSON.stringify(appDataPayloadResponse)),
184
+ json: jest.fn().mockResolvedValue(JSON.parse(appDataPayloadResponse))
185
+ })
186
+ .calledWith(`/app/migration/forge/v1/data/${invalidS3Key}`)
187
+ .mockReturnValue({
188
+ status: 403,
189
+ statusText: 'Forbidden'
190
+ });
191
+ });
192
+
193
+ it('should return Response when getAppDataPayload method called with valid S3Key', async () => {
194
+ const returnedValue = await migration.getAppDataPayload(s3Key);
195
+ await expect(returnedValue.text()).resolves.toBe('Sample app data');
196
+ });
197
+
198
+ it('should return 403 when getAppDataPayload method called with invalid S3Key', async () => {
199
+ const returnedValue = migration.getAppDataPayload(invalidS3Key);
200
+ await expect(returnedValue).rejects.toThrow(APIError.forStatus(403, 'Forbidden'));
201
+ });
202
+ });
203
+
204
+ describe('updateFeedback', () => {
205
+ beforeAll(() => {
206
+ feedbackPayload = { transferId: transferId, msg: 'Current Data processed, send next' };
207
+ when((global as any).api.asApp().__requestAtlassian)
208
+ .calledWith(`/app/migration/forge/v1/feedback/${transferId}`, {
209
+ method: 'POST',
210
+ headers: {
211
+ 'content-type': 'application/json'
212
+ },
213
+ body: JSON.stringify(feedbackPayload)
214
+ })
215
+ .mockReturnValue({
216
+ status: 200,
217
+ text: jest.fn().mockImplementationOnce(() => Promise.resolve()),
218
+ json: jest.fn().mockImplementationOnce(() => Promise.resolve())
219
+ })
220
+ .calledWith(`/app/migration/forge/v1/feedback/${invalidTransferId}`, {
221
+ method: 'POST',
222
+ headers: {
223
+ 'content-type': 'application/json'
224
+ },
225
+ body: JSON.stringify(feedbackPayload)
226
+ })
227
+ .mockReturnValue({
228
+ status: 403,
229
+ statusText: 'Forbidden'
230
+ });
231
+ });
232
+
233
+ it('should send feedback when updateFeedback method called with valid transferId', async () => {
234
+ const returnedValue = migration.updateFeedback(transferId, feedbackPayload);
235
+ await expect(returnedValue).resolves.toBeUndefined();
236
+ });
237
+
238
+ it('should return 403 when updateFeedback method called with invalid transferId', async () => {
239
+ const returnedValue = migration.updateFeedback(invalidTransferId, feedbackPayload);
240
+ await expect(returnedValue).rejects.toThrow(APIError.forStatus(403, 'Forbidden'));
241
+ });
242
+ });
243
+ });
@@ -0,0 +1,177 @@
1
+ import { ListResults, Result } from '../migration/migration-adaptor';
2
+ import { when } from 'jest-when';
3
+ import { migration } from '../migration';
4
+
5
+ describe('QueryApi', () => {
6
+ const transferId = '521ee36a-b673-3dc6-b60b-12460332c200';
7
+
8
+ (global as any).api = {
9
+ asApp: jest.fn().mockReturnValue({
10
+ __requestAtlassian: jest.fn()
11
+ })
12
+ };
13
+
14
+ describe('getMappings', () => {
15
+ let expectedMappingsResultMany: ListResults;
16
+ let expectedMappingsResultManyWithCursor: ListResults;
17
+ let expectedMappingsResultSingle: Result;
18
+ let lastEntityId: string;
19
+
20
+ beforeAll(() => {
21
+ lastEntityId = 'base64:ZW1haWwvYWRtaW5AZXhhbXBsZS5jb20vNg';
22
+ const mappingsResponse = `{"meta":{"pageSize":5000,"hasNext":false,"lastEntity":"${lastEntityId}"},"items":{"email/admin@example.com":"5a29c4ae20cfc31b0dc0eafe","confluence.userkey/2c9682714db22c7c014db22f51970002":"5a29c4ae20cfc31b0dc0eafe"}}`;
23
+
24
+ const mappingsResponseWithoutLastEntity =
25
+ '{"meta":{"pageSize":5000,"hasNext":false,"lastEntity":null},"items":{"email/admin@example.com":"5a29c4ae20cfc31b0dc0eafe","confluence.userkey/2c9682714db22c7c014db22f51970002":"5a29c4ae20cfc31b0dc0eafe"}}';
26
+
27
+ const valueObject = '5a29c4ae20cfc31b0dc0eafe' as any;
28
+ when((global as any).api.asApp().__requestAtlassian)
29
+ .calledWith(
30
+ `/app/migration/forge/v1/mapping/${transferId}/page?namespace=identity:user&lastEntity=${lastEntityId}`
31
+ )
32
+ .mockReturnValue({
33
+ status: 200,
34
+ text: jest.fn().mockResolvedValue(mappingsResponseWithoutLastEntity),
35
+ json: jest.fn().mockResolvedValue(JSON.parse(mappingsResponseWithoutLastEntity))
36
+ })
37
+ .calledWith(`/app/migration/forge/v1/mapping/${transferId}/page?namespace=identity:user`)
38
+ .mockReturnValue({
39
+ status: 200,
40
+ text: jest.fn().mockResolvedValue(mappingsResponse),
41
+ json: jest.fn().mockResolvedValue(JSON.parse(mappingsResponse))
42
+ })
43
+ .calledWith(`/app/migration/forge/v1/mapping/${transferId}/page?namespace=identity:user&pageSize=1`)
44
+ .mockReturnValue({
45
+ status: 200,
46
+ text: jest.fn().mockResolvedValue(mappingsResponse),
47
+ json: jest.fn().mockResolvedValue(JSON.parse(mappingsResponse))
48
+ })
49
+ .calledWith(
50
+ `/app/migration/forge/v1/mapping/${transferId}/page?namespace=identity:user&pageSize=1&lastEntity=${lastEntityId}`
51
+ )
52
+ .mockReturnValue({
53
+ status: 200,
54
+ text: jest.fn().mockResolvedValue(mappingsResponse),
55
+ json: jest.fn().mockResolvedValue(JSON.parse(mappingsResponse))
56
+ });
57
+
58
+ expectedMappingsResultMany = {
59
+ results: [
60
+ {
61
+ key: 'email/admin@example.com',
62
+ value: valueObject
63
+ },
64
+ { key: 'confluence.userkey/2c9682714db22c7c014db22f51970002', value: valueObject }
65
+ ],
66
+ nextCursor: undefined
67
+ };
68
+
69
+ expectedMappingsResultManyWithCursor = {
70
+ results: [
71
+ { key: 'email/admin@example.com', value: valueObject },
72
+ { key: 'confluence.userkey/2c9682714db22c7c014db22f51970002', value: valueObject }
73
+ ],
74
+ nextCursor: lastEntityId
75
+ };
76
+ expectedMappingsResultSingle = { key: 'email/admin@example.com', value: valueObject };
77
+ });
78
+
79
+ it('should fetch multiple values for getMappings', async () => {
80
+ const results = await migration.getMappings(transferId, 'identity:user').getMany();
81
+
82
+ expect(results).toEqual(expectedMappingsResultManyWithCursor);
83
+ });
84
+
85
+ it('should allow specifying a cursor for getMappings with getMany', async () => {
86
+ const result = await migration.getMappings(transferId, 'identity:user').cursor(lastEntityId).getMany();
87
+ expect(result).toEqual(expectedMappingsResultMany);
88
+ });
89
+
90
+ it('get single mapping when getMappings called with getOne', async () => {
91
+ const result = await migration.getMappings(transferId, 'identity:user').getOne();
92
+
93
+ expect(result).toEqual(expectedMappingsResultSingle);
94
+ });
95
+
96
+ it('should allow specifying a cursor for getMappings with getOne', async () => {
97
+ const result = await migration.getMappings(transferId, 'identity:user').cursor(lastEntityId).getOne();
98
+
99
+ expect(result).toEqual(expectedMappingsResultSingle);
100
+ });
101
+ });
102
+
103
+ describe('getContainers', () => {
104
+ let expectedResultContainersManyWithCursor: ListResults;
105
+ let expectedResultContainersManyWithoutCursor: ListResults;
106
+ let expectedResultContainersSingle: Result;
107
+ let lastEntityId: string;
108
+
109
+ beforeAll(() => {
110
+ lastEntityId = 'db11c8af-c82b-3569-9a11-f288c9cc9535';
111
+ const containersResponse = `{"meta":{"pageSize":0,"hasNext":false,"lastEntity":"${lastEntityId}"},"containers":[{ "key": "TES", "sourceId": "884737", "type": "ConfluenceSpace" }]}`;
112
+
113
+ const containersResponseWithoutLastEntity =
114
+ '{"meta":{"pageSize":0,"hasNext":false,"lastEntity":null},"containers":[{ "key": "TES", "sourceId": "884737", "type": "ConfluenceSpace" }]}';
115
+
116
+ expectedResultContainersManyWithCursor = {
117
+ results: [{ key: '0', value: { key: 'TES', sourceId: '884737', type: 'ConfluenceSpace' } }],
118
+ nextCursor: lastEntityId
119
+ };
120
+ expectedResultContainersManyWithoutCursor = {
121
+ results: [{ key: '0', value: { key: 'TES', sourceId: '884737', type: 'ConfluenceSpace' } }],
122
+ nextCursor: undefined
123
+ };
124
+ expectedResultContainersSingle = { key: '0', value: { key: 'TES', sourceId: '884737', type: 'ConfluenceSpace' } };
125
+
126
+ when((global as any).api.asApp().__requestAtlassian)
127
+ .calledWith(
128
+ `/app/migration/forge/v1/container/${transferId}/page?containerType=ConfluenceSpace&lastEntity=${lastEntityId}`
129
+ )
130
+ .mockReturnValue({
131
+ status: 200,
132
+ text: jest.fn().mockResolvedValue(containersResponseWithoutLastEntity),
133
+ json: jest.fn().mockResolvedValue(JSON.parse(containersResponseWithoutLastEntity))
134
+ })
135
+ .calledWith(`/app/migration/forge/v1/container/${transferId}/page?containerType=ConfluenceSpace`)
136
+ .mockReturnValue({
137
+ status: 200,
138
+ text: jest.fn().mockResolvedValue(containersResponse),
139
+ json: jest.fn().mockResolvedValue(JSON.parse(containersResponse))
140
+ })
141
+ .calledWith(`/app/migration/forge/v1/container/${transferId}/page?containerType=ConfluenceSpace&pageSize=1`)
142
+ .mockReturnValue({
143
+ status: 200,
144
+ text: jest.fn().mockResolvedValue(containersResponse),
145
+ json: jest.fn().mockResolvedValue(JSON.parse(containersResponse))
146
+ })
147
+ .calledWith(
148
+ `/app/migration/forge/v1/container/${transferId}/page?containerType=ConfluenceSpace&pageSize=1&lastEntity=${lastEntityId}`
149
+ )
150
+ .mockReturnValue({
151
+ status: 200,
152
+ text: jest.fn().mockResolvedValue(containersResponse),
153
+ json: jest.fn().mockResolvedValue(JSON.parse(containersResponse))
154
+ });
155
+ });
156
+
157
+ it('should allow specifying a cursor for getContainers with getMany', async () => {
158
+ const result = await migration.getContainers(transferId, 'ConfluenceSpace').cursor(lastEntityId).getMany();
159
+ expect(result).toEqual(expectedResultContainersManyWithoutCursor);
160
+ });
161
+
162
+ it('should fetch multiple values when getContainers is called with getMany', async () => {
163
+ const result = await migration.getContainers(transferId, 'ConfluenceSpace').getMany();
164
+ expect(result).toEqual(expectedResultContainersManyWithCursor);
165
+ });
166
+
167
+ it('should allow specifying a cursor for getContainers with getOne', async () => {
168
+ const result = await migration.getContainers(transferId, 'ConfluenceSpace').cursor(lastEntityId).getOne();
169
+ expect(result).toEqual(expectedResultContainersSingle);
170
+ });
171
+
172
+ it('get single mapping when getContainers called with getOne', async () => {
173
+ const result = await migration.getContainers(transferId, 'ConfluenceSpace').getOne();
174
+ expect(result).toEqual(expectedResultContainersSingle);
175
+ });
176
+ });
177
+ });
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { migration } from './migration';
2
+
3
+ export { migration };
@@ -0,0 +1,13 @@
1
+ export class APIError extends Error {
2
+ private constructor(message: string) {
3
+ super(message);
4
+ }
5
+
6
+ public static forStatus(status: number, message: string): APIError {
7
+ return new APIError(message);
8
+ }
9
+
10
+ public static forUnexpected(message: string): APIError {
11
+ return new APIError(message);
12
+ }
13
+ }