Skip to content

Commit

Permalink
Merge pull request #29 from contentstack/test/CS-44748-import
Browse files Browse the repository at this point in the history
test: Variant entries import unit test cases
  • Loading branch information
antonyagustine authored Apr 30, 2024
2 parents fd7928e + 60067e4 commit f8b32e9
Show file tree
Hide file tree
Showing 16 changed files with 355 additions and 21 deletions.
42 changes: 21 additions & 21 deletions packages/contentstack-variants/src/import/variant-entries.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import omit from 'lodash/omit';
import chunk from 'lodash/chunk';
import values from 'lodash/values';
import entries from 'lodash/entries';
import orderBy from 'lodash/orderBy';
import isEmpty from 'lodash/isEmpty';
import { join, resolve } from 'path';
import { readFileSync, existsSync } from 'fs';
Expand Down Expand Up @@ -67,7 +65,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
*/
async import() {
const filePath = resolve(this.entriesMapperPath, 'data-for-variant-entry.json');
const variantIdPath = join(
const variantIdPath = resolve(
this.config.backupDir,
'mapper',
this.personalizationconfig.dirName,
Expand Down Expand Up @@ -95,20 +93,24 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
const entriesUidMapperPath = join(this.entriesMapperPath, 'uid-mapping.json');
const assetUidMapperPath = resolve(this.config.backupDir, 'mapper', 'assets', 'uid-mapping.json');
const assetUrlMapperPath = resolve(this.config.backupDir, 'mapper', 'assets', 'url-mapping.json');
const taxonomiesPath = join(this.config.backupDir, 'mapper', 'taxonomies', 'terms', 'success.json');
const marketplaceAppMapperPath = join(this.config.backupDir, 'mapper', 'marketplace_apps', 'uid-mapping.json');
const taxonomiesPath = resolve(
this.config.backupDir,
'mapper',
this.config.modules.taxonomies.dirName,
'terms',
'success.json',
);
const marketplaceAppMapperPath = resolve(this.config.backupDir, 'mapper', 'marketplace_apps', 'uid-mapping.json');

// NOTE Read and store list of variant IDs
this.variantIdList = JSON.parse(readFileSync(variantIdPath, 'utf8'));

// NOTE entry relational data lookup dependencies.
this.entriesUidMapper = JSON.parse(await readFileSync(entriesUidMapperPath, 'utf8'));
this.installedExtensions = (
JSON.parse(await readFileSync(marketplaceAppMapperPath, 'utf8')) || { extension_uid: {} }
).extension_uid;
this.entriesUidMapper = JSON.parse(readFileSync(entriesUidMapperPath, 'utf8'));
this.installedExtensions = JSON.parse(readFileSync(marketplaceAppMapperPath, 'utf8')).extension_uid;
this.taxonomies = existsSync(taxonomiesPath) ? JSON.parse(readFileSync(taxonomiesPath, 'utf8')) : {};
this.assetUidMapper = JSON.parse(readFileSync(assetUidMapperPath, 'utf8')) || {};
this.assetUrlMapper = JSON.parse(readFileSync(assetUrlMapperPath, 'utf8')) || {};
this.assetUidMapper = JSON.parse(readFileSync(assetUidMapperPath, 'utf8'));
this.assetUrlMapper = JSON.parse(readFileSync(assetUrlMapperPath, 'utf8'));

for (const entriesForVariant of entriesForVariants) {
await this.importVariantEntries(entriesForVariant);
Expand All @@ -132,11 +134,9 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp

for (const _ in fs.indexFileContent) {
try {
const variantEntries = (await fs.readChunkFiles.next()) as VariantEntryStruct;
if (variantEntries) {
const apiContent = orderBy(values(variantEntries), '_version');
await this.handleCuncurrency(contentType, apiContent, entriesForVariant);
}
const variantEntries = (await fs.readChunkFiles.next()) as VariantEntryStruct[];

await this.handleCuncurrency(contentType, variantEntries, entriesForVariant);
} catch (error) {
this.log(this.config, error, 'error');
}
Expand Down Expand Up @@ -165,7 +165,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
let batchNo = 0;
const variantEntryConfig = this.config.modules.variantEntry;
const { content_type, locale, entry_uid } = entriesForVariant;
const batches = chunk(variantEntries, variantEntryConfig.apiConcurrency | 5);
const batches = chunk(variantEntries, variantEntryConfig.apiConcurrency || 5);

if (isEmpty(batches)) return;

Expand Down Expand Up @@ -252,13 +252,13 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
content_type: contentType,
},
this.entriesUidMapper,
join(this.entriesMapperPath, contentType.uid, variantEntry.locale),
);
resolve(this.entriesMapperPath, contentType.uid, variantEntry.locale),
).entry;

// NOTE: will remove term if term doesn't exists in taxonomy
// FIXME: Validate if taxonomy support available for variant entries,
// if not, feel free to remove this lookup flow.
lookUpTerms(contentType?.schema, variantEntry, this.taxonomies, this.config);
lookUpTerms(contentType.schema, variantEntry, this.taxonomies, this.config);

// NOTE Find and replace asset's UID
variantEntry = lookupAssets(
Expand All @@ -270,7 +270,7 @@ export default class VariantEntries extends VariantAdapter<VariantHttpClient<Imp
this.assetUrlMapper,
this.entriesDirPath,
this.installedExtensions,
);
).entry;
}

