Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .talismanrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
fileignoreconfig:
- filename: packages/contentstack-asset-management/src/import/spaces.ts
checksum: e972d66f6a889f72d72ba48112a12f9447de39285cfb568d3b99a4969cf9f5f9
- filename: pnpm-lock.yaml
checksum: 8b36c85c5be8e48fb34f600b0e5631ab9c6640ad115825a11a7f2bd161141b74
checksum: 8b5a2f43585d3191cdc71ad611f50c94b6d13fb7442cf4218ee0851a068af178
version: '1.0'
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect } from 'chai';
import { CloneHandler } from '../../../src/core/util/clone-handler';
import { CloneConfig } from '../../../src/types/clone-config';
import { STRUCTURE_LIST } from '../../../src/utils/constants';
import sinon from 'sinon';
import inquirer from 'inquirer';

Expand Down Expand Up @@ -137,6 +138,94 @@ describe('CloneHandler - Commands', () => {
expect(cmdArgs).to.include('-a');
expect(cmdArgs).to.include('source-alias');
});

it('should set filteredModules for structure-only export without assets (AM 2.0 needs full export)', async () => {
const exportCmdStub = {
run: sandbox.stub().returns(Promise.resolve()),
};
sandbox.stub(require('@contentstack/cli-cm-export'), 'default').value(exportCmdStub);

await handler.cmdExport();

expect(fsStub.writeFileSync.calledOnce).to.be.true;
const written = JSON.parse(fsStub.writeFileSync.firstCall.args[1] as string);
expect(written.filteredModules).to.deep.equal(['stack', ...STRUCTURE_LIST]);
expect(written.filteredModules).to.not.include('assets');
expect(written.filteredModules).to.not.include('entries');
});

it('should not set filteredModules for full clone so default export includes assets', async () => {
const config: CloneConfig = {
cloneContext: {
command: 'test',
module: 'clone',
email: '[email protected]',
},
source_stack: 'test-key',
cloneType: 'b',
};
handler = new CloneHandler(config);
const exportCmdStub = {
run: sandbox.stub().returns(Promise.resolve()),
};
sandbox.stub(require('@contentstack/cli-cm-export'), 'default').value(exportCmdStub);

await handler.cmdExport();

expect(fsStub.writeFileSync.calledOnce).to.be.true;
const written = JSON.parse(fsStub.writeFileSync.firstCall.args[1] as string);
expect(written).to.not.have.property('filteredModules');
});

it('should preserve region.assetManagementUrl in serialized export config for AM 2.0 export', async () => {
const amUrl = 'https://asset-management.example.com';
const config: CloneConfig = {
cloneContext: {
command: 'test',
module: 'clone',
email: '[email protected]',
},
source_stack: 'test-key',
cloneType: 'b',
region: { assetManagementUrl: amUrl },
};
handler = new CloneHandler(config);
const exportCmdStub = {
run: sandbox.stub().returns(Promise.resolve()),
};
sandbox.stub(require('@contentstack/cli-cm-export'), 'default').value(exportCmdStub);

await handler.cmdExport();

const written = JSON.parse(fsStub.writeFileSync.firstCall.args[1] as string);
expect(written.region).to.be.an('object');
expect(written.region.assetManagementUrl).to.equal(amUrl);
});

it('should merge export.region.assetManagementUrl from external export config', async () => {
const amUrl = 'https://asset-management-merged.example.com';
const config: CloneConfig = {
cloneContext: {
command: 'test',
module: 'clone',
email: '[email protected]',
},
source_stack: 'test-key',
cloneType: 'b',
export: { region: { assetManagementUrl: amUrl } },
};
handler = new CloneHandler(config);
const exportCmdStub = {
run: sandbox.stub().returns(Promise.resolve()),
};
sandbox.stub(require('@contentstack/cli-cm-export'), 'default').value(exportCmdStub);

await handler.cmdExport();

const written = JSON.parse(fsStub.writeFileSync.firstCall.args[1] as string);
expect(written.region.assetManagementUrl).to.equal(amUrl);
expect(written).to.not.have.property('export');
});
});

