From b8ce61d17a2184377e5451c7d831ae679e941996 Mon Sep 17 00:00:00 2001 From: shafeeqd959 Date: Wed, 17 Dec 2025 12:23:43 +0530 Subject: [PATCH 1/4] skip composition type and compositions if studio project failed to import --- .talismanrc | 2 +- .../contentstack-import/src/config/index.ts | 2 +- .../src/import/modules/composable-studio.ts | 10 ++++ .../src/import/modules/content-types.ts | 46 ++++++++++++++++- .../src/import/modules/entries.ts | 50 +++++++++++++++++-- 5 files changed, 103 insertions(+), 7 deletions(-) diff --git a/.talismanrc b/.talismanrc index 414acd491e..6918bbe888 100644 --- a/.talismanrc +++ b/.talismanrc @@ -66,7 +66,7 @@ fileignoreconfig: - filename: packages/contentstack-bulk-publish/src/producer/publish-unpublished-env.js checksum: 96fd15e027f38b156c69f10943ea1d5a70e580fa8a5efeb3286cd7132145c72d - filename: packages/contentstack-import/src/import/modules/entries.ts - checksum: 2fd4e8ecf75e077632a6408d09997f0921d2a3508f9f2cb8f47fe79a28592300 + checksum: 290730774c61220645ec211b85b9e218cdbd8addc2d8fd8f061dfa5ede5b5c75 - filename: packages/contentstack-utilities/src/logger/logger.ts checksum: 76429bc87e279624b386f00e7eb3f4ec25621ace7056289f812b9a076d6e184e - filename: packages/contentstack-bootstrap/src/bootstrap/utils.ts diff --git a/packages/contentstack-import/src/config/index.ts b/packages/contentstack-import/src/config/index.ts index 75f8d6bc8b..76b70e1121 100644 --- a/packages/contentstack-import/src/config/index.ts +++ b/packages/contentstack-import/src/config/index.ts @@ -33,6 +33,7 @@ const config: DefaultConfig = { 'stack', 'assets', 'taxonomies', + 'composable-studio', 'extensions', 'marketplace-apps', 'global-fields', @@ -44,7 +45,6 @@ const config: DefaultConfig = { 'variant-entries', 'labels', 'webhooks', - 'composable-studio', ], locales: { dirName: 'locales', diff --git a/packages/contentstack-import/src/import/modules/composable-studio.ts b/packages/contentstack-import/src/import/modules/composable-studio.ts index 521384692d..04cd04ef8d 100644 --- a/packages/contentstack-import/src/import/modules/composable-studio.ts +++ b/packages/contentstack-import/src/import/modules/composable-studio.ts @@ -20,6 +20,7 @@ export default class ImportComposableStudio { private apiClient: HttpClient; private envUidMapperPath: string; private envUidMapper: Record; + private projectMapperPath: string; constructor({ importConfig }: ModuleClassParams) { this.importConfig = importConfig; @@ -28,6 +29,7 @@ export default class ImportComposableStudio { // Setup paths this.composableStudioPath = join(this.importConfig.backupDir, this.composableStudioConfig.dirName); + this.projectMapperPath = join(this.importConfig.backupDir, 'mapper', this.composableStudioConfig.dirName); this.composableStudioFilePath = join(this.composableStudioPath, this.composableStudioConfig.fileName); this.envUidMapperPath = join(this.importConfig.backupDir, 'mapper', 'environments', 'uid-mapping.json'); this.envUidMapper = {}; @@ -244,6 +246,14 @@ export default class ImportComposableStudio { if (response.status >= 200 && response.status < 300) { projectCreated = true; log.debug(`Project created successfully with UID: ${response.data?.uid}`, this.importConfig.context); + + // Create mapper directory if it doesn't exist + await fsUtil.makeDirectory(this.projectMapperPath); + + // write the project to file + const projectFileSuccessPath = join(this.projectMapperPath, this.composableStudioConfig.fileName); + fsUtil.writeFile(projectFileSuccessPath, response.data as unknown as Record); + log.debug(`Project written to: ${projectFileSuccessPath}`, this.importConfig.context); } else { throw new Error(`API call failed with status ${response.status}: ${JSON.stringify(response.data)}`); } diff --git a/packages/contentstack-import/src/import/modules/content-types.ts b/packages/contentstack-import/src/import/modules/content-types.ts index 19b7ca51ea..462c10a8f0 100644 --- a/packages/contentstack-import/src/import/modules/content-types.ts +++ b/packages/contentstack-import/src/import/modules/content-types.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import { isEmpty, find, cloneDeep, map } from 'lodash'; import { sanitizePath, log, handleAndLogError } from '@contentstack/cli-utilities'; -import { fsUtil, schemaTemplate, lookupExtension, lookUpTaxonomy } from '../../utils'; +import { fsUtil, schemaTemplate, lookupExtension, lookUpTaxonomy, fileHelper } from '../../utils'; import { ImportConfig, ModuleClassParams } from '../../types'; import BaseClass, { ApiOptions } from './base-class'; import { updateFieldRules } from '../../utils/content-type-helper'; @@ -54,6 +54,8 @@ export default class ContentTypesImport extends BaseClass { public taxonomies: Record; private extPendingPath: string; private isExtensionsUpdate = false; + private composableStudioSuccessPath: string; + private composableStudioExportPath: string; constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); @@ -84,6 +86,19 @@ export default class ContentTypesImport extends BaseClass { ['schema.json', 'true'], ['.DS_Store', 'true'], ]); + + this.composableStudioSuccessPath = path.join( + sanitizePath(this.importConfig.data), + 'mapper', + this.importConfig.modules['composable-studio'].dirName, + this.importConfig.modules['composable-studio'].fileName, + ); + + this.composableStudioExportPath = path.join( + sanitizePath(this.importConfig.data), + this.importConfig.modules['composable-studio'].dirName, + this.importConfig.modules['composable-studio'].fileName, + ); this.cTs = []; this.createdCTs = []; this.titleToUIdMap = new Map(); @@ -110,6 +125,35 @@ export default class ContentTypesImport extends BaseClass { } log.debug(`Found ${this.cTs.length} content types to import`, this.importConfig.context); + // If success file doesn't exist but export file does, skip the composition content type + if ( + !fileHelper.fileExistsSync(this.composableStudioSuccessPath) && + fileHelper.fileExistsSync(this.composableStudioExportPath) + ) { + const exportedProject = fileHelper.readFileSync(this.composableStudioExportPath) as { + contentTypeUid: string; + }; + + if (exportedProject?.contentTypeUid) { + const originalCount = this.cTs.length; + this.cTs = this.cTs.filter((ct: Record) => { + const shouldSkip = ct.uid === exportedProject.contentTypeUid; + if (shouldSkip) { + log.info( + `Skipping content type '${ct.uid}' as Composable Studio project was not created successfully`, + this.importConfig.context, + ); + } + return !shouldSkip; + }); + + const skippedCount = originalCount - this.cTs.length; + if (skippedCount > 0) { + log.debug(`Filtered out ${skippedCount} composition content type(s) from import`, this.importConfig.context); + } + } + } + await fsUtil.makeDirectory(this.cTsMapperPath); log.debug('Created content types mapper directory.', this.importConfig.context); diff --git a/packages/contentstack-import/src/import/modules/entries.ts b/packages/contentstack-import/src/import/modules/entries.ts index 4cc7174c69..09e996c2f0 100644 --- a/packages/contentstack-import/src/import/modules/entries.ts +++ b/packages/contentstack-import/src/import/modules/entries.ts @@ -57,6 +57,8 @@ export default class EntriesImport extends BaseClass { public rteCTs: any; public rteCTsWithRef: any; public entriesForVariant: Array<{ content_type: string; locale: string; entry_uid: string }> = []; + private composableStudioSuccessPath: string; + private composableStudioExportPath: string; constructor({ importConfig, stackAPIClient }: ModuleClassParams) { super({ importConfig, stackAPIClient }); @@ -92,6 +94,18 @@ export default class EntriesImport extends BaseClass { sanitizePath(importConfig.modules.locales.dirName), sanitizePath(importConfig.modules.locales.fileName), ); + this.composableStudioSuccessPath = path.join( + sanitizePath(this.importConfig.data), + 'mapper', + this.importConfig.modules['composable-studio'].dirName, + this.importConfig.modules['composable-studio'].fileName, + ); + + this.composableStudioExportPath = path.join( + sanitizePath(this.importConfig.data), + this.importConfig.modules['composable-studio'].dirName, + this.importConfig.modules['composable-studio'].fileName, + ); this.importConcurrency = this.entriesConfig.importConcurrency || importConfig.importConcurrency; this.entriesUidMapper = {}; this.modifiedCTs = []; @@ -116,6 +130,37 @@ export default class EntriesImport extends BaseClass { return; } log.debug(`Found ${this.cTs.length} content types for entry import`, this.importConfig.context); + // If success file doesn't exist but export file does, skip the composition entries + if ( + !fileHelper.fileExistsSync(this.composableStudioSuccessPath) && + fileHelper.fileExistsSync(this.composableStudioExportPath) + ) { + const exportedProject = fileHelper.readFileSync(this.composableStudioExportPath) as { + contentTypeUid: string; + }; + + if (exportedProject?.contentTypeUid) { + const originalCount = this.cTs.length; + this.cTs = this.cTs.filter((ct: Record) => { + const shouldSkip = ct.uid === exportedProject.contentTypeUid; + if (shouldSkip) { + log.info( + `Skipping entries for content type '${ct.uid}' as Composable Studio project was not created successfully`, + this.importConfig.context, + ); + } + return !shouldSkip; + }); + + const skippedCount = originalCount - this.cTs.length; + if (skippedCount > 0) { + log.debug( + `Filtered out ${skippedCount} composition content type(s) from entry import`, + this.importConfig.context, + ); + } + } + } this.installedExtensions = ( (fsUtil.readFile(this.marketplaceAppMapperPath) as any) || { extension_uid: {} } @@ -124,10 +169,7 @@ export default class EntriesImport extends BaseClass { this.assetUidMapper = (fsUtil.readFile(this.assetUidMapperPath) as Record) || {}; this.assetUrlMapper = (fsUtil.readFile(this.assetUrlMapperPath) as Record) || {}; - log.debug( - `Loaded asset mappings – UIDs: ${Object.keys(this.assetUidMapper).length}`, - this.importConfig.context, - ); + log.debug(`Loaded asset mappings – UIDs: ${Object.keys(this.assetUidMapper).length}`, this.importConfig.context); this.taxonomies = (fsUtil.readFile(this.taxonomiesPath) || {}) as Record; log.debug('Loaded taxonomy data for entry processing.', this.importConfig.context); From f51a47b83ea079778c0d7bd011194355b761299f Mon Sep 17 00:00:00 2001 From: shafeeqd959 Date: Wed, 17 Dec 2025 12:54:52 +0530 Subject: [PATCH 2/4] bumped version --- package-lock.json | 24 ++++++++++---------- packages/contentstack-bootstrap/package.json | 4 ++-- packages/contentstack-clone/package.json | 4 ++-- packages/contentstack-import/package.json | 2 +- packages/contentstack-seed/package.json | 4 ++-- packages/contentstack/package.json | 10 ++++---- pnpm-lock.yaml | 14 ++++++------ 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d31b5551a..978aec1717 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26616,21 +26616,21 @@ }, "packages/contentstack": { "name": "@contentstack/cli", - "version": "1.53.1", + "version": "1.54.0", "license": "MIT", "dependencies": { "@contentstack/cli-audit": "~1.16.2", "@contentstack/cli-auth": "~1.6.3", - "@contentstack/cli-cm-bootstrap": "~1.17.2", + "@contentstack/cli-cm-bootstrap": "~1.18.0", "@contentstack/cli-cm-branches": "~1.6.2", "@contentstack/cli-cm-bulk-publish": "~1.10.4", - "@contentstack/cli-cm-clone": "~1.18.1", + "@contentstack/cli-cm-clone": "~1.19.0", "@contentstack/cli-cm-export": "~1.22.2", "@contentstack/cli-cm-export-to-csv": "~1.10.2", - "@contentstack/cli-cm-import": "~1.30.2", + "@contentstack/cli-cm-import": "~1.31.0", "@contentstack/cli-cm-import-setup": "~1.7.2", "@contentstack/cli-cm-migrate-rte": "~1.6.3", - "@contentstack/cli-cm-seed": "~1.13.2", + "@contentstack/cli-cm-seed": "~1.14.0", "@contentstack/cli-command": "~1.7.1", "@contentstack/cli-config": "~1.16.2", "@contentstack/cli-launch": "^1.9.2", @@ -26913,10 +26913,10 @@ }, "packages/contentstack-bootstrap": { "name": "@contentstack/cli-cm-bootstrap", - "version": "1.17.2", + "version": "1.18.0", "license": "MIT", "dependencies": { - "@contentstack/cli-cm-seed": "~1.13.2", + "@contentstack/cli-cm-seed": "~1.14.0", "@contentstack/cli-command": "~1.7.1", "@contentstack/cli-utilities": "~1.16.0", "@oclif/core": "^4.3.0", @@ -27046,12 +27046,12 @@ }, "packages/contentstack-clone": { "name": "@contentstack/cli-cm-clone", - "version": "1.18.1", + "version": "1.19.0", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", "@contentstack/cli-cm-export": "~1.22.2", - "@contentstack/cli-cm-import": "~1.30.2", + "@contentstack/cli-cm-import": "~1.31.0", "@contentstack/cli-command": "~1.7.1", "@contentstack/cli-utilities": "~1.16.0", "@oclif/core": "^4.3.0", @@ -28001,7 +28001,7 @@ }, "packages/contentstack-import": { "name": "@contentstack/cli-cm-import", - "version": "1.30.2", + "version": "1.31.0", "license": "MIT", "dependencies": { "@contentstack/cli-audit": "~1.16.2", @@ -28156,10 +28156,10 @@ }, "packages/contentstack-seed": { "name": "@contentstack/cli-cm-seed", - "version": "1.13.2", + "version": "1.14.0", "license": "MIT", "dependencies": { - "@contentstack/cli-cm-import": "1.30.2", + "@contentstack/cli-cm-import": "~1.31.0", "@contentstack/cli-command": "~1.7.1", "@contentstack/cli-utilities": "~1.16.0", "@contentstack/management": "~1.22.0", diff --git a/packages/contentstack-bootstrap/package.json b/packages/contentstack-bootstrap/package.json index f1e7d0ddff..0d0ee4f8a2 100644 --- a/packages/contentstack-bootstrap/package.json +++ b/packages/contentstack-bootstrap/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-bootstrap", "description": "Bootstrap contentstack apps", - "version": "1.17.2", + "version": "1.18.0", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "scripts": { @@ -16,7 +16,7 @@ "test:report": "nyc --reporter=lcov mocha \"test/**/*.test.js\"" }, "dependencies": { - "@contentstack/cli-cm-seed": "~1.13.2", + "@contentstack/cli-cm-seed": "~1.14.0", "@contentstack/cli-command": "~1.7.1", "@contentstack/cli-utilities": "~1.16.0", "@oclif/core": "^4.3.0", diff --git a/packages/contentstack-clone/package.json b/packages/contentstack-clone/package.json index a89f54e59b..bc3443d320 100644 --- a/packages/contentstack-clone/package.json +++ b/packages/contentstack-clone/package.json @@ -1,13 +1,13 @@ { "name": "@contentstack/cli-cm-clone", "description": "Contentstack stack clone plugin", - "version": "1.18.1", + "version": "1.19.0", "author": "Contentstack", "bugs": "https://github.com/rohitmishra209/cli-cm-clone/issues", "dependencies": { "@colors/colors": "^1.6.0", "@contentstack/cli-cm-export": "~1.22.2", - "@contentstack/cli-cm-import": "~1.30.2", + "@contentstack/cli-cm-import": "~1.31.0", "@contentstack/cli-command": "~1.7.1", "@contentstack/cli-utilities": "~1.16.0", "@oclif/core": "^4.3.0", diff --git a/packages/contentstack-import/package.json b/packages/contentstack-import/package.json index 1294bd1d70..96e6cdc73f 100644 --- a/packages/contentstack-import/package.json +++ b/packages/contentstack-import/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-import", "description": "Contentstack CLI plugin to import content into stack", - "version": "1.30.2", + "version": "1.31.0", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { diff --git a/packages/contentstack-seed/package.json b/packages/contentstack-seed/package.json index 49f221aef4..c5e5b472f3 100644 --- a/packages/contentstack-seed/package.json +++ b/packages/contentstack-seed/package.json @@ -1,11 +1,11 @@ { "name": "@contentstack/cli-cm-seed", "description": "create a Stack from existing content types, entries, assets, etc.", - "version": "1.13.2", + "version": "1.14.0", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-cm-import": "1.30.2", + "@contentstack/cli-cm-import": "~1.31.0", "@contentstack/cli-command": "~1.7.1", "@contentstack/cli-utilities": "~1.16.0", "@contentstack/management": "~1.22.0", diff --git a/packages/contentstack/package.json b/packages/contentstack/package.json index 96d490059d..e2718ed94c 100755 --- a/packages/contentstack/package.json +++ b/packages/contentstack/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli", "description": "Command-line tool (CLI) to interact with Contentstack", - "version": "1.53.1", + "version": "1.54.0", "author": "Contentstack", "bin": { "csdx": "./bin/run.js" @@ -24,16 +24,16 @@ "dependencies": { "@contentstack/cli-audit": "~1.16.2", "@contentstack/cli-cm-export": "~1.22.2", - "@contentstack/cli-cm-import": "~1.30.2", + "@contentstack/cli-cm-import": "~1.31.0", "@contentstack/cli-auth": "~1.6.3", - "@contentstack/cli-cm-bootstrap": "~1.17.2", + "@contentstack/cli-cm-bootstrap": "~1.18.0", "@contentstack/cli-cm-branches": "~1.6.2", "@contentstack/cli-cm-bulk-publish": "~1.10.4", - "@contentstack/cli-cm-clone": "~1.18.1", + "@contentstack/cli-cm-clone": "~1.19.0", "@contentstack/cli-cm-export-to-csv": "~1.10.2", "@contentstack/cli-cm-import-setup": "~1.7.2", "@contentstack/cli-cm-migrate-rte": "~1.6.3", - "@contentstack/cli-cm-seed": "~1.13.2", + "@contentstack/cli-cm-seed": "~1.14.0", "@contentstack/cli-command": "~1.7.1", "@contentstack/cli-config": "~1.16.2", "@contentstack/cli-launch": "^1.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c83b19d375..99a8f71f5c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,16 +14,16 @@ importers: specifiers: '@contentstack/cli-audit': ~1.16.2 '@contentstack/cli-auth': ~1.6.3 - '@contentstack/cli-cm-bootstrap': ~1.17.2 + '@contentstack/cli-cm-bootstrap': ~1.18.0 '@contentstack/cli-cm-branches': ~1.6.2 '@contentstack/cli-cm-bulk-publish': ~1.10.4 - '@contentstack/cli-cm-clone': ~1.18.1 + '@contentstack/cli-cm-clone': ~1.19.0 '@contentstack/cli-cm-export': ~1.22.2 '@contentstack/cli-cm-export-to-csv': ~1.10.2 - '@contentstack/cli-cm-import': ~1.30.2 + '@contentstack/cli-cm-import': ~1.31.0 '@contentstack/cli-cm-import-setup': ~1.7.2 '@contentstack/cli-cm-migrate-rte': ~1.6.3 - '@contentstack/cli-cm-seed': ~1.13.2 + '@contentstack/cli-cm-seed': ~1.14.0 '@contentstack/cli-command': ~1.7.1 '@contentstack/cli-config': ~1.16.2 '@contentstack/cli-launch': ^1.9.2 @@ -243,7 +243,7 @@ importers: packages/contentstack-bootstrap: specifiers: - '@contentstack/cli-cm-seed': ~1.13.2 + '@contentstack/cli-cm-seed': ~1.14.0 '@contentstack/cli-command': ~1.7.1 '@contentstack/cli-utilities': ~1.16.0 '@oclif/core': ^4.3.0 @@ -380,7 +380,7 @@ importers: specifiers: '@colors/colors': ^1.6.0 '@contentstack/cli-cm-export': ~1.22.2 - '@contentstack/cli-cm-import': ~1.30.2 + '@contentstack/cli-cm-import': ~1.31.0 '@contentstack/cli-command': ~1.7.1 '@contentstack/cli-utilities': ~1.16.0 '@oclif/core': ^4.3.0 @@ -886,7 +886,7 @@ importers: packages/contentstack-seed: specifiers: - '@contentstack/cli-cm-import': 1.30.2 + '@contentstack/cli-cm-import': ~1.31.0 '@contentstack/cli-command': ~1.7.1 '@contentstack/cli-utilities': ~1.16.0 '@contentstack/management': ~1.22.0 From ac20d7ba8606bf7df3805d324fc4259b9bf93e68 Mon Sep 17 00:00:00 2001 From: shafeeqd959 Date: Wed, 17 Dec 2025 17:44:33 +0530 Subject: [PATCH 3/4] added composable studio validation in audit --- .talismanrc | 6 +- package-lock.json | 6 +- packages/contentstack-audit/package.json | 2 +- .../src/audit-base-command.ts | 99 +++- .../contentstack-audit/src/config/index.ts | 27 +- .../contentstack-audit/src/messages/index.ts | 3 +- .../src/modules/composable-studio.ts | 434 ++++++++++++++++++ .../contentstack-audit/src/modules/index.ts | 3 +- .../src/modules/modulesData.ts | 89 ++-- .../src/types/content-types.ts | 1 + .../unit/modules/composable-studio.test.ts | 330 +++++++++++++ packages/contentstack-import/package.json | 2 +- packages/contentstack/package.json | 2 +- pnpm-lock.yaml | 12 +- 14 files changed, 932 insertions(+), 84 deletions(-) create mode 100644 packages/contentstack-audit/src/modules/composable-studio.ts create mode 100644 packages/contentstack-audit/test/unit/modules/composable-studio.test.ts diff --git a/.talismanrc b/.talismanrc index 73a915ab0f..536d7a39d5 100644 --- a/.talismanrc +++ b/.talismanrc @@ -95,8 +95,6 @@ fileignoreconfig: checksum: bbe1130f5f5ebf2fa452daef743fe4d40ae9f8fc05c7f8c59c82a3d3d1ed69e8 - filename: packages/contentstack-audit/src/modules/extensions.ts checksum: 32af019f0df8288448d11559fe9f7ef61d3e43c3791d45eeec25fd0937c6baad - - filename: packages/contentstack-audit/src/modules/modulesData.ts - checksum: bac8f1971ac2e39bc04d9297b81951fe34ed265dfc985137135f9bbe775cd63c - filename: packages/contentstack-audit/src/modules/assets.ts checksum: 5a007804c75976dd192ed2284b7b7edbc5b5fc269fc0e883908b52e4d4f206a8 - filename: packages/contentstack-audit/src/modules/workflows.ts @@ -273,4 +271,8 @@ fileignoreconfig: checksum: 9d7df9d79cec75f238a0072bf79c4934b4724bf1466451ea6f923adfd5c0b75b - filename: packages/contentstack-utilities/test/unit/logger.test.ts checksum: a1939dea16166b1893a248179524a76f2ed20b04b99c83bd1a5a13fcf6f0dadc + - filename: packages/contentstack-audit/src/modules/modulesData.ts + checksum: 1e6c1fba1172512401038d5454c8d218201ec62262449c5c878609592e0124c4 + - filename: packages/contentstack-audit/src/modules/composable-studio.ts + checksum: e2f67d6b383415fe503ca22514fea38f53cc99647a9a73551772ab1082572cfa version: '1.0' diff --git a/package-lock.json b/package-lock.json index 978aec1717..9ae1be3971 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26619,7 +26619,7 @@ "version": "1.54.0", "license": "MIT", "dependencies": { - "@contentstack/cli-audit": "~1.16.2", + "@contentstack/cli-audit": "~1.17.0", "@contentstack/cli-auth": "~1.6.3", "@contentstack/cli-cm-bootstrap": "~1.18.0", "@contentstack/cli-cm-branches": "~1.6.2", @@ -26688,7 +26688,7 @@ }, "packages/contentstack-audit": { "name": "@contentstack/cli-audit", - "version": "1.16.2", + "version": "1.17.0", "license": "MIT", "dependencies": { "@contentstack/cli-command": "~1.7.1", @@ -28004,7 +28004,7 @@ "version": "1.31.0", "license": "MIT", "dependencies": { - "@contentstack/cli-audit": "~1.16.2", + "@contentstack/cli-audit": "~1.17.0", "@contentstack/cli-command": "~1.7.1", "@contentstack/cli-utilities": "~1.16.0", "@contentstack/cli-variants": "~1.3.6", diff --git a/packages/contentstack-audit/package.json b/packages/contentstack-audit/package.json index 20ae88b776..3527536bc6 100644 --- a/packages/contentstack-audit/package.json +++ b/packages/contentstack-audit/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/cli-audit", - "version": "1.16.2", + "version": "1.17.0", "description": "Contentstack audit plugin", "author": "Contentstack CLI", "homepage": "https://github.com/contentstack/cli", diff --git a/packages/contentstack-audit/src/audit-base-command.ts b/packages/contentstack-audit/src/audit-base-command.ts index d16236c213..402d3906ed 100644 --- a/packages/contentstack-audit/src/audit-base-command.ts +++ b/packages/contentstack-audit/src/audit-base-command.ts @@ -5,7 +5,15 @@ import { v4 as uuid } from 'uuid'; import isEmpty from 'lodash/isEmpty'; import { join, resolve } from 'path'; import cloneDeep from 'lodash/cloneDeep'; -import { cliux, sanitizePath, TableFlags, TableHeader, log, configHandler, createLogContext } from '@contentstack/cli-utilities'; +import { + cliux, + sanitizePath, + TableFlags, + TableHeader, + log, + configHandler, + createLogContext, +} from '@contentstack/cli-utilities'; import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs'; import config from './config'; import { print } from './util/log'; @@ -21,6 +29,7 @@ import { FieldRule, ModuleDataReader, CustomRoles, + ComposableStudio, } from './modules'; import { @@ -50,7 +59,6 @@ export abstract class AuditBaseCommand extends BaseCommand; + public environmentUidSet: Set; + public localeCodeSet: Set; + public projectsWithIssues: any[]; + public composableStudioPath: string; + private projectsWithIssuesMap: Map; + + constructor({ fix, config, moduleName, ctSchema }: ModuleConstructorParam & Pick) { + this.config = config; + this.fix = fix ?? false; + this.ctSchema = ctSchema; + this.composableStudioProjects = []; + + log.debug(`Initializing ComposableStudio module`, this.config.auditContext); + log.debug(`Fix mode: ${this.fix}`, this.config.auditContext); + log.debug(`Content types count: ${ctSchema.length}`, this.config.auditContext); + log.debug(`Module name: ${moduleName}`, this.config.auditContext); + + this.moduleName = this.validateModules(moduleName!, this.config.moduleConfig); + this.fileName = config.moduleConfig[this.moduleName].fileName; + log.debug(`File name: ${this.fileName}`, this.config.auditContext); + + this.folderPath = resolve( + sanitizePath(config.basePath), + sanitizePath(config.moduleConfig[this.moduleName].dirName), + ); + log.debug(`Folder path: ${this.folderPath}`, this.config.auditContext); + + this.ctUidSet = new Set(); + this.environmentUidSet = new Set(); + this.localeCodeSet = new Set(); + this.projectsWithIssues = []; + this.projectsWithIssuesMap = new Map(); + this.composableStudioPath = ''; + + log.debug(`ComposableStudio module initialization completed`, this.config.auditContext); + } + + validateModules( + moduleName: keyof typeof auditConfig.moduleConfig, + moduleConfig: Record, + ): keyof typeof auditConfig.moduleConfig { + log.debug(`Validating module: ${moduleName}`, this.config.auditContext); + log.debug(`Available modules: ${Object.keys(moduleConfig).join(', ')}`, this.config.auditContext); + + if (Object.keys(moduleConfig).includes(moduleName)) { + log.debug(`Module ${moduleName} is valid`, this.config.auditContext); + return moduleName; + } + + log.debug(`Module ${moduleName} not found, defaulting to 'composable-studio'`, this.config.auditContext); + return 'composable-studio'; + } + + /** + * Load environments from the environments.json file + */ + async loadEnvironments() { + log.debug(`Loading environments`, this.config.auditContext); + const environmentsPath = resolve(this.config.basePath, 'environments', 'environments.json'); + + if (existsSync(environmentsPath)) { + log.debug(`Environments file path: ${environmentsPath}`, this.config.auditContext); + try { + const environments = JSON.parse(readFileSync(environmentsPath, 'utf-8')); + const envArray = Array.isArray(environments) ? environments : Object.values(environments); + envArray.forEach((env: any) => { + if (env.uid) { + this.environmentUidSet.add(env.uid); + } + }); + log.debug( + `Loaded ${this.environmentUidSet.size} environments: ${Array.from(this.environmentUidSet).join(', ')}`, + this.config.auditContext, + ); + } catch (error) { + log.debug(`Failed to load environments: ${error}`, this.config.auditContext); + } + } else { + log.debug(`Environments file not found at: ${environmentsPath}`, this.config.auditContext); + } + } + + /** + * Load locales from the locales.json and master-locale.json files + */ + async loadLocales() { + log.debug(`Loading locales`, this.config.auditContext); + const localesPath = resolve(this.config.basePath, 'locales', 'locales.json'); + const masterLocalePath = resolve(this.config.basePath, 'locales', 'master-locale.json'); + + // Load master locale + if (existsSync(masterLocalePath)) { + log.debug(`Master locale file path: ${masterLocalePath}`, this.config.auditContext); + try { + const masterLocales = JSON.parse(readFileSync(masterLocalePath, 'utf-8')); + const localeArray = Array.isArray(masterLocales) ? masterLocales : Object.values(masterLocales); + localeArray.forEach((locale: any) => { + if (locale.code) { + this.localeCodeSet.add(locale.code); + } + }); + log.debug(`Loaded ${this.localeCodeSet.size} master locales`, this.config.auditContext); + } catch (error) { + log.debug(`Failed to load master locales: ${error}`, this.config.auditContext); + } + } else { + log.debug(`Master locale file not found at: ${masterLocalePath}`, this.config.auditContext); + } + + // Load additional locales + if (existsSync(localesPath)) { + log.debug(`Locales file path: ${localesPath}`, this.config.auditContext); + try { + const locales = JSON.parse(readFileSync(localesPath, 'utf-8')); + const localeArray = Array.isArray(locales) ? locales : Object.values(locales); + localeArray.forEach((locale: any) => { + if (locale.code) { + this.localeCodeSet.add(locale.code); + } + }); + log.debug( + `Total locales after loading additional locales: ${this.localeCodeSet.size}`, + this.config.auditContext, + ); + } catch (error) { + log.debug(`Failed to load additional locales: ${error}`, this.config.auditContext); + } + } else { + log.debug(`Locales file not found at: ${localesPath}`, this.config.auditContext); + } + + log.debug(`Locale codes loaded: ${Array.from(this.localeCodeSet).join(', ')}`, this.config.auditContext); + } + + /** + * Main run method to audit composable studio projects + */ + async run() { + log.debug(`Starting ${this.moduleName} audit process`, this.config.auditContext); + log.debug(`Composable Studio folder path: ${this.folderPath}`, this.config.auditContext); + log.debug(`Fix mode: ${this.fix}`, this.config.auditContext); + + if (!existsSync(this.folderPath)) { + log.debug(`Skipping ${this.moduleName} audit - path does not exist`, this.config.auditContext); + log.warn(`Skipping ${this.moduleName} audit`, this.config.auditContext); + cliux.print($t(auditMsg.NOT_VALID_PATH, { path: this.folderPath }), { color: 'yellow' }); + return {}; + } + + this.composableStudioPath = join(this.folderPath, this.fileName); + log.debug(`Composable Studio file path: ${this.composableStudioPath}`, this.config.auditContext); + + // Load composable studio projects + log.debug(`Loading composable studio projects from file`, this.config.auditContext); + if (existsSync(this.composableStudioPath)) { + try { + const projectsData = JSON.parse(readFileSync(this.composableStudioPath, 'utf-8')); + this.composableStudioProjects = Array.isArray(projectsData) ? projectsData : [projectsData]; + log.debug( + `Loaded ${this.composableStudioProjects.length} composable studio projects`, + this.config.auditContext, + ); + } catch (error) { + log.debug(`Failed to load composable studio projects: ${error}`, this.config.auditContext); + cliux.print(`Failed to parse composable studio file: ${error}`, { color: 'red' }); + return {}; + } + } else { + log.debug(`Composable studio file not found`, this.config.auditContext); + return {}; + } + + // Build content type UID set + log.debug(`Building content type UID set from ${this.ctSchema.length} content types`, this.config.auditContext); + this.ctSchema.forEach((ct) => this.ctUidSet.add(ct.uid)); + log.debug(`Content type UID set contains: ${Array.from(this.ctUidSet).join(', ')}`, this.config.auditContext); + + // Load environments and locales + await this.loadEnvironments(); + await this.loadLocales(); + + // Process each project + log.debug( + `Processing ${this.composableStudioProjects.length} composable studio projects`, + this.config.auditContext, + ); + for (const project of this.composableStudioProjects) { + const { name, uid, contentTypeUid, settings } = project; + log.debug(`Processing composable studio project: ${name} (${uid})`, this.config.auditContext); + log.debug(`Content type UID: ${contentTypeUid}`, this.config.auditContext); + log.debug(`Environment: ${settings?.configuration?.environment}`, this.config.auditContext); + log.debug(`Locale: ${settings?.configuration?.locale}`, this.config.auditContext); + + let hasIssues = false; + const issuesList: string[] = []; + const invalidContentTypes: string[] = []; + const invalidEnvironments: string[] = []; + const invalidLocales: string[] = []; + + // Check content type + if (contentTypeUid && !this.ctUidSet.has(contentTypeUid)) { + log.debug(`Content type ${contentTypeUid} not found in project ${name}`, this.config.auditContext); + invalidContentTypes.push(contentTypeUid); + issuesList.push(`Invalid contentTypeUid: ${contentTypeUid}`); + hasIssues = true; + } + + // Check environment + if (settings?.configuration?.environment && !this.environmentUidSet.has(settings.configuration.environment)) { + log.debug( + `Environment ${settings.configuration.environment} not found in project ${name}`, + this.config.auditContext, + ); + invalidEnvironments.push(settings.configuration.environment); + issuesList.push(`Invalid environment: ${settings.configuration.environment}`); + hasIssues = true; + } + + // Check locale + if (settings?.configuration?.locale && !this.localeCodeSet.has(settings.configuration.locale)) { + log.debug(`Locale ${settings.configuration.locale} not found in project ${name}`, this.config.auditContext); + invalidLocales.push(settings.configuration.locale); + issuesList.push(`Invalid locale: ${settings.configuration.locale}`); + hasIssues = true; + } + + if (hasIssues) { + log.debug(`Project ${name} has validation issues`, this.config.auditContext); + // Store the original project for fixing + this.projectsWithIssuesMap.set(uid, project); + + // Create a report-friendly object + const reportEntry: any = { + title: name, + name: name, + uid: uid, + content_types: invalidContentTypes.length > 0 ? invalidContentTypes : undefined, + environment: invalidEnvironments.length > 0 ? invalidEnvironments : undefined, + locale: invalidLocales.length > 0 ? invalidLocales : undefined, + issues: issuesList.join(', '), + }; + this.projectsWithIssues.push(reportEntry); + } else { + log.debug(`Project ${name} has no validation issues`, this.config.auditContext); + } + + log.info( + $t(auditMsg.SCAN_CS_SUCCESS_MSG, { + name, + uid, + }), + this.config.auditContext, + ); + } + + log.debug( + `Composable Studio audit completed. Found ${this.projectsWithIssues.length} projects with issues`, + this.config.auditContext, + ); + + if (this.fix && this.projectsWithIssues.length) { + log.debug(`Fix mode enabled, fixing ${this.projectsWithIssues.length} projects`, this.config.auditContext); + await this.fixComposableStudioProjects(); + this.projectsWithIssues.forEach((project) => { + log.debug(`Marking project ${project.name} as fixed`, this.config.auditContext); + project.fixStatus = 'Fixed'; + }); + log.debug(`Composable Studio fix completed`, this.config.auditContext); + return this.projectsWithIssues; + } + + log.debug(`Composable Studio audit completed without fixes`, this.config.auditContext); + return this.projectsWithIssues; + } + + /** + * Fix composable studio projects by removing invalid references + */ + async fixComposableStudioProjects() { + log.debug(`Starting composable studio projects fix`, this.config.auditContext); + + log.debug( + `Loading current composable studio projects from: ${this.composableStudioPath}`, + this.config.auditContext, + ); + let projectsData: any; + try { + projectsData = JSON.parse(readFileSync(this.composableStudioPath, 'utf-8')); + } catch (error) { + log.debug(`Failed to load composable studio projects for fixing: ${error}`, this.config.auditContext); + return; + } + + const isArray = Array.isArray(projectsData); + const projects: ComposableStudioProject[] = isArray ? projectsData : [projectsData]; + + log.debug(`Loaded ${projects.length} projects for fixing`, this.config.auditContext); + + for (let i = 0; i < projects.length; i++) { + const project = projects[i]; + const { uid, name } = project; + log.debug(`Fixing project: ${name} (${uid})`, this.config.auditContext); + + let needsFix = false; + + // Check and fix content type + if (project.contentTypeUid && !this.ctUidSet.has(project.contentTypeUid)) { + log.debug( + `Removing invalid content type ${project.contentTypeUid} from project ${name}`, + this.config.auditContext, + ); + cliux.print( + `Warning: Project "${name}" has invalid content type "${project.contentTypeUid}". It will be removed.`, + { color: 'yellow' }, + ); + (project as any).contentTypeUid = undefined; + needsFix = true; + } + + // Check and fix environment + if ( + project.settings?.configuration?.environment && + !this.environmentUidSet.has(project.settings.configuration.environment) + ) { + log.debug( + `Removing invalid environment ${project.settings.configuration.environment} from project ${name}`, + this.config.auditContext, + ); + cliux.print( + `Warning: Project "${name}" has invalid environment "${project.settings.configuration.environment}". It will be removed.`, + { color: 'yellow' }, + ); + (project.settings.configuration as any).environment = undefined; + needsFix = true; + } + + // Check and fix locale + if (project.settings?.configuration?.locale && !this.localeCodeSet.has(project.settings.configuration.locale)) { + log.debug( + `Removing invalid locale ${project.settings.configuration.locale} from project ${name}`, + this.config.auditContext, + ); + cliux.print( + `Warning: Project "${name}" has invalid locale "${project.settings.configuration.locale}". It will be removed.`, + { color: 'yellow' }, + ); + (project.settings.configuration as any).locale = undefined; + needsFix = true; + } + + if (needsFix) { + log.debug(`Project ${name} was fixed`, this.config.auditContext); + } else { + log.debug(`Project ${name} did not need fixing`, this.config.auditContext); + } + } + + log.debug(`Composable studio projects fix completed, writing updated file`, this.config.auditContext); + await this.writeFixContent(isArray ? projects : projects[0]); + } + + /** + * Write fixed composable studio projects back to file + */ + async writeFixContent(fixedProjects: any) { + log.debug(`Writing fix content`, this.config.auditContext); + log.debug(`Fix mode: ${this.fix}`, this.config.auditContext); + log.debug(`Copy directory flag: ${this.config.flags['copy-dir']}`, this.config.auditContext); + log.debug( + `External config skip confirm: ${this.config.flags['external-config']?.skipConfirm}`, + this.config.auditContext, + ); + log.debug(`Yes flag: ${this.config.flags.yes}`, this.config.auditContext); + + if ( + this.fix && + (this.config.flags['copy-dir'] || + this.config.flags['external-config']?.skipConfirm || + this.config.flags.yes || + (await cliux.confirm(commonMsg.FIX_CONFIRMATION))) + ) { + const outputPath = join(this.folderPath, this.config.moduleConfig[this.moduleName].fileName); + log.debug(`Writing fixed composable studio projects to: ${outputPath}`, this.config.auditContext); + + writeFileSync(outputPath, JSON.stringify(fixedProjects, null, 2)); + log.debug(`Successfully wrote fixed composable studio projects to file`, this.config.auditContext); + } else { + log.debug(`Skipping file write - fix mode disabled or user declined confirmation`, this.config.auditContext); + } + } +} diff --git a/packages/contentstack-audit/src/modules/index.ts b/packages/contentstack-audit/src/modules/index.ts index c76eca4a72..2a09f77621 100644 --- a/packages/contentstack-audit/src/modules/index.ts +++ b/packages/contentstack-audit/src/modules/index.ts @@ -7,5 +7,6 @@ import CustomRoles from './custom-roles'; import Assets from './assets'; import FieldRule from './field_rules'; import ModuleDataReader from './modulesData'; +import ComposableStudio from './composable-studio'; -export { Entries, GlobalField, ContentType, Workflows, Extensions, Assets, CustomRoles, FieldRule, ModuleDataReader }; +export { Entries, GlobalField, ContentType, Workflows, Extensions, Assets, CustomRoles, FieldRule, ModuleDataReader, ComposableStudio }; diff --git a/packages/contentstack-audit/src/modules/modulesData.ts b/packages/contentstack-audit/src/modules/modulesData.ts index 84f0d0e5cf..dc638c66c1 100644 --- a/packages/contentstack-audit/src/modules/modulesData.ts +++ b/packages/contentstack-audit/src/modules/modulesData.ts @@ -1,15 +1,9 @@ import { join, resolve } from 'path'; import { existsSync, readFileSync } from 'fs'; import { FsUtility, sanitizePath, log } from '@contentstack/cli-utilities'; -import { - ConfigType, - ContentTypeStruct, - CtConstructorParam, - ModuleConstructorParam, -} from '../types'; +import { ConfigType, ContentTypeStruct, CtConstructorParam, ModuleConstructorParam } from '../types'; import { keys, values } from 'lodash'; - export default class ModuleDataReader { public config: ConfigType; public folderPath: string; @@ -25,14 +19,14 @@ export default class ModuleDataReader { this.config = config; this.ctSchema = ctSchema; this.gfSchema = gfSchema; - + log.debug(`Initializing ModuleDataReader`, this.config.auditContext); log.debug(`Content types count: ${ctSchema.length}`, this.config.auditContext); log.debug(`Global fields count: ${gfSchema.length}`, this.config.auditContext); - + this.folderPath = resolve(sanitizePath(config.basePath)); log.debug(`Folder path: ${this.folderPath}`, this.config.auditContext); - + log.debug(`ModuleDataReader initialization completed`, this.config.auditContext); } @@ -40,7 +34,7 @@ export default class ModuleDataReader { log.debug(`Getting item count for module: ${moduleName}`, this.config.auditContext); let count = 0; switch (moduleName) { - case "content-types": + case 'content-types': log.debug(`Counting content types`, this.config.auditContext); count = this.ctSchema.length; log.debug(`Content types count: ${count}`, this.config.auditContext); @@ -54,7 +48,7 @@ export default class ModuleDataReader { log.debug(`Counting assets`, this.config.auditContext); const assetsPath = join(this.folderPath, 'assets'); log.debug(`Assets path: ${assetsPath}`, this.config.auditContext); - count = await this.readEntryAssetsModule(assetsPath,'assets') || 0; + count = (await this.readEntryAssetsModule(assetsPath, 'assets')) || 0; log.debug(`Assets count: ${count}`, this.config.auditContext); break; } @@ -64,31 +58,42 @@ export default class ModuleDataReader { const localesFolderPath = resolve(this.config.basePath, this.config.moduleConfig.locales.dirName); const localesPath = join(localesFolderPath, this.config.moduleConfig.locales.fileName); const masterLocalesPath = join(localesFolderPath, 'master-locale.json'); - + log.debug(`Locales folder path: ${localesFolderPath}`, this.config.auditContext); log.debug(`Locales path: ${localesPath}`, this.config.auditContext); log.debug(`Master locales path: ${masterLocalesPath}`, this.config.auditContext); - + log.debug(`Loading master locales`, this.config.auditContext); this.locales = values(await this.readUsingFsModule(masterLocalesPath)); - log.debug(`Loaded ${this.locales.length} master locales: ${this.locales.map(locale => locale.code).join(', ')}`, this.config.auditContext); + log.debug( + `Loaded ${this.locales.length} master locales: ${this.locales.map((locale) => locale.code).join(', ')}`, + this.config.auditContext, + ); if (existsSync(localesPath)) { log.debug(`Loading additional locales from file`, this.config.auditContext); this.locales.push(...values(JSON.parse(readFileSync(localesPath, 'utf8')))); - log.debug(`Total locales after loading: ${this.locales.length} - ${this.locales.map(locale => locale.code).join(', ')}`, this.config.auditContext); + log.debug( + `Total locales after loading: ${this.locales.length} - ${this.locales + .map((locale) => locale.code) + .join(', ')}`, + this.config.auditContext, + ); } else { log.debug(`Additional locales file not found`, this.config.auditContext); } - - log.debug(`Processing ${this.locales.length} locales and ${this.ctSchema.length} content types`, this.config.auditContext); - for (const {code} of this.locales) { + + log.debug( + `Processing ${this.locales.length} locales and ${this.ctSchema.length} content types`, + this.config.auditContext, + ); + for (const { code } of this.locales) { log.debug(`Processing locale: ${code}`, this.config.auditContext); for (const ctSchema of this.ctSchema) { log.debug(`Processing content type: ${ctSchema.uid}`, this.config.auditContext); - const basePath = join(this.folderPath,'entries', ctSchema.uid, code); + const basePath = join(this.folderPath, 'entries', ctSchema.uid, code); log.debug(`Base path: ${basePath}`, this.config.auditContext); - const entryCount = await this.readEntryAssetsModule(basePath, 'index') || 0; + const entryCount = (await this.readEntryAssetsModule(basePath, 'index')) || 0; log.debug(`Found ${entryCount} entries for ${ctSchema.uid} in ${code}`, this.config.auditContext); count = count + entryCount; } @@ -98,7 +103,8 @@ export default class ModuleDataReader { break; case 'custom-roles': case 'extensions': - case 'workflows': { + case 'workflows': + case 'composable-studio': { log.debug(`Counting ${moduleName}`, this.config.auditContext); const modulePath = resolve( this.folderPath, @@ -106,43 +112,50 @@ export default class ModuleDataReader { sanitizePath(this.config.moduleConfig[moduleName].fileName), ); log.debug(`Reading module: ${moduleName} from file: ${modulePath}`, this.config.auditContext); - + const moduleData = await this.readUsingFsModule(modulePath); - count = keys(moduleData).length; + // For composable-studio, it could be a single object or an array + if (moduleName === 'composable-studio') { + count = Array.isArray(moduleData) ? moduleData.length : Object.keys(moduleData).length > 0 ? 1 : 0; + } else { + count = keys(moduleData).length; + } log.debug(`module:${moduleName} count: ${count}`, this.config.auditContext); break; } } - + log.debug(`Module ${moduleName} item count: ${count}`, this.config.auditContext); return count; - } - async readUsingFsModule(path: string): Promise>{ + async readUsingFsModule(path: string): Promise> { log.debug(`Reading file: ${path}`, this.config.auditContext); - - const data = existsSync(path) ? (JSON.parse(readFileSync(path, 'utf-8'))) : []; - log.debug(`File ${existsSync(path) ? 'exists' : 'not found'}, data type: ${Array.isArray(data) ? 'array' : 'object'}`, this.config.auditContext); - + + const data = existsSync(path) ? JSON.parse(readFileSync(path, 'utf-8')) : []; + log.debug( + `File ${existsSync(path) ? 'exists' : 'not found'}, data type: ${Array.isArray(data) ? 'array' : 'object'}`, + this.config.auditContext, + ); + if (existsSync(path)) { const dataSize = Array.isArray(data) ? data.length : Object.keys(data).length; log.debug(`Loaded ${dataSize} items from file`, this.config.auditContext); } else { log.debug(`Returning empty array for non-existent file`, this.config.auditContext); } - + return data; } async readEntryAssetsModule(basePath: string, module: string): Promise { log.debug(`Reading entry/assets module: ${module}`, this.config.auditContext); log.debug(`Base path: ${basePath}`, this.config.auditContext); - + let fsUtility = new FsUtility({ basePath, indexFileName: `${module}.json` }); let indexer = fsUtility.indexFileContent; log.debug(`Found ${Object.keys(indexer).length} index files`, this.config.auditContext); - + let count = 0; for (const _ in indexer) { log.debug(`Reading chunk file`, this.config.auditContext); @@ -151,7 +164,7 @@ export default class ModuleDataReader { log.debug(`Loaded ${chunkCount} items from chunk`, this.config.auditContext); count = count + chunkCount; } - + log.debug(`Total ${module} count: ${count}`, this.config.auditContext); return count; } @@ -159,16 +172,16 @@ export default class ModuleDataReader { async run(): Promise { log.debug(`Starting ModuleDataReader run process`, this.config.auditContext); log.debug(`Available modules: ${Object.keys(this.config.moduleConfig).join(', ')}`, this.config.auditContext); - + await Promise.allSettled( Object.keys(this.config.moduleConfig).map(async (module) => { log.debug(`Processing module: ${module}`, this.config.auditContext); const count = await this.getModuleItemCount(module); this.auditData[module] = { Total: count }; log.debug(`Module ${module} processed with count: ${count}`, this.config.auditContext); - }) + }), ); - + log.debug(`ModuleDataReader run completed`, this.config.auditContext); log.debug(`Audit data: ${JSON.stringify(this.auditData)}`, this.config.auditContext); return this.auditData; diff --git a/packages/contentstack-audit/src/types/content-types.ts b/packages/contentstack-audit/src/types/content-types.ts index fcc9b1d866..2a27e92af7 100644 --- a/packages/contentstack-audit/src/types/content-types.ts +++ b/packages/contentstack-audit/src/types/content-types.ts @@ -175,6 +175,7 @@ enum OutputColumn { "Non-Fixable"="Non-Fixable", "Fixed" = "Fixed", "Not-Fixed" = "Not-Fixed", + "Issues" = "issues", } export { diff --git a/packages/contentstack-audit/test/unit/modules/composable-studio.test.ts b/packages/contentstack-audit/test/unit/modules/composable-studio.test.ts new file mode 100644 index 0000000000..a5830a3dd4 --- /dev/null +++ b/packages/contentstack-audit/test/unit/modules/composable-studio.test.ts @@ -0,0 +1,330 @@ +import { resolve } from 'path'; +import { fancy } from 'fancy-test'; +import { expect } from 'chai'; +import cloneDeep from 'lodash/cloneDeep'; +import { ux } from '@contentstack/cli-utilities'; +import sinon from 'sinon'; + +import config from '../../../src/config'; +import { ComposableStudio } from '../../../src/modules'; +import { mockLogger } from '../mock-logger'; + +describe('ComposableStudio', () => { + beforeEach(() => { + // Mock the logger for all tests + sinon.stub(require('@contentstack/cli-utilities'), 'log').value(mockLogger); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('run method with invalid path for composable-studio', () => { + fancy + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(ux, 'confirm', async () => true) + .it('Should validate the base path for composable-studio', async () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { basePath: resolve(__dirname, '..', 'mock', 'invalid_path'), flags: {} }), + }); + const result = await cs.run(); + expect(result).to.eql({}); + }); + }); + + describe('run method with valid path and valid composable-studio project', () => { + fancy + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(ux, 'confirm', async () => true) + .it('should load projects and report issues if references are invalid', async () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents/`), + flags: {}, + }), + }); + + const missingRefs: any = await cs.run(); + expect(cs.composableStudioProjects).to.have.lengthOf(1); + expect(cs.composableStudioProjects[0].uid).to.equal('test_project_uid_1'); + expect(Array.isArray(missingRefs)).to.be.true; + }); + }); + + describe('run method with invalid composable-studio projects', () => { + fancy + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(ux, 'confirm', async () => true) + .it('should detect invalid references', async () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents/`), + flags: {}, + }), + }); + + // Mock readFileSync to return invalid data + const originalReadFileSync = require('fs').readFileSync; + const invalidProjects = require('./../mock/contents/composable_studio/invalid_composable_studio.json'); + + sinon.stub(require('fs'), 'readFileSync').callsFake((...args: any[]) => { + const path = args[0]; + if (path.includes('composable_studio.json')) { + return JSON.stringify(invalidProjects); + } + return originalReadFileSync(...args); + }); + + const missingRefs: any = await cs.run(); + + expect(cs.composableStudioProjects).to.have.lengthOf(4); + expect(cs.projectsWithIssues).to.have.lengthOf(4); + expect(Array.isArray(missingRefs)).to.be.true; + expect(missingRefs).to.have.lengthOf(4); + + // Check first project - invalid content type + const project1 = missingRefs.find((p: any) => p.uid === 'test_project_uid_2'); + expect(project1).to.exist; + expect(project1.content_types).to.deep.equal(['invalid_ct_999']); + expect(project1.issues).to.include('Invalid contentTypeUid: invalid_ct_999'); + + // Check second project - invalid environment + const project2 = missingRefs.find((p: any) => p.uid === 'test_project_uid_3'); + expect(project2).to.exist; + expect(project2.environment).to.deep.equal(['invalid_env_999']); + expect(project2.issues).to.include('Invalid environment: invalid_env_999'); + + // Check third project - invalid locale + const project3 = missingRefs.find((p: any) => p.uid === 'test_project_uid_4'); + expect(project3).to.exist; + expect(project3.locale).to.deep.equal(['invalid_locale_999']); + expect(project3.issues).to.include('Invalid locale: invalid_locale_999'); + + // Check fourth project - multiple issues + const project4 = missingRefs.find((p: any) => p.uid === 'test_project_uid_5'); + expect(project4).to.exist; + expect(project4.content_types).to.deep.equal(['invalid_ct_888']); + expect(project4.environment).to.deep.equal(['invalid_env_888']); + expect(project4.locale).to.deep.equal(['invalid_locale_888']); + expect(project4.issues).to.include('Invalid contentTypeUid: invalid_ct_888'); + expect(project4.issues).to.include('Invalid environment: invalid_env_888'); + expect(project4.issues).to.include('Invalid locale: invalid_locale_888'); + }); + }); + + describe('loadEnvironments method', () => { + fancy + .stdout({ print: process.env.PRINT === 'true' || false }) + .it('should load environments correctly', async () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents/composable_studio`), + flags: {}, + }), + }); + await cs.loadEnvironments(); + expect(cs.environmentUidSet.size).to.equal(2); + expect(cs.environmentUidSet.has('blt_env_dev')).to.be.true; + expect(cs.environmentUidSet.has('blt_env_prod')).to.be.true; + }); + }); + + describe('loadLocales method', () => { + fancy + .stdout({ print: process.env.PRINT === 'true' || false }) + .it('should load locales correctly', async () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents/composable_studio`), + flags: {}, + }), + }); + await cs.loadLocales(); + expect(cs.localeCodeSet.size).to.equal(3); // en-us (master) + fr-fr + de-de + expect(cs.localeCodeSet.has('en-us')).to.be.true; + expect(cs.localeCodeSet.has('fr-fr')).to.be.true; + expect(cs.localeCodeSet.has('de-de')).to.be.true; + }); + }); + + describe('run method with audit fix for composable-studio', () => { + fancy + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(ux, 'confirm', async () => true) + .it('should fix invalid projects and return fixed references', async () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents/`), + flags: { 'copy-dir': true }, + }), + fix: true, + }); + + // Mock readFileSync to return invalid data + const originalReadFileSync = require('fs').readFileSync; + const invalidProjects = require('./../mock/contents/composable_studio/invalid_composable_studio.json'); + + sinon.stub(require('fs'), 'readFileSync').callsFake((...args: any[]) => { + const path = args[0]; + if (path.includes('composable_studio.json')) { + return JSON.stringify(invalidProjects); + } + return originalReadFileSync(...args); + }); + + sinon.stub(cs, 'writeFixContent').resolves(); + + const fixedReferences: any = await cs.run(); + + expect(Array.isArray(fixedReferences)).to.be.true; + expect(fixedReferences.length).to.be.greaterThan(0); + + // All projects should have fixStatus set + fixedReferences.forEach((ref: any) => { + expect(ref.fixStatus).to.equal('Fixed'); + }); + + // Check that projects with issues were identified + expect(cs.projectsWithIssues.length).to.be.greaterThan(0); + }); + }); + + describe('validateModules method', () => { + it('should validate correct module name', () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents/composable_studio`), + flags: {}, + }), + }); + const result = cs.validateModules('composable-studio', config.moduleConfig); + expect(result).to.equal('composable-studio'); + }); + + it('should return default module name for invalid module', () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents/composable_studio`), + flags: {}, + }), + }); + const result = cs.validateModules('invalid-module' as any, config.moduleConfig); + expect(result).to.equal('composable-studio'); + }); + }); + + describe('Content type validation', () => { + fancy + .stdout({ print: process.env.PRINT === 'true' || false }) + .it('should build content type UID set correctly', async () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents/`), + flags: {}, + }), + }); + await cs.run(); + expect(cs.ctUidSet.size).to.equal(3); + expect(cs.ctUidSet.has('page_1')).to.be.true; + expect(cs.ctUidSet.has('page_2')).to.be.true; + expect(cs.ctUidSet.has('page_3')).to.be.true; + }); + }); + + describe('Report data structure', () => { + fancy + .stdout({ print: process.env.PRINT === 'true' || false }) + .stub(ux, 'confirm', async () => true) + .it('should return properly formatted report data', async () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents/`), + flags: {}, + }), + }); + + // Mock readFileSync to return invalid data + const originalReadFileSync = require('fs').readFileSync; + const invalidProjects = require('./../mock/contents/composable_studio/invalid_composable_studio.json'); + + sinon.stub(require('fs'), 'readFileSync').callsFake((...args: any[]) => { + const path = args[0]; + if (path.includes('composable_studio.json')) { + return JSON.stringify(invalidProjects); + } + return originalReadFileSync(...args); + }); + + const missingRefs: any = await cs.run(); + + expect(Array.isArray(missingRefs)).to.be.true; + expect(missingRefs.length).to.be.greaterThan(0); + + // Check that all report entries have required fields + missingRefs.forEach((ref: any) => { + expect(ref).to.have.property('title'); + expect(ref).to.have.property('name'); + expect(ref).to.have.property('uid'); + expect(ref).to.have.property('issues'); + }); + + // Check that issues field contains descriptive text + const projectWithCTIssue = missingRefs.find((ref: any) => ref.content_types); + if (projectWithCTIssue) { + expect(projectWithCTIssue.issues).to.be.a('string'); + expect(projectWithCTIssue.issues).to.include('contentTypeUid'); + } + }); + }); + + describe('Empty and edge cases', () => { + it('should handle empty content type schema gracefully', async () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: [], + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents/composable_studio`), + flags: {}, + }), + }); + + await cs.run(); + expect(cs.ctUidSet.size).to.equal(0); + }); + + it('should handle missing composable_studio.json file', async () => { + const cs = new ComposableStudio({ + moduleName: 'composable-studio', + ctSchema: cloneDeep(require('./../mock/contents/composable_studio/ctSchema.json')), + config: Object.assign(config, { + basePath: resolve(`./test/unit/mock/contents`), + flags: {}, + }), + }); + + const result = await cs.run(); + // When the file exists and has projects with validation issues, it returns an array + expect(result).to.exist; + }); + }); +}); diff --git a/packages/contentstack-import/package.json b/packages/contentstack-import/package.json index 96e6cdc73f..89dafc7a5f 100644 --- a/packages/contentstack-import/package.json +++ b/packages/contentstack-import/package.json @@ -5,7 +5,7 @@ "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { - "@contentstack/cli-audit": "~1.16.2", + "@contentstack/cli-audit": "~1.17.0", "@contentstack/cli-command": "~1.7.1", "@contentstack/cli-utilities": "~1.16.0", "@contentstack/management": "~1.22.0", diff --git a/packages/contentstack/package.json b/packages/contentstack/package.json index e2718ed94c..272cadd25c 100755 --- a/packages/contentstack/package.json +++ b/packages/contentstack/package.json @@ -22,7 +22,7 @@ "prepack": "pnpm compile && oclif manifest && oclif readme" }, "dependencies": { - "@contentstack/cli-audit": "~1.16.2", + "@contentstack/cli-audit": "~1.17.0", "@contentstack/cli-cm-export": "~1.22.2", "@contentstack/cli-cm-import": "~1.31.0", "@contentstack/cli-auth": "~1.6.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99a8f71f5c..cd800a357b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ importers: packages/contentstack: specifiers: - '@contentstack/cli-audit': ~1.16.2 + '@contentstack/cli-audit': ~1.17.0 '@contentstack/cli-auth': ~1.6.3 '@contentstack/cli-cm-bootstrap': ~1.18.0 '@contentstack/cli-cm-branches': ~1.6.2 @@ -652,7 +652,7 @@ importers: packages/contentstack-import: specifiers: - '@contentstack/cli-audit': ~1.16.2 + '@contentstack/cli-audit': ~1.17.0 '@contentstack/cli-command': ~1.7.1 '@contentstack/cli-utilities': ~1.16.0 '@contentstack/cli-variants': ~1.3.6 @@ -8735,7 +8735,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 8.50.0_avq3eyf5kaj6ssrwo7fvkrwnji + '@typescript-eslint/parser': 8.50.0_k2rwabtyo525wwqr6566umnmhy debug: 3.2.7 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 @@ -8765,7 +8765,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 6.21.0_avq3eyf5kaj6ssrwo7fvkrwnji + '@typescript-eslint/parser': 6.21.0_k2rwabtyo525wwqr6566umnmhy debug: 3.2.7 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 @@ -8861,7 +8861,7 @@ packages: optional: true dependencies: '@rtsao/scc': 1.1.0 - '@typescript-eslint/parser': 6.21.0_avq3eyf5kaj6ssrwo7fvkrwnji + '@typescript-eslint/parser': 6.21.0_k2rwabtyo525wwqr6566umnmhy array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 @@ -8898,7 +8898,7 @@ packages: optional: true dependencies: '@rtsao/scc': 1.1.0 - '@typescript-eslint/parser': 8.50.0_avq3eyf5kaj6ssrwo7fvkrwnji + '@typescript-eslint/parser': 8.50.0_k2rwabtyo525wwqr6566umnmhy array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 From 77eedb4b210ca5e453cc973795dfb9cf38b719eb Mon Sep 17 00:00:00 2001 From: shafeeqd959 Date: Thu, 18 Dec 2025 12:44:23 +0530 Subject: [PATCH 4/4] updated test cases --- .talismanrc | 2 +- .../unit/import/modules/content-types.test.ts | 4 + .../test/unit/import/modules/entries.test.ts | 1368 +++++++++-------- 3 files changed, 741 insertions(+), 633 deletions(-) diff --git a/.talismanrc b/.talismanrc index 536d7a39d5..678ce7be82 100644 --- a/.talismanrc +++ b/.talismanrc @@ -116,7 +116,7 @@ fileignoreconfig: - filename: packages/contentstack-import/test/unit/import/modules/mock-data/entries/environments.json checksum: 17f94f500dcb265575b60f8d2cb7464372a234e452527b3bdec6052c606cee28 - filename: packages/contentstack-import/test/unit/import/modules/entries.test.ts - checksum: 7b984d292a534f9d075d801de2aeff802b2832bc5e2efadf8613a7059f4317fc + checksum: d8e4f6ad185b36b6f84b38dce169144e7d5a195668aac11f914eed5e1e4b5478 - filename: packages/contentstack-import/test/unit/import/modules/labels.test.ts checksum: 46fe0d1602ab386f7eaee9839bc376b98ab8d4262f823784eda9cfa2bf893758 - filename: packages/contentstack-export/test/unit/export/modules/assets.test.ts diff --git a/packages/contentstack-import/test/unit/import/modules/content-types.test.ts b/packages/contentstack-import/test/unit/import/modules/content-types.test.ts index 09036ba4e3..5f9989289f 100644 --- a/packages/contentstack-import/test/unit/import/modules/content-types.test.ts +++ b/packages/contentstack-import/test/unit/import/modules/content-types.test.ts @@ -82,6 +82,10 @@ describe('ImportContentTypes', () => { writeConcurrency: 1, fileName: 'globalfields.json', limit: 100 + }, + 'composable-studio': { + dirName: 'composable_studio', + fileName: 'composable_studio.json' } }, backupDir: '/test/backup', diff --git a/packages/contentstack-import/test/unit/import/modules/entries.test.ts b/packages/contentstack-import/test/unit/import/modules/entries.test.ts index 3815ff8e53..eaaa2ba6f1 100644 --- a/packages/contentstack-import/test/unit/import/modules/entries.test.ts +++ b/packages/contentstack-import/test/unit/import/modules/entries.test.ts @@ -6,7 +6,6 @@ import { FsUtility } from '@contentstack/cli-utilities'; import { fsUtil, fileHelper } from '../../../../src/utils'; import * as path from 'path'; - const mockData = require('./mock-data/entries/content-types.json'); const mockEntries = require('./mock-data/entries/entries.json'); const mockLocales = require('./mock-data/entries/locales.json'); @@ -49,12 +48,12 @@ describe('EntriesImport', () => { delete: sinon.stub().resolves({ uid: 'deleted-entry-uid' }), publish: sinon.stub().resolves({ uid: 'published-entry-uid' }), query: sinon.stub().returns({ - findOne: sinon.stub().resolves({ items: [{ uid: 'existing-entry-uid', title: 'Existing Entry' }] }) - }) + findOne: sinon.stub().resolves({ items: [{ uid: 'existing-entry-uid', title: 'Existing Entry' }] }), + }), }), fetch: sinon.stub().resolves({ uid: 'ct-uid', schema: [] }), - update: sinon.stub().resolves({ uid: 'updated-ct-uid' }) - }) + update: sinon.stub().resolves({ uid: 'updated-ct-uid' }), + }), }; mockImportConfig = { @@ -73,23 +72,27 @@ describe('EntriesImport', () => { sessionId: 'session-123', apiKey: 'test', orgId: 'org-123', - authenticationMethod: 'Basic Auth' + authenticationMethod: 'Basic Auth', }, modules: { types: ['entries'], - entries: { + entries: { dirName: 'entries', chunkFileSize: 100, invalidKeys: ['_version', 'created_at', 'updated_at'], - importConcurrency: 5 + importConcurrency: 5, }, 'content-types': { - dirName: 'content_types' + dirName: 'content_types', }, locales: { dirName: 'locales', - fileName: 'locales.json' - } + fileName: 'locales.json', + }, + 'composable-studio': { + dirName: 'composable_studio', + fileName: 'composable_studio.json', + }, }, backupDir: '/test/backup', cliLogsPath: '/test/logs', @@ -103,13 +106,13 @@ describe('EntriesImport', () => { skipEntriesPublish: false, 'exclude-global-modules': false, replaceExisting: false, - importConcurrency: 5 + importConcurrency: 5, } as any; entriesImport = new EntriesImport({ importConfig: mockImportConfig as any, stackAPIClient: mockStackClient, - moduleName: 'entries' + moduleName: 'entries', }); makeConcurrentCallStub = sinon.stub(entriesImport as any, 'makeConcurrentCall').resolves(); @@ -171,16 +174,16 @@ describe('EntriesImport', () => { entries: { dirName: 'entries', chunkFileSize: 100, - invalidKeys: ['_version', 'created_at', 'updated_at'] + invalidKeys: ['_version', 'created_at', 'updated_at'], // No importConcurrency - } - } + }, + }, }; const entriesImportFallback = new EntriesImport({ importConfig: configWithoutEntriesConcurrency as any, stackAPIClient: mockStackClient, - moduleName: 'entries' + moduleName: 'entries', }); expect(entriesImportFallback['importConcurrency']).to.equal(5); @@ -193,7 +196,7 @@ describe('EntriesImport', () => { mockData.simpleContentType, mockData.contentTypeWithReferences, mockData.contentTypeWithJsonRte, - mockData.contentTypeWithAssets + mockData.contentTypeWithAssets, ]; entriesImport['installedExtensions'] = mockMappers.installedExtensions; }); @@ -204,7 +207,7 @@ describe('EntriesImport', () => { const onSuccess = options.apiParams.resolve; onSuccess({ response: { uid: 'ct-uid' }, - apiData: { uid: 'ct-uid' } + apiData: { uid: 'ct-uid' }, }); }); @@ -219,7 +222,7 @@ describe('EntriesImport', () => { const onReject = options.apiParams.reject; onReject({ error: new Error('Content type processing failed'), - apiData: { uid: 'ct-uid' } + apiData: { uid: 'ct-uid' }, }); }); @@ -239,16 +242,16 @@ describe('EntriesImport', () => { uid: 'mandatory_field', data_type: 'text', display_name: 'Mandatory Field', - mandatory: true - } - ] + mandatory: true, + }, + ], }; const apiOptions = { apiData: contentTypeWithReferences, entity: 'update-cts' as const, resolve: sinon.stub(), - reject: sinon.stub() + reject: sinon.stub(), }; const result = entriesImport['serializeUpdateCTs'](apiOptions); @@ -267,16 +270,16 @@ describe('EntriesImport', () => { uid: 'mandatory_field', data_type: 'text', display_name: 'Mandatory Field', - mandatory: true - } - ] + mandatory: true, + }, + ], }; const apiOptions = { apiData: contentTypeWithJsonRte, entity: 'update-cts' as const, resolve: sinon.stub(), - reject: sinon.stub() + reject: sinon.stub(), }; const result = entriesImport['serializeUpdateCTs'](apiOptions); @@ -297,7 +300,7 @@ describe('EntriesImport', () => { data_type: 'text', display_name: 'Title', mandatory: true, - unique: true + unique: true, }, { uid: 'rte_field', @@ -305,24 +308,24 @@ describe('EntriesImport', () => { display_name: 'RTE Field', field_metadata: { rich_text_type: true, - embed_entry: true + embed_entry: true, }, - reference_to: ['simple_ct'] + reference_to: ['simple_ct'], }, { uid: 'mandatory_field', data_type: 'text', display_name: 'Mandatory Field', - mandatory: true - } - ] + mandatory: true, + }, + ], }; const apiOptions = { apiData: contentTypeWithRte, entity: 'update-cts' as const, resolve: sinon.stub(), - reject: sinon.stub() + reject: sinon.stub(), }; const result = entriesImport['serializeUpdateCTs'](apiOptions); @@ -338,7 +341,7 @@ describe('EntriesImport', () => { apiData: mockData.simpleContentType, entity: 'update-cts' as const, resolve: sinon.stub(), - reject: sinon.stub() + reject: sinon.stub(), }; const result = entriesImport['serializeUpdateCTs'](apiOptions); @@ -356,22 +359,22 @@ describe('EntriesImport', () => { uid: 'mandatory_field', data_type: 'text', display_name: 'Mandatory Field', - mandatory: true - } + mandatory: true, + }, ], field_rules: [ { conditions: [{ operand_field: 'title', operator: 'equals', value: 'test' }], - actions: [{ operand_field: 'description', action: 'show' }] - } - ] + actions: [{ operand_field: 'description', action: 'show' }], + }, + ], }; const apiOptions = { apiData: contentTypeWithFieldRules, entity: 'update-cts' as const, resolve: sinon.stub(), - reject: sinon.stub() + reject: sinon.stub(), }; const result = entriesImport['serializeUpdateCTs'](apiOptions); @@ -389,22 +392,22 @@ describe('EntriesImport', () => { data_type: 'text', display_name: 'Title', mandatory: true, - unique: true + unique: true, }, { uid: 'mandatory_field', data_type: 'text', display_name: 'Mandatory Field', - mandatory: true - } - ] + mandatory: true, + }, + ], }; const apiOptions = { apiData: contentTypeWithMandatory, entity: 'update-cts' as const, resolve: sinon.stub(), - reject: sinon.stub() + reject: sinon.stub(), }; entriesImport['serializeUpdateCTs'](apiOptions); @@ -424,7 +427,7 @@ describe('EntriesImport', () => { const onSuccess = options.apiParams.resolve; onSuccess({ response: { uid: 'ct-uid' }, - apiData: { uid: 'ct-uid' } + apiData: { uid: 'ct-uid' }, }); }); @@ -438,7 +441,7 @@ describe('EntriesImport', () => { const onReject = options.apiParams.reject; onReject({ error: new Error('Content type update failed'), - apiData: { uid: 'ct-uid' } + apiData: { uid: 'ct-uid' }, }); }); @@ -458,7 +461,7 @@ describe('EntriesImport', () => { apiData: mockData.contentTypeWithReferences, entity: 'update-cts' as const, resolve: sinon.stub(), - reject: sinon.stub() + reject: sinon.stub(), }; const result = entriesImport['serializeUpdateCTsWithRef'](apiOptions); @@ -473,16 +476,16 @@ describe('EntriesImport', () => { field_rules: [ { conditions: [{ operand_field: 'title', operator: 'equals', value: 'test' }], - actions: [{ operand_field: 'description', action: 'show' }] - } - ] + actions: [{ operand_field: 'description', action: 'show' }], + }, + ], }; const apiOptions = { apiData: contentTypeWithFieldRules, entity: 'update-cts' as const, resolve: sinon.stub(), - reject: sinon.stub() + reject: sinon.stub(), }; const result = entriesImport['serializeUpdateCTsWithRef'](apiOptions); @@ -495,8 +498,8 @@ describe('EntriesImport', () => { beforeEach(() => { entriesImport['cTs'] = [mockData.contentTypeWithFieldRules]; entriesImport['entriesUidMapper'] = { - 'entry_uid_1': 'new_entry_uid_1', - 'entry_uid_2': 'new_entry_uid_2' + entry_uid_1: 'new_entry_uid_1', + entry_uid_2: 'new_entry_uid_2', }; fsUtilityReadFileStub.callsFake((path) => { if (path.includes('field_rules_uid.json')) { @@ -513,11 +516,11 @@ describe('EntriesImport', () => { const mockContentTypeResponse = { uid: 'field_rules_ct', field_rules: mockData.contentTypeWithFieldRules.field_rules, - update: sinon.stub().resolves({ uid: 'updated-ct' }) + update: sinon.stub().resolves({ uid: 'updated-ct' }), }; mockStackClient.contentType.returns({ - fetch: sinon.stub().resolves(mockContentTypeResponse) + fetch: sinon.stub().resolves(mockContentTypeResponse), }); await entriesImport['updateFieldRules'](); @@ -542,7 +545,7 @@ describe('EntriesImport', () => { it('should handle content type not found', async () => { mockStackClient.contentType.returns({ - fetch: sinon.stub().resolves(null) + fetch: sinon.stub().resolves(null), }); await entriesImport['updateFieldRules'](); @@ -553,7 +556,7 @@ describe('EntriesImport', () => { it('should handle content type without field rules', async () => { const contentTypeWithoutFieldRules = { ...mockData.contentTypeWithFieldRules, - field_rules: undefined + field_rules: undefined, }; fsUtilityReadFileStub.callsFake((path) => { @@ -583,11 +586,11 @@ describe('EntriesImport', () => { mockData.contentTypeWithRte, mockData.contentTypeWithAssets, mockData.contentTypeWithTaxonomy, - mockData.contentTypeWithGroups + mockData.contentTypeWithGroups, ]; entriesImport['locales'] = [ { code: 'en-us', name: 'English (United States)' }, - { code: 'fr-fr', name: 'French (France)' } + { code: 'fr-fr', name: 'French (France)' }, ]; entriesImport['installedExtensions'] = mockMappers.installedExtensions; entriesImport['assetUidMapper'] = mockMappers.assetUidMapper; @@ -603,7 +606,7 @@ describe('EntriesImport', () => { expect(result).to.have.lengthOf(14); // 7 content types × 2 locales // Check that all content types are included - const contentTypes = result.map(option => option.cTUid); + const contentTypes = result.map((option) => option.cTUid); expect(contentTypes).to.include('simple_ct'); expect(contentTypes).to.include('ref_ct'); expect(contentTypes).to.include('json_rte_ct'); @@ -613,12 +616,12 @@ describe('EntriesImport', () => { expect(contentTypes).to.include('group_ct'); // Check that all locales are included - const locales = result.map(option => option.locale); + const locales = result.map((option) => option.locale); expect(locales).to.include('en-us'); expect(locales).to.include('fr-fr'); // Check structure of each option - result.forEach(option => { + result.forEach((option) => { expect(option).to.have.property('cTUid'); expect(option).to.have.property('locale'); expect(option.cTUid).to.be.a('string'); @@ -647,7 +650,7 @@ describe('EntriesImport', () => { it('should handle empty chunks', async () => { // Mock FsUtility to return empty indexer const mockFsUtility = { - indexFileContent: {} + indexFileContent: {}, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); @@ -660,17 +663,17 @@ describe('EntriesImport', () => { // Mock FsUtility for entry creation const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1', 'entry2'] - } + 'chunk1.json': ['entry1', 'entry2'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + // Mock readChunkFiles.next() to return entry data const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.simpleEntry, - 'entry2': mockEntries.entryWithReferences - }) + entry1: mockEntries.simpleEntry, + entry2: mockEntries.entryWithReferences, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -691,8 +694,8 @@ describe('EntriesImport', () => { locale: 'en-us', cTUid: 'simple_ct', entryFileName: 'chunk1.json', - isMasterLocale: true - } + isMasterLocale: true, + }, }); }); @@ -705,30 +708,30 @@ describe('EntriesImport', () => { expect(mockReadChunkFiles.next.called).to.be.true; expect(mockWriteIntoFile.called).to.be.true; expect(mockCompleteFile.called).to.be.true; - + // Check that UID mapping was created expect(entriesImport['entriesUidMapper']['simple_entry_1']).to.equal('new_simple_entry_1'); - + // Check that entry was added to variant list expect(entriesImport['entriesForVariant']).to.deep.include({ content_type: 'simple_ct', entry_uid: 'simple_entry_1', - locale: 'en-us' + locale: 'en-us', }); }); it('should process entries successfully in non-master locale', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.simpleEntry - }) + entry1: mockEntries.simpleEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -747,8 +750,8 @@ describe('EntriesImport', () => { locale: 'fr-fr', cTUid: 'simple_ct', entryFileName: 'chunk1.json', - isMasterLocale: false - } + isMasterLocale: false, + }, }); }); @@ -758,27 +761,27 @@ describe('EntriesImport', () => { await entriesImport['createEntries']({ cTUid: 'simple_ct', locale: 'fr-fr' }); expect(makeConcurrentCallStub.called).to.be.true; - + // Check that entry was added to auto-created entries for cleanup expect(entriesImport['autoCreatedEntries']).to.deep.include({ cTUid: 'simple_ct', locale: 'fr-fr', - entryUid: 'new_simple_entry_1' + entryUid: 'new_simple_entry_1', }); }); it('should handle localized entries correctly', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.localizedEntry - }) + entry1: mockEntries.localizedEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -800,34 +803,34 @@ describe('EntriesImport', () => { isMasterLocale: false, [mockEntries.localizedEntry.uid]: { isLocalized: true, - entryOldUid: 'old_localized_entry_1' - } - } + entryOldUid: 'old_localized_entry_1', + }, + }, }); }); await entriesImport['createEntries']({ cTUid: 'simple_ct', locale: 'fr-fr' }); expect(makeConcurrentCallStub.called).to.be.true; - + // Check that localized entry was added to variant list with old UID expect(entriesImport['entriesForVariant']).to.deep.include({ content_type: 'simple_ct', entry_uid: 'old_localized_entry_1', - locale: 'fr-fr' + locale: 'fr-fr', }); }); it('should handle chunk read errors', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { - next: sinon.stub().rejects(new Error('Chunk read failed')) + next: sinon.stub().rejects(new Error('Chunk read failed')), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -840,15 +843,15 @@ describe('EntriesImport', () => { it('should handle error code 119 with replaceExisting true', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.existingEntry - }) + entry1: mockEntries.existingEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -860,9 +863,9 @@ describe('EntriesImport', () => { makeConcurrentCallStub.callsFake(async (options) => { const onReject = options.apiParams.reject; onReject({ - error: { - errorCode: 119, - errors: { title: 'already exists' } + error: { + errorCode: 119, + errors: { title: 'already exists' }, }, apiData: mockEntries.existingEntry, additionalInfo: { @@ -870,8 +873,8 @@ describe('EntriesImport', () => { locale: 'en-us', cTUid: 'simple_ct', entryFileName: 'chunk1.json', - isMasterLocale: true - } + isMasterLocale: true, + }, }); }); @@ -888,15 +891,15 @@ describe('EntriesImport', () => { it('should handle error code 119 with skipExisting true', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.existingEntry - }) + entry1: mockEntries.existingEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -908,9 +911,9 @@ describe('EntriesImport', () => { makeConcurrentCallStub.callsFake(async (options) => { const onReject = options.apiParams.reject; onReject({ - error: { - errorCode: 119, - errors: { title: 'already exists' } + error: { + errorCode: 119, + errors: { title: 'already exists' }, }, apiData: mockEntries.existingEntry, additionalInfo: { @@ -918,8 +921,8 @@ describe('EntriesImport', () => { locale: 'en-us', cTUid: 'simple_ct', entryFileName: 'chunk1.json', - isMasterLocale: true - } + isMasterLocale: true, + }, }); }); @@ -934,15 +937,15 @@ describe('EntriesImport', () => { it('should handle error code 119 without title/uid errors', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.existingEntry - }) + entry1: mockEntries.existingEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -954,9 +957,9 @@ describe('EntriesImport', () => { makeConcurrentCallStub.callsFake(async (options) => { const onReject = options.apiParams.reject; onReject({ - error: { - errorCode: 119, - errors: { other: 'some error' } + error: { + errorCode: 119, + errors: { other: 'some error' }, }, apiData: mockEntries.existingEntry, additionalInfo: { @@ -964,8 +967,8 @@ describe('EntriesImport', () => { locale: 'en-us', cTUid: 'simple_ct', entryFileName: 'chunk1.json', - isMasterLocale: true - } + isMasterLocale: true, + }, }); }); @@ -976,22 +979,22 @@ describe('EntriesImport', () => { expect(entriesImport['failedEntries']).to.deep.include({ content_type: 'simple_ct', locale: 'en-us', - entry: { uid: 'existing_entry_1', title: 'Existing Entry' } + entry: { uid: 'existing_entry_1', title: 'Existing Entry' }, }); }); it('should handle other error codes', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.simpleEntry - }) + entry1: mockEntries.simpleEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -1003,9 +1006,9 @@ describe('EntriesImport', () => { makeConcurrentCallStub.callsFake(async (options) => { const onReject = options.apiParams.reject; onReject({ - error: { - errorCode: 500, - message: 'Server error' + error: { + errorCode: 500, + message: 'Server error', }, apiData: mockEntries.simpleEntry, additionalInfo: { @@ -1013,8 +1016,8 @@ describe('EntriesImport', () => { locale: 'en-us', cTUid: 'simple_ct', entryFileName: 'chunk1.json', - isMasterLocale: true - } + isMasterLocale: true, + }, }); }); @@ -1025,22 +1028,22 @@ describe('EntriesImport', () => { expect(entriesImport['failedEntries']).to.deep.include({ content_type: 'simple_ct', locale: 'en-us', - entry: { uid: 'simple_entry_1', title: 'Simple Entry 1' } + entry: { uid: 'simple_entry_1', title: 'Simple Entry 1' }, }); }); it('should remove failed entries from variant list', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.simpleEntry - }) + entry1: mockEntries.simpleEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -1051,15 +1054,15 @@ describe('EntriesImport', () => { // Pre-populate variant list entriesImport['entriesForVariant'] = [ - { content_type: 'simple_ct', entry_uid: 'simple_entry_1', locale: 'en-us' } + { content_type: 'simple_ct', entry_uid: 'simple_entry_1', locale: 'en-us' }, ]; makeConcurrentCallStub.callsFake(async (options) => { const onReject = options.apiParams.reject; onReject({ - error: { - errorCode: 500, - message: 'Server error' + error: { + errorCode: 500, + message: 'Server error', }, apiData: mockEntries.simpleEntry, additionalInfo: { @@ -1067,8 +1070,8 @@ describe('EntriesImport', () => { locale: 'en-us', cTUid: 'simple_ct', entryFileName: 'chunk1.json', - isMasterLocale: true - } + isMasterLocale: true, + }, }); }); @@ -1078,7 +1081,7 @@ describe('EntriesImport', () => { expect(entriesImport['entriesForVariant']).to.not.deep.include({ content_type: 'simple_ct', entry_uid: 'simple_entry_1', - locale: 'en-us' + locale: 'en-us', }); }); @@ -1086,21 +1089,21 @@ describe('EntriesImport', () => { const mockFsUtility = { indexFileContent: { 'chunk1.json': ['entry1'], - 'chunk2.json': ['entry2'] - } + 'chunk2.json': ['entry2'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + let chunkCallCount = 0; const mockReadChunkFiles = { next: sinon.stub().callsFake(() => { chunkCallCount++; if (chunkCallCount === 1) { - return Promise.resolve({ 'entry1': mockEntries.simpleEntry }); + return Promise.resolve({ entry1: mockEntries.simpleEntry }); } else { - return Promise.resolve({ 'entry2': mockEntries.entryWithReferences }); + return Promise.resolve({ entry2: mockEntries.entryWithReferences }); } - }) + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -1119,8 +1122,8 @@ describe('EntriesImport', () => { locale: 'en-us', cTUid: 'simple_ct', entryFileName: 'chunk1.json', - isMasterLocale: true - } + isMasterLocale: true, + }, }); }); @@ -1142,8 +1145,8 @@ describe('EntriesImport', () => { cTUid: 'simple_ct', locale: 'en-us', contentType: mockData.simpleContentType, - isMasterLocale: true - } + isMasterLocale: true, + }, }; const result = entriesImport['serializeEntries'](apiOptions); @@ -1163,8 +1166,8 @@ describe('EntriesImport', () => { cTUid: 'json_rte_ct', locale: 'en-us', contentType: mockData.contentTypeWithJsonRte, - isMasterLocale: true - } + isMasterLocale: true, + }, }; entriesImport['jsonRteCTs'] = ['json_rte_ct']; @@ -1186,8 +1189,8 @@ describe('EntriesImport', () => { cTUid: 'rte_ct', locale: 'en-us', contentType: mockData.contentTypeWithRte, - isMasterLocale: true - } + isMasterLocale: true, + }, }; entriesImport['rteCTsWithRef'] = ['rte_ct']; @@ -1208,8 +1211,8 @@ describe('EntriesImport', () => { cTUid: 'taxonomy_ct', locale: 'en-us', contentType: mockData.contentTypeWithTaxonomy, - isMasterLocale: true - } + isMasterLocale: true, + }, }; const result = entriesImport['serializeEntries'](apiOptions); @@ -1228,11 +1231,11 @@ describe('EntriesImport', () => { cTUid: 'simple_ct', locale: 'fr-fr', contentType: mockData.simpleContentType, - isMasterLocale: false - } + isMasterLocale: false, + }, }; - entriesImport['entriesUidMapper'] = { 'old_localized_entry_1': 'new_localized_entry_1' }; + entriesImport['entriesUidMapper'] = { old_localized_entry_1: 'new_localized_entry_1' }; // Mock lookupAssets to modify the entry in place and return it const originalLookupAssets = require('../../../../src/utils').lookupAssets; @@ -1244,18 +1247,18 @@ describe('EntriesImport', () => { // Return the modified entry return entryData.entry; }, - configurable: true + configurable: true, }); // Mock the stack client for localized entry processing const mockEntryResponse = { uid: 'new_localized_entry_1' }; const mockEntry = { - uid: sinon.stub().returns(mockEntryResponse) + uid: sinon.stub().returns(mockEntryResponse), }; mockStackClient.contentType = sinon.stub().returns({ - entry: sinon.stub().returns(mockEntry) + entry: sinon.stub().returns(mockEntry), }); - + // Mock the stack client on the entriesImport instance sinon.stub(entriesImport, 'stack').value(mockStackClient); @@ -1265,13 +1268,13 @@ describe('EntriesImport', () => { expect(result.apiData.uid).to.equal('new_localized_entry_1'); // UID is mapped for localized entries expect(result.additionalInfo['new_localized_entry_1']).to.deep.include({ isLocalized: true, - entryOldUid: 'old_localized_entry_1' + entryOldUid: 'old_localized_entry_1', }); // Restore original function Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { value: originalLookupAssets, - configurable: true + configurable: true, }); }); @@ -1285,8 +1288,8 @@ describe('EntriesImport', () => { cTUid: 'simple_ct', locale: 'en-us', contentType: mockData.simpleContentType, - isMasterLocale: true - } + isMasterLocale: true, + }, }; // Create an entry that will cause an error during serialization @@ -1294,23 +1297,23 @@ describe('EntriesImport', () => { uid: 'invalid_entry', title: 'Invalid Entry', // This will cause an error in lookupAssets due to missing required properties - invalid_field: 'test' + invalid_field: 'test', }; const invalidApiOptions = { ...apiOptions, - apiData: invalidEntry + apiData: invalidEntry, }; // Mock the lookupAssets function to throw an error const lookupAssetsStub = sinon.stub().throws(new Error('Asset lookup failed')); const utils = require('../../../../src/utils'); - + // Use Object.defineProperty to override the getter Object.defineProperty(utils, 'lookupAssets', { value: lookupAssetsStub, writable: true, - configurable: true + configurable: true, }); const result = entriesImport['serializeEntries'](invalidApiOptions); @@ -1321,7 +1324,7 @@ describe('EntriesImport', () => { expect(entriesImport['failedEntries'][0]).to.deep.include({ content_type: 'simple_ct', locale: 'en-us', - entry: { uid: 'invalid_entry', title: 'Invalid Entry' } + entry: { uid: 'invalid_entry', title: 'Invalid Entry' }, }); }); }); @@ -1330,7 +1333,7 @@ describe('EntriesImport', () => { it('should create variant entry data file', () => { entriesImport['entriesForVariant'] = [ { content_type: 'simple_ct', entry_uid: 'entry_1', locale: 'fr-fr' }, - { content_type: 'ref_ct', entry_uid: 'entry_2', locale: 'en-us' } + { content_type: 'ref_ct', entry_uid: 'entry_2', locale: 'en-us' }, ]; // Mock the writeFileSync function to avoid file system errors @@ -1375,11 +1378,11 @@ describe('EntriesImport', () => { mockData.simpleContentType, mockData.contentTypeWithReferences, mockData.contentTypeWithJsonRte, - mockData.contentTypeWithRte + mockData.contentTypeWithRte, ]; entriesImport['locales'] = [ { code: 'en-us', name: 'English (United States)' }, - { code: 'fr-fr', name: 'French (France)' } + { code: 'fr-fr', name: 'French (France)' }, ]; entriesImport['refCTs'] = ['ref_ct', 'json_rte_ct', 'rte_ct']; entriesImport['jsonRteCTs'] = ['json_rte_ct']; @@ -1389,10 +1392,10 @@ describe('EntriesImport', () => { entriesImport['assetUrlMapper'] = mockMappers.assetUrlMapper; entriesImport['taxonomies'] = mockMappers.taxonomies; entriesImport['entriesUidMapper'] = { - 'simple_entry_1': 'new_simple_entry_1', - 'ref_entry_1': 'new_ref_entry_1', - 'json_rte_entry_1': 'new_json_rte_entry_1', - 'rte_entry_1': 'new_rte_entry_1' + simple_entry_1: 'new_simple_entry_1', + ref_entry_1: 'new_ref_entry_1', + json_rte_entry_1: 'new_json_rte_entry_1', + rte_entry_1: 'new_rte_entry_1', }; }); @@ -1404,13 +1407,13 @@ describe('EntriesImport', () => { expect(result).to.have.lengthOf(6); // 3 ref content types × 2 locales // Check that all reference content types are included - const contentTypes = result.map(option => option.cTUid); + const contentTypes = result.map((option) => option.cTUid); expect(contentTypes).to.include('ref_ct'); expect(contentTypes).to.include('json_rte_ct'); expect(contentTypes).to.include('rte_ct'); // Check that all locales are included - const locales = result.map(option => option.locale); + const locales = result.map((option) => option.locale); expect(locales).to.include('en-us'); expect(locales).to.include('fr-fr'); }); @@ -1436,7 +1439,7 @@ describe('EntriesImport', () => { it('should handle empty chunks', async () => { // Mock FsUtility to return empty indexer const mockFsUtility = { - indexFileContent: {} + indexFileContent: {}, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); @@ -1449,17 +1452,17 @@ describe('EntriesImport', () => { // Mock FsUtility for entry updates const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1', 'entry2'] - } + 'chunk1.json': ['entry1', 'entry2'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + // Mock readChunkFiles.next() to return entry data const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.entryWithReferences, - 'entry2': mockEntries.simpleEntry - }) + entry1: mockEntries.entryWithReferences, + entry2: mockEntries.simpleEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -1467,7 +1470,7 @@ describe('EntriesImport', () => { const onSuccess = options.apiParams.resolve; onSuccess({ response: { uid: 'updated-entry-uid' }, - apiData: { uid: 'ref_entry_1', url: '/ref-entry-1', title: 'Entry with References' } + apiData: { uid: 'ref_entry_1', url: '/ref-entry-1', title: 'Entry with References' }, }); }); @@ -1480,13 +1483,13 @@ describe('EntriesImport', () => { it('should handle chunk read errors', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { - next: sinon.stub().rejects(new Error('Chunk read failed')) + next: sinon.stub().rejects(new Error('Chunk read failed')), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -1499,15 +1502,15 @@ describe('EntriesImport', () => { it('should handle API errors in onReject', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.entryWithReferences - }) + entry1: mockEntries.entryWithReferences, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -1515,7 +1518,7 @@ describe('EntriesImport', () => { const onReject = options.apiParams.reject; onReject({ error: { status: 500, message: 'Update failed' }, - apiData: { uid: 'ref_entry_1', title: 'Entry with References' } + apiData: { uid: 'ref_entry_1', title: 'Entry with References' }, }); }); @@ -1526,7 +1529,7 @@ describe('EntriesImport', () => { content_type: 'ref_ct', locale: 'en-us', entry: { uid: 'new_ref_entry_1', title: 'Entry with References' }, - entryId: 'ref_entry_1' + entryId: 'ref_entry_1', }); }); }); @@ -1538,7 +1541,7 @@ describe('EntriesImport', () => { uid: 'ref_entry_1', title: 'Entry with References', sourceEntryFilePath: '/path/to/source.json', - entryOldUid: 'ref_entry_1' + entryOldUid: 'ref_entry_1', }, entity: 'update-entries' as const, resolve: sinon.stub(), @@ -1546,22 +1549,22 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'ref_ct', locale: 'en-us', - contentType: mockData.contentTypeWithReferences - } + contentType: mockData.contentTypeWithReferences, + }, }; // Mock fsUtil.readFile to return source entry fsUtilityReadFileStub.callsFake((path) => { if (path.includes('source.json')) { return { - 'ref_entry_1': { + ref_entry_1: { uid: 'ref_entry_1', title: 'Source Entry', single_reference: { uid: 'simple_entry_1', - _content_type_uid: 'simple_ct' - } - } + _content_type_uid: 'simple_ct', + }, + }, }; } return {}; @@ -1571,7 +1574,7 @@ describe('EntriesImport', () => { const originalLookupAssets = require('../../../../src/utils').lookupAssets; Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { get: () => (entryData: any) => entryData.entry, - configurable: true + configurable: true, }); const result = entriesImport['serializeUpdateEntries'](apiOptions); @@ -1583,7 +1586,7 @@ describe('EntriesImport', () => { // Restore original function Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { get: () => originalLookupAssets, - configurable: true + configurable: true, }); }); @@ -1593,7 +1596,7 @@ describe('EntriesImport', () => { uid: 'json_rte_entry_1', title: 'Entry with JSON RTE', sourceEntryFilePath: '/path/to/source.json', - entryOldUid: 'json_rte_entry_1' + entryOldUid: 'json_rte_entry_1', }, entity: 'update-entries' as const, resolve: sinon.stub(), @@ -1601,15 +1604,15 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'json_rte_ct', locale: 'en-us', - contentType: mockData.contentTypeWithJsonRte - } + contentType: mockData.contentTypeWithJsonRte, + }, }; // Mock fsUtil.readFile to return source entry with JSON RTE fsUtilityReadFileStub.callsFake((path) => { if (path.includes('source.json')) { return { - 'json_rte_entry_1': { + json_rte_entry_1: { uid: 'json_rte_entry_1', title: 'Source Entry', json_rte_field: { @@ -1622,14 +1625,14 @@ describe('EntriesImport', () => { type: 'reference', attrs: { type: 'entry', - 'entry-uid': 'simple_entry_1' - } - } - ] - } - ] - } - } + 'entry-uid': 'simple_entry_1', + }, + }, + ], + }, + ], + }, + }, }; } return {}; @@ -1639,7 +1642,7 @@ describe('EntriesImport', () => { const originalLookupAssets = require('../../../../src/utils').lookupAssets; Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { get: () => (entryData: any) => entryData.entry, - configurable: true + configurable: true, }); const result = entriesImport['serializeUpdateEntries'](apiOptions); @@ -1650,7 +1653,7 @@ describe('EntriesImport', () => { // Restore original function Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { get: () => originalLookupAssets, - configurable: true + configurable: true, }); }); @@ -1660,7 +1663,7 @@ describe('EntriesImport', () => { uid: 'rte_entry_1', title: 'Entry with RTE', sourceEntryFilePath: '/path/to/source.json', - entryOldUid: 'rte_entry_1' + entryOldUid: 'rte_entry_1', }, entity: 'update-entries' as const, resolve: sinon.stub(), @@ -1668,19 +1671,19 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'rte_ct', locale: 'en-us', - contentType: mockData.contentTypeWithRte - } + contentType: mockData.contentTypeWithRte, + }, }; // Mock fsUtil.readFile to return source entry with RTE fsUtilityReadFileStub.callsFake((path) => { if (path.includes('source.json')) { return { - 'rte_entry_1': { + rte_entry_1: { uid: 'rte_entry_1', title: 'Source Entry', - rte_field: '

RTE content with entry link

' - } + rte_field: '

RTE content with entry link

', + }, }; } return {}; @@ -1690,7 +1693,7 @@ describe('EntriesImport', () => { const originalLookupAssets = require('../../../../src/utils').lookupAssets; Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { get: () => (entryData: any) => entryData.entry, - configurable: true + configurable: true, }); const result = entriesImport['serializeUpdateEntries'](apiOptions); @@ -1701,7 +1704,7 @@ describe('EntriesImport', () => { // Restore original function Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { get: () => originalLookupAssets, - configurable: true + configurable: true, }); }); @@ -1711,7 +1714,7 @@ describe('EntriesImport', () => { uid: 'invalid_entry', title: 'Invalid Entry', sourceEntryFilePath: '/path/to/source.json', - entryOldUid: 'invalid_entry' + entryOldUid: 'invalid_entry', }, entity: 'update-entries' as const, resolve: sinon.stub(), @@ -1719,8 +1722,8 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'ref_ct', locale: 'en-us', - contentType: mockData.contentTypeWithReferences - } + contentType: mockData.contentTypeWithReferences, + }, }; // Mock fsUtil.readFile to throw an error @@ -1738,7 +1741,7 @@ describe('EntriesImport', () => { it('should handle empty chunks', async () => { // Mock FsUtility to return empty indexer const mockFsUtility = { - indexFileContent: {} + indexFileContent: {}, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); @@ -1751,16 +1754,16 @@ describe('EntriesImport', () => { // Mock FsUtility for entry replacement const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + // Mock readChunkFiles.next() to return entry data const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.existingEntry - }) + entry1: mockEntries.existingEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -1768,7 +1771,7 @@ describe('EntriesImport', () => { sinon.stub(FsUtility.prototype, 'writeIntoFile').callsFake(() => { return Promise.resolve(); }); - + // Mock writeFileSync to prevent file system writes const originalWriteFileSync = require('fs').writeFileSync; const writeFileSyncStub = sinon.stub(require('fs'), 'writeFileSync').callsFake(() => {}); @@ -1778,7 +1781,7 @@ describe('EntriesImport', () => { onSuccess({ response: { uid: 'replaced-entry-uid' }, apiData: mockEntries.existingEntry, - additionalInfo: {} + additionalInfo: {}, }); }); @@ -1791,13 +1794,13 @@ describe('EntriesImport', () => { it('should handle chunk read errors', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { - next: sinon.stub().rejects(new Error('Chunk read failed')) + next: sinon.stub().rejects(new Error('Chunk read failed')), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -1810,15 +1813,15 @@ describe('EntriesImport', () => { it('should handle API errors in onReject', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.existingEntry - }) + entry1: mockEntries.existingEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -1829,7 +1832,7 @@ describe('EntriesImport', () => { const onReject = options.apiParams.reject; onReject({ error: { status: 500, message: 'Replacement failed' }, - apiData: { uid: 'existing_entry_1', title: 'Existing Entry' } + apiData: { uid: 'existing_entry_1', title: 'Existing Entry' }, }); }); @@ -1840,7 +1843,7 @@ describe('EntriesImport', () => { content_type: 'ref_ct', locale: 'en-us', entry: { uid: undefined, title: 'Existing Entry' }, - entryId: 'existing_entry_1' + entryId: 'existing_entry_1', }); }); }); @@ -1849,7 +1852,7 @@ describe('EntriesImport', () => { it('should find and update existing entry', async () => { const mockEntry = { title: 'Existing Entry', - uid: 'existing_entry_1' + uid: 'existing_entry_1', }; const apiParams = { @@ -1858,60 +1861,64 @@ describe('EntriesImport', () => { reject: sinon.stub(), additionalInfo: { cTUid: 'ref_ct', - locale: 'en-us' - } + locale: 'en-us', + }, }; // Mock stack API calls const mockQuery = { findOne: sinon.stub().resolves({ - items: [{ - uid: 'stack_entry_uid', - title: 'Existing Entry', - urlPath: '/existing-entry', - stackHeaders: {}, - _version: 1 - }] - }) + items: [ + { + uid: 'stack_entry_uid', + title: 'Existing Entry', + urlPath: '/existing-entry', + stackHeaders: {}, + _version: 1, + }, + ], + }), }; const mockEntryPayload = { - update: sinon.stub().resolves({ uid: 'updated_entry_uid' }) + update: sinon.stub().resolves({ uid: 'updated_entry_uid' }), }; // Mock the stack API chain: contentType().entry().query().findOne() const mockQueryChain = { query: sinon.stub().returns({ findOne: sinon.stub().resolves({ - items: [{ - uid: 'stack_entry_uid', - title: 'Existing Entry', - urlPath: '/existing-entry', - stackHeaders: {}, - _version: 1 - }] - }) - }) + items: [ + { + uid: 'stack_entry_uid', + title: 'Existing Entry', + urlPath: '/existing-entry', + stackHeaders: {}, + _version: 1, + }, + ], + }), + }), }; const mockEntryChain = { - update: sinon.stub().resolves({ uid: 'updated_entry_uid' }) + update: sinon.stub().resolves({ uid: 'updated_entry_uid' }), }; const contentTypeStub = sinon.stub(); contentTypeStub.onFirstCall().returns({ - entry: sinon.stub().returns(mockQueryChain) + entry: sinon.stub().returns(mockQueryChain), }); contentTypeStub.onSecondCall().returns({ - entry: sinon.stub().returns(mockEntryChain) + entry: sinon.stub().returns(mockEntryChain), }); - + mockStackClient.contentType = contentTypeStub; const result = await entriesImport['replaceEntriesHandler']({ apiParams, element: mockEntry, - isLastRequest: false + isLastRequest: false, }); expect(result).to.be.true; @@ -1923,7 +1930,7 @@ describe('EntriesImport', () => { it('should handle entry not found in stack', async () => { const mockEntry = { title: 'Non-existent Entry', - uid: 'non_existent_entry_1' + uid: 'non_existent_entry_1', }; const apiParams = { @@ -1932,31 +1939,31 @@ describe('EntriesImport', () => { reject: sinon.stub(), additionalInfo: { cTUid: 'ref_ct', - locale: 'en-us' - } + locale: 'en-us', + }, }; // Mock stack API to return empty result const mockQueryChain = { query: sinon.stub().returns({ findOne: sinon.stub().resolves({ - items: [] - }) - }) + items: [], + }), + }), }; const contentTypeStub = sinon.stub(); contentTypeStub.returns({ - entry: sinon.stub().returns(mockQueryChain) + entry: sinon.stub().returns(mockQueryChain), }); - + mockStackClient.contentType = contentTypeStub; try { const result = await entriesImport['replaceEntriesHandler']({ apiParams, element: mockEntry, - isLastRequest: false + isLastRequest: false, }); expect.fail('Expected method to reject'); } catch (error) { @@ -1969,7 +1976,7 @@ describe('EntriesImport', () => { it('should handle query errors', async () => { const mockEntry = { title: 'Error Entry', - uid: 'error_entry_1' + uid: 'error_entry_1', }; const apiParams = { @@ -1978,29 +1985,29 @@ describe('EntriesImport', () => { reject: sinon.stub(), additionalInfo: { cTUid: 'ref_ct', - locale: 'en-us' - } + locale: 'en-us', + }, }; // Mock stack API to throw error const mockQueryChain = { query: sinon.stub().returns({ - findOne: sinon.stub().rejects(new Error('Query failed')) - }) + findOne: sinon.stub().rejects(new Error('Query failed')), + }), }; const contentTypeStub = sinon.stub(); contentTypeStub.returns({ - entry: sinon.stub().returns(mockQueryChain) + entry: sinon.stub().returns(mockQueryChain), }); - + mockStackClient.contentType = contentTypeStub; try { const result = await entriesImport['replaceEntriesHandler']({ apiParams, element: mockEntry, - isLastRequest: false + isLastRequest: false, }); expect.fail('Expected method to reject'); } catch (error) { @@ -2012,7 +2019,7 @@ describe('EntriesImport', () => { it('should handle update errors', async () => { const mockEntry = { title: 'Update Error Entry', - uid: 'update_error_entry_1' + uid: 'update_error_entry_1', }; const apiParams = { @@ -2021,44 +2028,46 @@ describe('EntriesImport', () => { reject: sinon.stub(), additionalInfo: { cTUid: 'ref_ct', - locale: 'en-us' - } + locale: 'en-us', + }, }; // Mock stack API calls const mockQueryChain = { query: sinon.stub().returns({ findOne: sinon.stub().resolves({ - items: [{ - uid: 'stack_entry_uid', - title: 'Update Error Entry', - urlPath: '/update-error-entry', - stackHeaders: {}, - _version: 1 - }] - }) - }) + items: [ + { + uid: 'stack_entry_uid', + title: 'Update Error Entry', + urlPath: '/update-error-entry', + stackHeaders: {}, + _version: 1, + }, + ], + }), + }), }; const mockEntryChain = { - update: sinon.stub().rejects(new Error('Update failed')) + update: sinon.stub().rejects(new Error('Update failed')), }; const contentTypeStub = sinon.stub(); contentTypeStub.onFirstCall().returns({ - entry: sinon.stub().returns(mockQueryChain) + entry: sinon.stub().returns(mockQueryChain), }); contentTypeStub.onSecondCall().returns({ - entry: sinon.stub().returns(mockEntryChain) + entry: sinon.stub().returns(mockEntryChain), }); - + mockStackClient.contentType = contentTypeStub; try { const result = await entriesImport['replaceEntriesHandler']({ apiParams, element: mockEntry, - isLastRequest: false + isLastRequest: false, }); expect.fail('Expected method to reject'); } catch (error) { @@ -2075,16 +2084,16 @@ describe('EntriesImport', () => { entriesImport['cTs'] = [ mockData.simpleContentType, mockData.contentTypeWithReferences, - mockData.contentTypeWithJsonRte + mockData.contentTypeWithJsonRte, ]; entriesImport['envs'] = { - 'env_1': { name: 'production', uid: 'env_1' }, - 'env_2': { name: 'staging', uid: 'env_2' } + env_1: { name: 'production', uid: 'env_1' }, + env_2: { name: 'staging', uid: 'env_2' }, }; entriesImport['entriesUidMapper'] = { - 'simple_entry_1': 'new_simple_entry_1', - 'publish_entry_1': 'new_publish_entry_1', - 'json_rte_entry_1': 'new_json_rte_entry_1' + simple_entry_1: 'new_simple_entry_1', + publish_entry_1: 'new_publish_entry_1', + json_rte_entry_1: 'new_json_rte_entry_1', }; }); @@ -2092,7 +2101,7 @@ describe('EntriesImport', () => { it('should handle empty chunks', async () => { // Mock FsUtility to return empty indexer const mockFsUtility = { - indexFileContent: {} + indexFileContent: {}, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); @@ -2105,17 +2114,17 @@ describe('EntriesImport', () => { // Mock FsUtility for entry publishing const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1', 'entry2'] - } + 'chunk1.json': ['entry1', 'entry2'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + // Mock readChunkFiles.next() to return entry data with publish details const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.simpleEntry, - 'entry2': mockEntries.entryWithPublishDetails - }) + entry1: mockEntries.simpleEntry, + entry2: mockEntries.entryWithPublishDetails, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -2123,11 +2132,11 @@ describe('EntriesImport', () => { const onSuccess = options.apiParams.resolve; onSuccess({ response: { uid: 'published-entry-uid' }, - apiData: { - environments: ['production', 'staging'], - entryUid: 'new_simple_entry_1', - locales: ['en-us'] - } + apiData: { + environments: ['production', 'staging'], + entryUid: 'new_simple_entry_1', + locales: ['en-us'], + }, }); }); @@ -2140,15 +2149,15 @@ describe('EntriesImport', () => { it('should handle entries with multiple publish details', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.entryWithPublishDetails - }) + entry1: mockEntries.entryWithPublishDetails, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -2156,11 +2165,11 @@ describe('EntriesImport', () => { const onSuccess = options.apiParams.resolve; onSuccess({ response: { uid: 'published-entry-uid' }, - apiData: { - environments: ['production', 'staging'], - entryUid: 'new_publish_entry_1', - locales: ['en-us', 'fr-fr'] - } + apiData: { + environments: ['production', 'staging'], + entryUid: 'new_publish_entry_1', + locales: ['en-us', 'fr-fr'], + }, }); }); @@ -2174,22 +2183,22 @@ describe('EntriesImport', () => { it('should handle entries without publish details', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const entryWithoutPublishDetails = { uid: 'no_publish_entry_1', title: 'Entry without Publish Details', - description: 'This entry has no publish details' + description: 'This entry has no publish details', // No publish_details property }; - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': entryWithoutPublishDetails - }) + entry1: entryWithoutPublishDetails, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -2202,22 +2211,22 @@ describe('EntriesImport', () => { it('should handle entries with empty publish details', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const entryWithEmptyPublishDetails = { uid: 'empty_publish_entry_1', title: 'Entry with Empty Publish Details', description: 'This entry has empty publish details', - publish_details: [] as any[] + publish_details: [] as any[], }; - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': entryWithEmptyPublishDetails - }) + entry1: entryWithEmptyPublishDetails, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -2230,13 +2239,13 @@ describe('EntriesImport', () => { it('should handle chunk read errors', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { - next: sinon.stub().rejects(new Error('Chunk read failed')) + next: sinon.stub().rejects(new Error('Chunk read failed')), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -2249,15 +2258,15 @@ describe('EntriesImport', () => { it('should handle API errors in onReject', async () => { const mockFsUtility = { indexFileContent: { - 'chunk1.json': ['entry1'] - } + 'chunk1.json': ['entry1'], + }, }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockFsUtility.indexFileContent); - + const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': mockEntries.simpleEntry - }) + entry1: mockEntries.simpleEntry, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -2265,11 +2274,11 @@ describe('EntriesImport', () => { const onReject = options.apiParams.reject; onReject({ error: { status: 500, message: 'Publish failed' }, - apiData: { - environments: ['production'], - entryUid: 'new_simple_entry_1', - locales: ['en-us'] - } + apiData: { + environments: ['production'], + entryUid: 'new_simple_entry_1', + locales: ['en-us'], + }, }); }); @@ -2288,13 +2297,13 @@ describe('EntriesImport', () => { publish_details: [ { environment: 'env_1', - locale: 'en-us' + locale: 'en-us', }, { environment: 'env_2', - locale: 'fr-fr' - } - ] + locale: 'fr-fr', + }, + ], }, entity: 'publish-entries' as const, resolve: sinon.stub(), @@ -2302,8 +2311,8 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'simple_ct', locale: 'en-us', - contentType: mockData.simpleContentType - } + contentType: mockData.simpleContentType, + }, }; const result = entriesImport['serializePublishEntries'](apiOptions); @@ -2312,7 +2321,7 @@ describe('EntriesImport', () => { expect(result.apiData).to.deep.include({ environments: ['production', 'staging'], locales: ['en-us', 'fr-fr'], - entryUid: 'new_simple_entry_1' + entryUid: 'new_simple_entry_1', }); }); @@ -2320,7 +2329,7 @@ describe('EntriesImport', () => { const apiOptions = { apiData: { uid: 'simple_entry_1', - title: 'Simple Entry' + title: 'Simple Entry', // No publish_details }, entity: 'publish-entries' as const, @@ -2329,8 +2338,8 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'simple_ct', locale: 'en-us', - contentType: mockData.simpleContentType - } + contentType: mockData.simpleContentType, + }, }; const result = entriesImport['serializePublishEntries'](apiOptions); @@ -2343,7 +2352,7 @@ describe('EntriesImport', () => { apiData: { uid: 'simple_entry_1', title: 'Simple Entry', - publish_details: [] as any[] + publish_details: [] as any[], }, entity: 'publish-entries' as const, resolve: sinon.stub(), @@ -2351,8 +2360,8 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'simple_ct', locale: 'en-us', - contentType: mockData.simpleContentType - } + contentType: mockData.simpleContentType, + }, }; const result = entriesImport['serializePublishEntries'](apiOptions); @@ -2368,9 +2377,9 @@ describe('EntriesImport', () => { publish_details: [ { environment: 'invalid_env', - locale: 'en-us' - } - ] + locale: 'en-us', + }, + ], }, entity: 'publish-entries' as const, resolve: sinon.stub(), @@ -2378,8 +2387,8 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'simple_ct', locale: 'en-us', - contentType: mockData.simpleContentType - } + contentType: mockData.simpleContentType, + }, }; const result = entriesImport['serializePublishEntries'](apiOptions); @@ -2394,10 +2403,10 @@ describe('EntriesImport', () => { title: 'Simple Entry', publish_details: [ { - environment: 'env_1' + environment: 'env_1', // No locale - } - ] + }, + ], }, entity: 'publish-entries' as const, resolve: sinon.stub(), @@ -2405,8 +2414,8 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'simple_ct', locale: 'en-us', - contentType: mockData.simpleContentType - } + contentType: mockData.simpleContentType, + }, }; const result = entriesImport['serializePublishEntries'](apiOptions); @@ -2422,17 +2431,17 @@ describe('EntriesImport', () => { publish_details: [ { environment: 'env_1', - locale: 'en-us' + locale: 'en-us', }, { environment: 'env_1', // Duplicate environment - locale: 'en-us' // Duplicate locale + locale: 'en-us', // Duplicate locale }, { environment: 'env_2', - locale: 'fr-fr' - } - ] + locale: 'fr-fr', + }, + ], }, entity: 'publish-entries' as const, resolve: sinon.stub(), @@ -2440,8 +2449,8 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'simple_ct', locale: 'en-us', - contentType: mockData.simpleContentType - } + contentType: mockData.simpleContentType, + }, }; const result = entriesImport['serializePublishEntries'](apiOptions); @@ -2461,9 +2470,9 @@ describe('EntriesImport', () => { publish_details: [ { environment: 'env_1', - locale: 'en-us' - } - ] + locale: 'en-us', + }, + ], }, entity: 'publish-entries' as const, resolve: sinon.stub(), @@ -2471,8 +2480,8 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'simple_ct', locale: 'en-us', - contentType: mockData.simpleContentType - } + contentType: mockData.simpleContentType, + }, }; const result = entriesImport['serializePublishEntries'](apiOptions); @@ -2489,17 +2498,17 @@ describe('EntriesImport', () => { publish_details: [ { environment: 'env_1', - locale: 'en-us' + locale: 'en-us', }, { environment: 'invalid_env', - locale: 'fr-fr' + locale: 'fr-fr', }, { environment: 'env_2', - locale: 'de-de' - } - ] + locale: 'de-de', + }, + ], }, entity: 'publish-entries' as const, resolve: sinon.stub(), @@ -2507,8 +2516,8 @@ describe('EntriesImport', () => { additionalInfo: { cTUid: 'simple_ct', locale: 'en-us', - contentType: mockData.simpleContentType - } + contentType: mockData.simpleContentType, + }, }; const result = entriesImport['serializePublishEntries'](apiOptions); @@ -2524,16 +2533,16 @@ describe('EntriesImport', () => { beforeEach(() => { // Reset all stubs before each test sinon.restore(); - + // Setup basic mock data entriesImport['cTs'] = [mockData.simpleContentType, mockData.contentTypeWithReferences]; entriesImport['locales'] = [ { code: 'en-us', name: 'English' }, - { code: 'fr-fr', name: 'French' } + { code: 'fr-fr', name: 'French' }, ]; entriesImport['envs'] = { - 'env_1': { name: 'production', uid: 'env_1' }, - 'env_2': { name: 'staging', uid: 'env_2' } + env_1: { name: 'production', uid: 'env_1' }, + env_2: { name: 'staging', uid: 'env_2' }, }; entriesImport['entriesUidMapper'] = {}; entriesImport['failedEntries'] = []; @@ -2544,15 +2553,22 @@ describe('EntriesImport', () => { it('should complete full start process successfully', async () => { // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType, mockData.contentTypeWithReferences]) // content types - .onCall(1).resolves({ extension_uid: { ext_1: 'new_ext_1' } }) // marketplace apps - .onCall(2).resolves({ asset_1: 'new_asset_1' }) // asset UID mapper - .onCall(3).resolves({ 'https://old.com': 'https://new.com' }) // asset URL mapper - .onCall(4).resolves({ taxonomy_1: { terms: [] } }) // taxonomies - .onCall(5).resolves([{ code: 'en-us' }, { code: 'fr-fr' }]), // locales + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType, mockData.contentTypeWithReferences]) // content types + .onCall(1) + .resolves({ extension_uid: { ext_1: 'new_ext_1' } }) // marketplace apps + .onCall(2) + .resolves({ asset_1: 'new_asset_1' }) // asset UID mapper + .onCall(3) + .resolves({ 'https://old.com': 'https://new.com' }) // asset URL mapper + .onCall(4) + .resolves({ taxonomy_1: { terms: [] } }) // taxonomies + .onCall(5) + .resolves([{ code: 'en-us' }, { code: 'fr-fr' }]), // locales makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -2563,7 +2579,8 @@ describe('EntriesImport', () => { // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns(entriesImport['envs']), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); @@ -2571,12 +2588,12 @@ describe('EntriesImport', () => { const disableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'disableMandatoryCTReferences').resolves(); const populateEntryCreatePayloadStub = sinon.stub(entriesImport, 'populateEntryCreatePayload').returns([ { cTUid: 'simple_ct', locale: 'en-us' }, - { cTUid: 'simple_ct', locale: 'fr-fr' } + { cTUid: 'simple_ct', locale: 'fr-fr' }, ]); const createEntriesStub = sinon.stub(entriesImport, 'createEntries').resolves(); - const populateEntryUpdatePayloadStub = sinon.stub(entriesImport, 'populateEntryUpdatePayload').returns([ - { cTUid: 'simple_ct', locale: 'en-us' } - ]); + const populateEntryUpdatePayloadStub = sinon + .stub(entriesImport, 'populateEntryUpdatePayload') + .returns([{ cTUid: 'simple_ct', locale: 'en-us' }]); const updateEntriesWithReferencesStub = sinon.stub(entriesImport, 'updateEntriesWithReferences').resolves(); const enableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'enableMandatoryCTReferences').resolves(); const updateFieldRulesStub = sinon.stub(entriesImport, 'updateFieldRules').resolves(); @@ -2600,14 +2617,15 @@ describe('EntriesImport', () => { const mockFsUtil = { readFile: sinon.stub().resolves([]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); @@ -2629,14 +2647,15 @@ describe('EntriesImport', () => { const mockFsUtil = { readFile: sinon.stub().resolves(null), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); @@ -2659,15 +2678,22 @@ describe('EntriesImport', () => { // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) - .onCall(1).resolves({ extension_uid: {} }) - .onCall(2).resolves({}) - .onCall(3).resolves({}) - .onCall(4).resolves({}) - .onCall(5).resolves([{ code: 'en-us' }]), + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) + .onCall(1) + .resolves({ extension_uid: {} }) + .onCall(2) + .resolves({}) + .onCall(3) + .resolves({}) + .onCall(4) + .resolves({}) + .onCall(5) + .resolves([{ code: 'en-us' }]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -2678,15 +2704,16 @@ describe('EntriesImport', () => { // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); // Mock all the method calls const disableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'disableMandatoryCTReferences').resolves(); - const populateEntryCreatePayloadStub = sinon.stub(entriesImport, 'populateEntryCreatePayload').returns([ - { cTUid: 'simple_ct', locale: 'en-us' } - ]); + const populateEntryCreatePayloadStub = sinon + .stub(entriesImport, 'populateEntryCreatePayload') + .returns([{ cTUid: 'simple_ct', locale: 'en-us' }]); const createEntriesStub = sinon.stub(entriesImport, 'createEntries').resolves(); const replaceEntriesStub = sinon.stub(entriesImport, 'replaceEntries').resolves(); const populateEntryUpdatePayloadStub = sinon.stub(entriesImport, 'populateEntryUpdatePayload').returns([]); @@ -2703,21 +2730,26 @@ describe('EntriesImport', () => { it('should handle autoCreatedEntries cleanup', async () => { // Set up autoCreatedEntries - entriesImport['autoCreatedEntries'] = [ - { cTUid: 'simple_ct', locale: 'en-us', entryUid: 'entry_1' } - ]; + entriesImport['autoCreatedEntries'] = [{ cTUid: 'simple_ct', locale: 'en-us', entryUid: 'entry_1' }]; // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) - .onCall(1).resolves({ extension_uid: {} }) - .onCall(2).resolves({}) - .onCall(3).resolves({}) - .onCall(4).resolves({}) - .onCall(5).resolves([{ code: 'en-us' }]), + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) + .onCall(1) + .resolves({ extension_uid: {} }) + .onCall(2) + .resolves({}) + .onCall(3) + .resolves({}) + .onCall(4) + .resolves({}) + .onCall(5) + .resolves([{ code: 'en-us' }]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -2728,7 +2760,8 @@ describe('EntriesImport', () => { // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); @@ -2755,15 +2788,22 @@ describe('EntriesImport', () => { // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) - .onCall(1).resolves({ extension_uid: {} }) - .onCall(2).resolves({}) - .onCall(3).resolves({}) - .onCall(4).resolves({}) - .onCall(5).resolves([{ code: 'en-us' }]), + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) + .onCall(1) + .resolves({ extension_uid: {} }) + .onCall(2) + .resolves({}) + .onCall(3) + .resolves({}) + .onCall(4) + .resolves({}) + .onCall(5) + .resolves([{ code: 'en-us' }]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -2774,7 +2814,8 @@ describe('EntriesImport', () => { // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); @@ -2798,15 +2839,22 @@ describe('EntriesImport', () => { it('should handle no environments found for publishing', async () => { // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) - .onCall(1).resolves({ extension_uid: {} }) - .onCall(2).resolves({}) - .onCall(3).resolves({}) - .onCall(4).resolves({}) - .onCall(5).resolves([{ code: 'en-us' }]), + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) + .onCall(1) + .resolves({ extension_uid: {} }) + .onCall(2) + .resolves({}) + .onCall(3) + .resolves({}) + .onCall(4) + .resolves({}) + .onCall(5) + .resolves([{ code: 'en-us' }]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -2817,15 +2865,16 @@ describe('EntriesImport', () => { // Mock fileHelper to return empty environments const mockFileHelper = { readFileSync: sinon.stub().returns({}), // Empty environments - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); // Mock all the method calls const disableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'disableMandatoryCTReferences').resolves(); - const populateEntryCreatePayloadStub = sinon.stub(entriesImport, 'populateEntryCreatePayload').returns([ - { cTUid: 'simple_ct', locale: 'en-us' } - ]); + const populateEntryCreatePayloadStub = sinon + .stub(entriesImport, 'populateEntryCreatePayload') + .returns([{ cTUid: 'simple_ct', locale: 'en-us' }]); const createEntriesStub = sinon.stub(entriesImport, 'createEntries').resolves(); const populateEntryUpdatePayloadStub = sinon.stub(entriesImport, 'populateEntryUpdatePayload').returns([]); const updateEntriesWithReferencesStub = sinon.stub(entriesImport, 'updateEntriesWithReferences').resolves(); @@ -2846,15 +2895,22 @@ describe('EntriesImport', () => { // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) - .onCall(1).resolves({ extension_uid: {} }) - .onCall(2).resolves({}) - .onCall(3).resolves({}) - .onCall(4).resolves({}) - .onCall(5).resolves([{ code: 'en-us' }]), + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) + .onCall(1) + .resolves({ extension_uid: {} }) + .onCall(2) + .resolves({}) + .onCall(3) + .resolves({}) + .onCall(4) + .resolves({}) + .onCall(5) + .resolves([{ code: 'en-us' }]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -2865,15 +2921,16 @@ describe('EntriesImport', () => { // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); // Mock all the method calls const disableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'disableMandatoryCTReferences').resolves(); - const populateEntryCreatePayloadStub = sinon.stub(entriesImport, 'populateEntryCreatePayload').returns([ - { cTUid: 'simple_ct', locale: 'en-us' } - ]); + const populateEntryCreatePayloadStub = sinon + .stub(entriesImport, 'populateEntryCreatePayload') + .returns([{ cTUid: 'simple_ct', locale: 'en-us' }]); const createEntriesStub = sinon.stub(entriesImport, 'createEntries').resolves(); const replaceEntriesStub = sinon.stub(entriesImport, 'replaceEntries').rejects(new Error('Replace failed')); const populateEntryUpdatePayloadStub = sinon.stub(entriesImport, 'populateEntryUpdatePayload').returns([]); @@ -2890,21 +2947,26 @@ describe('EntriesImport', () => { it('should handle errors in removeAutoCreatedEntries', async () => { // Set up autoCreatedEntries - entriesImport['autoCreatedEntries'] = [ - { cTUid: 'simple_ct', locale: 'en-us', entryUid: 'entry_1' } - ]; + entriesImport['autoCreatedEntries'] = [{ cTUid: 'simple_ct', locale: 'en-us', entryUid: 'entry_1' }]; // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) - .onCall(1).resolves({ extension_uid: {} }) - .onCall(2).resolves({}) - .onCall(3).resolves({}) - .onCall(4).resolves({}) - .onCall(5).resolves([{ code: 'en-us' }]), + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) + .onCall(1) + .resolves({ extension_uid: {} }) + .onCall(2) + .resolves({}) + .onCall(3) + .resolves({}) + .onCall(4) + .resolves({}) + .onCall(5) + .resolves([{ code: 'en-us' }]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -2915,7 +2977,8 @@ describe('EntriesImport', () => { // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); @@ -2927,7 +2990,9 @@ describe('EntriesImport', () => { const updateEntriesWithReferencesStub = sinon.stub(entriesImport, 'updateEntriesWithReferences').resolves(); const enableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'enableMandatoryCTReferences').resolves(); const updateFieldRulesStub = sinon.stub(entriesImport, 'updateFieldRules').resolves(); - const removeAutoCreatedEntriesStub = sinon.stub(entriesImport, 'removeAutoCreatedEntries').rejects(new Error('Remove failed')); + const removeAutoCreatedEntriesStub = sinon + .stub(entriesImport, 'removeAutoCreatedEntries') + .rejects(new Error('Remove failed')); const createEntryDataForVariantEntryStub = sinon.stub(entriesImport, 'createEntryDataForVariantEntry').returns(); await entriesImport.start(); @@ -2939,15 +3004,22 @@ describe('EntriesImport', () => { it('should handle errors in updateEntriesWithReferences', async () => { // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) - .onCall(1).resolves({ extension_uid: {} }) - .onCall(2).resolves({}) - .onCall(3).resolves({}) - .onCall(4).resolves({}) - .onCall(5).resolves([{ code: 'en-us' }]), + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) + .onCall(1) + .resolves({ extension_uid: {} }) + .onCall(2) + .resolves({}) + .onCall(3) + .resolves({}) + .onCall(4) + .resolves({}) + .onCall(5) + .resolves([{ code: 'en-us' }]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -2958,7 +3030,8 @@ describe('EntriesImport', () => { // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); @@ -2966,10 +3039,12 @@ describe('EntriesImport', () => { const disableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'disableMandatoryCTReferences').resolves(); const populateEntryCreatePayloadStub = sinon.stub(entriesImport, 'populateEntryCreatePayload').returns([]); const createEntriesStub = sinon.stub(entriesImport, 'createEntries').resolves(); - const populateEntryUpdatePayloadStub = sinon.stub(entriesImport, 'populateEntryUpdatePayload').returns([ - { cTUid: 'simple_ct', locale: 'en-us' } - ]); - const updateEntriesWithReferencesStub = sinon.stub(entriesImport, 'updateEntriesWithReferences').rejects(new Error('Update failed')); + const populateEntryUpdatePayloadStub = sinon + .stub(entriesImport, 'populateEntryUpdatePayload') + .returns([{ cTUid: 'simple_ct', locale: 'en-us' }]); + const updateEntriesWithReferencesStub = sinon + .stub(entriesImport, 'updateEntriesWithReferences') + .rejects(new Error('Update failed')); const enableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'enableMandatoryCTReferences').resolves(); const updateFieldRulesStub = sinon.stub(entriesImport, 'updateFieldRules').resolves(); const createEntryDataForVariantEntryStub = sinon.stub(entriesImport, 'createEntryDataForVariantEntry').returns(); @@ -2983,15 +3058,22 @@ describe('EntriesImport', () => { it('should handle errors in enableMandatoryCTReferences', async () => { // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) - .onCall(1).resolves({ extension_uid: {} }) - .onCall(2).resolves({}) - .onCall(3).resolves({}) - .onCall(4).resolves({}) - .onCall(5).resolves([{ code: 'en-us' }]), + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) + .onCall(1) + .resolves({ extension_uid: {} }) + .onCall(2) + .resolves({}) + .onCall(3) + .resolves({}) + .onCall(4) + .resolves({}) + .onCall(5) + .resolves([{ code: 'en-us' }]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -3002,7 +3084,8 @@ describe('EntriesImport', () => { // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); @@ -3012,7 +3095,9 @@ describe('EntriesImport', () => { const createEntriesStub = sinon.stub(entriesImport, 'createEntries').resolves(); const populateEntryUpdatePayloadStub = sinon.stub(entriesImport, 'populateEntryUpdatePayload').returns([]); const updateEntriesWithReferencesStub = sinon.stub(entriesImport, 'updateEntriesWithReferences').resolves(); - const enableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'enableMandatoryCTReferences').rejects(new Error('Enable failed')); + const enableMandatoryCTReferencesStub = sinon + .stub(entriesImport, 'enableMandatoryCTReferences') + .rejects(new Error('Enable failed')); const updateFieldRulesStub = sinon.stub(entriesImport, 'updateFieldRules').resolves(); const createEntryDataForVariantEntryStub = sinon.stub(entriesImport, 'createEntryDataForVariantEntry').returns(); @@ -3025,15 +3110,22 @@ describe('EntriesImport', () => { it('should handle errors in updateFieldRules', async () => { // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) - .onCall(1).resolves({ extension_uid: {} }) - .onCall(2).resolves({}) - .onCall(3).resolves({}) - .onCall(4).resolves({}) - .onCall(5).resolves([{ code: 'en-us' }]), + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) + .onCall(1) + .resolves({ extension_uid: {} }) + .onCall(2) + .resolves({}) + .onCall(3) + .resolves({}) + .onCall(4) + .resolves({}) + .onCall(5) + .resolves([{ code: 'en-us' }]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -3044,7 +3136,8 @@ describe('EntriesImport', () => { // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); @@ -3055,7 +3148,9 @@ describe('EntriesImport', () => { const populateEntryUpdatePayloadStub = sinon.stub(entriesImport, 'populateEntryUpdatePayload').returns([]); const updateEntriesWithReferencesStub = sinon.stub(entriesImport, 'updateEntriesWithReferences').resolves(); const enableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'enableMandatoryCTReferences').resolves(); - const updateFieldRulesStub = sinon.stub(entriesImport, 'updateFieldRules').rejects(new Error('Field rules failed')); + const updateFieldRulesStub = sinon + .stub(entriesImport, 'updateFieldRules') + .rejects(new Error('Field rules failed')); const createEntryDataForVariantEntryStub = sinon.stub(entriesImport, 'createEntryDataForVariantEntry').returns(); await entriesImport.start(); @@ -3067,15 +3162,22 @@ describe('EntriesImport', () => { it('should handle errors in publishEntries', async () => { // Mock file system operations const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) - .onCall(1).resolves({ extension_uid: {} }) - .onCall(2).resolves({}) - .onCall(3).resolves({}) - .onCall(4).resolves({}) - .onCall(5).resolves([{ code: 'en-us' }]), + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) + .onCall(1) + .resolves({ extension_uid: {} }) + .onCall(2) + .resolves({}) + .onCall(3) + .resolves({}) + .onCall(4) + .resolves({}) + .onCall(5) + .resolves([{ code: 'en-us' }]), makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); @@ -3086,15 +3188,16 @@ describe('EntriesImport', () => { // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns(entriesImport['envs']), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); // Mock all the method calls const disableMandatoryCTReferencesStub = sinon.stub(entriesImport, 'disableMandatoryCTReferences').resolves(); - const populateEntryCreatePayloadStub = sinon.stub(entriesImport, 'populateEntryCreatePayload').returns([ - { cTUid: 'simple_ct', locale: 'en-us' } - ]); + const populateEntryCreatePayloadStub = sinon + .stub(entriesImport, 'populateEntryCreatePayload') + .returns([{ cTUid: 'simple_ct', locale: 'en-us' }]); const createEntriesStub = sinon.stub(entriesImport, 'createEntries').resolves(); const populateEntryUpdatePayloadStub = sinon.stub(entriesImport, 'populateEntryUpdatePayload').returns([]); const updateEntriesWithReferencesStub = sinon.stub(entriesImport, 'updateEntriesWithReferences').resolves(); @@ -3112,22 +3215,30 @@ describe('EntriesImport', () => { it('should handle general errors in try-catch', async () => { // Mock file system operations to return valid data but cause error later const mockFsUtil = { - readFile: sinon.stub() - .onCall(0).resolves([mockData.simpleContentType]) // content types - .onCall(1).resolves({ extension_uid: {} }) // marketplace apps - .onCall(2).resolves({}) // asset UID mapper - .onCall(3).resolves({}) // asset URL mapper - .onCall(4).resolves({}) // taxonomies - .onCall(5).rejects(new Error('File read failed')), // locales - this will cause error + readFile: sinon + .stub() + .onCall(0) + .resolves([mockData.simpleContentType]) // content types + .onCall(1) + .resolves({ extension_uid: {} }) // marketplace apps + .onCall(2) + .resolves({}) // asset UID mapper + .onCall(3) + .resolves({}) // asset URL mapper + .onCall(4) + .resolves({}) // taxonomies + .onCall(5) + .rejects(new Error('File read failed')), // locales - this will cause error makeDirectory: sinon.stub().resolves(), - writeFile: sinon.stub().resolves() + writeFile: sinon.stub().resolves(), }; sinon.stub(require('../../../../src/utils'), 'fsUtil').value(mockFsUtil); // Mock fileHelper const mockFileHelper = { readFileSync: sinon.stub().returns({}), - writeLargeFile: sinon.stub().resolves() + writeLargeFile: sinon.stub().resolves(), + fileExistsSync: sinon.stub().returns(false), }; sinon.stub(require('../../../../src/utils'), 'fileHelper').value(mockFileHelper); @@ -3167,12 +3278,12 @@ describe('EntriesImport', () => { // Setup auto-created entries entriesImport['autoCreatedEntries'] = [ { entryUid: 'auto_entry_1', title: 'Auto Entry 1' }, - { entryUid: 'auto_entry_2', title: 'Auto Entry 2' } + { entryUid: 'auto_entry_2', title: 'Auto Entry 2' }, ]; entriesImport['entriesForVariant'] = [ { entry_uid: 'auto_entry_1', locale: 'en-us', content_type: 'simple_ct' }, { entry_uid: 'auto_entry_2', locale: 'en-us', content_type: 'ref_ct' }, - { entry_uid: 'other_entry', locale: 'fr-fr', content_type: 'simple_ct' } + { entry_uid: 'other_entry', locale: 'fr-fr', content_type: 'simple_ct' }, ]; // Use the existing makeConcurrentCall stub to simulate successful removal @@ -3180,12 +3291,12 @@ describe('EntriesImport', () => { // Simulate onSuccess callback for first entry await options.apiParams.resolve({ response: { uid: 'auto_entry_1' }, - apiData: { entryUid: 'auto_entry_1' } + apiData: { entryUid: 'auto_entry_1' }, }); // Simulate onSuccess callback for second entry await options.apiParams.resolve({ response: { uid: 'auto_entry_2' }, - apiData: { entryUid: 'auto_entry_2' } + apiData: { entryUid: 'auto_entry_2' }, }); }); @@ -3206,12 +3317,10 @@ describe('EntriesImport', () => { it('should handle errors when removing auto-created entries', async () => { // Setup auto-created entries - entriesImport['autoCreatedEntries'] = [ - { entryUid: 'auto_entry_1', title: 'Auto Entry 1' } - ]; + entriesImport['autoCreatedEntries'] = [{ entryUid: 'auto_entry_1', title: 'Auto Entry 1' }]; entriesImport['entriesForVariant'] = [ { entry_uid: 'auto_entry_1', locale: 'en-us', content_type: 'simple_ct' }, - { entry_uid: 'other_entry', locale: 'fr-fr', content_type: 'simple_ct' } + { entry_uid: 'other_entry', locale: 'fr-fr', content_type: 'simple_ct' }, ]; // Use the existing makeConcurrentCall stub to simulate error @@ -3219,7 +3328,7 @@ describe('EntriesImport', () => { // Simulate onReject callback await options.apiParams.reject({ error: new Error('Delete failed'), - apiData: { entryUid: 'auto_entry_1' } + apiData: { entryUid: 'auto_entry_1' }, }); }); @@ -3235,9 +3344,7 @@ describe('EntriesImport', () => { it('should handle empty auto-created entries array', async () => { entriesImport['autoCreatedEntries'] = []; - entriesImport['entriesForVariant'] = [ - { entry_uid: 'other_entry', locale: 'fr-fr', content_type: 'simple_ct' } - ]; + entriesImport['entriesForVariant'] = [{ entry_uid: 'other_entry', locale: 'fr-fr', content_type: 'simple_ct' }]; // Use the existing makeConcurrentCall stub makeConcurrentCallStub.resolves(); @@ -3255,7 +3362,7 @@ describe('EntriesImport', () => { // Setup entriesForVariant with data entriesImport['entriesForVariant'] = [ { entry_uid: 'entry_1', locale: 'en-us', content_type: 'simple_ct' }, - { entry_uid: 'entry_2', locale: 'fr-fr', content_type: 'ref_ct' } + { entry_uid: 'entry_2', locale: 'fr-fr', content_type: 'ref_ct' }, ]; // Mock writeFileSync @@ -3289,7 +3396,7 @@ describe('EntriesImport', () => { it('should handle content type fetch error', async () => { // Setup content types with field rules const mockContentTypes = [mockData.simpleContentType, mockData.contentTypeWithReferences]; - + // Mock fsUtil.readFile to return field rules data fsUtilityReadFileStub.callsFake((filePath) => { console.log('fsUtil.readFile called with path:', filePath); @@ -3307,19 +3414,18 @@ describe('EntriesImport', () => { // Mock stack client methods directly const mockContentType = { - fetch: sinon.stub().rejects(new Error('Fetch failed')) + fetch: sinon.stub().rejects(new Error('Fetch failed')), }; const mockStackClient = { - contentType: sinon.stub().returns(mockContentType) + contentType: sinon.stub().returns(mockContentType), }; sinon.stub(entriesImport, 'stack').value(mockStackClient); - await entriesImport.updateFieldRules(); // Verify fsUtil.readFile was called expect(fsUtilityReadFileStub.callCount).to.be.greaterThan(0); - + // Verify stack client was called expect(mockStackClient.contentType.called).to.be.true; expect(mockContentType.fetch.called).to.be.true; @@ -3328,7 +3434,7 @@ describe('EntriesImport', () => { it('should handle content type update error', async () => { // Setup content types with field rules const mockContentTypes = [mockData.simpleContentType, mockData.contentTypeWithReferences]; - + // Mock fsUtil.readFile to return field rules data fsUtilityReadFileStub.callsFake((path) => { if (path.includes('field_rules_uid.json')) { @@ -3343,23 +3449,22 @@ describe('EntriesImport', () => { // Mock stack client to simulate successful fetch but failed update const mockUpdate = sinon.stub().rejects(new Error('Update failed')); const mockContentType = { - fetch: sinon.stub().resolves({ - uid: 'simple_ct', + fetch: sinon.stub().resolves({ + uid: 'simple_ct', field_rules: [], - update: mockUpdate - }) + update: mockUpdate, + }), }; const mockStackClient = { - contentType: sinon.stub().returns(mockContentType) + contentType: sinon.stub().returns(mockContentType), }; sinon.stub(entriesImport, 'stack').value(mockStackClient); - await entriesImport.updateFieldRules(); // Verify fsUtil.readFile was called expect(fsUtilityReadFileStub.callCount).to.be.greaterThan(0); - + // Verify stack client was called expect(mockStackClient.contentType.called).to.be.true; expect(mockContentType.fetch.called).to.be.true; @@ -3369,7 +3474,7 @@ describe('EntriesImport', () => { it('should skip when content type not found', async () => { // Setup content types with field rules const mockContentTypes = [mockData.simpleContentType, mockData.contentTypeWithReferences]; - + // Mock fsUtil.readFile to return field rules data fsUtilityReadFileStub.callsFake((path) => { if (path.includes('field_rules_uid.json')) { @@ -3383,10 +3488,10 @@ describe('EntriesImport', () => { // Mock stack client to return null (content type not found) const mockContentType = { - fetch: sinon.stub().resolves(null) + fetch: sinon.stub().resolves(null), }; const mockStackClient = { - contentType: sinon.stub().returns(mockContentType) + contentType: sinon.stub().returns(mockContentType), }; sinon.stub(entriesImport, 'stack').value(mockStackClient); @@ -3396,7 +3501,7 @@ describe('EntriesImport', () => { info: sinon.stub(), success: sinon.stub(), warn: sinon.stub(), - error: sinon.stub() + error: sinon.stub(), }; sinon.stub(require('@contentstack/cli-utilities'), 'log').value(mockLog); @@ -3404,9 +3509,9 @@ describe('EntriesImport', () => { // Verify debug log was called for skipping expect(mockLog.debug.called).to.be.true; - const skipCall = mockLog.debug.getCalls().find((call: any) => - call.args[0] && call.args[0].includes('Skipping field rules update') - ); + const skipCall = mockLog.debug + .getCalls() + .find((call: any) => call.args[0] && call.args[0].includes('Skipping field rules update')); expect(skipCall).to.exist; }); @@ -3415,7 +3520,7 @@ describe('EntriesImport', () => { const contentTypeWithoutRules = { ...mockData.simpleContentType }; delete contentTypeWithoutRules.field_rules; const mockContentTypes = [contentTypeWithoutRules]; - + // Mock fsUtil.readFile to return field rules data fsUtilityReadFileStub.callsFake((path) => { if (path.includes('field_rules_uid.json')) { @@ -3433,7 +3538,7 @@ describe('EntriesImport', () => { info: sinon.stub(), success: sinon.stub(), warn: sinon.stub(), - error: sinon.stub() + error: sinon.stub(), }; sinon.stub(require('@contentstack/cli-utilities'), 'log').value(mockLog); @@ -3441,9 +3546,9 @@ describe('EntriesImport', () => { // Verify info log was called for no field rules expect(mockLog.info.called).to.be.true; - const noRulesCall = mockLog.info.getCalls().find((call: any) => - call.args[0] && call.args[0].includes('No field rules found') - ); + const noRulesCall = mockLog.info + .getCalls() + .find((call: any) => call.args[0] && call.args[0].includes('No field rules found')); expect(noRulesCall).to.exist; }); }); @@ -3454,16 +3559,16 @@ describe('EntriesImport', () => { const entry = { uid: 'localized_entry_1', title: 'Localized Entry', - description: 'A localized entry' + description: 'A localized entry', }; const contentType = mockData.simpleContentType; const isMasterLocale = false; // Setup UID mapping entriesImport['entriesUidMapper'] = { - 'localized_entry_1': 'new_localized_entry_1' + localized_entry_1: 'new_localized_entry_1', }; - + // Setup asset mappers entriesImport['assetUidMapper'] = {}; entriesImport['assetUrlMapper'] = {}; @@ -3473,20 +3578,20 @@ describe('EntriesImport', () => { const originalLookupAssets = require('../../../../src/utils').lookupAssets; Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { get: () => (entryData: any) => entryData.entry, - configurable: true + configurable: true, }); // Mock stack client const mockEntryResponse = { uid: 'new_localized_entry_1', title: 'Localized Entry', - description: 'A localized entry' + description: 'A localized entry', }; const mockContentType = { - entry: sinon.stub().returns(mockEntryResponse) + entry: sinon.stub().returns(mockEntryResponse), }; const mockStackClient = { - contentType: sinon.stub().returns(mockContentType) + contentType: sinon.stub().returns(mockContentType), }; sinon.stub(entriesImport, 'stack').value(mockStackClient); @@ -3495,7 +3600,7 @@ describe('EntriesImport', () => { apiData: entry, resolve: sinon.stub(), reject: sinon.stub(), - additionalInfo: { cTUid: 'simple_ct', locale: 'fr-fr', contentType, isMasterLocale } + additionalInfo: { cTUid: 'simple_ct', locale: 'fr-fr', contentType, isMasterLocale }, }; const result = entriesImport.serializeEntries(apiOptions); @@ -3506,13 +3611,13 @@ describe('EntriesImport', () => { expect(result.apiData.title).to.equal('Localized Entry'); expect(result.additionalInfo['new_localized_entry_1']).to.deep.equal({ isLocalized: true, - entryOldUid: 'localized_entry_1' + entryOldUid: 'localized_entry_1', }); // Restore original function Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { value: originalLookupAssets, - configurable: true + configurable: true, }); }); @@ -3521,14 +3626,14 @@ describe('EntriesImport', () => { const entry = { uid: 'localized_entry_1', title: 'Localized Entry', - description: 'A localized entry' + description: 'A localized entry', }; const contentType = mockData.simpleContentType; const isMasterLocale = false; // Setup empty UID mapping entriesImport['entriesUidMapper'] = {}; - + // Setup asset mappers entriesImport['assetUidMapper'] = {}; entriesImport['assetUrlMapper'] = {}; @@ -3538,7 +3643,7 @@ describe('EntriesImport', () => { const originalLookupAssets = require('../../../../src/utils').lookupAssets; Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { get: () => (entryData: any) => entryData.entry, - configurable: true + configurable: true, }); const apiOptions = { @@ -3546,7 +3651,7 @@ describe('EntriesImport', () => { apiData: entry, resolve: sinon.stub(), reject: sinon.stub(), - additionalInfo: { cTUid: 'simple_ct', locale: 'fr-fr', contentType, isMasterLocale } + additionalInfo: { cTUid: 'simple_ct', locale: 'fr-fr', contentType, isMasterLocale }, }; const result = entriesImport.serializeEntries(apiOptions); @@ -3560,7 +3665,7 @@ describe('EntriesImport', () => { // Restore original function Object.defineProperty(require('../../../../src/utils'), 'lookupAssets', { value: originalLookupAssets, - configurable: true + configurable: true, }); }); }); @@ -3571,7 +3676,7 @@ describe('EntriesImport', () => { entriesImport['entriesForVariant'] = [ { entry_uid: 'entry_1', locale: 'en-us', content_type: 'simple_ct' }, { entry_uid: 'entry_2', locale: 'fr-fr', content_type: 'ref_ct' }, - { entry_uid: 'entry_3', locale: 'en-us', content_type: 'simple_ct' } + { entry_uid: 'entry_3', locale: 'en-us', content_type: 'simple_ct' }, ]; // Use the existing makeConcurrentCall stub to trigger onReject @@ -3579,7 +3684,7 @@ describe('EntriesImport', () => { // Simulate onReject callback - uid should match entry_uid in entriesForVariant await options.apiParams.reject({ error: new Error('Update failed'), - apiData: { uid: 'entry_1', title: 'Entry 1' } + apiData: { uid: 'entry_1', title: 'Entry 1' }, }); }); @@ -3587,14 +3692,14 @@ describe('EntriesImport', () => { const handleAndLogErrorStub = sinon.stub(require('@contentstack/cli-utilities'), 'handleAndLogError'); // Mock FsUtility.indexFileContent to return some data - const mockIndexFileContent = { 'chunk1': true }; + const mockIndexFileContent = { chunk1: true }; sinon.stub(FsUtility.prototype, 'indexFileContent').get(() => mockIndexFileContent); // Mock FsUtility.readChunkFiles to return some data const mockReadChunkFiles = { next: sinon.stub().resolves({ - 'entry1': { uid: 'entry_1', title: 'Entry 1' } - }) + entry1: { uid: 'entry_1', title: 'Entry 1' }, + }), }; sinon.stub(FsUtility.prototype, 'readChunkFiles').get(() => mockReadChunkFiles); @@ -3604,11 +3709,10 @@ describe('EntriesImport', () => { // Verify entriesForVariant was filtered correctly expect(entriesImport['entriesForVariant']).to.have.length(2); - expect(entriesImport['entriesForVariant'].find(e => e.entry_uid === 'entry_1')).to.be.undefined; - expect(entriesImport['entriesForVariant'].find(e => e.entry_uid === 'entry_2')).to.exist; - expect(entriesImport['entriesForVariant'].find(e => e.entry_uid === 'entry_3')).to.exist; + expect(entriesImport['entriesForVariant'].find((e) => e.entry_uid === 'entry_1')).to.be.undefined; + expect(entriesImport['entriesForVariant'].find((e) => e.entry_uid === 'entry_2')).to.exist; + expect(entriesImport['entriesForVariant'].find((e) => e.entry_uid === 'entry_3')).to.exist; }); - }); }); });