@contentstack/cli-variants 0.0.1-alpha
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/lib/export/attributes.d.ts +15 -0
- package/lib/export/attributes.js +62 -0
- package/lib/export/audiences.d.ts +15 -0
- package/lib/export/audiences.js +63 -0
- package/lib/export/events.d.ts +15 -0
- package/lib/export/events.js +63 -0
- package/lib/export/experiences.d.ts +9 -0
- package/lib/export/experiences.js +99 -0
- package/lib/export/index.d.ts +9 -0
- package/lib/export/index.js +21 -0
- package/lib/export/projects.d.ts +9 -0
- package/lib/export/projects.js +73 -0
- package/lib/export/variant-entries.d.ts +18 -0
- package/lib/export/variant-entries.js +109 -0
- package/lib/import/attribute.d.ts +17 -0
- package/lib/import/attribute.js +64 -0
- package/lib/import/audiences.d.ts +19 -0
- package/lib/import/audiences.js +71 -0
- package/lib/import/events.d.ts +17 -0
- package/lib/import/events.js +62 -0
- package/lib/import/experiences.d.ts +46 -0
- package/lib/import/experiences.js +214 -0
- package/lib/import/index.d.ts +14 -0
- package/lib/import/index.js +21 -0
- package/lib/import/project.d.ts +13 -0
- package/lib/import/project.js +74 -0
- package/lib/import/variant-entries.d.ts +98 -0
- package/lib/import/variant-entries.js +407 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +21 -0
- package/lib/messages/index.d.ts +35 -0
- package/lib/messages/index.js +55 -0
- package/lib/types/adapter-helper.d.ts +8 -0
- package/lib/types/adapter-helper.js +2 -0
- package/lib/types/content-types.d.ts +19 -0
- package/lib/types/content-types.js +2 -0
- package/lib/types/export-config.d.ts +264 -0
- package/lib/types/export-config.js +2 -0
- package/lib/types/import-config.d.ts +92 -0
- package/lib/types/import-config.js +2 -0
- package/lib/types/index.d.ts +8 -0
- package/lib/types/index.js +24 -0
- package/lib/types/personalization-api-adapter.d.ts +152 -0
- package/lib/types/personalization-api-adapter.js +2 -0
- package/lib/types/utils.d.ts +7 -0
- package/lib/types/utils.js +2 -0
- package/lib/types/variant-api-adapter.d.ts +49 -0
- package/lib/types/variant-api-adapter.js +2 -0
- package/lib/types/variant-entry.d.ts +47 -0
- package/lib/types/variant-entry.js +2 -0
- package/lib/utils/adapter-helper.d.ts +30 -0
- package/lib/utils/adapter-helper.js +95 -0
- package/lib/utils/attributes-helper.d.ts +7 -0
- package/lib/utils/attributes-helper.js +37 -0
- package/lib/utils/audiences-helper.d.ts +8 -0
- package/lib/utils/audiences-helper.js +49 -0
- package/lib/utils/error-helper.d.ts +6 -0
- package/lib/utils/error-helper.js +27 -0
- package/lib/utils/events-helper.d.ts +8 -0
- package/lib/utils/events-helper.js +27 -0
- package/lib/utils/helper.d.ts +4 -0
- package/lib/utils/helper.js +51 -0
- package/lib/utils/index.d.ts +9 -0
- package/lib/utils/index.js +25 -0
- package/lib/utils/logger.d.ts +3 -0
- package/lib/utils/logger.js +175 -0
- package/lib/utils/personalization-api-adapter.d.ts +73 -0
- package/lib/utils/personalization-api-adapter.js +184 -0
- package/lib/utils/variant-api-adapter.d.ts +79 -0
- package/lib/utils/variant-api-adapter.js +263 -0
- package/package.json +38 -0
- package/src/export/attributes.ts +55 -0
- package/src/export/audiences.ts +57 -0
- package/src/export/events.ts +57 -0
- package/src/export/experiences.ts +80 -0
- package/src/export/index.ts +11 -0
- package/src/export/projects.ts +45 -0
- package/src/export/variant-entries.ts +88 -0
- package/src/import/attribute.ts +60 -0
- package/src/import/audiences.ts +69 -0
- package/src/import/events.ts +58 -0
- package/src/import/experiences.ts +224 -0
- package/src/import/index.ts +16 -0
- package/src/import/project.ts +71 -0
- package/src/import/variant-entries.ts +483 -0
- package/src/index.ts +5 -0
- package/src/messages/index.ts +63 -0
- package/src/types/adapter-helper.ts +10 -0
- package/src/types/content-types.ts +41 -0
- package/src/types/export-config.ts +292 -0
- package/src/types/import-config.ts +95 -0
- package/src/types/index.ts +8 -0
- package/src/types/personalization-api-adapter.ts +197 -0
- package/src/types/utils.ts +8 -0
- package/src/types/variant-api-adapter.ts +56 -0
- package/src/types/variant-entry.ts +61 -0
- package/src/utils/adapter-helper.ts +79 -0
- package/src/utils/attributes-helper.ts +31 -0
- package/src/utils/audiences-helper.ts +50 -0
- package/src/utils/error-helper.ts +26 -0
- package/src/utils/events-helper.ts +26 -0
- package/src/utils/helper.ts +34 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/logger.ts +160 -0
- package/src/utils/personalization-api-adapter.ts +188 -0
- package/src/utils/variant-api-adapter.ts +326 -0
- package/test/unit/export/variant-entries.test.ts +80 -0
- package/test/unit/import/variant-entries.test.ts +200 -0
- package/test/unit/mock/contents/content_types/CT-1.json +7 -0
- package/test/unit/mock/contents/entries/CT-1/en-us/variants/E-1/9b0da6xd7et72y-6gv7he23.json +12 -0
- package/test/unit/mock/contents/entries/CT-1/en-us/variants/E-1/index.json +3 -0
- package/test/unit/mock/contents/entries/CT-1/en-us/variants/E-2/9b0da6xd7et72y-6gv7he23.json +12 -0
- package/test/unit/mock/contents/entries/CT-1/en-us/variants/E-2/index.json +3 -0
- package/test/unit/mock/contents/mapper/assets/uid-mapping.json +6 -0
- package/test/unit/mock/contents/mapper/assets/url-mapping.json +6 -0
- package/test/unit/mock/contents/mapper/entries/data-for-variant-entry.json +6 -0
- package/test/unit/mock/contents/mapper/entries/empty-data/data-for-variant-entry.json +1 -0
- package/test/unit/mock/contents/mapper/entries/uid-mapping.json +6 -0
- package/test/unit/mock/contents/mapper/marketplace_apps/uid-mapping.json +3 -0
- package/test/unit/mock/contents/mapper/personalization/experiences/variants-uid-mapping.json +5 -0
- package/test/unit/mock/contents/mapper/taxonomies/terms/success.json +1 -0
- package/test/unit/mock/export-config.json +48 -0
- package/test/unit/mock/import-config.json +63 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import omit from 'lodash/omit';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import {
|
|
4
|
+
HttpClient,
|
|
5
|
+
HttpResponse,
|
|
6
|
+
HttpClientOptions,
|
|
7
|
+
ContentstackClient,
|
|
8
|
+
ContentstackConfig,
|
|
9
|
+
managementSDKClient,
|
|
10
|
+
} from '@contentstack/cli-utilities';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
APIConfig,
|
|
14
|
+
AdapterType,
|
|
15
|
+
AnyProperty,
|
|
16
|
+
ExportConfig,
|
|
17
|
+
ImportConfig,
|
|
18
|
+
VariantOptions,
|
|
19
|
+
VariantsOption,
|
|
20
|
+
VariantInterface,
|
|
21
|
+
VariantEntryStruct,
|
|
22
|
+
CreateVariantEntryDto,
|
|
23
|
+
CreateVariantEntryOptions,
|
|
24
|
+
APIResponse,
|
|
25
|
+
PublishVariantEntryDto,
|
|
26
|
+
PublishVariantEntryOptions,
|
|
27
|
+
} from '../types';
|
|
28
|
+
import messages from '../messages';
|
|
29
|
+
import { AdapterHelper } from './adapter-helper';
|
|
30
|
+
import { formatErrors } from './error-helper';
|
|
31
|
+
|
|
32
|
+
export class VariantHttpClient<C> extends AdapterHelper<C, HttpClient> implements VariantInterface<C, HttpClient> {
|
|
33
|
+
public baseURL: string;
|
|
34
|
+
constructor(config: APIConfig, options?: HttpClientOptions) {
|
|
35
|
+
super(config, options);
|
|
36
|
+
this.baseURL = config.baseURL?.includes('http') ? `${config.baseURL}/v3` : `https://${config.baseURL}/v3`;
|
|
37
|
+
this.apiClient.baseUrl(this.baseURL);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async variantEntry(options: VariantOptions) {
|
|
41
|
+
// TODO single entry variant
|
|
42
|
+
return { entry: {} };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The function `variantEntries` retrieves variant entries based on specified options and stores them
|
|
47
|
+
* in an array.
|
|
48
|
+
* @param {VariantsOption} options - The `options` parameter in the `variantEntries` function is an
|
|
49
|
+
* object that contains the following properties:
|
|
50
|
+
* @param {Record<string, any>[]} entries - The `entries` parameter in the `variantEntries` function
|
|
51
|
+
* is an array of objects where each object represents a record with key-value pairs. This parameter
|
|
52
|
+
* is used to store the entries retrieved from the API response or passed down recursively when
|
|
53
|
+
* fetching all data.
|
|
54
|
+
* @returns The function `variantEntries` returns a Promise that resolves to an object with an
|
|
55
|
+
* `entries` property containing an array of record objects or an unknown array. The function can
|
|
56
|
+
* also return void if certain conditions are not met.
|
|
57
|
+
*/
|
|
58
|
+
async variantEntries(
|
|
59
|
+
options: VariantsOption,
|
|
60
|
+
entries: Record<string, any>[] = [],
|
|
61
|
+
): Promise<{ entries?: Record<string, any>[] | unknown[] } | void> {
|
|
62
|
+
const variantConfig = (this.config as ExportConfig).modules.variantEntry;
|
|
63
|
+
const {
|
|
64
|
+
callback,
|
|
65
|
+
entry_uid,
|
|
66
|
+
getAllData,
|
|
67
|
+
returnResult,
|
|
68
|
+
content_type_uid,
|
|
69
|
+
locale,
|
|
70
|
+
skip = variantConfig.query.skip || 0,
|
|
71
|
+
limit = variantConfig.query.limit || 100,
|
|
72
|
+
include_variant = variantConfig.query.include_variant || true,
|
|
73
|
+
include_count = variantConfig.query.include_count || true,
|
|
74
|
+
include_publish_details = variantConfig.query.include_publish_details || true,
|
|
75
|
+
} = options;
|
|
76
|
+
|
|
77
|
+
if (variantConfig.serveMockData && callback) {
|
|
78
|
+
let data = [] as Record<string, any>[];
|
|
79
|
+
|
|
80
|
+
if (existsSync(variantConfig.mockDataPath)) {
|
|
81
|
+
data = require(variantConfig.mockDataPath) as Record<string, any>[];
|
|
82
|
+
}
|
|
83
|
+
callback(data);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!locale) return;
|
|
87
|
+
|
|
88
|
+
const start = Date.now();
|
|
89
|
+
let endpoint = `/content_types/${content_type_uid}/entries/${entry_uid}/variants?locale=${locale}`;
|
|
90
|
+
|
|
91
|
+
if (skip) {
|
|
92
|
+
endpoint = endpoint.concat(`&skip=${skip}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (limit) {
|
|
96
|
+
endpoint = endpoint.concat(`&limit=${limit}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (include_variant) {
|
|
100
|
+
endpoint = endpoint.concat(`&include_variant=${include_variant}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (include_count) {
|
|
104
|
+
endpoint = endpoint.concat(`&include_count=${include_count}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (include_publish_details) {
|
|
108
|
+
endpoint = endpoint.concat(`&include_publish_details=${include_publish_details}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const query = this.constructQuery(
|
|
112
|
+
omit(variantConfig.query, [
|
|
113
|
+
'skip',
|
|
114
|
+
'limit',
|
|
115
|
+
'locale',
|
|
116
|
+
'include_variant',
|
|
117
|
+
'include_count',
|
|
118
|
+
'include_publish_details',
|
|
119
|
+
]),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if (query) {
|
|
123
|
+
endpoint = endpoint.concat(query);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const data = await this.apiClient.get(endpoint);
|
|
127
|
+
const response = this.handleVariantAPIRes(data) as { entries: VariantEntryStruct[]; count: number };
|
|
128
|
+
|
|
129
|
+
if (callback) {
|
|
130
|
+
callback(response.entries);
|
|
131
|
+
} else {
|
|
132
|
+
entries = entries.concat(response.entries);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (getAllData && skip + limit < response.count) {
|
|
136
|
+
const end = Date.now();
|
|
137
|
+
const exeTime = end - start;
|
|
138
|
+
|
|
139
|
+
if (exeTime < 1000) {
|
|
140
|
+
// 1 API call per second
|
|
141
|
+
await this.delay(1000 - exeTime);
|
|
142
|
+
}
|
|
143
|
+
if (!options.skip) {
|
|
144
|
+
options['skip'] = 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
options.skip += limit;
|
|
148
|
+
return await this.variantEntries(options, entries);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (returnResult) return { entries };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Creates a variant entry.
|
|
156
|
+
*
|
|
157
|
+
* @param input - The input data for the variant entry.
|
|
158
|
+
* @param options - The options for creating the variant entry.
|
|
159
|
+
* @param apiParams - Additional parameters for the API.
|
|
160
|
+
* @returns A Promise that resolves to a VariantEntryStruct, a string, or void.
|
|
161
|
+
*/
|
|
162
|
+
async createVariantEntry(
|
|
163
|
+
input: CreateVariantEntryDto,
|
|
164
|
+
options: CreateVariantEntryOptions,
|
|
165
|
+
apiParams: Record<string, any>,
|
|
166
|
+
): Promise<VariantEntryStruct | string | void> {
|
|
167
|
+
const { reject, resolve, variantUid, log } = apiParams;
|
|
168
|
+
const variantConfig = (this.config as ImportConfig).modules.variantEntry;
|
|
169
|
+
const { locale = variantConfig.query.locale || 'en-us', variant_id, entry_uid, content_type_uid } = options;
|
|
170
|
+
let endpoint = `content_types/${content_type_uid}/entries/${entry_uid}/variants/${variant_id}?locale=${locale}`;
|
|
171
|
+
|
|
172
|
+
const query = this.constructQuery(omit(variantConfig.query, ['locale']));
|
|
173
|
+
|
|
174
|
+
if (query) {
|
|
175
|
+
endpoint = endpoint.concat(query);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const onSuccess = (response: any) => resolve({ response, apiData: { variantUid, entryUid: entry_uid }, log });
|
|
179
|
+
const onReject = (error: any) =>
|
|
180
|
+
reject({
|
|
181
|
+
error,
|
|
182
|
+
apiData: { variantUid, entryUid: entry_uid },
|
|
183
|
+
log,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const res = await this.apiClient.put<VariantEntryStruct>(endpoint, { entry: input });
|
|
188
|
+
const data = this.handleVariantAPIRes(res);
|
|
189
|
+
|
|
190
|
+
if (res.status >= 200 && res.status < 300) {
|
|
191
|
+
onSuccess(data);
|
|
192
|
+
} else {
|
|
193
|
+
onReject(data);
|
|
194
|
+
}
|
|
195
|
+
} catch (error: any) {
|
|
196
|
+
onReject(error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Publishes a variant entry.
|
|
202
|
+
*
|
|
203
|
+
* @param input - The input data for publishing the variant entry.
|
|
204
|
+
* @param options - The options for publishing the variant entry.
|
|
205
|
+
* @param apiParams - Additional API parameters.
|
|
206
|
+
* @returns A Promise that resolves to the published variant entry response.
|
|
207
|
+
*/
|
|
208
|
+
async publishVariantEntry(
|
|
209
|
+
input: PublishVariantEntryDto,
|
|
210
|
+
options: PublishVariantEntryOptions,
|
|
211
|
+
apiParams: Record<string, any>,
|
|
212
|
+
) {
|
|
213
|
+
const { reject, resolve, log, variantUid } = apiParams;
|
|
214
|
+
const { entry_uid, content_type_uid } = options;
|
|
215
|
+
let endpoint = `content_types/${content_type_uid}/entries/${entry_uid}/publish`;
|
|
216
|
+
|
|
217
|
+
const onSuccess = (response: any) =>
|
|
218
|
+
resolve({ response, apiData: { entryUid: entry_uid, variantUid }, log });
|
|
219
|
+
const onReject = (error: any) =>
|
|
220
|
+
reject({
|
|
221
|
+
error,
|
|
222
|
+
apiData: { entryUid: entry_uid, variantUid },
|
|
223
|
+
log,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
this.apiClient.headers({ api_version: 3.2 });
|
|
228
|
+
const res = await this.apiClient.post<any>(endpoint, input);
|
|
229
|
+
const data = this.handleVariantAPIRes(res);
|
|
230
|
+
|
|
231
|
+
if (res.status >= 200 && res.status < 300) {
|
|
232
|
+
onSuccess(data);
|
|
233
|
+
} else {
|
|
234
|
+
onReject(data);
|
|
235
|
+
}
|
|
236
|
+
} catch (error: any) {
|
|
237
|
+
onReject(error);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Handles the API response for variant requests.
|
|
243
|
+
* @param res - The API response object.
|
|
244
|
+
* @returns The variant API response data.
|
|
245
|
+
* @throws If the API response status is not within the success range, an error message is thrown.
|
|
246
|
+
*/
|
|
247
|
+
handleVariantAPIRes(
|
|
248
|
+
res: APIResponse,
|
|
249
|
+
): VariantEntryStruct | { entries: VariantEntryStruct[]; count: number } | string | any {
|
|
250
|
+
const { status, data } = res;
|
|
251
|
+
|
|
252
|
+
if (status >= 200 && status < 300) {
|
|
253
|
+
return data;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const errorMsg = data?.errors
|
|
257
|
+
? formatErrors(data.errors)
|
|
258
|
+
: data?.error_message || data?.message || 'Something went wrong while processing entry variant request!';
|
|
259
|
+
|
|
260
|
+
throw errorMsg;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export class VariantManagementSDK<T>
|
|
265
|
+
extends AdapterHelper<T, ContentstackClient>
|
|
266
|
+
implements VariantInterface<T, ContentstackClient>
|
|
267
|
+
{
|
|
268
|
+
public override apiClient!: ContentstackClient;
|
|
269
|
+
|
|
270
|
+
async init(): Promise<void> {
|
|
271
|
+
this.apiClient = await managementSDKClient(this.config);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async variantEntry(options: VariantOptions) {
|
|
275
|
+
// TODO SDK implementation
|
|
276
|
+
return { entry: {} };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async variantEntries(options: VariantsOption) {
|
|
280
|
+
// TODO SDK implementation
|
|
281
|
+
return { entries: [{}] };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
createVariantEntry(
|
|
285
|
+
input: CreateVariantEntryDto,
|
|
286
|
+
options: CreateVariantEntryOptions,
|
|
287
|
+
apiParams: Record<string, any>,
|
|
288
|
+
): Promise<VariantEntryStruct | string | void> {
|
|
289
|
+
// FIXME placeholder
|
|
290
|
+
return Promise.resolve({} as VariantEntryStruct);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
handleVariantAPIRes(
|
|
294
|
+
res: APIResponse,
|
|
295
|
+
): VariantEntryStruct | { entries: VariantEntryStruct[]; count: number } | string {
|
|
296
|
+
return res.data;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
constructQuery(query: Record<string, any>): string | void {}
|
|
300
|
+
|
|
301
|
+
async delay(ms: number): Promise<void> {}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export class VariantAdapter<T> {
|
|
305
|
+
protected variantInstance;
|
|
306
|
+
public readonly messages: typeof messages;
|
|
307
|
+
|
|
308
|
+
constructor(config: ContentstackConfig & AnyProperty & AdapterType<T, ContentstackConfig>);
|
|
309
|
+
constructor(config: APIConfig & AdapterType<T, APIConfig & AnyProperty>, options?: HttpClientOptions);
|
|
310
|
+
constructor(
|
|
311
|
+
config: APIConfig & AdapterType<T, (APIConfig & AnyProperty) | ContentstackConfig>,
|
|
312
|
+
options?: HttpClientOptions,
|
|
313
|
+
) {
|
|
314
|
+
if (config.httpClient) {
|
|
315
|
+
const { httpClient, Adapter, ...restConfig } = config;
|
|
316
|
+
this.variantInstance = new Adapter(restConfig, options);
|
|
317
|
+
} else {
|
|
318
|
+
const { Adapter, ...restConfig } = config;
|
|
319
|
+
this.variantInstance = new Adapter(restConfig);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.messages = messages;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export default VariantAdapter;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { expect } from '@oclif/test';
|
|
2
|
+
import { FsUtility } from '@contentstack/cli-utilities';
|
|
3
|
+
import { fancy } from '@contentstack/cli-dev-dependencies';
|
|
4
|
+
|
|
5
|
+
import exportConf from '../mock/export-config.json';
|
|
6
|
+
import { Export, ExportConfig, VariantHttpClient, VariantsOption } from '../../../src';
|
|
7
|
+
|
|
8
|
+
describe('Variant Entries Export', () => {
|
|
9
|
+
let config: ExportConfig;
|
|
10
|
+
|
|
11
|
+
const exportEntryData = {
|
|
12
|
+
locale: 'en-us',
|
|
13
|
+
contentTypeUid: 'CT-ID',
|
|
14
|
+
entries: [{ uid: 'E-UID-1', title: 'Entry 1' }],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const test = fancy
|
|
18
|
+
.stdout({ print: process.env.PRINT === 'true' || false })
|
|
19
|
+
.stub(FsUtility.prototype, 'completeFile', () => {})
|
|
20
|
+
.stub(FsUtility.prototype, 'writeIntoFile', () => {})
|
|
21
|
+
.stub(FsUtility.prototype, 'createFolderIfNotExist', () => {});
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
config = exportConf as unknown as ExportConfig;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('exportVariantEntry method', () => {
|
|
28
|
+
test
|
|
29
|
+
.stub(VariantHttpClient.prototype, 'variantEntries', async () => {})
|
|
30
|
+
.spy(VariantHttpClient.prototype, 'variantEntries')
|
|
31
|
+
.spy(FsUtility.prototype, 'completeFile')
|
|
32
|
+
.spy(FsUtility.prototype, 'createFolderIfNotExist')
|
|
33
|
+
.it('should call export variant entry method (API call)', async ({ spy }) => {
|
|
34
|
+
let entryVariantInstace = new Export.VariantEntries(config);
|
|
35
|
+
await entryVariantInstace.exportVariantEntry(exportEntryData);
|
|
36
|
+
|
|
37
|
+
expect(spy.variantEntries.callCount).to.be.equals(1);
|
|
38
|
+
expect(spy.completeFile.callCount).to.be.equals(1);
|
|
39
|
+
expect(spy.createFolderIfNotExist.callCount).to.be.equals(1);
|
|
40
|
+
expect(spy.completeFile.alwaysCalledWith(true)).to.be.true;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test
|
|
44
|
+
.stub(VariantHttpClient.prototype, 'variantEntries', async (...args: any) => {
|
|
45
|
+
const { callback } = args[0] as VariantsOption;
|
|
46
|
+
if (callback) {
|
|
47
|
+
callback([{ uid: 'E-UID-1', title: 'Entry 1' }]);
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
.spy(FsUtility.prototype, 'writeIntoFile')
|
|
51
|
+
.it('should write data in files (As chunk)', async ({ spy }) => {
|
|
52
|
+
let entryVariantInstace = new Export.VariantEntries(config);
|
|
53
|
+
await entryVariantInstace.exportVariantEntry(exportEntryData);
|
|
54
|
+
|
|
55
|
+
expect(spy.writeIntoFile.callCount).to.be.equals(1);
|
|
56
|
+
expect(spy.writeIntoFile.alwaysCalledWith([{ uid: 'E-UID-1', title: 'Entry 1' }])).to.be.true;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test
|
|
60
|
+
.stub(VariantHttpClient.prototype, 'variantEntries', async (...args: any) => {
|
|
61
|
+
const { callback } = args[0] as VariantsOption;
|
|
62
|
+
if (callback) {
|
|
63
|
+
callback([]); // NOTE API callback with empty response
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
.spy(FsUtility.prototype, 'writeIntoFile')
|
|
67
|
+
.spy(VariantHttpClient.prototype, 'variantEntries')
|
|
68
|
+
.it(
|
|
69
|
+
'should skip write data in files (Empty data check validation), should set default file chunk 1MB if chunk size is not passed in config',
|
|
70
|
+
async ({ spy }) => {
|
|
71
|
+
config.modules.variantEntry.chunkFileSize = null as any;
|
|
72
|
+
let entryVariantInstace = new Export.VariantEntries(config, () => {});
|
|
73
|
+
await entryVariantInstace.exportVariantEntry(exportEntryData);
|
|
74
|
+
|
|
75
|
+
expect(spy.writeIntoFile.callCount).to.be.equals(0);
|
|
76
|
+
expect(spy.variantEntries.callCount).to.be.equals(1);
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { expect } from '@oclif/test';
|
|
3
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
4
|
+
import { fancy } from '@contentstack/cli-dev-dependencies';
|
|
5
|
+
|
|
6
|
+
import importConf from '../mock/import-config.json';
|
|
7
|
+
import ContentType from '../mock/contents/content_types/CT-1.json';
|
|
8
|
+
import { Import, ImportConfig, VariantHttpClient } from '../../../src';
|
|
9
|
+
import variantEntryData from '../mock/contents/mapper/entries/data-for-variant-entry.json';
|
|
10
|
+
import variantEntries from '../mock/contents/entries/CT-1/en-us/variants/E-1/9b0da6xd7et72y-6gv7he23.json';
|
|
11
|
+
|
|
12
|
+
describe('Variant Entries Import', () => {
|
|
13
|
+
let config: ImportConfig;
|
|
14
|
+
|
|
15
|
+
const test = fancy.stdout({ print: process.env.PRINT === 'true' || false });
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
config = cloneDeep(importConf) as unknown as ImportConfig;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('import method', () => {
|
|
22
|
+
test
|
|
23
|
+
.stub(Import.VariantEntries.prototype, 'importVariantEntries', async () => {})
|
|
24
|
+
.spy(Import.VariantEntries.prototype, 'importVariantEntries')
|
|
25
|
+
.it('should call import variant entry method (API call)', async ({ spy }) => {
|
|
26
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
27
|
+
await entryVariantInstace.import();
|
|
28
|
+
|
|
29
|
+
expect(spy.importVariantEntries.called).to.be.true;
|
|
30
|
+
expect(spy.importVariantEntries.calledWith(variantEntryData[0])).to.be.true;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test
|
|
34
|
+
.stub(Import.VariantEntries.prototype, 'importVariantEntries', async () => {})
|
|
35
|
+
.spy(Import.VariantEntries.prototype, 'importVariantEntries')
|
|
36
|
+
.it('should return with entry not found message', async (ctx) => {
|
|
37
|
+
config.backupDir = './';
|
|
38
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
39
|
+
await entryVariantInstace.import();
|
|
40
|
+
|
|
41
|
+
expect(ctx.stdout).to.be.includes(entryVariantInstace.messages.IMPORT_ENTRY_NOT_FOUND);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test
|
|
45
|
+
.stub(Import.VariantEntries.prototype, 'importVariantEntries', async () => {})
|
|
46
|
+
.spy(Import.VariantEntries.prototype, 'importVariantEntries')
|
|
47
|
+
.it('should return with variant UID mapper file not found message', async (ctx) => {
|
|
48
|
+
config.modules.personalization.dirName = 'wrong-dir';
|
|
49
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
50
|
+
await entryVariantInstace.import();
|
|
51
|
+
|
|
52
|
+
expect(ctx.stdout).to.be.includes(entryVariantInstace.messages.EMPTY_VARIANT_UID_DATA);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test
|
|
56
|
+
.stub(Import.VariantEntries.prototype, 'importVariantEntries', async () => {})
|
|
57
|
+
.spy(Import.VariantEntries.prototype, 'importVariantEntries')
|
|
58
|
+
.it('should return with entry not found message if empty content found on file', async (ctx) => {
|
|
59
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
60
|
+
entryVariantInstace.entriesMapperPath = join(entryVariantInstace.entriesMapperPath, 'empty-data');
|
|
61
|
+
await entryVariantInstace.import();
|
|
62
|
+
|
|
63
|
+
expect(ctx.stdout).to.be.includes(entryVariantInstace.messages.IMPORT_ENTRY_NOT_FOUND);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test
|
|
67
|
+
.stub(Import.VariantEntries.prototype, 'importVariantEntries', async () => {})
|
|
68
|
+
.spy(Import.VariantEntries.prototype, 'importVariantEntries')
|
|
69
|
+
.it('should check taxonomies folder existence', async (ctx) => {
|
|
70
|
+
config.modules.taxonomies.dirName = 'wrong-dir';
|
|
71
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
72
|
+
await entryVariantInstace.import();
|
|
73
|
+
|
|
74
|
+
expect(entryVariantInstace.taxonomies).to.contain({});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('importVariantEntries method', () => {
|
|
79
|
+
test
|
|
80
|
+
.stub(Import.VariantEntries.prototype, 'handleCuncurrency', async () => {})
|
|
81
|
+
.spy(Import.VariantEntries.prototype, 'handleCuncurrency')
|
|
82
|
+
.it('should call handle Cuncurrency method to manage import batch', async ({ spy }) => {
|
|
83
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
84
|
+
await entryVariantInstace.importVariantEntries(variantEntryData[0]);
|
|
85
|
+
|
|
86
|
+
expect(spy.handleCuncurrency.called).to.be.true;
|
|
87
|
+
expect(spy.handleCuncurrency.calledWith(ContentType, variantEntries, variantEntryData[0])).to.be.true;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test
|
|
91
|
+
.stub(Import.VariantEntries.prototype, 'handleCuncurrency', async () => {
|
|
92
|
+
throw new Error('Dummy error');
|
|
93
|
+
})
|
|
94
|
+
.spy(Import.VariantEntries.prototype, 'handleCuncurrency')
|
|
95
|
+
.it('should catch and log errors on catch block', async (ctx) => {
|
|
96
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
97
|
+
await entryVariantInstace.importVariantEntries(variantEntryData[0]);
|
|
98
|
+
|
|
99
|
+
expect(ctx.stdout).to.be.includes('Dummy error');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('handleCuncurrency method', () => {
|
|
104
|
+
test
|
|
105
|
+
.stub(VariantHttpClient.prototype, 'createVariantEntry', async () => {})
|
|
106
|
+
.stub(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData', () => variantEntries[0])
|
|
107
|
+
.spy(VariantHttpClient.prototype, 'createVariantEntry')
|
|
108
|
+
.spy(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData')
|
|
109
|
+
.it('should call handle Cuncurrency method to manage import batch', async ({ spy }) => {
|
|
110
|
+
const variantEntry = variantEntries[0];
|
|
111
|
+
const { content_type, entry_uid, locale } = variantEntryData[0];
|
|
112
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
113
|
+
entryVariantInstace.variantIdList = { 'VARIANT-ID-1': 'VARIANT-ID-2' };
|
|
114
|
+
await entryVariantInstace.handleCuncurrency(ContentType, variantEntries, variantEntryData[0]);
|
|
115
|
+
|
|
116
|
+
expect(spy.createVariantEntry.called).to.be.true;
|
|
117
|
+
expect(spy.handleVariantEntryRelationalData.called).to.be.true;
|
|
118
|
+
expect(spy.handleVariantEntryRelationalData.calledWith(ContentType, variantEntry)).to.be.true;
|
|
119
|
+
expect(
|
|
120
|
+
spy.createVariantEntry.calledWith(variantEntry, {
|
|
121
|
+
locale,
|
|
122
|
+
entry_uid,
|
|
123
|
+
variant_id: 'VARIANT-ID-2',
|
|
124
|
+
content_type_uid: content_type,
|
|
125
|
+
}),
|
|
126
|
+
).to.be.true;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test
|
|
130
|
+
.stub(VariantHttpClient.prototype, 'createVariantEntry', async () => {})
|
|
131
|
+
.stub(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData', () => variantEntries[0])
|
|
132
|
+
.spy(VariantHttpClient.prototype, 'createVariantEntry')
|
|
133
|
+
.spy(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData')
|
|
134
|
+
.it('should return without any execution if empty batch found', async (ctx) => {
|
|
135
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
136
|
+
const result = await entryVariantInstace.handleCuncurrency(ContentType, [], variantEntryData[0]);
|
|
137
|
+
|
|
138
|
+
expect(result).to.be.undefined;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test
|
|
142
|
+
.stub(VariantHttpClient.prototype, 'createVariantEntry', async () => {})
|
|
143
|
+
.stub(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData', () => variantEntries[0])
|
|
144
|
+
.spy(VariantHttpClient.prototype, 'createVariantEntry')
|
|
145
|
+
.spy(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData')
|
|
146
|
+
.it('should log error message if variant UID not found on the mapper file', async (ctx) => {
|
|
147
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
148
|
+
entryVariantInstace.config.modules.variantEntry.apiConcurrency = null as any; // NOTE Missing apiConcurrency value in config
|
|
149
|
+
entryVariantInstace.variantIdList = { 'VARIANT-ID-2': 'VARIANT-ID-NEW-2' };
|
|
150
|
+
await entryVariantInstace.handleCuncurrency(ContentType, variantEntries, variantEntryData[0]);
|
|
151
|
+
|
|
152
|
+
expect(ctx.stdout).to.be.includes(entryVariantInstace.messages.VARIANT_ID_NOT_FOUND);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('handleVariantEntryRelationalData method', () => {
|
|
157
|
+
test.it('should call handle Cuncurrency method to manage import batch', async () => {
|
|
158
|
+
// NOTE passing helper methods along with config
|
|
159
|
+
let conf = Object.assign(config, {
|
|
160
|
+
helpers: {
|
|
161
|
+
lookUpTerms: () => {},
|
|
162
|
+
lookupExtension: () => {},
|
|
163
|
+
lookupAssets: (entry: any) => entry,
|
|
164
|
+
lookupEntries: (entry: any) => entry,
|
|
165
|
+
restoreJsonRteEntryRefs: (entry: any) => entry,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
const variantEntry = variantEntries[0];
|
|
169
|
+
let entryVariantInstace = new Import.VariantEntries(conf);
|
|
170
|
+
const entry = await entryVariantInstace.handleVariantEntryRelationalData(ContentType, variantEntry);
|
|
171
|
+
|
|
172
|
+
expect(entry).to.contain(variantEntry);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test.it('should skip calling lookupExtension if not available in helper', async () => {
|
|
176
|
+
// NOTE passing helper methods along with config
|
|
177
|
+
let conf = Object.assign(config, {
|
|
178
|
+
helpers: {
|
|
179
|
+
lookUpTerms: () => {},
|
|
180
|
+
lookupAssets: (entry: any) => entry,
|
|
181
|
+
lookupEntries: (entry: any) => entry,
|
|
182
|
+
restoreJsonRteEntryRefs: (entry: any) => entry,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
const variantEntry = variantEntries[0];
|
|
186
|
+
let entryVariantInstace = new Import.VariantEntries(conf);
|
|
187
|
+
const entry = await entryVariantInstace.handleVariantEntryRelationalData(ContentType, variantEntry);
|
|
188
|
+
|
|
189
|
+
expect(entry).to.contain(variantEntry);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test.it('will skip calling lookup function if helper is not present in config', async () => {
|
|
193
|
+
const variantEntry = variantEntries[0];
|
|
194
|
+
let entryVariantInstace = new Import.VariantEntries(config);
|
|
195
|
+
const entry = await entryVariantInstace.handleVariantEntryRelationalData(ContentType, variantEntry);
|
|
196
|
+
|
|
197
|
+
expect(entry).to.contain(variantEntry);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "content_type": "CT-1", "locale": "en-us", "entry_uid": "E-1" },
|
|
3
|
+
{ "content_type": "CT-2", "locale": "en-us", "entry_uid": "E-2" },
|
|
4
|
+
{ "content_type": "CT-3", "locale": "en-us", "entry_uid": "E-3" },
|
|
5
|
+
{ "content_type": "CT-4", "locale": "en-us", "entry_uid": "E-4" }
|
|
6
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|