describe('cmdImport', () => {
Expand Down Expand Up @@ -398,7 +487,7 @@ describe('CloneHandler - Commands', () => {
// Mock configHandler FIRST before creating handler - following import plugin pattern
const configHandler = require('@contentstack/cli-utilities').configHandler;
configHandlerGetStub = sandbox.stub(configHandler, 'get').returns(undefined);

// Stub ora spinner - following import plugin pattern
const oraModule = require('ora');
const mockSpinner = {
Expand All @@ -412,7 +501,7 @@ describe('CloneHandler - Commands', () => {
writable: true,
configurable: true,
});

const config: CloneConfig = {
cloneContext: {
command: 'test',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ describe('ExportAssets', () => {
enableDownloadStatus: false,
includeVersionedAssets: false,
},
'asset-management': {
chunkFileSizeMb: 1,
},
content_types: {
dirName: 'content_types',
fileName: 'content_types.json',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ describe('BaseClass', () => {
enableDownloadStatus: false,
includeVersionedAssets: false,
},
'asset-management': {
chunkFileSizeMb: 1,
},
content_types: {
dirName: 'content_types',
fileName: 'content_types.json',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as utilities from '@contentstack/cli-utilities';
import ExportMarketplaceApps from '../../../../src/export/modules/marketplace-apps';
import ExportConfig from '../../../../src/types/export-config';
import * as marketplaceAppHelper from '../../../../src/utils/marketplace-app-helper';
import * as utils from '../../../../src/utils';

describe('ExportMarketplaceApps', () => {
let exportMarketplaceApps: any;
Expand Down Expand Up @@ -106,10 +105,6 @@ describe('ExportMarketplaceApps', () => {
sinon.stub(marketplaceAppHelper, 'getDeveloperHubUrl').resolves('https://developer-api.contentstack.io');
sinon.stub(marketplaceAppHelper, 'createNodeCryptoInstance').resolves(mockNodeCrypto);
sinon.stub(marketplaceAppHelper, 'askEncryptionKey').resolves('test-encryption-key');
sinon.stub(utils, 'getOrgUid').resolves('test-org-uid');
sinon.stub(utils, 'getDeveloperHubUrl').resolves('https://developer-api.contentstack.io');
sinon.stub(utils, 'createNodeCryptoInstance').resolves(mockNodeCrypto);
sinon.stub(utils, 'askEncryptionKey').resolves('test-encryption-key');
});

afterEach(() => {
Expand Down Expand Up @@ -300,14 +295,7 @@ describe('ExportMarketplaceApps', () => {

await exportMarketplaceApps.start();

// Source imports from utils barrel; resolution may use utils or marketplaceAppHelper depending on env (CI vs local)
const helperCalled = (marketplaceAppHelper.createNodeCryptoInstance as sinon.SinonStub).calledOnce;
const utilsCalled = (utils.createNodeCryptoInstance as sinon.SinonStub).calledOnce;
expect(helperCalled || utilsCalled, 'createNodeCryptoInstance should be called exactly once').to.be.true;
expect(
(marketplaceAppHelper.createNodeCryptoInstance as sinon.SinonStub).callCount +
(utils.createNodeCryptoInstance as sinon.SinonStub).callCount,
).to.equal(1);
expect((marketplaceAppHelper.createNodeCryptoInstance as sinon.SinonStub).calledOnce).to.be.true;

getAppsCountStub.restore();
exportAppsStub.restore();
Expand Down Expand Up @@ -409,12 +397,12 @@ describe('ExportMarketplaceApps', () => {
const getAppManifestAndAppConfigStub = sinon.stub(exportMarketplaceApps, 'getAppManifestAndAppConfig').resolves();

// Reset the stub call count since it might have been called in previous tests
(utils.createNodeCryptoInstance as sinon.SinonStub).resetHistory();
(marketplaceAppHelper.createNodeCryptoInstance as sinon.SinonStub).resetHistory();

await exportMarketplaceApps.exportApps();

// NodeCrypto should not be initialized if no configurations
expect((utils.createNodeCryptoInstance as sinon.SinonStub).called).to.be.false;
expect((marketplaceAppHelper.createNodeCryptoInstance as sinon.SinonStub).called).to.be.false;

getStackSpecificAppsStub.restore();
getAppManifestAndAppConfigStub.restore();
Expand Down Expand Up @@ -785,11 +773,11 @@ describe('ExportMarketplaceApps', () => {
});

// Reset the stub call count since it was called in beforeEach
(utils.createNodeCryptoInstance as sinon.SinonStub).resetHistory();
(marketplaceAppHelper.createNodeCryptoInstance as sinon.SinonStub).resetHistory();

await exportMarketplaceApps.getAppConfigurations(0, exportMarketplaceApps.installedApps[0]);

expect((utils.createNodeCryptoInstance as sinon.SinonStub).called).to.be.true;
expect((marketplaceAppHelper.createNodeCryptoInstance as sinon.SinonStub).called).to.be.true;
expect(exportMarketplaceApps.nodeCrypto).to.exist;
expect((marketplaceAppHelper.createNodeCryptoInstance as sinon.SinonStub).called).to.be.true;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export default abstract class BaseClass {
processName,
indexerCount,
currentIndexer,
concurrencyLimit = this.importConfig.modules.apiConcurrency,
concurrencyLimit = this.importConfig.fetchConcurrency,
} = env;

/* eslint-disable no-async-promise-executor */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function buildImportSpacesOptions(
sourceApiKey: importConfig.source_stack,
context: importConfig.context as unknown as Record<string, unknown>,
backupDir: importConfig.backupDir,
apiConcurrency: importConfig.modules?.apiConcurrency ?? importConfig.fetchConcurrency,
apiConcurrency: importConfig.fetchConcurrency,
uploadAssetsConcurrency: am?.uploadAssetsConcurrency,
importFoldersConcurrency: am?.importFoldersConcurrency,
spacesDirName: am?.dirName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ describe('ModuleImporter', () => {

await importer.start();

expect(importer['stackAPIClient'].locale.calledOnce).to.be.true;
expect(masterLocalDetailsStub.calledOnce).to.be.true;
expect(importer['importConfig'].master_locale).to.deep.equal({ code: 'en-us' });
expect(importer['importConfig'].masterLocale).to.deep.equal({ code: 'en-us' });
});
Expand All @@ -534,13 +534,7 @@ describe('ModuleImporter', () => {

it('should set both master_locale and masterLocale', async () => {
mockImportConfig.master_locale = undefined;

const localeMock = {
query: sandbox.stub().returnsThis(),
find: sandbox.stub().resolves({ items: [{ code: 'de-de' }] }),
};
mockStackClient.locale = sandbox.stub().returns(localeMock);
mockStackClient._localeMock = localeMock;
masterLocalDetailsStub.resolves({ code: 'de-de' });

const importer = new ModuleImporter(mockManagementClient as any, mockImportConfig);

Expand All @@ -552,13 +546,7 @@ describe('ModuleImporter', () => {

it('should handle error when masterLocalDetails fails', async () => {
mockImportConfig.master_locale = undefined;

const localeMock = {
query: sandbox.stub().returnsThis(),
find: sandbox.stub().rejects(new Error('Master locale fetch failed')),
};
mockStackClient.locale = sandbox.stub().returns(localeMock);
mockStackClient._localeMock = localeMock;
masterLocalDetailsStub.rejects(new Error('Master locale fetch failed'));

const importer = new ModuleImporter(mockManagementClient as any, mockImportConfig);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ describe('ImportAssets', () => {
region: 'us',
master_locale: { code: 'en-us' },
masterLocale: { code: 'en-us' },
fetchConcurrency: 5,
writeConcurrency: 1,
context: {
command: 'cm:stacks:import',
module: 'assets',
Expand All @@ -55,7 +57,6 @@ describe('ImportAssets', () => {
dirName: 'assets',
validKeys: ['title', 'filename', 'content_type', 'parent_uid', 'description', 'tags'],
folderValidKeys: ['name', 'parent_uid'],
apiConcurrency: 5,
importFoldersConcurrency: 3,
uploadAssetsConcurrency: 5,
includeVersionedAssets: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@ describe('BaseClass', () => {
orgId: 'org-123',
authenticationMethod: 'Management Token',
},
fetchConcurrency: 5,
writeConcurrency: 1,
modules: {
types: ['assets', 'content-types'],
apiConcurrency: 5,
assets: {
dirName: 'assets',
fileName: 'assets.json',
Expand All @@ -120,7 +121,6 @@ describe('BaseClass', () => {
dirName: 'content_types',
fileName: 'content_types.json',
validKeys: ['title', 'uid', 'schema'],
apiConcurrency: 1,
},
} as any,
backupDir: '/test/backup',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ describe('ImportContentTypes', () => {
master_locale: { code: 'en-us' },
masterLocale: { code: 'en-us' },
writeConcurrency: 2,
fetchConcurrency: 5,
context: {
command: 'cm:stacks:import',
module: 'content-types',
Expand All @@ -146,15 +147,13 @@ describe('ImportContentTypes', () => {
'content-types': {
dirName: 'content_types',
validKeys: ['title', 'uid', 'schema'],
apiConcurrency: 5,
writeConcurrency: 3,
fileName: 'content_types.json',
limit: 100,
},
'global-fields': {
dirName: 'global_fields',
validKeys: ['title', 'uid', 'schema'],
apiConcurrency: 5,
writeConcurrency: 1,
fileName: 'globalfields.json',
limit: 100,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe('ImportGlobalFields', () => {
master_locale: { code: 'en-us' },
masterLocale: { code: 'en-us' },
writeConcurrency: 2,
fetchConcurrency: 5,
context: {
command: 'cm:stacks:import',
module: 'global-fields',
Expand All @@ -67,7 +68,6 @@ describe('ImportGlobalFields', () => {
'global-fields': {
dirName: 'global_fields',
validKeys: ['title', 'uid', 'schema'],
apiConcurrency: 5,
writeConcurrency: 3,
fileName: 'globalfields.json',
limit: 100,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('ImportLocales', () => {
management_token: 'test-token',
contentDir: tempDir,
modules: {
apiConcurrency: 1,
apiConcurrency: 5,
types: [],
locales: {
dirName: 'locales',
Expand Down Expand Up @@ -112,6 +112,24 @@ describe('ImportLocales', () => {
apiBaseUrl: 'https://composable-studio-api.contentstack.com/v1',
apiVersion: 'v1',
},
'asset-management': {
dirName: 'spaces',
fieldsDir: 'fields',
assetTypesDir: 'asset_types',
fieldsFileName: 'fields.json',
assetTypesFileName: 'asset-types.json',
foldersFileName: 'folders.json',
assetsFileName: 'assets.json',
fieldsImportInvalidKeys: [],
assetTypesImportInvalidKeys: [],
mapperRootDir: 'mapper',
mapperAssetsModuleDir: 'assets',
mapperUidFileName: 'uid-mapping.json',
mapperUrlFileName: 'url-mapping.json',
mapperSpaceUidFileName: 'space-uid-mapping.json',
uploadAssetsConcurrency: 2,
importFoldersConcurrency: 1,
},
personalize: {
baseURL: {},
dirName: 'personalize',
Expand Down
Loading
Loading