return variantEntry;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { join } from 'path';
import { expect } from '@oclif/test';
import cloneDeep from 'lodash/cloneDeep';
import { fancy } from '@contentstack/cli-dev-dependencies';

import importConf from '../mock/import-config.json';
import ContentType from '../mock/contents/content_types/CT-1.json';
import { Import, ImportConfig, VariantHttpClient } from '../../../src';
import variantEntryData from '../mock/contents/mapper/entries/data-for-variant-entry.json';
import variantEntries from '../mock/contents/entries/CT-1/en-us/variants/E-1/9b0da6xd7et72y-6gv7he23.json';

describe('Variant Entries Import', () => {
let config: ImportConfig;

const test = fancy.stdout({ print: process.env.PRINT === 'true' || false });

beforeEach(() => {
config = cloneDeep(importConf) as unknown as ImportConfig;
});

describe('import method', () => {
test
.stub(Import.VariantEntries.prototype, 'importVariantEntries', async () => {})
.spy(Import.VariantEntries.prototype, 'importVariantEntries')
.it('should call import variant entry method (API call)', async ({ spy }) => {
let entryVariantInstace = new Import.VariantEntries(config);
await entryVariantInstace.import();

expect(spy.importVariantEntries.called).to.be.true;
expect(spy.importVariantEntries.calledWith(variantEntryData[0])).to.be.true;
});

test
.stub(Import.VariantEntries.prototype, 'importVariantEntries', async () => {})
.spy(Import.VariantEntries.prototype, 'importVariantEntries')
.it('should return with entry not found message', async (ctx) => {
config.backupDir = './';
let entryVariantInstace = new Import.VariantEntries(config);
await entryVariantInstace.import();

expect(ctx.stdout).to.be.includes(entryVariantInstace.messages.IMPORT_ENTRY_NOT_FOUND);
});

test
.stub(Import.VariantEntries.prototype, 'importVariantEntries', async () => {})
.spy(Import.VariantEntries.prototype, 'importVariantEntries')
.it('should return with variant UID mapper file not found message', async (ctx) => {
config.modules.personalization.dirName = 'wrong-dir';
let entryVariantInstace = new Import.VariantEntries(config);
await entryVariantInstace.import();

expect(ctx.stdout).to.be.includes(entryVariantInstace.messages.EMPTY_VARIANT_UID_DATA);
});

test
.stub(Import.VariantEntries.prototype, 'importVariantEntries', async () => {})
.spy(Import.VariantEntries.prototype, 'importVariantEntries')
.it('should return with entry not found message if empty content found on file', async (ctx) => {
let entryVariantInstace = new Import.VariantEntries(config);
entryVariantInstace.entriesMapperPath = join(entryVariantInstace.entriesMapperPath, 'empty-data');
await entryVariantInstace.import();

expect(ctx.stdout).to.be.includes(entryVariantInstace.messages.IMPORT_ENTRY_NOT_FOUND);
});

test
.stub(Import.VariantEntries.prototype, 'importVariantEntries', async () => {})
.spy(Import.VariantEntries.prototype, 'importVariantEntries')
.it('should check taxonomies folder existence', async (ctx) => {
config.modules.taxonomies.dirName = 'wrong-dir';
let entryVariantInstace = new Import.VariantEntries(config);
await entryVariantInstace.import();

expect(entryVariantInstace.taxonomies).to.contain({});
});
});

describe('importVariantEntries method', () => {
test
.stub(Import.VariantEntries.prototype, 'handleCuncurrency', async () => {})
.spy(Import.VariantEntries.prototype, 'handleCuncurrency')
.it('should call handle Cuncurrency method to manage import batch', async ({ spy }) => {
let entryVariantInstace = new Import.VariantEntries(config);
await entryVariantInstace.importVariantEntries(variantEntryData[0]);

expect(spy.handleCuncurrency.called).to.be.true;
expect(spy.handleCuncurrency.calledWith(ContentType, variantEntries, variantEntryData[0])).to.be.true;
});

test
.stub(Import.VariantEntries.prototype, 'handleCuncurrency', async () => {
throw new Error('Dummy error');
})
.spy(Import.VariantEntries.prototype, 'handleCuncurrency')
.it('should catch and log errors on catch block', async (ctx) => {
let entryVariantInstace = new Import.VariantEntries(config);
await entryVariantInstace.importVariantEntries(variantEntryData[0]);

expect(ctx.stdout).to.be.includes('Dummy error');
});
});

describe('handleCuncurrency method', () => {
test
.stub(VariantHttpClient.prototype, 'createVariantEntry', async () => {})
.stub(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData', () => variantEntries[0])
.spy(VariantHttpClient.prototype, 'createVariantEntry')
.spy(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData')
.it('should call handle Cuncurrency method to manage import batch', async ({ spy }) => {
const variantEntry = variantEntries[0];
const { content_type, entry_uid, locale } = variantEntryData[0];
let entryVariantInstace = new Import.VariantEntries(config);
entryVariantInstace.variantIdList = { 'VARIANT-ID-1': 'VARIANT-ID-2' };
await entryVariantInstace.handleCuncurrency(ContentType, variantEntries, variantEntryData[0]);

expect(spy.createVariantEntry.called).to.be.true;
expect(spy.handleVariantEntryRelationalData.called).to.be.true;
expect(spy.handleVariantEntryRelationalData.calledWith(ContentType, variantEntry)).to.be.true;
expect(
spy.createVariantEntry.calledWith(variantEntry, {
locale,
entry_uid,
variant_id: 'VARIANT-ID-2',
content_type_uid: content_type,
}),
).to.be.true;
});

test
.stub(VariantHttpClient.prototype, 'createVariantEntry', async () => {})
.stub(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData', () => variantEntries[0])
.spy(VariantHttpClient.prototype, 'createVariantEntry')
.spy(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData')
.it('should return without any execution if empty batch found', async (ctx) => {
let entryVariantInstace = new Import.VariantEntries(config);
const result = await entryVariantInstace.handleCuncurrency(ContentType, [], variantEntryData[0]);

expect(result).to.be.undefined;
});

test
.stub(VariantHttpClient.prototype, 'createVariantEntry', async () => {})
.stub(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData', () => variantEntries[0])
.spy(VariantHttpClient.prototype, 'createVariantEntry')
.spy(Import.VariantEntries.prototype, 'handleVariantEntryRelationalData')
.it('should log error message if variant UID not found on the mapper file', async (ctx) => {
let entryVariantInstace = new Import.VariantEntries(config);
entryVariantInstace.config.modules.variantEntry.apiConcurrency = null as any; // NOTE Missing apiConcurrency value in config
entryVariantInstace.variantIdList = { 'VARIANT-ID-2': 'VARIANT-ID-NEW-2' };
await entryVariantInstace.handleCuncurrency(ContentType, variantEntries, variantEntryData[0]);

expect(ctx.stdout).to.be.includes(entryVariantInstace.messages.VARIANT_ID_NOT_FOUND);
});
});

describe('handleVariantEntryRelationalData method', () => {
test.it('should call handle Cuncurrency method to manage import batch', async () => {
// NOTE passing helper methods along with config
let conf = Object.assign(config, {
helpers: {
lookUpTerms: () => {},
lookupExtension: () => {},
lookupAssets: (entry: any) => entry,
lookupEntries: (entry: any) => entry,
restoreJsonRteEntryRefs: (entry: any) => entry,
},
});
const variantEntry = variantEntries[0];
let entryVariantInstace = new Import.VariantEntries(conf);
const entry = await entryVariantInstace.handleVariantEntryRelationalData(ContentType, variantEntry);

expect(entry).to.contain(variantEntry);
});

test.it('should skip calling lookupExtension if not available in helper', async () => {
// NOTE passing helper methods along with config
let conf = Object.assign(config, {
helpers: {
lookUpTerms: () => {},
lookupAssets: (entry: any) => entry,
lookupEntries: (entry: any) => entry,
restoreJsonRteEntryRefs: (entry: any) => entry,
},
});
const variantEntry = variantEntries[0];
let entryVariantInstace = new Import.VariantEntries(conf);
const entry = await entryVariantInstace.handleVariantEntryRelationalData(ContentType, variantEntry);

expect(entry).to.contain(variantEntry);
});

test.it('will skip calling lookup function if helper is not present in config', async () => {
const variantEntry = variantEntries[0];
let entryVariantInstace = new Import.VariantEntries(config);
const entry = await entryVariantInstace.handleVariantEntryRelationalData(ContentType, variantEntry);

expect(entry).to.contain(variantEntry);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"uid": "CT-1",
"title": "CT-1",
"description": "test CT",
"schema": [],
"options": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[{
"uid": "E-1",
"locale": "en-us",
"title": "Variant 1",
"variant_id": "VARIANT-ID-1",
"_version": 1,
"_variant": {
"uid": "UID-1",
"_change_set": [],
"_base_entry_version": 1
}
}]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"1": "9b0da6xd7et72y-6gv7he23.json"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[{
"uid": "E-1",
"locale": "en-us",
"title": "Variant 1",
"variant_id": "VARIANT-ID-1",
"_version": 1,
"_variant": {
"uid": "UID-1",
"_change_set": [],
"_base_entry_version": 1
}
}]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"1": "9b0da6xd7et72y-6gv7he23.json"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"O-A-UID-1": "N-A-UID-1",
"O-A-UID-2": "N-A-UID-2",
"O-A-UID-3": "N-A-UID-3",
"O-A-UID-4": "N-A-UID-4"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"o-ass-url-1.com": "n-ass-url-1.com",
"o-ass-url-2.com": "n-ass-url-2.com",
"o-ass-url-3.com": "n-ass-url-3.com",
"o-ass-url-4.com": "n-ass-url-4.com"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
{ "content_type": "CT-1", "locale": "en-us", "entry_uid": "E-1" },
{ "content_type": "CT-2", "locale": "en-us", "entry_uid": "E-2" },
{ "content_type": "CT-3", "locale": "en-us", "entry_uid": "E-3" },
{ "content_type": "CT-4", "locale": "en-us", "entry_uid": "E-4" }
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"E-UID-1": "E-UID-1",
"E-UID-2": "E-UID-2",
"E-UID-3": "E-UID-3",
"E-UID-4": "E-UID-4"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extension_uid": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"OLD-UID-1": "NEW-UID-1",
"OLD-UID-2": "NEW-UID-2",
"OLD-UID-3": "NEW-UID-3"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading

0 comments on commit f8b32e9

Please sign in to comment.