From d4c509ac7bab8686f62b0d4fe51b83c8e6b36e84 Mon Sep 17 00:00:00 2001 From: Netraj Patel Date: Thu, 20 Nov 2025 18:44:47 +0530 Subject: [PATCH 1/3] Removed deprecated code from Seed plugin --- .talismanrc | 16 +++----- packages/contentstack-seed/README.md | 26 ++++++------- packages/contentstack-seed/package.json | 8 +--- .../src/commands/cm/stacks/seed.ts | 38 ++++--------------- packages/contentstack/README.md | 26 ++++++------- 5 files changed, 40 insertions(+), 74 deletions(-) diff --git a/.talismanrc b/.talismanrc index ef2d67cb88..527acc72b2 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,12 +1,8 @@ fileignoreconfig: - - filename: package-lock.json - checksum: dbb4e0ec893dd445678e47864d3f3a0b14c3c0eff933fb50ef5ba6b300c1cba2 - - filename: packages/contentstack-migration/test/unit/utils/map.test.js - checksum: 7d570280b2f379531dde84946b06171d50f92489ff0de6336f7fbd81c336ee89 - - filename: packages/contentstack-migration/test/unit/modules/parser.test.js - checksum: 243fa1c45875675f719f660c0c988e2ba9266c562a37aaeb09b0db93a0cb037d - - filename: packages/contentstack-migration/test/unit/validators/create-content-type-validator.test.js - checksum: f92e39a542cd2d561c441d23395515cadc24c9514de55c3edb038f70bd2458b3 - - filename: packages/contentstack-migration/test/unit/validators/edit-content-type-validator.test.js - checksum: bde4bc6b2a90e7ce5872e6fbbabef9f2db352705be4d2f8d28d71d84209a714e + - filename: packages/contentstack-seed/src/commands/cm/stacks/seed.ts + checksum: d04770564196b080878566255ea0faf1c82c1460161d2004d2b1edece0546493 + - filename: packages/contentstack/README.md + checksum: 4408912cb1af1bd383af181b0e2faa21009d0a09915c37b674c8159178774314 + - filename: packages/contentstack-seed/README.md + checksum: 13982c74b6428b73f5409bbd3943a44debcae16d39ed7ff6db3291b3a7036ddf version: '1.0' diff --git a/packages/contentstack-seed/README.md b/packages/contentstack-seed/README.md index d2bb983a30..24fb855912 100644 --- a/packages/contentstack-seed/README.md +++ b/packages/contentstack-seed/README.md @@ -10,26 +10,25 @@ To import content to your stack, you can choose from the following two sources: ## Commands -* [`csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value--k-value--n-value--y-value--s-value---locale-value) -* [`csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value--k-value--n-value--y-value--s-value---locale-value) +* [`csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value---stack-api-key-value---stack-name-value---yes-value---alias-value---locale-value) +* [`csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value---stack-api-key-value---stack-name-value---yes-value---alias-value---locale-value) -## `csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]` +## `csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]` Create a stack from existing content types, entries, assets, etc ``` USAGE - $ csdx cm:seed cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s - ] [--locale ] + $ csdx cm:seed cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name + ] [--yes ] [--alias ] [--locale ] FLAGS -a, --alias= Alias of the management token -k, --stack-api-key= Provide stack API key to seed content to -n, --stack-name= Name of a new stack that needs to be created. - -o, --org= Provide Organization UID to create a new stack - -r, --repo= GitHub organization name or GitHub user name/repository name. - -s, --stack= Provide the stack UID to seed content. -y, --yes= [Optional] Skip the stack confirmation. + --org= Provide Organization UID to create a new stack + --repo= GitHub organization name or GitHub user name/repository name. DESCRIPTION Create a stack from existing content types, entries, assets, etc @@ -49,23 +48,22 @@ EXAMPLES $ csdx cm:stacks:seed --repo "account/repository" --org "your-org-uid" --stack-name "stack-name" //create a new stack in given org uid ``` -## `csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]` +## `csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]` Create a stack from existing content types, entries, assets, etc ``` USAGE - $ csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] - [--locale ] + $ csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes + ] [--alias ] [--locale ] FLAGS -a, --alias= Alias of the management token -k, --stack-api-key= Provide stack API key to seed content to -n, --stack-name= Name of a new stack that needs to be created. - -o, --org= Provide Organization UID to create a new stack - -r, --repo= GitHub organization name or GitHub user name/repository name. - -s, --stack= Provide the stack UID to seed content. -y, --yes= [Optional] Skip the stack confirmation. + --org= Provide Organization UID to create a new stack + --repo= GitHub organization name or GitHub user name/repository name. DESCRIPTION Create a stack from existing content types, entries, assets, etc diff --git a/packages/contentstack-seed/package.json b/packages/contentstack-seed/package.json index f54cb018eb..42126d5eae 100644 --- a/packages/contentstack-seed/package.json +++ b/packages/contentstack-seed/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-cm-seed", "description": "create a Stack from existing content types, entries, assets, etc.", - "version": "2.0.0-beta.2", + "version": "2.0.0-beta.3", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "dependencies": { @@ -53,12 +53,8 @@ "repositoryPrefix": "<%- repo %>/blob/main/packages/contentstack-seed/<%- commandPath %>" }, "csdxConfig": { - "expiredCommands": { - "cm:seed": "csdx cm:stacks:seed" - }, "shortCommandName": { - "cm:stacks:seed": "SEED", - "cm:seed": "O-SEED" + "cm:stacks:seed": "SEED" } }, "repository": "contentstack/cli", diff --git a/packages/contentstack-seed/src/commands/cm/stacks/seed.ts b/packages/contentstack-seed/src/commands/cm/stacks/seed.ts index 44edbcbe70..c3beca01ef 100644 --- a/packages/contentstack-seed/src/commands/cm/stacks/seed.ts +++ b/packages/contentstack-seed/src/commands/cm/stacks/seed.ts @@ -1,12 +1,5 @@ import { Command } from '@contentstack/cli-command'; -import { - printFlagDeprecation, - flags, - isAuthenticated, - FlagInput, - cliux, - configHandler, -} from '@contentstack/cli-utilities'; +import { flags, isAuthenticated, FlagInput, cliux, configHandler } from '@contentstack/cli-utilities'; import ContentModelSeeder, { ContentModelSeederOptions } from '../../../seed'; export default class SeedCommand extends Command { @@ -20,37 +13,34 @@ export default class SeedCommand extends Command { '$ csdx cm:stacks:seed --repo "account/repository" --org "your-org-uid" --stack-name "stack-name" //create a new stack in given org uid', ]; - static usage = 'cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]'; + static usage = + 'cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]'; static flags: FlagInput = { repo: flags.string({ - char: 'r', description: 'GitHub organization name or GitHub user name/repository name.', multiple: false, required: false, - parse: printFlagDeprecation(['-r'], ['--repo']), }), org: flags.string({ - char: 'o', description: 'Provide Organization UID to create a new stack', multiple: false, required: false, - exclusive: ['stack'], - parse: printFlagDeprecation(['-o'], ['--org']), + exclusive: ['stack-api-key'], }), 'stack-api-key': flags.string({ char: 'k', description: 'Provide stack API key to seed content to', multiple: false, required: false, - exclusive: ['org'], + exclusive: ['org', 'stack-name'], }), 'stack-name': flags.string({ char: 'n', description: 'Name of a new stack that needs to be created.', multiple: false, required: false, - exclusive: ['stack'], + exclusive: ['stack-api-key'], }), 'fetch-limit': flags.string({ char: 'l', @@ -64,16 +54,6 @@ export default class SeedCommand extends Command { required: false, description: '[Optional] Skip the stack confirmation.', }), - - //To be deprecated - stack: flags.string({ - char: 's', - description: 'Provide the stack UID to seed content.', - multiple: false, - required: false, - exclusive: ['org', 'name'], - parse: printFlagDeprecation(['s', 'stack'], ['-k', 'stack-api-key']), - }), alias: flags.string({ char: 'a', description: 'Alias of the management token', @@ -84,8 +64,6 @@ export default class SeedCommand extends Command { }), }; - static aliases = ['cm:seed']; - async run() { try { const { flags: seedFlags } = await this.parse(SeedCommand); @@ -103,7 +81,7 @@ export default class SeedCommand extends Command { cmaHost: this.cmaHost, gitHubPath: seedFlags.repo, orgUid: seedFlags.org, - stackUid: seedFlags['stack-api-key'] || seedFlags.stack, + stackUid: seedFlags['stack-api-key'], stackName: seedFlags['stack-name'], fetchLimit: seedFlags['fetch-limit'], skipStackConfirmation: seedFlags['yes'], @@ -132,4 +110,4 @@ export default class SeedCommand extends Command { this.error(errorObj, { exit: 1, suggestions: errorObj.suggestions }); } } -} \ No newline at end of file +} diff --git a/packages/contentstack/README.md b/packages/contentstack/README.md index 8d62b517f2..e762322a46 100644 --- a/packages/contentstack/README.md +++ b/packages/contentstack/README.md @@ -70,7 +70,7 @@ USAGE * [`csdx cm:stacks:import-setup [-k ] [-d ] [-a ] [--modules ]`](#csdx-cmstacksimport-setup--k-value--d-value--a-value---modules-valuevalue) * [`csdx cm:migrate-rte`](#csdx-cmmigrate-rte) * [`csdx cm:stacks:migration [-k ] [-a ] [--file-path ] [--branch ] [--config-file ] [--config ] [--multiple]`](#csdx-cmstacksmigration--k-value--a-value---file-path-value---branch-value---config-file-value---config-value---multiple) -* [`csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value--k-value--n-value--y-value--s-value---locale-value) +* [`csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value---stack-api-key-value---stack-name-value---yes-value---alias-value---locale-value) * [`csdx cm:stacks:clone [--source-branch ] [--target-branch ] [--source-management-token-alias ] [--destination-management-token-alias ] [-n ] [--type a|b] [--source-stack-api-key ] [--destination-stack-api-key ] [--import-webhook-status disable|current]`](#csdx-cmstacksclone---source-branch-value---target-branch-value---source-management-token-alias-value---destination-management-token-alias-value--n-value---type-ab---source-stack-api-key-value---destination-stack-api-key-value---import-webhook-status-disablecurrent) * [`csdx cm:stacks:audit`](#csdx-cmstacksaudit) * [`csdx cm:stacks:audit:fix`](#csdx-cmstacksauditfix) @@ -83,7 +83,7 @@ USAGE * [`csdx cm:stacks:publish-clear-logs`](#csdx-cmstackspublish-clear-logs) * [`csdx cm:stacks:publish-configure`](#csdx-cmstackspublish-configure) * [`csdx cm:stacks:publish-revert`](#csdx-cmstackspublish-revert) -* [`csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value--k-value--n-value--y-value--s-value---locale-value) +* [`csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value---stack-api-key-value---stack-name-value---yes-value---alias-value---locale-value) * [`csdx csdx cm:stacks:unpublish [-a ] [-e ] [-c ] [-y] [--locale ] [--branch ] [--retry-failed ] [--bulk-unpublish ] [--content-type ] [--delivery-token ] [--only-assets] [--only-entries]`](#csdx-csdx-cmstacksunpublish--a-value--e-value--c-value--y---locale-value---branch-value---retry-failed-value---bulk-unpublish-value---content-type-value---delivery-token-value---only-assets---only-entries) * [`csdx config:get:base-branch`](#csdx-configgetbase-branch) * [`csdx config:get:ea-header`](#csdx-configgetea-header) @@ -2571,23 +2571,22 @@ EXAMPLES $ csdx cm:migration --alias --file-path ``` -## `csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]` +## `csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]` Create a stack from existing content types, entries, assets, etc ``` USAGE - $ csdx cm:seed cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s - ] [--locale ] + $ csdx cm:seed cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name + ] [--yes ] [--alias ] [--locale ] FLAGS -a, --alias= Alias of the management token -k, --stack-api-key= Provide stack API key to seed content to -n, --stack-name= Name of a new stack that needs to be created. - -o, --org= Provide Organization UID to create a new stack - -r, --repo= GitHub organization name or GitHub user name/repository name. - -s, --stack= Provide the stack UID to seed content. -y, --yes= [Optional] Skip the stack confirmation. + --org= Provide Organization UID to create a new stack + --repo= GitHub organization name or GitHub user name/repository name. DESCRIPTION Create a stack from existing content types, entries, assets, etc @@ -3187,23 +3186,22 @@ EXAMPLES _See code: [@contentstack/cli-cm-bulk-publish](https://github.com/contentstack/cli/blob/main/packages/contentstack-bulk-publish/src/commands/cm/stacks/publish-revert.js)_ -## `csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] [--locale ]` +## `csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]` Create a stack from existing content types, entries, assets, etc ``` USAGE - $ csdx cm:stacks:seed [--repo ] [--org ] [-k ] [-n ] [-y ] [-s ] - [--locale ] + $ csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes + ] [--alias ] [--locale ] FLAGS -a, --alias= Alias of the management token -k, --stack-api-key= Provide stack API key to seed content to -n, --stack-name= Name of a new stack that needs to be created. - -o, --org= Provide Organization UID to create a new stack - -r, --repo= GitHub organization name or GitHub user name/repository name. - -s, --stack= Provide the stack UID to seed content. -y, --yes= [Optional] Skip the stack confirmation. + --org= Provide Organization UID to create a new stack + --repo= GitHub organization name or GitHub user name/repository name. DESCRIPTION Create a stack from existing content types, entries, assets, etc From ada4637f1f7a12064c235e58f03c141fdeb54d43 Mon Sep 17 00:00:00 2001 From: Netraj Patel Date: Fri, 21 Nov 2025 12:02:44 +0530 Subject: [PATCH 2/3] Updated unit test cases --- .talismanrc | 14 +- packages/contentstack-seed/README.md | 39 - packages/contentstack-seed/jest.config.js | 27 +- packages/contentstack-seed/package.json | 1 - .../test/commands/cm/stacks/seed.test.ts | 367 ++++++++++ .../test/seed/contentstack/client.test.ts | 676 ++++++++++++++++++ .../test/seed/contentstack/error.test.ts | 56 ++ .../contentstack-seed/test/seed/error.test.ts | 48 ++ .../test/seed/github/client.test.ts | 387 ++++++++++ .../test/seed/github/error.test.ts | 56 ++ .../test/seed/importer.test.ts | 180 +++++ .../test/seed/interactive.test.ts | 332 +++++++++ packages/contentstack-seed/tests/config.json | 7 - .../tests/contentstack.error.test.ts | 14 - .../tests/contentstack.test.ts | 126 ---- .../contentstack-seed/tests/github.error.ts | 14 - .../contentstack-seed/tests/github.test.ts | 97 --- .../contentstack-seed/tests/importer.test.ts | 27 - .../tests/interactive.test.ts | 139 ---- .../contentstack-seed/tests/seeder.test.ts | 190 ----- packages/contentstack-seed/tsconfig.test.json | 15 + 21 files changed, 2142 insertions(+), 670 deletions(-) create mode 100644 packages/contentstack-seed/test/commands/cm/stacks/seed.test.ts create mode 100644 packages/contentstack-seed/test/seed/contentstack/client.test.ts create mode 100644 packages/contentstack-seed/test/seed/contentstack/error.test.ts create mode 100644 packages/contentstack-seed/test/seed/error.test.ts create mode 100644 packages/contentstack-seed/test/seed/github/client.test.ts create mode 100644 packages/contentstack-seed/test/seed/github/error.test.ts create mode 100644 packages/contentstack-seed/test/seed/importer.test.ts create mode 100644 packages/contentstack-seed/test/seed/interactive.test.ts delete mode 100644 packages/contentstack-seed/tests/config.json delete mode 100644 packages/contentstack-seed/tests/contentstack.error.test.ts delete mode 100644 packages/contentstack-seed/tests/contentstack.test.ts delete mode 100644 packages/contentstack-seed/tests/github.error.ts delete mode 100644 packages/contentstack-seed/tests/github.test.ts delete mode 100644 packages/contentstack-seed/tests/importer.test.ts delete mode 100644 packages/contentstack-seed/tests/interactive.test.ts delete mode 100644 packages/contentstack-seed/tests/seeder.test.ts create mode 100644 packages/contentstack-seed/tsconfig.test.json diff --git a/.talismanrc b/.talismanrc index 527acc72b2..4cfc95c0b5 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,8 +1,10 @@ fileignoreconfig: - - filename: packages/contentstack-seed/src/commands/cm/stacks/seed.ts - checksum: d04770564196b080878566255ea0faf1c82c1460161d2004d2b1edece0546493 - - filename: packages/contentstack/README.md - checksum: 4408912cb1af1bd383af181b0e2faa21009d0a09915c37b674c8159178774314 - - filename: packages/contentstack-seed/README.md - checksum: 13982c74b6428b73f5409bbd3943a44debcae16d39ed7ff6db3291b3a7036ddf + - filename: packages/contentstack-seed/test/seed/importer.test.ts + checksum: 77bc27f5217c6d69c21bac51afc94d677ad67374c1b39b0575646300eb0decd3 + - filename: packages/contentstack-seed/test/seed/interactive.test.ts + checksum: e7a823051b5eb27f2674ca2c31719205fa822e9cac1524dbd14e48b1ec078c06 + - filename: packages/contentstack-seed/test/commands/cm/stacks/seed.test.ts + checksum: 61143bbb2ac86c24afc6972d17d9179c6181ec68a909b84570afdad2aaa13ade + - filename: packages/contentstack-seed/test/seed/contentstack/client.test.ts + checksum: f1bc369c9c3c4a84ddd590864c0f3e8b13be956b8fb8891b6324f44cdcc7d568 version: '1.0' diff --git a/packages/contentstack-seed/README.md b/packages/contentstack-seed/README.md index 24fb855912..54dbca9385 100644 --- a/packages/contentstack-seed/README.md +++ b/packages/contentstack-seed/README.md @@ -11,42 +11,6 @@ To import content to your stack, you can choose from the following two sources: ## Commands * [`csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value---stack-api-key-value---stack-name-value---yes-value---alias-value---locale-value) -* [`csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]`](#csdx-cmstacksseed---repo-value---org-value---stack-api-key-value---stack-name-value---yes-value---alias-value---locale-value) - -## `csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]` - -Create a stack from existing content types, entries, assets, etc - -``` -USAGE - $ csdx cm:seed cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name - ] [--yes ] [--alias ] [--locale ] - -FLAGS - -a, --alias= Alias of the management token - -k, --stack-api-key= Provide stack API key to seed content to - -n, --stack-name= Name of a new stack that needs to be created. - -y, --yes= [Optional] Skip the stack confirmation. - --org= Provide Organization UID to create a new stack - --repo= GitHub organization name or GitHub user name/repository name. - -DESCRIPTION - Create a stack from existing content types, entries, assets, etc - -ALIASES - $ csdx cm:seed - -EXAMPLES - $ csdx cm:stacks:seed - - $ csdx cm:stacks:seed --repo "account" - - $ csdx cm:stacks:seed --repo "account/repository" - - $ csdx cm:stacks:seed --repo "account/repository" --stack-api-key "stack-api-key" //seed content into specific stack - - $ csdx cm:stacks:seed --repo "account/repository" --org "your-org-uid" --stack-name "stack-name" //create a new stack in given org uid -``` ## `csdx cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]` @@ -68,9 +32,6 @@ FLAGS DESCRIPTION Create a stack from existing content types, entries, assets, etc -ALIASES - $ csdx cm:seed - EXAMPLES $ csdx cm:stacks:seed diff --git a/packages/contentstack-seed/jest.config.js b/packages/contentstack-seed/jest.config.js index b7ac236362..ae1f472366 100644 --- a/packages/contentstack-seed/jest.config.js +++ b/packages/contentstack-seed/jest.config.js @@ -1,12 +1,19 @@ module.exports = { - "roots": [ - "" - ], - "testMatch": [ - "**/tests/**/*.+(ts|tsx)", - "**/?(*.)+(spec|test).+(ts|tsx)" - ], - "transform": { - "^.+\\.(ts|tsx)$": "ts-jest" + roots: [''], + testMatch: ['**/test/**/*.+(ts|tsx)', '**/?(*.)+(spec|test).+(ts|tsx)'], + transform: { + '^.+\\.(ts|tsx)$': ['ts-jest', { + tsconfig: 'tsconfig.test.json', + }], }, -} \ No newline at end of file + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], + testEnvironment: 'node', + collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'], + coverageDirectory: 'coverage', + verbose: true, + setupFilesAfterEnv: [], + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, + testPathIgnorePatterns: ['/node_modules/', '/old_tests/'], +}; \ No newline at end of file diff --git a/packages/contentstack-seed/package.json b/packages/contentstack-seed/package.json index 42126d5eae..1169d92b30 100644 --- a/packages/contentstack-seed/package.json +++ b/packages/contentstack-seed/package.json @@ -62,7 +62,6 @@ "test": "jest", "pack": "npm pack --dry-run", "postpack": "rm -f oclif.manifest.json", - "posttest": "eslint . --ext .ts --config .eslintrc", "prepack": "rm -rf lib && tsc -b && oclif manifest && oclif readme", "version": "oclif readme && git add README.md", "clean": "rm -rf ./node_modules tsconfig.build.tsbuildinfo" diff --git a/packages/contentstack-seed/test/commands/cm/stacks/seed.test.ts b/packages/contentstack-seed/test/commands/cm/stacks/seed.test.ts new file mode 100644 index 0000000000..889d133f37 --- /dev/null +++ b/packages/contentstack-seed/test/commands/cm/stacks/seed.test.ts @@ -0,0 +1,367 @@ +import SeedCommand from '../../../../src/commands/cm/stacks/seed'; +import ContentModelSeeder from '../../../../src/seed/index'; +import { isAuthenticated, configHandler, cliux } from '@contentstack/cli-utilities'; + +// Mock dependencies +jest.mock('../../../../src/seed/index'); +jest.mock('@contentstack/cli-utilities', () => ({ + ...jest.requireActual('@contentstack/cli-utilities'), + isAuthenticated: jest.fn(), + configHandler: { + get: jest.fn(), + }, + cliux: { + print: jest.fn(), + loader: jest.fn(), + error: jest.fn(), + }, +})); + +describe('SeedCommand', () => { + let mockSeeder: jest.Mocked; + let command: SeedCommand; + + beforeEach(() => { + jest.clearAllMocks(); + + // Mock ContentModelSeeder + mockSeeder = { + run: jest.fn(), + } as any; + + (ContentModelSeeder as jest.Mock) = jest.fn().mockImplementation(() => mockSeeder); + + // Mock utilities + (isAuthenticated as jest.Mock) = jest.fn().mockReturnValue(true); + (configHandler.get as jest.Mock) = jest.fn().mockReturnValue({}); + (cliux.print as jest.Mock) = jest.fn(); + (cliux.loader as jest.Mock) = jest.fn(); + + command = new SeedCommand([], {} as any); + Object.defineProperty(command, 'cdaHost', { + value: 'https://cdn.contentstack.io', + writable: true, + configurable: true, + }); + Object.defineProperty(command, 'cmaHost', { + value: 'https://api.contentstack.io', + writable: true, + configurable: true, + }); + }); + + describe('run', () => { + it('should successfully run seed command with all flags', async () => { + const flags = { + repo: 'user/repo', + org: 'org-123', + 'stack-api-key': undefined, + 'stack-name': 'New Stack', + 'fetch-limit': '50', + yes: undefined, + alias: undefined, + locale: 'en-us', + }; + + jest.spyOn(command as any, 'parse').mockResolvedValue({ + flags, + } as any); + + mockSeeder.run.mockResolvedValue({ api_key: 'api-key-123' }); + + await command.run(); + + expect(ContentModelSeeder).toHaveBeenCalledWith({ + parent: command, + cdaHost: 'https://cdn.contentstack.io', + cmaHost: 'https://api.contentstack.io', + gitHubPath: 'user/repo', + orgUid: 'org-123', + stackUid: undefined, + stackName: 'New Stack', + fetchLimit: '50', + skipStackConfirmation: undefined, + isAuthenticated: true, + alias: undefined, + master_locale: 'en-us', + }); + expect(mockSeeder.run).toHaveBeenCalled(); + }); + + it('should use stack-api-key when provided', async () => { + const flags = { + repo: 'user/repo', + org: undefined, + 'stack-api-key': 'api-key-123', + 'stack-name': undefined, + 'fetch-limit': undefined, + yes: undefined, + alias: undefined, + locale: undefined, + }; + + jest.spyOn(command as any, 'parse').mockResolvedValue({ + flags, + } as any); + + mockSeeder.run.mockResolvedValue({ api_key: 'api-key-123' }); + + await command.run(); + + expect(ContentModelSeeder).toHaveBeenCalledWith( + expect.objectContaining({ + stackUid: 'api-key-123', + orgUid: undefined, + stackName: undefined, + }), + ); + }); + + it('should use management token alias when provided', async () => { + const flags = { + repo: 'user/repo', + org: undefined, + 'stack-api-key': undefined, + 'stack-name': undefined, + 'fetch-limit': undefined, + yes: undefined, + alias: 'my-alias', + locale: undefined, + }; + + const mockTokens = { + 'my-alias': { + token: 'management-token-123', + apiKey: 'api-key-123', + }, + }; + + (configHandler.get as jest.Mock) = jest.fn().mockReturnValue(mockTokens); + + jest.spyOn(command as any, 'parse').mockResolvedValue({ + flags, + } as any); + + mockSeeder.run.mockResolvedValue({ api_key: 'api-key-123' }); + + await command.run(); + + expect(ContentModelSeeder).toHaveBeenCalledWith( + expect.objectContaining({ + alias: 'my-alias', + managementToken: 'management-token-123', + stackUid: 'api-key-123', + }), + ); + }); + + + it('should handle seeder errors with message', async () => { + const flags = { + repo: 'user/repo', + org: undefined, + 'stack-api-key': undefined, + 'stack-name': undefined, + 'fetch-limit': undefined, + yes: undefined, + alias: undefined, + locale: undefined, + }; + + jest.spyOn(command as any, 'parse').mockResolvedValue({ + flags, + } as any); + + const error = new Error('Seeder failed'); + mockSeeder.run.mockRejectedValue(error); + + jest.spyOn(command, 'exit').mockImplementation(() => { + throw new Error('Exit called'); + }); + + await expect(command.run()).rejects.toThrow('Exit called'); + + expect(cliux.loader).toHaveBeenCalled(); + expect(cliux.print).toHaveBeenCalledWith('Error: Seeder failed', { color: 'red' }); + expect(command.exit).toHaveBeenCalledWith(1); + }); + + it('should handle seeder errors without message', async () => { + const flags = { + repo: 'user/repo', + org: undefined, + 'stack-api-key': undefined, + 'stack-name': undefined, + 'fetch-limit': undefined, + yes: undefined, + alias: undefined, + locale: undefined, + }; + + jest.spyOn(command as any, 'parse').mockResolvedValue({ + flags, + } as any); + + const error = { suggestions: ['Try again'] }; + mockSeeder.run.mockRejectedValue(error); + + jest.spyOn(command, 'error').mockImplementation(() => { + throw new Error('Command error'); + }); + + await expect(command.run()).rejects.toThrow('Command error'); + + expect(command.error).toHaveBeenCalledWith(error, { + exit: 1, + suggestions: ['Try again'], + }); + }); + + it('should handle skipStackConfirmation flag', async () => { + const flags = { + repo: 'user/repo', + org: undefined, + 'stack-api-key': undefined, + 'stack-name': undefined, + 'fetch-limit': undefined, + yes: 'yes', + alias: undefined, + locale: undefined, + }; + + jest.spyOn(command as any, 'parse').mockResolvedValue({ + flags, + } as any); + + mockSeeder.run.mockResolvedValue({ api_key: 'api-key-123' }); + + await command.run(); + + expect(ContentModelSeeder).toHaveBeenCalledWith( + expect.objectContaining({ + skipStackConfirmation: 'yes', + }), + ); + }); + + it('should handle all optional flags', async () => { + const flags = { + repo: 'user/repo', + org: 'org-123', + 'stack-api-key': undefined, + 'stack-name': 'My Stack', + 'fetch-limit': '100', + yes: 'yes', + alias: 'my-alias', + locale: 'fr-fr', + }; + + jest.spyOn(command as any, 'parse').mockResolvedValue({ + flags, + } as any); + + mockSeeder.run.mockResolvedValue({ api_key: 'api-key-123' }); + + await command.run(); + + expect(ContentModelSeeder).toHaveBeenCalledWith({ + parent: command, + cdaHost: 'https://cdn.contentstack.io', + cmaHost: 'https://api.contentstack.io', + gitHubPath: 'user/repo', + orgUid: 'org-123', + stackUid: undefined, + stackName: 'My Stack', + fetchLimit: '100', + skipStackConfirmation: 'yes', + isAuthenticated: true, + alias: 'my-alias', + master_locale: 'fr-fr', + }); + }); + + it('should return result from seeder', async () => { + const flags = { + repo: 'user/repo', + org: undefined, + 'stack-api-key': undefined, + 'stack-name': undefined, + 'fetch-limit': undefined, + yes: undefined, + alias: undefined, + locale: undefined, + }; + + jest.spyOn(command as any, 'parse').mockResolvedValue({ + flags, + } as any); + + const expectedResult = { api_key: 'api-key-123' }; + mockSeeder.run.mockResolvedValue(expectedResult); + + const result = await command.run(); + + expect(result).toEqual(expectedResult); + }); + + it('should handle undefined flags gracefully', async () => { + const flags = { + repo: undefined, + org: undefined, + 'stack-api-key': undefined, + 'stack-name': undefined, + 'fetch-limit': undefined, + yes: undefined, + alias: undefined, + locale: undefined, + }; + + jest.spyOn(command as any, 'parse').mockResolvedValue({ + flags, + } as any); + + mockSeeder.run.mockResolvedValue({ api_key: 'api-key-123' }); + + await command.run(); + + expect(ContentModelSeeder).toHaveBeenCalledWith( + expect.objectContaining({ + gitHubPath: undefined, + orgUid: undefined, + stackUid: undefined, + stackName: undefined, + }), + ); + }); + }); + + describe('static properties', () => { + it('should have correct description', () => { + expect(SeedCommand.description).toBe( + 'Create a stack from existing content types, entries, assets, etc', + ); + }); + + it('should have correct examples', () => { + expect(SeedCommand.examples).toContain('$ csdx cm:stacks:seed'); + expect(SeedCommand.examples).toContain('$ csdx cm:stacks:seed --repo "account"'); + }); + + it('should have correct usage', () => { + expect(SeedCommand.usage).toBe( + 'cm:stacks:seed [--repo ] [--org ] [--stack-api-key ] [--stack-name ] [--yes ] [--alias ] [--locale ]', + ); + }); + + it('should have all required flags defined', () => { + expect(SeedCommand.flags).toBeDefined(); + expect(SeedCommand.flags.repo).toBeDefined(); + expect(SeedCommand.flags.org).toBeDefined(); + expect(SeedCommand.flags['stack-api-key']).toBeDefined(); + expect(SeedCommand.flags['stack-name']).toBeDefined(); + expect(SeedCommand.flags.yes).toBeDefined(); + expect(SeedCommand.flags.alias).toBeDefined(); + expect(SeedCommand.flags.locale).toBeDefined(); + }); + }); +}); diff --git a/packages/contentstack-seed/test/seed/contentstack/client.test.ts b/packages/contentstack-seed/test/seed/contentstack/client.test.ts new file mode 100644 index 0000000000..880db27e6f --- /dev/null +++ b/packages/contentstack-seed/test/seed/contentstack/client.test.ts @@ -0,0 +1,676 @@ +// Mock utilities before importing anything that uses them +jest.mock('@contentstack/cli-utilities', () => { + const actual = jest.requireActual('@contentstack/cli-utilities'); + return { + ...actual, + configHandler: { + get: jest.fn().mockReturnValue(null), + }, + managementSDKClient: jest.fn(), + }; +}); + +// Mock dependencies +jest.mock('@contentstack/management'); + +import ContentstackClient from '../../../src/seed/contentstack/client'; +import ContentstackError from '../../../src/seed/contentstack/error'; +import { managementSDKClient, configHandler } from '@contentstack/cli-utilities'; +import * as ContentstackManagementSDK from '@contentstack/management'; + +describe('ContentstackClient', () => { + let mockClient: any; + let contentstackClient: ContentstackClient; + const cmaHost = 'https://api.contentstack.io'; + const limit = 100; + + beforeEach(() => { + jest.clearAllMocks(); + + // Mock management SDK client + mockClient = { + organization: jest.fn(), + stack: jest.fn(), + }; + + (managementSDKClient as jest.Mock) = jest.fn().mockResolvedValue(mockClient); + (configHandler.get as jest.Mock) = jest.fn().mockReturnValue(null); + + contentstackClient = new ContentstackClient(cmaHost, limit); + }); + + describe('constructor', () => { + it('should initialize with cmaHost and limit', () => { + const client = new ContentstackClient(cmaHost, limit); + expect(client.limit).toBe(limit); + expect(managementSDKClient).toHaveBeenCalledWith({ host: cmaHost }); + }); + + it('should use provided limit', () => { + const client = new ContentstackClient(cmaHost, 50); + expect(client.limit).toBe(50); + }); + }); + + describe('getOrganization', () => { + it('should fetch organization by UID', async () => { + const orgUid = 'org-123'; + const mockOrg = { + uid: orgUid, + name: 'Test Org', + enabled: true, + }; + + const mockOrgInstance = { + fetch: jest.fn().mockResolvedValue(mockOrg), + }; + + mockClient.organization.mockReturnValue(mockOrgInstance); + + const result = await contentstackClient.getOrganization(orgUid); + + expect(mockClient.organization).toHaveBeenCalledWith(orgUid); + expect(mockOrgInstance.fetch).toHaveBeenCalled(); + expect(result).toEqual({ + uid: orgUid, + name: 'Test Org', + enabled: true, + }); + }); + + it('should throw ContentstackError on failure', async () => { + const orgUid = 'org-123'; + const mockError = { + errorMessage: 'Organization not found', + status: 404, + }; + + const mockOrgInstance = { + fetch: jest.fn().mockRejectedValue(mockError), + }; + + mockClient.organization.mockReturnValue(mockOrgInstance); + + await expect(contentstackClient.getOrganization(orgUid)).rejects.toThrow( + ContentstackError, + ); + }); + }); + + describe('getOrganizations', () => { + it('should fetch all organizations', async () => { + const mockOrgs = [ + { uid: 'org-1', name: 'Org 1', enabled: true }, + { uid: 'org-2', name: 'Org 2', enabled: true }, + ]; + + const mockResponse = { + items: mockOrgs, + count: 2, + }; + + const mockOrgInstance = { + fetchAll: jest.fn().mockResolvedValue(mockResponse), + }; + + mockClient.organization.mockReturnValue(mockOrgInstance); + + const result = await contentstackClient.getOrganizations(); + + expect(mockOrgInstance.fetchAll).toHaveBeenCalledWith({ + limit: limit, + asc: 'name', + include_count: true, + skip: 0, + }); + expect(result).toHaveLength(2); + expect(result[0]).toEqual({ + uid: 'org-1', + name: 'Org 1', + enabled: true, + }); + }); + + it('should use oauthOrgUid from config when available', async () => { + const mockOrg = { + uid: 'oauth-org-123', + name: 'OAuth Org', + enabled: true, + }; + + (configHandler.get as jest.Mock) = jest.fn().mockReturnValue('oauth-org-123'); + + const mockOrgInstance = { + fetch: jest.fn().mockResolvedValue(mockOrg), + }; + + mockClient.organization.mockReturnValue(mockOrgInstance); + + const result = await contentstackClient.getOrganizations(); + + expect(mockClient.organization).toHaveBeenCalledWith('oauth-org-123'); + expect(result).toHaveLength(1); + expect(result[0].uid).toBe('oauth-org-123'); + }); + + it('should paginate when count exceeds limit', async () => { + const mockResponse1 = { + items: Array.from({ length: limit }, (_, i) => ({ + uid: `org-${i}`, + name: `Org ${i}`, + enabled: true, + })), + count: 150, + }; + + const mockResponse2 = { + items: Array.from({ length: 50 }, (_, i) => ({ + uid: `org-${limit + i}`, + name: `Org ${limit + i}`, + enabled: true, + })), + count: 150, + }; + + const mockOrgInstance = { + fetchAll: jest + .fn() + .mockResolvedValueOnce(mockResponse1) + .mockResolvedValueOnce(mockResponse2), + }; + + mockClient.organization.mockReturnValue(mockOrgInstance); + + const result = await contentstackClient.getOrganizations(); + + expect(mockOrgInstance.fetchAll).toHaveBeenCalledTimes(2); + expect(result).toHaveLength(150); + }); + + it('should throw ContentstackError on failure', async () => { + const mockError = { + errorMessage: 'Unauthorized', + status: 401, + }; + + const mockOrgInstance = { + fetchAll: jest.fn().mockRejectedValue(mockError), + }; + + mockClient.organization.mockReturnValue(mockOrgInstance); + + await expect(contentstackClient.getOrganizations()).rejects.toThrow(ContentstackError); + }); + }); + + describe('getStack', () => { + it('should fetch stack by UID', async () => { + const stackUid = 'stack-123'; + const mockStack = { + uid: 'stack-123', + name: 'Test Stack', + master_locale: 'en-us', + api_key: 'api-key-123', + org_uid: 'org-123', + }; + + const mockStackInstance = { + fetch: jest.fn().mockResolvedValue(mockStack), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + const result = await contentstackClient.getStack(stackUid); + + expect(mockClient.stack).toHaveBeenCalledWith({ api_key: stackUid }); + expect(result).toEqual({ + uid: 'stack-123', + name: 'Test Stack', + master_locale: 'en-us', + api_key: 'api-key-123', + org_uid: 'org-123', + }); + }); + + it('should throw ContentstackError on failure', async () => { + const stackUid = 'stack-123'; + const mockError = { + errorMessage: 'Stack not found', + status: 404, + }; + + const mockStackInstance = { + fetch: jest.fn().mockRejectedValue(mockError), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + await expect(contentstackClient.getStack(stackUid)).rejects.toThrow(ContentstackError); + }); + }); + + describe('getStacks', () => { + it('should fetch all stacks for an organization', async () => { + const orgUid = 'org-123'; + const mockStacks = [ + { + uid: 'stack-1', + name: 'Stack 1', + master_locale: 'en-us', + api_key: 'api-key-1', + org_uid: orgUid, + }, + { + uid: 'stack-2', + name: 'Stack 2', + master_locale: 'en-us', + api_key: 'api-key-2', + org_uid: orgUid, + }, + ]; + + const mockResponse = { + items: mockStacks, + count: 2, + }; + + const mockQueryInstance = { + find: jest.fn().mockResolvedValue(mockResponse), + }; + + const mockStackInstance = { + query: jest.fn().mockReturnValue(mockQueryInstance), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + const result = await contentstackClient.getStacks(orgUid); + + expect(mockClient.stack).toHaveBeenCalledWith({ organization_uid: orgUid }); + expect(mockStackInstance.query).toHaveBeenCalledWith({ + limit: limit, + include_count: true, + skip: 0, + query: {}, + }); + expect(result).toHaveLength(2); + }); + + it('should paginate when count exceeds limit', async () => { + const orgUid = 'org-123'; + const mockResponse1 = { + items: Array.from({ length: limit }, (_, i) => ({ + uid: `stack-${i}`, + name: `Stack ${i}`, + master_locale: 'en-us', + api_key: `api-key-${i}`, + org_uid: orgUid, + })), + count: 150, + }; + + const mockResponse2 = { + items: Array.from({ length: 50 }, (_, i) => ({ + uid: `stack-${limit + i}`, + name: `Stack ${limit + i}`, + master_locale: 'en-us', + api_key: `api-key-${limit + i}`, + org_uid: orgUid, + })), + count: 150, + }; + + const mockQueryInstance = { + find: jest + .fn() + .mockResolvedValueOnce(mockResponse1) + .mockResolvedValueOnce(mockResponse2), + }; + + const mockStackInstance = { + query: jest.fn().mockReturnValue(mockQueryInstance), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + const result = await contentstackClient.getStacks(orgUid); + + expect(result).toHaveLength(150); + }); + + it('should throw ContentstackError on failure', async () => { + const orgUid = 'org-123'; + const mockError = { + errorMessage: 'Unauthorized', + status: 401, + }; + + const mockQueryInstance = { + find: jest.fn().mockRejectedValue(mockError), + }; + + const mockStackInstance = { + query: jest.fn().mockReturnValue(mockQueryInstance), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + await expect(contentstackClient.getStacks(orgUid)).rejects.toThrow(ContentstackError); + }); + }); + + describe('getContentTypeCount', () => { + it('should get content type count for a stack', async () => { + const apiKey = 'api-key-123'; + const mockResponse = { + count: 5, + }; + + const mockQueryInstance = { + find: jest.fn().mockResolvedValue(mockResponse), + }; + + const mockContentTypeInstance = { + query: jest.fn().mockReturnValue(mockQueryInstance), + }; + + const mockStackInstance = { + contentType: jest.fn().mockReturnValue(mockContentTypeInstance), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + const result = await contentstackClient.getContentTypeCount(apiKey); + + expect(mockClient.stack).toHaveBeenCalledWith({ api_key: apiKey }); + expect(mockContentTypeInstance.query).toHaveBeenCalledWith({ include_count: true }); + expect(result).toBe(5); + }); + + it('should use management token when provided', async () => { + const apiKey = 'api-key-123'; + const managementToken = 'token-123'; + const mockResponse = { count: 3 }; + + const mockQueryInstance = { + find: jest.fn().mockResolvedValue(mockResponse), + }; + + const mockContentTypeInstance = { + query: jest.fn().mockReturnValue(mockQueryInstance), + }; + + const mockStackInstance = { + contentType: jest.fn().mockReturnValue(mockContentTypeInstance), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + await contentstackClient.getContentTypeCount(apiKey, managementToken); + + expect(mockClient.stack).toHaveBeenCalledWith({ + api_key: apiKey, + management_token: managementToken, + }); + }); + + it('should throw ContentstackError on failure', async () => { + const apiKey = 'api-key-123'; + const mockError = { + errorMessage: 'Unauthorized', + status: 401, + }; + + const mockQueryInstance = { + find: jest.fn().mockRejectedValue(mockError), + }; + + const mockContentTypeInstance = { + query: jest.fn().mockReturnValue(mockQueryInstance), + }; + + const mockStackInstance = { + contentType: jest.fn().mockReturnValue(mockContentTypeInstance), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + await expect(contentstackClient.getContentTypeCount(apiKey)).rejects.toThrow( + ContentstackError, + ); + }); + }); + + describe('createStack', () => { + it('should create a new stack', async () => { + const options = { + name: 'New Stack', + description: 'Test description', + master_locale: 'en-us', + org_uid: 'org-123', + }; + + const mockStack = { + uid: 'stack-123', + api_key: 'api-key-123', + master_locale: 'en-us', + name: 'New Stack', + org_uid: 'org-123', + }; + + const mockStackInstance = { + create: jest.fn().mockResolvedValue(mockStack), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + const result = await contentstackClient.createStack(options); + + expect(mockStackInstance.create).toHaveBeenCalledWith( + { + stack: { + name: options.name, + description: options.description, + master_locale: options.master_locale, + }, + }, + { organization_uid: options.org_uid }, + ); + expect(result).toEqual(mockStack); + }); + + it('should throw ContentstackError on failure', async () => { + const options = { + name: 'New Stack', + description: 'Test description', + master_locale: 'en-us', + org_uid: 'org-123', + }; + + const mockError = { + errorMessage: 'Stack creation failed', + status: 400, + }; + + const mockStackInstance = { + create: jest.fn().mockRejectedValue(mockError), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + await expect(contentstackClient.createStack(options)).rejects.toThrow(ContentstackError); + }); + }); + + describe('createManagementToken', () => { + it('should create management token successfully', async () => { + const apiKey = 'api-key-123'; + const managementToken = 'existing-token'; + const options = { + name: 'Test Token', + description: 'Test description', + expires_on: '3000-01-01', + scope: [ + { + module: 'content_type', + acl: { read: true, write: true }, + }, + ], + }; + + const mockResponse = { + errorCode: undefined, + errorMessage: undefined, + }; + + const mockTokenInstance = { + create: jest.fn().mockResolvedValue(mockResponse), + }; + + const mockStackInstance = { + managementToken: jest.fn().mockReturnValue(mockTokenInstance), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + const result = await contentstackClient.createManagementToken( + apiKey, + managementToken, + options, + ); + + expect(mockClient.stack).toHaveBeenCalledWith({ + api_key: apiKey, + management_token: managementToken, + }); + expect(mockTokenInstance.create).toHaveBeenCalledWith({ + token: { + name: options.name, + description: options.description, + scope: options.scope, + expires_on: options.expires_on, + }, + }); + expect(result.response_code).toBeUndefined(); + }); + + it('should handle error code 401', async () => { + const apiKey = 'api-key-123'; + const managementToken = 'existing-token'; + const options = { + name: 'Test Token', + description: 'Test description', + expires_on: '3000-01-01', + scope: [], + }; + + const mockError = { + errorCode: '401', + }; + + const mockTokenInstance = { + create: jest.fn().mockRejectedValue(mockError), + }; + + const mockStackInstance = { + managementToken: jest.fn().mockReturnValue(mockTokenInstance), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + const result = await contentstackClient.createManagementToken( + apiKey, + managementToken, + options, + ); + + expect(result.response_code).toBe('401'); + expect(result.response_message).toContain('do not have access'); + }); + + it('should throw ContentstackError on other errors', async () => { + const apiKey = 'api-key-123'; + const managementToken = 'existing-token'; + const options = { + name: 'Test Token', + description: 'Test description', + expires_on: '3000-01-01', + scope: [], + }; + + const mockError = { + errorCode: '500', + errorMessage: 'Internal server error', + status: 500, + }; + + const mockTokenInstance = { + create: jest.fn().mockRejectedValue(mockError), + }; + + const mockStackInstance = { + managementToken: jest.fn().mockReturnValue(mockTokenInstance), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + await expect( + contentstackClient.createManagementToken(apiKey, managementToken, options), + ).rejects.toThrow(ContentstackError); + }); + }); + + describe('buildError', () => { + it('should build error from errorMessage', () => { + const mockError = { + errorMessage: 'Test error', + status: 400, + }; + + // Access private method through getStack which uses buildError + const mockStackInstance = { + fetch: jest.fn().mockRejectedValue(mockError), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + return expect(contentstackClient.getStack('stack-123')).rejects.toThrow('Test error'); + }); + + it('should build error from response.data.errorMessage', () => { + const mockError = { + response: { + data: { + errorMessage: 'Response error', + }, + }, + status: 404, + }; + + const mockStackInstance = { + fetch: jest.fn().mockRejectedValue(mockError), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + return expect(contentstackClient.getStack('stack-123')).rejects.toThrow('Response error'); + }); + + it('should build error from response.statusText', () => { + const mockError = { + response: { + statusText: 'Not Found', + }, + status: 404, + }; + + const mockStackInstance = { + fetch: jest.fn().mockRejectedValue(mockError), + }; + + mockClient.stack.mockReturnValue(mockStackInstance); + + return expect(contentstackClient.getStack('stack-123')).rejects.toThrow('Not Found'); + }); + }); +}); diff --git a/packages/contentstack-seed/test/seed/contentstack/error.test.ts b/packages/contentstack-seed/test/seed/contentstack/error.test.ts new file mode 100644 index 0000000000..15176c36aa --- /dev/null +++ b/packages/contentstack-seed/test/seed/contentstack/error.test.ts @@ -0,0 +1,56 @@ +import ContentstackError from '../../../src/seed/contentstack/error'; + +describe('ContentstackError', () => { + describe('constructor', () => { + it('should create an error with message and status', () => { + const message = 'Test error message'; + const status = 404; + const error = new ContentstackError(message, status); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toBe(message); + expect(error.status).toBe(status); + expect(error.name).toBe('ContentstackError'); + }); + + it('should handle different status codes', () => { + const statusCodes = [400, 401, 403, 404, 500]; + statusCodes.forEach((status) => { + const error = new ContentstackError('Test', status); + expect(error.status).toBe(status); + }); + }); + + it('should have a stack trace', () => { + const error = new ContentstackError('Test', 500); + expect(error.stack).toBeDefined(); + expect(typeof error.stack).toBe('string'); + }); + }); + + describe('error inheritance', () => { + it('should be an instance of Error', () => { + const error = new ContentstackError('Test', 404); + expect(error instanceof Error).toBe(true); + }); + + it('should be throwable', () => { + const error = new ContentstackError('Test', 500); + expect(() => { + throw error; + }).toThrow('Test'); + }); + }); + + describe('status code handling', () => { + it('should store status code correctly', () => { + const error = new ContentstackError('Not found', 404); + expect(error.status).toBe(404); + }); + + it('should handle zero status code', () => { + const error = new ContentstackError('Test', 0); + expect(error.status).toBe(0); + }); + }); +}); diff --git a/packages/contentstack-seed/test/seed/error.test.ts b/packages/contentstack-seed/test/seed/error.test.ts new file mode 100644 index 0000000000..3733620d4d --- /dev/null +++ b/packages/contentstack-seed/test/seed/error.test.ts @@ -0,0 +1,48 @@ +import ContentModelSeederError from '../../src/seed/error'; + +describe('ContentModelSeederError', () => { + describe('constructor', () => { + it('should create an error with message and suggestions', () => { + const message = 'Test error message'; + const suggestions = ['Suggestion 1', 'Suggestion 2']; + const error = new ContentModelSeederError(message, suggestions); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toBe(message); + expect(error.suggestions).toEqual(suggestions); + expect(error.name).toBe('ContentModelSeederError'); + }); + + it('should have a stack trace', () => { + const error = new ContentModelSeederError('Test', []); + expect(error.stack).toBeDefined(); + expect(typeof error.stack).toBe('string'); + }); + + it('should handle empty suggestions array', () => { + const error = new ContentModelSeederError('Test error', []); + expect(error.suggestions).toEqual([]); + }); + + it('should handle multiple suggestions', () => { + const suggestions = ['Suggestion 1', 'Suggestion 2', 'Suggestion 3']; + const error = new ContentModelSeederError('Test', suggestions); + expect(error.suggestions).toHaveLength(3); + expect(error.suggestions).toEqual(suggestions); + }); + }); + + describe('error inheritance', () => { + it('should be an instance of Error', () => { + const error = new ContentModelSeederError('Test', []); + expect(error instanceof Error).toBe(true); + }); + + it('should be throwable', () => { + const error = new ContentModelSeederError('Test', []); + expect(() => { + throw error; + }).toThrow('Test'); + }); + }); +}); diff --git a/packages/contentstack-seed/test/seed/github/client.test.ts b/packages/contentstack-seed/test/seed/github/client.test.ts new file mode 100644 index 0000000000..4968b40500 --- /dev/null +++ b/packages/contentstack-seed/test/seed/github/client.test.ts @@ -0,0 +1,387 @@ +// Mock utilities before importing anything that uses them +jest.mock('@contentstack/cli-utilities', () => { + const actual = jest.requireActual('@contentstack/cli-utilities'); + return { + ...actual, + configHandler: { + get: jest.fn().mockReturnValue(null), + }, + HttpClient: { + create: jest.fn(), + }, + }; +}); + +// Mock dependencies +jest.mock('tar'); +jest.mock('zlib'); +jest.mock('mkdirp'); +jest.mock('https'); + +import GitHubClient from '../../../src/seed/github/client'; +import GithubError from '../../../src/seed/github/error'; +import { HttpClient } from '@contentstack/cli-utilities'; +import * as tar from 'tar'; +import * as zlib from 'zlib'; +import * as https from 'https'; +import * as mkdirp from 'mkdirp'; +import { Stream } from 'stream'; + +describe('GitHubClient', () => { + let mockHttpClient: any; + let githubClient: GitHubClient; + const DEFAULT_STACK_PATTERN = 'stack-'; + + beforeEach(() => { + jest.clearAllMocks(); + + // Mock HttpClient + mockHttpClient = { + get: jest.fn(), + options: jest.fn().mockReturnThis(), + resetConfig: jest.fn(), + }; + + (HttpClient.create as jest.Mock) = jest.fn().mockReturnValue(mockHttpClient); + + githubClient = new GitHubClient('testuser', DEFAULT_STACK_PATTERN); + }); + + describe('constructor', () => { + it('should initialize with username and default stack pattern', () => { + const client = new GitHubClient('testuser', DEFAULT_STACK_PATTERN); + expect(client.username).toBe('testuser'); + expect(client.gitHubRepoUrl).toBe('https://api.github.com/repos/testuser'); + expect(client.gitHubUserUrl).toContain('testuser'); + expect(client.gitHubUserUrl).toContain(DEFAULT_STACK_PATTERN); + }); + + it('should create HttpClient instance', () => { + expect(HttpClient.create).toHaveBeenCalled(); + }); + }); + + describe('parsePath', () => { + it('should parse full path with username and repo', () => { + const result = GitHubClient.parsePath('username/repo-name'); + expect(result.username).toBe('username'); + expect(result.repo).toBe('repo-name'); + }); + + it('should parse path with only username', () => { + const result = GitHubClient.parsePath('username'); + expect(result.username).toBe('username'); + expect(result.repo).toBe(''); + }); + + it('should handle undefined path', () => { + const result = GitHubClient.parsePath(undefined); + expect(result.username).toBe(''); + expect(result.repo).toBe(''); + }); + + it('should handle empty string', () => { + const result = GitHubClient.parsePath(''); + expect(result.username).toBe(''); + expect(result.repo).toBe(''); + }); + + }); + + describe('getAllRepos', () => { + it('should fetch all repositories successfully', async () => { + const mockRepos = { + data: { + items: [ + { name: 'stack-repo1', html_url: 'https://github.com/testuser/stack-repo1' }, + { name: 'stack-repo2', html_url: 'https://github.com/testuser/stack-repo2' }, + ], + }, + }; + + mockHttpClient.get.mockResolvedValue(mockRepos); + + const result = await githubClient.getAllRepos(); + + expect(mockHttpClient.get).toHaveBeenCalledWith( + expect.stringContaining('testuser'), + ); + expect(result).toEqual(mockRepos.data.items); + }); + + it('should handle custom count parameter', async () => { + const mockRepos = { data: { items: [] } }; + mockHttpClient.get.mockResolvedValue(mockRepos); + + await githubClient.getAllRepos(50); + + expect(mockHttpClient.get).toHaveBeenCalledWith( + expect.stringContaining('per_page=50'), + ); + }); + + it('should throw GithubError on API failure', async () => { + const mockError = { + response: { + status: 404, + statusText: 'Not Found', + data: { error_message: 'Repository not found' }, + }, + }; + + mockHttpClient.get.mockRejectedValue(mockError); + + await expect(githubClient.getAllRepos()).rejects.toThrow(GithubError); + }); + }); + + describe('getLatestTarballUrl', () => { + it('should fetch latest release tarball URL', async () => { + const mockRelease = { + data: { + tarball_url: 'https://api.github.com/repos/testuser/repo/tarball/v1.0.0', + }, + }; + + mockHttpClient.get.mockResolvedValue(mockRelease); + + const result = await githubClient.getLatestTarballUrl('repo'); + + expect(mockHttpClient.get).toHaveBeenCalledWith( + 'https://api.github.com/repos/testuser/repo/releases/latest', + ); + expect(result).toBe(mockRelease.data.tarball_url); + }); + + it('should throw GithubError on failure', async () => { + const mockError = { + response: { + status: 404, + statusText: 'Not Found', + data: { error_message: 'Release not found' }, + }, + }; + + mockHttpClient.get.mockRejectedValue(mockError); + + await expect(githubClient.getLatestTarballUrl('repo')).rejects.toThrow(GithubError); + }); + }); + + describe('streamRelease', () => { + it('should stream release data', async () => { + const mockStream = new Stream(); + const mockResponse = { + data: mockStream, + }; + + mockHttpClient.get.mockResolvedValue(mockResponse); + + const result = await githubClient.streamRelease('https://example.com/tarball.tar.gz'); + + expect(mockHttpClient.options).toHaveBeenCalledWith({ + responseType: 'stream', + }); + expect(mockHttpClient.get).toHaveBeenCalled(); + expect(mockHttpClient.resetConfig).toHaveBeenCalled(); + expect(result).toBe(mockStream); + }); + }); + + describe('extract', () => { + it('should extract tarball to destination', async () => { + const mockStream = new Stream(); + const mockUnzip = new Stream(); + const mockExtract = new Stream(); + + (zlib.createUnzip as jest.Mock) = jest.fn().mockReturnValue(mockUnzip); + (tar.extract as jest.Mock) = jest.fn().mockReturnValue(mockExtract); + + // Mock pipe chain + mockStream.pipe = jest.fn().mockReturnValue(mockUnzip); + mockUnzip.pipe = jest.fn().mockReturnValue(mockExtract); + mockExtract.on = jest.fn().mockImplementation((event, callback) => { + if (event === 'end') { + setTimeout(() => callback(), 0); + } + return mockExtract; + }); + + await githubClient.extract('/tmp/dest', mockStream); + + expect(zlib.createUnzip).toHaveBeenCalled(); + expect(tar.extract).toHaveBeenCalledWith({ + cwd: '/tmp/dest', + strip: 1, + }); + }); + + it('should reject on extraction error', async () => { + const mockStream = new Stream(); + const mockUnzip = new Stream(); + const mockExtract = new Stream(); + + (zlib.createUnzip as jest.Mock) = jest.fn().mockReturnValue(mockUnzip); + (tar.extract as jest.Mock) = jest.fn().mockReturnValue(mockExtract); + + mockStream.pipe = jest.fn().mockReturnValue(mockUnzip); + mockUnzip.pipe = jest.fn().mockReturnValue(mockExtract); + mockExtract.on = jest.fn().mockImplementation((event, callback) => { + if (event === 'error') { + setTimeout(() => callback(new Error('Extraction failed')), 0); + } + return mockExtract; + }); + + await expect(githubClient.extract('/tmp/dest', mockStream)).rejects.toThrow( + 'Extraction failed', + ); + }); + }); + + describe('getLatest', () => { + it('should download and extract latest release', async () => { + const mockTarballUrl = 'https://api.github.com/repos/testuser/repo/tarball/v1.0.0'; + const mockStream = new Stream(); + + jest.spyOn(githubClient, 'getLatestTarballUrl').mockResolvedValue(mockTarballUrl); + jest.spyOn(githubClient, 'streamRelease').mockResolvedValue(mockStream); + jest.spyOn(githubClient, 'extract').mockResolvedValue(undefined); + + (mkdirp as any).mockResolvedValue(undefined); + + await githubClient.getLatest('repo', '/tmp/dest'); + + expect(githubClient.getLatestTarballUrl).toHaveBeenCalledWith('repo'); + expect(githubClient.streamRelease).toHaveBeenCalledWith(mockTarballUrl); + expect(mkdirp).toHaveBeenCalledWith('/tmp/dest'); + expect(githubClient.extract).toHaveBeenCalledWith('/tmp/dest', mockStream); + }); + }); + + describe('makeHeadApiCall', () => { + it('should make HEAD request to check repository', (done) => { + const mockResponse = { + statusCode: 200, + on: jest.fn(), + }; + + (https.request as jest.Mock) = jest.fn().mockImplementation((options, callback) => { + callback(mockResponse); + return { + on: jest.fn(), + end: jest.fn(), + }; + }); + + githubClient.makeHeadApiCall('repo').then((response: any) => { + expect(https.request).toHaveBeenCalled(); + expect(response).toBe(mockResponse); + done(); + }); + }); + + it('should handle request errors', (done) => { + const mockError = new Error('Network error'); + + (https.request as jest.Mock) = jest.fn().mockReturnValue({ + on: jest.fn().mockImplementation((event, callback) => { + if (event === 'error') { + callback(mockError); + } + }), + end: jest.fn(), + }); + + githubClient.makeHeadApiCall('repo').catch((error: any) => { + expect(error).toBe(mockError); + done(); + }); + }); + }); + + describe('makeGetApiCall', () => { + it('should make GET request and return response data', (done) => { + const mockResponse = { + statusCode: 200, + rawHeaders: ['X-RateLimit-Reset', '1234567890'], + on: jest.fn().mockImplementation((event, callback) => { + if (event === 'data') { + callback(Buffer.from(JSON.stringify({ data: 'test' }))); + } else if (event === 'end') { + callback(); + } + }), + }; + + (https.request as jest.Mock) = jest.fn().mockImplementation((options, callback) => { + callback(mockResponse); + return { + on: jest.fn(), + end: jest.fn(), + }; + }); + + githubClient.makeGetApiCall('repo').then((response: any) => { + expect(response.statusCode).toBe(200); + expect(response.data).toEqual({ data: 'test' }); + done(); + }); + }); + + + it('should handle request errors', (done) => { + const mockError = new Error('Network error'); + + (https.request as jest.Mock) = jest.fn().mockReturnValue({ + on: jest.fn().mockImplementation((event, callback) => { + if (event === 'error') { + callback(mockError); + } + }), + end: jest.fn(), + }); + + githubClient.makeGetApiCall('repo').catch((error: any) => { + expect(error).toBe(mockError); + done(); + }); + }); + }); + + describe('checkIfRepoExists', () => { + it('should return true if repository exists', async () => { + jest.spyOn(githubClient, 'makeHeadApiCall').mockResolvedValue({ + statusCode: 200, + } as any); + + const result = await githubClient.checkIfRepoExists('repo'); + + expect(result).toBe(true); + expect(githubClient.makeHeadApiCall).toHaveBeenCalledWith('repo'); + }); + + it('should return false if repository does not exist', async () => { + jest.spyOn(githubClient, 'makeHeadApiCall').mockResolvedValue({ + statusCode: 404, + } as any); + + const result = await githubClient.checkIfRepoExists('repo'); + + expect(result).toBe(false); + }); + + it('should return false on error', async () => { + jest.spyOn(githubClient, 'makeHeadApiCall').mockRejectedValue(new Error('Network error')); + + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + + const result = await githubClient.checkIfRepoExists('repo'); + + expect(result).toBe(false); + expect(consoleSpy).toHaveBeenCalled(); + + consoleSpy.mockRestore(); + }); + }); +}); diff --git a/packages/contentstack-seed/test/seed/github/error.test.ts b/packages/contentstack-seed/test/seed/github/error.test.ts new file mode 100644 index 0000000000..718afe1e10 --- /dev/null +++ b/packages/contentstack-seed/test/seed/github/error.test.ts @@ -0,0 +1,56 @@ +import GithubError from '../../../src/seed/github/error'; + +describe('GithubError', () => { + describe('constructor', () => { + it('should create an error with message and status', () => { + const message = 'Test error message'; + const status = 404; + const error = new GithubError(message, status); + + expect(error).toBeInstanceOf(Error); + expect(error.message).toBe(message); + expect(error.status).toBe(status); + expect(error.name).toBe('GithubError'); + }); + + it('should handle different status codes', () => { + const statusCodes = [400, 401, 403, 404, 500]; + statusCodes.forEach((status) => { + const error = new GithubError('Test', status); + expect(error.status).toBe(status); + }); + }); + + it('should have a stack trace', () => { + const error = new GithubError('Test', 500); + expect(error.stack).toBeDefined(); + expect(typeof error.stack).toBe('string'); + }); + }); + + describe('error inheritance', () => { + it('should be an instance of Error', () => { + const error = new GithubError('Test', 404); + expect(error instanceof Error).toBe(true); + }); + + it('should be throwable', () => { + const error = new GithubError('Test', 500); + expect(() => { + throw error; + }).toThrow('Test'); + }); + }); + + describe('status code handling', () => { + it('should store status code correctly', () => { + const error = new GithubError('Not found', 404); + expect(error.status).toBe(404); + }); + + it('should handle rate limit status code', () => { + const error = new GithubError('Rate limit exceeded', 403); + expect(error.status).toBe(403); + }); + }); +}); diff --git a/packages/contentstack-seed/test/seed/importer.test.ts b/packages/contentstack-seed/test/seed/importer.test.ts new file mode 100644 index 0000000000..73291a5b2c --- /dev/null +++ b/packages/contentstack-seed/test/seed/importer.test.ts @@ -0,0 +1,180 @@ +// Mock utilities before importing +jest.mock('@contentstack/cli-utilities', () => { + const actual = jest.requireActual('@contentstack/cli-utilities'); + return { + ...actual, + configHandler: { + get: jest.fn().mockReturnValue(null), + }, + }; +}); + +// Mock dependencies +jest.mock('@contentstack/cli-cm-import'); +jest.mock('@contentstack/cli-utilities'); + +import * as importer from '../../src/seed/importer'; +import ImportCommand from '@contentstack/cli-cm-import'; +import * as path from 'node:path'; +import * as cliUtilities from '@contentstack/cli-utilities'; + +// Mock process.chdir +const mockChdir = jest.fn(); +jest.spyOn(process, 'chdir').mockImplementation(mockChdir); + +describe('Importer', () => { + const mockOptions = { + master_locale: 'en-us', + api_key: 'test-api-key', + tmpPath: '/tmp/test-path', + cmaHost: 'https://api.contentstack.io', + cdaHost: 'https://cdn.contentstack.io', + isAuthenticated: true, + }; + + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(cliUtilities, 'pathValidator').mockImplementation((p: any) => p); + jest.spyOn(cliUtilities, 'sanitizePath').mockImplementation((p: any) => p); + (ImportCommand.run as jest.Mock) = jest.fn().mockResolvedValue(undefined); + }); + + describe('run', () => { + it('should run import command with correct arguments', async () => { + await importer.run(mockOptions); + + const expectedPath = path.resolve(mockOptions.tmpPath, 'stack'); + expect(cliUtilities.pathValidator).toHaveBeenCalledWith(expectedPath); + expect(cliUtilities.sanitizePath).toHaveBeenCalledWith(mockOptions.tmpPath); + expect(mockChdir).toHaveBeenCalledWith(mockOptions.tmpPath); + expect(ImportCommand.run).toHaveBeenCalledWith(['-k', mockOptions.api_key, '-d', expectedPath, '--skip-audit']); + }); + + it('should include alias in arguments when provided', async () => { + const optionsWithAlias = { + ...mockOptions, + alias: 'test-alias', + }; + + await importer.run(optionsWithAlias); + + expect(ImportCommand.run).toHaveBeenCalledWith([ + '-k', + optionsWithAlias.api_key, + '-d', + path.resolve(optionsWithAlias.tmpPath, 'stack'), + '--alias', + 'test-alias', + '--skip-audit', + ]); + }); + + it('should not include alias when not provided', async () => { + await importer.run(mockOptions); + + const args = (ImportCommand.run as jest.Mock).mock.calls[0][0]; + expect(args).not.toContain('--alias'); + }); + + it('should always include --skip-audit flag', async () => { + await importer.run(mockOptions); + + const args = (ImportCommand.run as jest.Mock).mock.calls[0][0]; + expect(args).toContain('--skip-audit'); + }); + + it('should handle different master locales', async () => { + const optionsWithLocale = { + ...mockOptions, + master_locale: 'fr-fr', + }; + + await importer.run(optionsWithLocale); + + expect(ImportCommand.run).toHaveBeenCalled(); + }); + + it('should handle unauthenticated state', async () => { + const unauthenticatedOptions = { + ...mockOptions, + isAuthenticated: false, + }; + + await importer.run(unauthenticatedOptions); + + expect(ImportCommand.run).toHaveBeenCalled(); + }); + + it('should resolve path correctly with different tmpPath values', async () => { + const testPaths = ['/tmp/test', './relative/path', String.raw`C:\Windows\Path`, '/tmp/path with spaces']; + + for (const testPath of testPaths) { + const options = { + ...mockOptions, + tmpPath: testPath, + }; + + await importer.run(options); + + const expectedPath = path.resolve(testPath, 'stack'); + expect(cliUtilities.pathValidator).toHaveBeenCalledWith(expectedPath); + expect(mockChdir).toHaveBeenCalledWith(testPath); + } + }); + + it('should handle path sanitization', async () => { + const unsafePath = '../../../etc/passwd'; + const sanitizedPath = '/safe/path'; + const options = { + ...mockOptions, + tmpPath: unsafePath, + }; + + jest.spyOn(cliUtilities, 'sanitizePath').mockReturnValue(sanitizedPath); + + await importer.run(options); + + expect(cliUtilities.sanitizePath).toHaveBeenCalledWith(unsafePath); + expect(cliUtilities.pathValidator).toHaveBeenCalledWith(path.resolve(sanitizedPath, 'stack')); + }); + + it('should handle path validation', async () => { + const invalidPath = '/invalid/path'; + const validatedPath = '/valid/path'; + const options = { + ...mockOptions, + tmpPath: invalidPath, + }; + + jest.spyOn(cliUtilities, 'pathValidator').mockReturnValue(validatedPath); + + await importer.run(options); + + expect(cliUtilities.pathValidator).toHaveBeenCalled(); + }); + + it('should change directory before running import', async () => { + await importer.run(mockOptions); + + // Verify chdir is called before ImportCommand.run + const chdirCallOrder = mockChdir.mock.invocationCallOrder[0]; + const importCallOrder = (ImportCommand.run as jest.Mock).mock.invocationCallOrder[0]; + + expect(chdirCallOrder).toBeLessThan(importCallOrder); + }); + + it('should handle import command errors', async () => { + const mockError = new Error('Import failed'); + (ImportCommand.run as jest.Mock) = jest.fn().mockRejectedValue(mockError); + + await expect(importer.run(mockOptions)).rejects.toThrow('Import failed'); + }); + + it('should use correct stack folder name', async () => { + await importer.run(mockOptions); + + const expectedPath = path.resolve(mockOptions.tmpPath, 'stack'); + expect(cliUtilities.pathValidator).toHaveBeenCalledWith(expectedPath); + }); + }); +}); diff --git a/packages/contentstack-seed/test/seed/interactive.test.ts b/packages/contentstack-seed/test/seed/interactive.test.ts new file mode 100644 index 0000000000..c587725cb6 --- /dev/null +++ b/packages/contentstack-seed/test/seed/interactive.test.ts @@ -0,0 +1,332 @@ +// Mock inquirer before importing anything +const mockInquirer = { + prompt: jest.fn(), +}; + +jest.mock('inquirer', () => mockInquirer); + +import * as interactive from '../../src/seed/interactive'; +import { Organization, Stack } from '../../src/seed/contentstack/client'; + +describe('Interactive', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('inquireRepo', () => { + it('should return single repo when only one is provided', async () => { + const repos = [ + { + name: 'stack-test', + html_url: 'https://github.com/user/stack-test', + }, + ]; + + const result = await interactive.inquireRepo(repos); + + expect(result.choice).toBe('stack-test'); + expect(mockInquirer.prompt).not.toHaveBeenCalled(); + }); + + it('should prompt user when multiple repos are available', async () => { + const repos = [ + { + name: 'stack-repo1', + html_url: 'https://github.com/user/stack-repo1', + }, + { + name: 'stack-repo2', + html_url: 'https://github.com/user/stack-repo2', + }, + ]; + + mockInquirer.prompt = jest.fn().mockResolvedValue({ + choice: 'stack-repo1', + }); + + const result = await interactive.inquireRepo(repos); + + expect(mockInquirer.prompt).toHaveBeenCalledWith([ + expect.objectContaining({ + type: 'list', + name: 'choice', + message: 'Select a Stack to Import', + }), + ]); + expect(result.choice).toBe('stack-repo1'); + }); + + it('should throw error when no repos are provided', async () => { + await expect(interactive.inquireRepo([])).rejects.toThrow( + 'Precondition failed: No Repositories found.', + ); + }); + + it('should throw error when repos is null', async () => { + await expect(interactive.inquireRepo(null as any)).rejects.toThrow( + 'Precondition failed: No Repositories found.', + ); + }); + + }); + + describe('inquireOrganization', () => { + it('should return single organization when only one is provided', async () => { + const organizations: Organization[] = [ + { + uid: 'org-1', + name: 'Organization 1', + enabled: true, + }, + ]; + + const result = await interactive.inquireOrganization(organizations); + + expect(result).toEqual(organizations[0]); + expect(mockInquirer.prompt).not.toHaveBeenCalled(); + }); + + it('should prompt user when multiple organizations are available', async () => { + const organizations: Organization[] = [ + { + uid: 'org-1', + name: 'Organization 1', + enabled: true, + }, + { + uid: 'org-2', + name: 'Organization 2', + enabled: true, + }, + ]; + + mockInquirer.prompt = jest.fn().mockResolvedValue({ + uid: 'org-2', + }); + + const result = await interactive.inquireOrganization(organizations); + + expect(mockInquirer.prompt).toHaveBeenCalledWith([ + expect.objectContaining({ + type: 'list', + name: 'uid', + message: 'Select an Organization', + }), + ]); + expect(result).toEqual(organizations[1]); + }); + + it('should throw error when no organizations are provided', async () => { + await expect(interactive.inquireOrganization([])).rejects.toThrow( + 'Precondition failed: No Organizations found.', + ); + }); + + it('should throw error when organizations is null', async () => { + await expect(interactive.inquireOrganization(null as any)).rejects.toThrow( + 'Precondition failed: No Organizations found.', + ); + }); + }); + + describe('inquireProceed', () => { + it('should return true when user confirms', async () => { + mockInquirer.prompt = jest.fn().mockResolvedValue({ + choice: true, + }); + + const result = await interactive.inquireProceed(); + + expect(mockInquirer.prompt).toHaveBeenCalledWith([ + expect.objectContaining({ + type: 'confirm', + name: 'choice', + message: 'This Stack contains content. Do you wish to continue?', + }), + ]); + expect(result).toBe(true); + }); + + it('should return false when user declines', async () => { + mockInquirer.prompt = jest.fn().mockResolvedValue({ + choice: false, + }); + + const result = await interactive.inquireProceed(); + + expect(result).toBe(false); + }); + }); + + describe('inquireStack', () => { + it('should create new stack when stackName is provided', async () => { + const stacks: Stack[] = []; + const stackName = 'My New Stack'; + + const result = await interactive.inquireStack(stacks, stackName); + + expect(result.isNew).toBe(true); + expect(result.name).toBe('My New Stack'); + expect(mockInquirer.prompt).not.toHaveBeenCalled(); + }); + + it('should prompt for new or existing when stacks exist and no stackName', async () => { + const stacks: Stack[] = [ + { + uid: 'stack-1', + name: 'Existing Stack', + master_locale: 'en-us', + api_key: 'api-key-1', + org_uid: 'org-1', + }, + ]; + + (mockInquirer.prompt as jest.Mock) + .mockResolvedValueOnce({ + choice: true, // New stack + }) + .mockResolvedValueOnce({ + name: 'My New Stack', + }); + + const result = await interactive.inquireStack(stacks); + + expect(result.isNew).toBe(true); + expect(result.name).toBe('My New Stack'); + }); + + it('should select existing stack when user chooses existing', async () => { + const stacks: Stack[] = [ + { + uid: 'stack-1', + name: 'Existing Stack', + master_locale: 'en-us', + api_key: 'api-key-1', + org_uid: 'org-1', + }, + { + uid: 'stack-2', + name: 'Another Stack', + master_locale: 'en-us', + api_key: 'api-key-2', + org_uid: 'org-1', + }, + ]; + + (mockInquirer.prompt as jest.Mock) + .mockResolvedValueOnce({ + choice: false, // Existing stack + }) + .mockResolvedValueOnce({ + uid: 'stack-2', + }); + + const result = await interactive.inquireStack(stacks); + + expect(result.isNew).toBe(false); + expect(result.name).toBe('Another Stack'); + expect(result.uid).toBe('stack-2'); + expect(result.api_key).toBe('api-key-2'); + }); + + it('should prompt for stack name when creating new stack without stackName', async () => { + const stacks: Stack[] = []; + + (mockInquirer.prompt as jest.Mock).mockResolvedValue({ + name: 'User Entered Stack', + }); + + const result = await interactive.inquireStack(stacks); + + expect(result.isNew).toBe(true); + expect(result.name).toBe('User Entered Stack'); + expect(mockInquirer.prompt).toHaveBeenCalledWith([ + expect.objectContaining({ + type: 'input', + name: 'name', + message: 'Enter a stack name', + }), + ]); + }); + + it('should validate stack name input', async () => { + const stacks: Stack[] = []; + + const validateFn = jest.fn(); + (mockInquirer.prompt as jest.Mock).mockImplementation((questions: any) => { + const question = questions[0]; + if (question.validate) { + expect(question.validate('')).toBe('Required'); + expect(question.validate(' ')).toBe('Required'); + expect(question.validate('Valid Name')).toBe(true); + } + return Promise.resolve({ name: 'Valid Name' }); + }); + + await interactive.inquireStack(stacks); + + expect(mockInquirer.prompt).toHaveBeenCalled(); + }); + + it('should trim stack name input', async () => { + const stacks: Stack[] = []; + const stackName = ' Trimmed Stack Name '; + + const result = await interactive.inquireStack(stacks, stackName); + + expect(result.name).toBe('Trimmed Stack Name'); + }); + + it('should sort stack choices alphabetically', async () => { + const stacks: Stack[] = [ + { + uid: 'stack-3', + name: 'Zebra Stack', + master_locale: 'en-us', + api_key: 'api-key-3', + org_uid: 'org-1', + }, + { + uid: 'stack-1', + name: 'Alpha Stack', + master_locale: 'en-us', + api_key: 'api-key-1', + org_uid: 'org-1', + }, + { + uid: 'stack-2', + name: 'Beta Stack', + master_locale: 'en-us', + api_key: 'api-key-2', + org_uid: 'org-1', + }, + ]; + + (mockInquirer.prompt as jest.Mock) + .mockResolvedValueOnce({ + choice: false, + }) + .mockResolvedValueOnce({ + uid: 'stack-1', + }); + + await interactive.inquireStack(stacks); + + const promptCall = (mockInquirer.prompt as jest.Mock).mock.calls[1][0][0]; + const choices = promptCall.choices; + expect(choices[0].name).toBe('Alpha Stack'); + expect(choices[1].name).toBe('Beta Stack'); + expect(choices[2].name).toBe('Zebra Stack'); + }); + + it('should handle empty stacks array with stackName', async () => { + const stacks: Stack[] = []; + const stackName = 'New Stack'; + + const result = await interactive.inquireStack(stacks, stackName); + + expect(result.isNew).toBe(true); + expect(result.name).toBe('New Stack'); + }); + }); +}); diff --git a/packages/contentstack-seed/tests/config.json b/packages/contentstack-seed/tests/config.json deleted file mode 100644 index 80f1165a64..0000000000 --- a/packages/contentstack-seed/tests/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "API_KEY": "***REMOVED***", - "api_key": "***REMOVED***", - "api_key_1": "api_key_1", - "api_key_2": "api_key_2", - "api_key_3": "api_key_3" -} \ No newline at end of file diff --git a/packages/contentstack-seed/tests/contentstack.error.test.ts b/packages/contentstack-seed/tests/contentstack.error.test.ts deleted file mode 100644 index 709495a2ea..0000000000 --- a/packages/contentstack-seed/tests/contentstack.error.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import ContentstackError from '../src/seed/contentstack/error'; - -describe('ContentstackError', () => { - test('should test properties', () => { - const message = 'some_message'; - const status = 200; - - const error = new ContentstackError(message, status); - - expect(error.message).toBe(message); - expect(error.status).toBe(status); - expect(error.name).toBe(ContentstackError.name); - }); -}); diff --git a/packages/contentstack-seed/tests/contentstack.test.ts b/packages/contentstack-seed/tests/contentstack.test.ts deleted file mode 100644 index b51bad1171..0000000000 --- a/packages/contentstack-seed/tests/contentstack.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -jest.mock('axios'); - -/* eslint-disable @typescript-eslint/no-unused-vars */ -import axios from 'axios'; -/* eslint-enable @typescript-eslint/no-unused-vars */ - -import ContentstackClient, { CreateStackOptions } from '../src/seed/contentstack/client'; -import * as config from './config.json'; - -const CMA_HOST = 'cs.api.com'; -const BASE_URL = `https://${CMA_HOST}/v3/`; -const API_KEY = config.API_KEY; -const ORG_UID = 'org_12345'; -const STACK_UID = 'stack_12345'; -const ORG_NAME = 'org_name_12345'; -const STACK_NAME = 'stack_name_12345'; -const MASTER_LOCALE = 'en-us'; - -// @ts-ignore -axios = { - name: axios.name, - create: jest.fn().mockReturnValue({ get: jest.fn(), post: jest.fn(), defaults: { baseURL: BASE_URL } }), -}; - -describe('ContentstackClient', () => { - test('should create client', () => { - const client = new ContentstackClient(CMA_HOST, CMA_AUTH_TOKEN); - expect(client.instance.defaults.baseURL).toBe(`https://${CMA_HOST}/v3/`); - }); - - test('should get Organizations', async () => { - const client = new ContentstackClient(CMA_HOST, CMA_AUTH_TOKEN); - const getMock = jest.spyOn(client.instance, 'get'); - - const input = [{ uid: ORG_UID, name: ORG_NAME, enabled: true }]; - - // @ts-ignore - getMock.mockReturnValue({ data: { organizations: input } }); - - const organizations = await client.getOrganizations(); - - expect(getMock).toBeCalledWith('/organizations', { params: { asc: 'name' } }); - expect(organizations).toStrictEqual(input); - }); - - test('should get Stacks', async () => { - const client = new ContentstackClient(CMA_HOST, CMA_AUTH_TOKEN); - const getMock = jest.spyOn(client.instance, 'get'); - const input = [ - { uid: STACK_UID, api_key: API_KEY, org_uid: ORG_UID, name: STACK_NAME, master_locale: MASTER_LOCALE }, - ]; - - // @ts-ignore - getMock.mockReturnValue({ data: { stacks: input } }); - - const stacks = await client.getStacks(ORG_UID); - - expect(getMock).toBeCalledWith('/stacks', { params: { organization_uid: ORG_UID } }); - expect(stacks).toStrictEqual(input); - }); - - test('should get Content Type count', async () => { - const client = new ContentstackClient(CMA_HOST, CMA_AUTH_TOKEN); - const getMock = jest.spyOn(client.instance, 'get'); - - // @ts-ignore - getMock.mockReturnValue({ data: { count: 2 } }); - - const count = await client.getContentTypeCount(API_KEY); - - expect(getMock).toBeCalledWith('/content_types', { params: { api_key: API_KEY, include_count: true } }); - expect(count).toBe(2); - }); - - test('should create Stack', async () => { - const client = new ContentstackClient(CMA_HOST, CMA_AUTH_TOKEN); - const getMock = jest.spyOn(client.instance, 'post'); - - const options = { - description: 'description 12345', - master_locale: MASTER_LOCALE, - name: STACK_NAME, - org_uid: ORG_UID, - } as CreateStackOptions; - - const body = { - stack: { - name: options.name, - description: options.description, - master_locale: options.master_locale, - }, - }; - - const params = { - headers: { - 'Content-Type': 'application/json', - organization_uid: options.org_uid, - }, - }; - - const stack = { - uid: STACK_UID, - api_key: API_KEY, - master_locale: options.master_locale, - name: options.name, - org_uid: ORG_UID, - }; - - // @ts-ignore - getMock.mockReturnValue({ data: { stack: stack } }); - - const result = await client.createStack(options); - expect(getMock).toBeCalledWith('/stacks', body, params); - expect(result).toStrictEqual(stack); - }); - - test('should test error condition', async () => { - const client = new ContentstackClient(CMA_HOST, CMA_AUTH_TOKEN); - const getMock = jest.spyOn(client.instance, 'get'); - - // @ts-ignore - getMock.mockRejectedValue({ response: { status: 500, data: { error_message: 'error occurred' } } }); - - await expect(client.getContentTypeCount(API_KEY)).rejects.toThrow('error occurred'); - }); -}); diff --git a/packages/contentstack-seed/tests/github.error.ts b/packages/contentstack-seed/tests/github.error.ts deleted file mode 100644 index 0cef67f240..0000000000 --- a/packages/contentstack-seed/tests/github.error.ts +++ /dev/null @@ -1,14 +0,0 @@ -import GithubError from '../src/seed/github/error'; - -describe('GitHubError', () => { - test('should test properties', () => { - const message = 'some_message'; - const status = 200; - - const error = new GithubError(message, status); - - expect(error.message).toBe(message); - expect(error.status).toBe(status); - expect(error.name).toBe(GithubError.name); - }); -}); diff --git a/packages/contentstack-seed/tests/github.test.ts b/packages/contentstack-seed/tests/github.test.ts deleted file mode 100644 index db7d5ceb47..0000000000 --- a/packages/contentstack-seed/tests/github.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -jest.mock('axios'); -jest.mock('mkdirp'); - -import axios from 'axios'; -import GitHubClient from '../src/seed/github/client'; -import * as mkdirp from 'mkdirp'; - -const owner = 'owner'; -const repo = 'repo'; -const url = 'http://www.google.com'; - -describe('GitHub', () => { - test('should test parsePath', () => { - expect(GitHubClient.parsePath('')).toStrictEqual({ repo: '', username: '' }); - expect(GitHubClient.parsePath('owner')).toStrictEqual({ repo: '', username: 'owner' }); - expect(GitHubClient.parsePath('owner/repo')).toStrictEqual({ repo: 'repo', username: 'owner' }); - }); - - test('should set GitHub repository', () => { - const client = new GitHubClient(owner); - expect(client.gitHubRepoUrl).toBe(`https://api.github.com/repos/${owner}`); - }); - - test('should test getAllRepos', async () => { - const client = new GitHubClient(owner); - const getMock = jest.spyOn(axios, 'get'); - const repos = [{ name: 'ignored' }, { name: 'ignored' }]; - - // @ts-ignore - getMock.mockReturnValue({ data: repos }); - - const result = await client.getAllRepos(100); - - expect(getMock).toBeCalled(); - expect(result).toStrictEqual(repos); - }); - - test('should check GitHub folder existence', async () => { - const client = new GitHubClient(owner); - const headMock = jest.spyOn(axios, 'head'); - - // @ts-ignore - headMock.mockReturnValueOnce({ status: 200 }).mockImplementationOnce({ status: 404 }); - - const doesExist = await client.checkIfRepoExists(repo); - const doesNotExist = await client.checkIfRepoExists(repo); - - expect(doesExist).toBe(true); - expect(doesNotExist).toBe(false); - expect(headMock).toHaveBeenCalledWith(`https://api.github.com/repos/${owner}/${repo}/contents`); - }); - - test('should get latest tarball url', async () => { - const client = new GitHubClient(owner); - const getMock = jest.spyOn(axios, 'get'); - - // @ts-ignore - getMock.mockReturnValue({ data: { tarball_url: url } }); - - const response = await client.getLatestTarballUrl(repo); - - expect(getMock).toHaveBeenCalledWith(`https://api.github.com/repos/${owner}/${repo}/releases/latest`); - expect(response).toBe(url); - }); - - test('should get latest', async () => { - const destination = '/var/tmp'; - - const client = new GitHubClient(owner); - const getLatestTarballUrlMock = jest.spyOn(client, 'getLatestTarballUrl'); - const streamReleaseMock = jest.spyOn(client, 'streamRelease'); - const extractMock = jest.spyOn(client, 'extract'); - - // @ts-ignore - getLatestTarballUrlMock.mockReturnValue(url); - - // @ts-ignore - extractMock.mockResolvedValue({}); - - await client.getLatest(repo, destination); - - expect(getLatestTarballUrlMock).toHaveBeenCalledWith(repo); - expect(streamReleaseMock).toHaveBeenCalledWith(url); - expect(extractMock).toHaveBeenCalled(); - expect(mkdirp).toHaveBeenCalledWith(destination); - }); - - test('should test error condition', async () => { - const client = new GitHubClient(owner); - const getMock = jest.spyOn(axios, 'get'); - - // @ts-ignore - getMock.mockRejectedValue({ response: { status: 500, data: { error_message: 'error occurred' } } }); - - await expect(client.getAllRepos(100)).rejects.toThrow('error occurred'); - }); -}); diff --git a/packages/contentstack-seed/tests/importer.test.ts b/packages/contentstack-seed/tests/importer.test.ts deleted file mode 100644 index b78d3b7f5c..0000000000 --- a/packages/contentstack-seed/tests/importer.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -jest.mock('@contentstack/cli-cm-import/src/lib/util/import-flags'); -jest.mock('path'); - -import * as process from 'process'; -import * as path from 'path'; -import * as importer from '../src/seed/importer'; - -const template = 'stack'; -const tmpPath = '/var/tmp'; - -describe('importer', () => { - test('should cwd into temp path', () => { - // eslint-disable-next-line - const chdirMock = jest.spyOn(process, 'chdir').mockImplementation(() => {}); - - importer.run({ - api_key: '', - cdaHost: '', - cmaHost: '', - master_locale: '', - tmpPath: tmpPath, - }); - - expect(path.resolve).toHaveBeenCalledWith(tmpPath, template); - expect(chdirMock).toHaveBeenCalledWith(tmpPath); - }); -}); diff --git a/packages/contentstack-seed/tests/interactive.test.ts b/packages/contentstack-seed/tests/interactive.test.ts deleted file mode 100644 index 5c74641e50..0000000000 --- a/packages/contentstack-seed/tests/interactive.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -jest.mock('inquirer'); - -const inquirer = require('inquirer'); -import { Organization, Stack } from '../src/seed/contentstack/client'; -import * as interactive from '../src/seed/interactive'; -import * as config from './config.json'; - -const account = 'account'; -const repo = 'repo'; -const ghPath = `${account}/${repo}`; - -describe('interactive', () => { - beforeEach(() => { - jest.restoreAllMocks(); - }); - - test('should return error for no GitHub repos', async () => { - try { - expect.assertions(1); - const repos = [] as any[]; - await interactive.inquireRepo(repos); - } catch (error) { - expect(error.message).toMatch(/No Repositories/); - } - }); - - test('should return first GitHub repo when user has access to only one', async () => { - const repos = [ - { - html_url: ghPath, - }, - ]; - - const response = await interactive.inquireRepo(repos); - - expect(response).toStrictEqual({ choice: repo }); - }); - - test('should return multiple GitHub repos if they exist', async () => { - const repos = [ - { - name: 'stack-this-is-a-test', - html_url: 'account/stack-this-is-a-test', - }, - { - name: 'stack-this-is-cool', - html_url: 'account/stack-this-is-cool', - }, - ]; - - // @ts-ignore - jest.spyOn(inquirer, 'prompt').mockImplementation((options) => { - return options; - }); - - await interactive.inquireRepo(repos); - }); - - test('should return error for no organizations', async () => { - try { - expect.assertions(1); - const organizations: Organization[] = []; - await interactive.inquireOrganization(organizations); - } catch (error) { - expect(error.message).toMatch(/No Organizations/); - } - }); - - test('should return first organization when user has access to only one', async () => { - const organizations: Organization[] = [{ enabled: true, name: 'Organization 1', uid: '1' }]; - - const response = await interactive.inquireOrganization(organizations); - - expect(response).toBe(organizations[0]); - }); - - test('should return selected organization', async () => { - const uid = 'expected_uid'; - - const organizations: Organization[] = [ - { enabled: true, name: 'Organization 1', uid: '1' }, - { enabled: true, name: 'Organization 2', uid }, - { enabled: true, name: 'Organization 3', uid: '3' }, - ]; - - // @ts-ignore - jest.spyOn(inquirer, 'prompt').mockImplementation(() => { - return { uid }; - }); - - const response = await interactive.inquireOrganization(organizations); - - expect(response).toBe(organizations[1]); - }); - - test('should create new stack', async () => { - const stacks: Stack[] = []; - - // @ts-ignore - jest.spyOn(inquirer, 'prompt').mockImplementation(() => { - return { name: ' Stack Name ' }; - }); - - const response = await interactive.inquireStack(stacks); - - expect(response).toStrictEqual({ isNew: true, name: 'Stack Name' }); - }); - - test('should choose existing stack', async () => { - const expected_uid = 'uid_2'; - - const stacks: Stack[] = [ - { uid: 'uid_1', name: 'Stack 1', api_key: config.api_key_1, master_locale: 'en-us', org_uid: 'org_uid_1' }, - { uid: expected_uid, name: 'Stack 2', api_key: config.api_key_2, master_locale: 'en-us', org_uid: 'org_uid_2' }, - { uid: 'uid_3', name: 'Stack 3', api_key: config.api_key_3, master_locale: 'en-us', org_uid: 'org_uid_3' }, - ]; - - // select existing stack - // @ts-ignore - jest - .spyOn(inquirer, 'prompt') - .mockImplementationOnce(() => { - return { choice: false }; - // @ts-ignore - }) - .mockImplementationOnce(() => { - return { uid: expected_uid }; - }); - - const response = await interactive.inquireStack(stacks); - - expect(response).toStrictEqual({ - isNew: false, - name: stacks[1].name, - uid: stacks[1].uid, - api_key: stacks[1].api_key, - }); - }); -}); diff --git a/packages/contentstack-seed/tests/seeder.test.ts b/packages/contentstack-seed/tests/seeder.test.ts deleted file mode 100644 index 10d1e2d7d4..0000000000 --- a/packages/contentstack-seed/tests/seeder.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -jest.mock('../src/seed/github/client'); -jest.mock('../src/seed/contentstack/client'); -jest.mock('../src/seed/interactive'); -jest.mock('tmp'); -jest.mock('@contentstack/cli-utilities'); -jest.mock('inquirer'); - -import GitHubClient from '../src/seed/github/client'; -import ContentstackClient, { Organization } from '../src/seed/contentstack/client'; -import ContentModelSeeder, { ContentModelSeederOptions } from '../src/seed'; -import { inquireOrganization, inquireProceed, inquireStack, inquireRepo } from '../src/seed/interactive'; - -import * as tmp from 'tmp'; -import { cliux } from '@contentstack/cli-utilities'; -import * as config from './config.json'; - -const org_name = 'Test Organization'; -const org_uid = 'xxxxxxxxxx'; -const api_key = config.api_key; -const tmpDirName = '/var/tmp/xxxxxx/'; -const repo = 'stack-gatsby-blog'; - -const options: ContentModelSeederOptions = { - cdaHost: '', - cmaHost: '', - gitHubPath: '', -}; - -// @ts-ignore -cli = { - debug: jest.fn(), - error: jest.fn(), - action: { - start: jest.fn(), - stop: jest.fn(), - }, -}; - -const mockParsePath = jest.fn().mockReturnValue({ - username: 'fakeUserName55', - repo: repo, -}); - -GitHubClient.parsePath = mockParsePath; - -describe('ContentModelSeeder', () => { - beforeEach(() => { - jest.restoreAllMocks(); - }); - - test('should create temp folder and download release', async () => { - // @ts-ignore - const dirSyncMock = jest.spyOn(tmp, 'dirSync').mockReturnValue({ - name: tmpDirName, - }); - - GitHubClient.prototype.getLatest = jest.fn().mockResolvedValue(true); - - const seeder = new ContentModelSeeder(options); - const tmpDir = await seeder.downloadRelease(); - - expect(dirSyncMock).toHaveBeenCalled(); - expect(cliux.loader).toHaveBeenCalled(); - expect(GitHubClient.prototype.getLatest).toHaveBeenCalledWith(repo, tmpDirName); - expect(cliux.loader).toHaveBeenCalled(); - expect(tmpDir).toBe(tmpDirName); - }); - - test('should automatically proceed when no content types', async () => { - ContentstackClient.prototype.getContentTypeCount = jest.fn().mockResolvedValue(0); - - const seeder = new ContentModelSeeder(options); - const proceed = await seeder.shouldProceed(api_key); - - expect(proceed).toBe(true); - }); - - test('should not proceed when content types exists and user cancels', async () => { - ContentstackClient.prototype.getContentTypeCount = jest.fn().mockResolvedValue(1); - - // @ts-ignore - inquireProceed.mockReturnValue(false); - - const seeder = new ContentModelSeeder(options); - const proceed = await seeder.shouldProceed(api_key); - - expect(proceed).toBe(false); - }); - - test('should proceed when content types exists and user accepts risk', async () => { - ContentstackClient.prototype.getContentTypeCount = jest.fn().mockResolvedValue(1); - - // @ts-ignore - inquireProceed.mockReturnValue(true); - - const seeder = new ContentModelSeeder(options); - const proceed = await seeder.shouldProceed(api_key); - - expect(proceed).toBe(true); - }); - - test('should create stack', async () => { - ContentstackClient.prototype.createStack = jest.fn().mockResolvedValue({ - api_key: api_key, - }); - - const organization: Organization = { - enabled: true, - name: org_name, - uid: org_uid, - }; - - const seeder = new ContentModelSeeder(options); - const result = await seeder.createStack(organization, 'Test Stack'); - - expect(cliux.loader).toHaveBeenCalled(); - expect(ContentstackClient.prototype.createStack).toHaveBeenCalled(); - expect(cliux.loader).toHaveBeenCalled(); - expect(result).toBe(api_key); - }); - - test('should throw error when user does not have access to any organizations', async () => { - ContentstackClient.prototype.getOrganizations = jest.fn().mockResolvedValue([]); - - try { - const seeder = new ContentModelSeeder(options); - await seeder.getInput(); - - throw new Error('Failed'); - } catch (error) { - expect(error.message).toMatch(/You do not have access/gi); - } - }); - - test('should throw error when template folder does not exist in github', async () => { - ContentstackClient.prototype.getOrganizations = jest.fn().mockResolvedValue([{ uid: org_uid }]); - GitHubClient.prototype.checkIfRepoExists = jest.fn().mockResolvedValue(false); - - const seeder = new ContentModelSeeder(options); - await seeder.getInput(); - expect(cliux.error).toHaveBeenCalled(); - }); - - test('should prompt for input when organizations and github folder exists', async () => { - GitHubClient.prototype.checkIfRepoExists = jest.fn().mockResolvedValue(true); - ContentstackClient.prototype.getOrganizations = jest.fn().mockResolvedValue([{ uid: org_uid }]); - ContentstackClient.prototype.getStacks = jest.fn().mockResolvedValue([{ uid: api_key }]); - - // @ts-ignore - inquireOrganization.mockReturnValue({ uid: org_uid }); - - // @ts-ignore - inquireStack.mockReturnValue({ uid: api_key }); - - const seeder = new ContentModelSeeder(options); - const result = await seeder.getInput(); - - expect(inquireOrganization).toHaveBeenCalled(); - expect(ContentstackClient.prototype.getStacks).toHaveBeenCalledWith(org_uid); - expect(inquireStack).toHaveBeenCalled(); - - expect(result).toHaveProperty('organizationResponse'); - expect(result).toHaveProperty('stackResponse'); - }); - - test('should test inquire GitHub repo and filter out not stacks', async () => { - const repos = [ - { - name: 'stack-this-is-a-test', - html_url: 'account/stack-this-is-a-test', - }, - { - name: 'stack-this-is-cool', - html_url: 'account/stack-this-is-cool', - }, - { - name: 'ignore-this', - html_url: 'account/ignore-this', - }, - ]; - - // @ts-ignore - GitHubClient.prototype.getAllRepos = jest.fn().mockResolvedValue(repos); - - const seeder = new ContentModelSeeder(options); - await seeder.inquireGitHubRepo(); - - expect(inquireRepo).toBeCalled(); - }); -}); diff --git a/packages/contentstack-seed/tsconfig.test.json b/packages/contentstack-seed/tsconfig.test.json new file mode 100644 index 0000000000..4721f95e46 --- /dev/null +++ b/packages/contentstack-seed/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "noEmit": true, + "baseUrl": ".", + "paths": { + "*": ["src/*", "test/*", "node_modules/*"] + } + }, + "include": [ + "src/**/*", + "test/**/*" + ] +} From 4e840fa9e4e768a6a508c7526b8d737ebb4c8abb Mon Sep 17 00:00:00 2001 From: Netraj Patel Date: Fri, 21 Nov 2025 19:05:48 +0530 Subject: [PATCH 3/3] Removed alies of deprecated commands --- .talismanrc | 10 - packages/contentstack-clone/README.md | 59 ---- .../src/commands/cm/stacks/clone.js | 14 +- packages/contentstack-import-setup/README.md | 41 --- packages/contentstack-migrate-rte/README.md | 65 ----- .../commands/cm/entries/migrate-html-rte.js | 2 +- packages/contentstack-migration/README.md | 47 ---- .../src/commands/cm/stacks/migration.js | 2 +- packages/contentstack/README.md | 251 ------------------ 9 files changed, 8 insertions(+), 483 deletions(-) diff --git a/.talismanrc b/.talismanrc index 4cfc95c0b5..e69de29bb2 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,10 +0,0 @@ -fileignoreconfig: - - filename: packages/contentstack-seed/test/seed/importer.test.ts - checksum: 77bc27f5217c6d69c21bac51afc94d677ad67374c1b39b0575646300eb0decd3 - - filename: packages/contentstack-seed/test/seed/interactive.test.ts - checksum: e7a823051b5eb27f2674ca2c31719205fa822e9cac1524dbd14e48b1ec078c06 - - filename: packages/contentstack-seed/test/commands/cm/stacks/seed.test.ts - checksum: 61143bbb2ac86c24afc6972d17d9179c6181ec68a909b84570afdad2aaa13ade - - filename: packages/contentstack-seed/test/seed/contentstack/client.test.ts - checksum: f1bc369c9c3c4a84ddd590864c0f3e8b13be956b8fb8891b6324f44cdcc7d568 -version: '1.0' diff --git a/packages/contentstack-clone/README.md b/packages/contentstack-clone/README.md index 9b24ef4906..e32d682c85 100644 --- a/packages/contentstack-clone/README.md +++ b/packages/contentstack-clone/README.md @@ -38,62 +38,6 @@ USAGE * [`csdx cm:stacks:clone [--source-branch ] [--target-branch ] [--source-management-token-alias ] [--destination-management-token-alias ] [-n ] [--type a|b] [--source-stack-api-key ] [--destination-stack-api-key ] [--import-webhook-status disable|current]`](#csdx-cmstacksclone---source-branch-value---target-branch-value---source-management-token-alias-value---destination-management-token-alias-value--n-value---type-ab---source-stack-api-key-value---destination-stack-api-key-value---import-webhook-status-disablecurrent) -* [`csdx cm:stacks:clone [--source-branch ] [--target-branch ] [--source-management-token-alias ] [--destination-management-token-alias ] [-n ] [--type a|b] [--source-stack-api-key ] [--destination-stack-api-key ] [--import-webhook-status disable|current]`](#csdx-cmstacksclone---source-branch-value---target-branch-value---source-management-token-alias-value---destination-management-token-alias-value--n-value---type-ab---source-stack-api-key-value---destination-stack-api-key-value---import-webhook-status-disablecurrent) - -## `csdx cm:stacks:clone [--source-branch ] [--target-branch ] [--source-management-token-alias ] [--destination-management-token-alias ] [-n ] [--type a|b] [--source-stack-api-key ] [--destination-stack-api-key ] [--import-webhook-status disable|current]` - -Clone data (structure/content or both) of a stack into another stack - -``` -USAGE - $ csdx cm:stack-clone cm:stacks:clone [--source-branch ] [--target-branch ] - [--source-management-token-alias ] [--destination-management-token-alias ] [-n ] [--type a|b] - [--source-stack-api-key ] [--destination-stack-api-key ] [--import-webhook-status disable|current] - -FLAGS - -c, --config= Path for the external configuration - -n, --stack-name= Provide a name for the new stack to store the cloned content. - -y, --yes Force override all Marketplace prompts. - --destination-management-token-alias= Destination management token alias. - --destination-stack-api-key= Destination stack API key - --import-webhook-status=