From ac5a0676106ddb818b37d1cfeeca63ee3691ea75 Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Fri, 2 Jan 2026 18:53:21 +0530 Subject: [PATCH 1/6] @W-20683414 initial ODS E2E tests --- packages/b2c-cli/package.json | 2 + .../b2c-cli/test/functional/e2e_cli_test.sh | 200 ++++++++++++++++-- .../test/functional/e2e_ods_example.test.ts | 101 +++++++++ .../cartridges/plugin_example/.project | 18 ++ .../cartridges/plugin_example/package.json | 7 + .../src/cli/instance-command.ts | 1 + pnpm-lock.yaml | 125 ++++++++++- 7 files changed, 433 insertions(+), 21 deletions(-) mode change 100644 => 100755 packages/b2c-cli/test/functional/e2e_cli_test.sh create mode 100644 packages/b2c-cli/test/functional/e2e_ods_example.test.ts create mode 100644 packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/package.json diff --git a/packages/b2c-cli/package.json b/packages/b2c-cli/package.json index 47a267db..9e1173d0 100644 --- a/packages/b2c-cli/package.json +++ b/packages/b2c-cli/package.json @@ -32,6 +32,7 @@ "eslint-config-oclif": "^6", "eslint-config-prettier": "^10", "eslint-plugin-prettier": "^5.5.4", + "execa": "^9.5.2", "mocha": "^10", "oclif": "^4", "prettier": "^3.6.2", @@ -120,6 +121,7 @@ "test": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/**/*.test.ts\"", "test:ci": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only --reporter json --reporter-option output=test-results.json \"test/**/*.test.ts\"", "test:unit": "env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/**/*.test.ts\"", + "test:e2e": "./test/functional/e2e_cli_test.sh", "coverage": "c8 report", "version": "oclif readme && git add README.md", "dev": "node ./bin/dev.js" diff --git a/packages/b2c-cli/test/functional/e2e_cli_test.sh b/packages/b2c-cli/test/functional/e2e_cli_test.sh old mode 100644 new mode 100755 index f5002e35..3d09b482 --- a/packages/b2c-cli/test/functional/e2e_cli_test.sh +++ b/packages/b2c-cli/test/functional/e2e_cli_test.sh @@ -9,32 +9,206 @@ set -e -# 1. Create on demand sandbox +# Get the directory where this script is located +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Create ODS will automatically configure webdav and ocapi for the SFCC_CLIENT_ID we're using -# just like sfcc-ci -ODS_CREATE_RESULT=$(../../bin/run.js ods create --realm "$TEST_REALM" --wait --json) +# Determine CLI binary to use +if [ -x "$(npm bin)/b2c-cli" ]; then + CLI_BIN="$(npm bin)/b2c-cli" +else + CLI_BIN="$SCRIPT_DIR/../../bin/run.js" +fi + +# Test fixtures directory +FIXTURES_DIR="$SCRIPT_DIR/fixtures" + +# Suppress logs for clean JSON output +export SFCC_LOG_LEVEL=silent + +echo "=== B2C CLI E2E Test ===" +echo "" + +# Check required environment variables +if [ -z "$SFCC_CLIENT_ID" ] || [ -z "$SFCC_CLIENT_SECRET" ] || [ -z "$TEST_REALM" ]; then + echo "[FAIL] Required environment variables missing:" + echo " SFCC_CLIENT_ID, SFCC_CLIENT_SECRET, TEST_REALM" + exit 1 +fi + +# Set Account Manager host for test environment (if not already set) +if [ -z "$SFCC_ACCOUNT_MANAGER_HOST" ]; then + export SFCC_ACCOUNT_MANAGER_HOST="account-pod5.demandware.net" + echo "[INFO] Using Account Manager host: $SFCC_ACCOUNT_MANAGER_HOST" +fi + +echo "Configuration:" +echo " Realm: $TEST_REALM" +echo " Client ID: ${SFCC_CLIENT_ID:0:10}..." +echo " Account Manager: $SFCC_ACCOUNT_MANAGER_HOST" +echo " CLI Path: $CLI_BIN" +echo "" + +# 1. Create on-demand sandbox with TTL=24 +echo "Step 1: Creating on-demand sandbox..." +ODS_CREATE_RESULT=$("$CLI_BIN" ods create --realm "$TEST_REALM" --ttl 24 --wait --set-permissions --json) -ODS_ID=$(echo "$ODS_CREATE_RESULT" | jq -r '.[0].id') +# Debug: Show raw output if parsing fails +if ! echo "$ODS_CREATE_RESULT" | jq empty 2>/dev/null; then + echo "[DEBUG] Raw output:" + echo "$ODS_CREATE_RESULT" | head -c 500 + echo "" + echo "[FAIL] Invalid JSON response from ods create" + exit 1 +fi + +# Parse the result - could be array or object depending on implementation +ODS_ID=$(echo "$ODS_CREATE_RESULT" | jq -r 'if type == "array" then .[0].id else .id end') +SERVER=$(echo "$ODS_CREATE_RESULT" | jq -r 'if type == "array" then .[0].hostName else .hostName end') if [ -z "$ODS_ID" ] || [ "$ODS_ID" == "null" ]; then - echo "Failed to create on demand sandbox" + echo "[FAIL] Failed to extract sandbox ID" + echo "[DEBUG] Response: $(echo "$ODS_CREATE_RESULT" | jq -c '.')" + exit 1 +fi + +if [ -z "$SERVER" ] || [ "$SERVER" == "null" ]; then + echo "[FAIL] Failed to extract server hostname" + echo "[DEBUG] Response: $(echo "$ODS_CREATE_RESULT" | jq -c '.')" exit 1 fi -echo "Created on demand sandbox with ID: $ODS_ID" +echo "[PASS] Created on demand sandbox with ID: $ODS_ID" +echo "[INFO] Server hostname: $SERVER" # 2. List on demand sandboxes and verify the created one is present -ODS_LIST_RESULT=$(../../bin/run.js ods list --realm "$TEST_REALM" --json) -ODS_PRESENT=$(echo "$ODS_LIST_RESULT" | jq -r --arg ODS_ID "$ODS_ID" '.[] | select(.id == $ODS_ID) | .id') +echo "Step 2: Verifying sandbox in list..." +ODS_LIST_RESULT=$("$CLI_BIN" ods list --realm "$TEST_REALM" --json) + +# Validate JSON +if ! echo "$ODS_LIST_RESULT" | jq empty 2>/dev/null; then + echo "[FAIL] Invalid JSON response from ods list" + exit 1 +fi + +# List returns {count, total, data: [...]} +ODS_PRESENT=$(echo "$ODS_LIST_RESULT" | jq -r --arg ODS_ID "$ODS_ID" '.data[]? | select(.id == $ODS_ID) | .id') if [ "$ODS_PRESENT" != "$ODS_ID" ]; then - echo "Created on demand sandbox not found in list" + echo "[FAIL] Created sandbox not found in list" + echo "Expected: $ODS_ID" exit 1 fi -SERVER=$(echo "$ODS_CREATE_RESULT" | jq -r '.[0].server') +echo "[PASS] Sandbox found in list" +echo "" + +: <&1) + DEPLOY_EXIT=$? + set -e + + # Check exit code - if 0, deployment succeeded + if [ $DEPLOY_EXIT -eq 0 ]; then + echo "[PASS] Code deployment succeeded" + else + echo "[FAIL] Code deployment failed (exit code: $DEPLOY_EXIT)" + echo "[DEBUG] Output: $(echo "$DEPLOY_RESULT" | head -n 10)" + exit 1 + fi +else + echo "[WARN] Test cartridge not found at: $FIXTURES_DIR/cartridges/plugin_example" + echo "[INFO] Skipping deployment test" +fi +echo "" +COMMENT -# 3. Import code into the created sandbox +# 4. Test stop operation +echo "Step 4: Testing stop operation..." +set +e +STOP_RESULT=$("$CLI_BIN" ods stop "$ODS_ID" --json 2>&1) +STOP_EXIT=$? +set -e + +if [ $STOP_EXIT -eq 0 ]; then + echo "[PASS] Stop command succeeded" +else + echo "[WARN] Stop command failed with exit code: $STOP_EXIT" +fi +echo "" + +# 5. Test start operation +echo "Step 5: Testing start operation..." +set +e +START_RESULT=$("$CLI_BIN" ods start "$ODS_ID" --json 2>&1) +START_EXIT=$? +set -e + +if [ $START_EXIT -eq 0 ]; then + echo "[PASS] Start command succeeded" +else + echo "[WARN] Start command failed with exit code: $START_EXIT" +fi +echo "" + +# 6. Test restart operation +echo "Step 6: Testing restart operation..." +set +e +RESTART_RESULT=$("$CLI_BIN" ods restart "$ODS_ID" --json 2>&1) +RESTART_EXIT=$? +set -e + +if [ $RESTART_EXIT -eq 0 ]; then + echo "[PASS] Restart command succeeded" +else + echo "[WARN] Restart command failed with exit code: $RESTART_EXIT" +fi +echo "" + +# 7. Get sandbox status +echo "Step 7: Getting sandbox status..." +set +e +STATUS=$("$CLI_BIN" ods get "$ODS_ID" --json 2>&1) +STATUS_EXIT=$? +set -e + +if [ $STATUS_EXIT -eq 0 ]; then + echo "[PASS] Get command succeeded" +else + echo "[WARN] Get command failed with exit code: $STATUS_EXIT" +fi +echo "" + +# 8. Delete sandbox +echo "Step 8: Deleting sandbox..." +set +e +DELETE_RESULT=$("$CLI_BIN" ods delete "$ODS_ID" --force --json 2>&1) +DELETE_EXIT=$? +set -e + +if [ $DELETE_EXIT -eq 0 ]; then + echo "[PASS] Delete command succeeded" +else + echo "[WARN] Delete command failed with exit code: $DELETE_EXIT" +fi +echo "" -IMPORT_RESULT=$(../../bin/run.js code deploy --server --sandbox "$ODS_ID" --source ./test/functional/sample_code --wait --json) +echo "=== E2E Tests Completed ===" +echo "" +echo "Summary:" +echo " ✓ Step 1: Create sandbox (TTL=24, --wait)" +echo " ✓ Step 2: List sandboxes" +echo " ✓ Step 3: Deploy code" +echo " ✓ Step 4: Stop sandbox" +echo " ✓ Step 5: Start sandbox" +echo " ✓ Step 6: Restart sandbox" +echo " ✓ Step 7: Get sandbox status" +echo " ✓ Step 8: Delete sandbox" +echo "" diff --git a/packages/b2c-cli/test/functional/e2e_ods_example.test.ts b/packages/b2c-cli/test/functional/e2e_ods_example.test.ts new file mode 100644 index 00000000..4f26226d --- /dev/null +++ b/packages/b2c-cli/test/functional/e2e_ods_example.test.ts @@ -0,0 +1,101 @@ +/** + * Example E2E test using Mocha + Chai + Execa + * + * This is a POC to compare with shell-based E2E tests. + * + * To run: pnpm mocha test/functional/e2e_ods_example.test.ts + */ + +import {expect} from 'chai'; +import {execa} from 'execa'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +describe('E2E: ODS functionality', function () { + // Increase timeout for real API calls + this.timeout(30000); + + const CLI_PATH = path.resolve(__dirname, '../../bin/run.js'); + + // Helper to run CLI commands + async function runCli(command: string, args: string[] = [], options: {logLevel?: string} = {}) { + const result = await execa('node', [CLI_PATH, command, ...args], { + env: { + ...process.env, + SFCC_CLIENT_ID: process.env.SFCC_CLIENT_ID, + SFCC_CLIENT_SECRET: process.env.SFCC_CLIENT_SECRET, + SFCC_LOG_LEVEL: options.logLevel || process.env.SFCC_LOG_LEVEL || 'info', + }, + reject: false, // Don't throw on non-zero exit + }); + + return { + stdout: result.stdout, + stderr: result.stderr, + exitCode: result.exitCode, + parseJson: () => JSON.parse(result.stdout), + }; + } + + it('should list sandboxes with --json flag', async function () { + // Arrange + const realm = process.env.TEST_REALM; + if (!realm) { + this.skip(); + } + + // Act - test with normal logging to ensure JSON parsing works with mixed output + const result = await runCli('ods', ['list', '--realm', realm!, '--json'], {logLevel: 'info'}); + + // Assert + expect(result.exitCode).to.equal(0, 'Command should succeed'); + + // Parse JSON even with log messages present + const json = result.parseJson(); + + expect(json).to.have.property('count'); + expect(json).to.have.property('data'); + expect(json.data).to.be.an('array'); + + console.log(`✓ Found ${json.data.length} sandboxes (with logging)`); + }); + + it('should list sandboxes in table format (default)', async function () { + // Arrange + const realm = process.env.TEST_REALM; + if (!realm) { + this.skip(); + } + + // Act + const result = await runCli('ods', ['list', '--realm', realm!]); + + // Assert + expect(result.exitCode).to.equal(0); + expect(result.stdout).to.include('ID'); // Table header + expect(result.stdout).to.include('Realm'); // Table header + + console.log('✓ Table output looks correct'); + }); + + it('should handle authentication errors gracefully', async function () { + // Act - run with invalid credentials + const result = await execa('node', [CLI_PATH, 'ods', 'list', '--realm', 'zzzz'], { + env: { + SFCC_CLIENT_ID: 'invalid', + SFCC_CLIENT_SECRET: 'invalid', + }, + reject: false, + }); + + // Assert + expect(result.exitCode).to.not.equal(0); + expect(result.stderr).to.match(/auth|401|unauthorized/i); + + console.log('✓ Authentication error handled correctly'); + }); +}); + diff --git a/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/.project b/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/.project index e69de29b..a408b73d 100644 --- a/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/.project +++ b/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/.project @@ -0,0 +1,18 @@ + + + plugin_example + + + + + + com.demandware.studio.core.beehiveElementBuilder + + + + + + com.demandware.studio.core.beehiveNature + + + diff --git a/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/package.json b/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/package.json new file mode 100644 index 00000000..24a41358 --- /dev/null +++ b/packages/b2c-cli/test/functional/fixtures/cartridges/plugin_example/package.json @@ -0,0 +1,7 @@ +{ + "name": "plugin_example", + "version": "1.0.0", + "description": "Example test cartridge for E2E testing", + "main": "cartridge/scripts/test.js" +} + diff --git a/packages/b2c-tooling-sdk/src/cli/instance-command.ts b/packages/b2c-tooling-sdk/src/cli/instance-command.ts index 64b04c5b..dd6b7b50 100644 --- a/packages/b2c-tooling-sdk/src/cli/instance-command.ts +++ b/packages/b2c-tooling-sdk/src/cli/instance-command.ts @@ -137,6 +137,7 @@ export abstract class InstanceCommand extends OAuthCom clientId: config.clientId, clientSecret: config.clientSecret, scopes: config.scopes, + accountManagerHost: this.accountManagerHost, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 12329049..f56bc3a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: eslint-plugin-prettier: specifier: ^5.5.4 version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.6.2) + execa: + specifier: ^9.5.2 + version: 9.6.1 mocha: specifier: ^10 version: 10.8.2 @@ -1876,6 +1879,9 @@ packages: '@salesforce/dev-config@4.3.2': resolution: {integrity: sha512-mxhsWV1rzHfhGMVSFQRLOHZTGfB1R2FtqbuIb3hrgDFsW1NLjEDS2U+eZWBJiCYod1JeGpJxnETNq587lem1Gg==} + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@shikijs/core@2.5.0': resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==} @@ -1916,6 +1922,10 @@ packages: resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + '@smithy/abort-controller@4.2.5': resolution: {integrity: sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==} engines: {node: '>=18.0.0'} @@ -3419,6 +3429,10 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + express-rate-limit@7.5.1: resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} engines: {node: '>= 16'} @@ -3484,6 +3498,10 @@ packages: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -3609,6 +3627,10 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + get-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} @@ -3776,6 +3798,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + i18next@25.6.3: resolution: {integrity: sha512-AEQvoPDljhp67a1+NsnG/Wb1Nh6YoSvtrmeEd24sfGn3uujCtXCF3cXpr7ulhMywKNFF7p3TX1u2j7y+caLOJg==} peerDependencies: @@ -3975,6 +4001,10 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -3991,6 +4021,10 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -4383,6 +4417,10 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + npm@10.9.4: resolution: {integrity: sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -4591,6 +4629,10 @@ packages: resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} engines: {node: '>=18'} + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + parse-statements@1.0.11: resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} @@ -4714,6 +4756,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} @@ -5195,6 +5241,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -5428,6 +5478,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} @@ -5710,6 +5764,10 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} @@ -7696,6 +7754,8 @@ snapshots: '@salesforce/dev-config@4.3.2': {} + '@sec-ant/readable-stream@0.4.1': {} + '@shikijs/core@2.5.0': dependencies: '@shikijs/engine-javascript': 2.5.0 @@ -7756,6 +7816,8 @@ snapshots: '@sindresorhus/is@5.6.0': {} + '@sindresorhus/merge-streams@4.0.0': {} + '@smithy/abort-controller@4.2.5': dependencies: '@smithy/types': 4.9.0 @@ -9290,8 +9352,8 @@ snapshots: eslint-config-oclif: 5.2.2(eslint@9.39.1) eslint-config-xo: 0.49.0(eslint@9.39.1) eslint-config-xo-space: 0.35.0(eslint@9.39.1) - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1) eslint-plugin-jsdoc: 50.8.0(eslint@9.39.1) eslint-plugin-mocha: 10.5.0(eslint@9.39.1) eslint-plugin-n: 17.23.1(eslint@9.39.1)(typescript@5.9.3) @@ -9336,7 +9398,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3(supports-color@10.2.2) @@ -9347,18 +9409,18 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.46.4(eslint@9.39.1)(typescript@5.9.3) eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1) transitivePeerDependencies: - supports-color @@ -9379,7 +9441,7 @@ snapshots: dependencies: eslint: 9.39.1 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -9390,7 +9452,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1))(eslint@9.39.1))(eslint@9.39.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -9615,6 +9677,21 @@ snapshots: dependencies: eventsource-parser: 3.0.6 + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + express-rate-limit@7.5.1(express@5.2.1): dependencies: express: 5.2.1 @@ -9699,6 +9776,10 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -9826,6 +9907,11 @@ snapshots: get-stream@6.0.1: {} + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + get-symbol-description@1.1.0: dependencies: call-bound: 1.0.4 @@ -10019,6 +10105,8 @@ snapshots: transitivePeerDependencies: - supports-color + human-signals@8.0.1: {} + i18next@25.6.3(typescript@5.9.3): dependencies: '@babel/runtime': 7.28.4 @@ -10187,6 +10275,8 @@ snapshots: is-stream@2.0.1: {} + is-stream@4.0.1: {} + is-string@1.1.1: dependencies: call-bound: 1.0.4 @@ -10204,6 +10294,8 @@ snapshots: is-unicode-supported@0.1.0: {} + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -10587,6 +10679,11 @@ snapshots: dependencies: path-key: 4.0.0 + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + npm@10.9.4: {} object-assign@4.1.1: {} @@ -10808,6 +10905,8 @@ snapshots: index-to-position: 1.2.0 type-fest: 4.41.0 + parse-ms@4.0.0: {} + parse-statements@1.0.11: {} parseurl@1.3.3: {} @@ -10922,6 +11021,10 @@ snapshots: prettier@3.6.2: {} + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + prismjs@1.30.0: {} proc-log@4.2.0: {} @@ -11492,6 +11595,8 @@ snapshots: strip-bom@3.0.0: {} + strip-final-newline@4.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -11731,6 +11836,8 @@ snapshots: undici-types@6.21.0: {} + unicorn-magic@0.3.0: {} + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -12060,6 +12167,8 @@ snapshots: yoctocolors-cjs@2.1.3: {} + yoctocolors@2.1.2: {} + zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2 From e2da64f52feff5876544262fc5f0fdb4f3be74a6 Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Mon, 5 Jan 2026 18:49:09 +0530 Subject: [PATCH 2/6] added end to end ODS lifecycle tests --- .github/workflows/ci.yml | 12 + packages/b2c-cli/package.json | 8 +- .../test/functional/e2e/ods-lifecycle.test.ts | 339 ++++++++++++++++++ .../b2c-cli/test/functional/e2e_cli_test.sh | 214 ----------- .../test/functional/e2e_ods_example.test.ts | 101 ------ 5 files changed, 355 insertions(+), 319 deletions(-) create mode 100644 packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts delete mode 100755 packages/b2c-cli/test/functional/e2e_cli_test.sh delete mode 100644 packages/b2c-cli/test/functional/e2e_ods_example.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bf07c06..a0ca6ceb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,6 +65,18 @@ jobs: working-directory: packages/b2c-cli run: pnpm run test:ci && pnpm run lint + - name: Run E2E tests + id: e2e-test + if: always() && steps.cli-test.conclusion == 'success' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push') + working-directory: packages/b2c-cli + env: + SFCC_CLIENT_ID: ${{ secrets.SFCC_CLIENT_ID }} + SFCC_CLIENT_SECRET: ${{ secrets.SFCC_CLIENT_SECRET }} + TEST_REALM: ${{ secrets.TEST_REALM }} + SFCC_ACCOUNT_MANAGER_HOST: ${{ secrets.SFCC_ACCOUNT_MANAGER_HOST }} + SFCC_SANDBOX_API_HOST: ${{ secrets.SFCC_SANDBOX_API_HOST }} + run: pnpm --filter @salesforce/b2c-cli run test:e2e + - name: Test Report uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0 if: always() && steps.sdk-test.conclusion != 'cancelled' diff --git a/packages/b2c-cli/package.json b/packages/b2c-cli/package.json index 9e1173d0..ea40c200 100644 --- a/packages/b2c-cli/package.json +++ b/packages/b2c-cli/package.json @@ -118,10 +118,10 @@ "posttest": "pnpm run lint", "prepack": "oclif manifest && oclif readme", "pretest": "tsc --noEmit -p test", - "test": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/**/*.test.ts\"", - "test:ci": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only --reporter json --reporter-option output=test-results.json \"test/**/*.test.ts\"", - "test:unit": "env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/**/*.test.ts\"", - "test:e2e": "./test/functional/e2e_cli_test.sh", + "test": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" \"test/**/*.test.ts\"", + "test:ci": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" --reporter json --reporter-option output=test-results.json \"test/**/*.test.ts\"", + "test:unit": "env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" \"test/**/*.test.ts\"", + "test:e2e": "env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/functional/e2e/**/*.test.ts\"", "coverage": "c8 report", "version": "oclif readme && git add README.md", "dev": "node ./bin/dev.js" diff --git a/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts new file mode 100644 index 00000000..8027bd9c --- /dev/null +++ b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2025, Salesforce, Inc. + * SPDX-License-Identifier: Apache-2 + * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 + */ + +import { expect } from 'chai'; +import { execa } from 'execa'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * E2E Tests for ODS (On-Demand Sandbox) Lifecycle + * + * This test suite covers the complete lifecycle of an ODS sandbox: + * 1. Create sandbox with permissions + * 2. List sandboxes and verify creation + * 3. Deploy code to sandbox + * 4. Stop sandbox + * 5. Start sandbox + * 6. Restart sandbox + * 7. Get sandbox status + * 8. Delete sandbox + */ +describe('ODS Lifecycle E2E Tests', function () { + // Timeout for entire test suite + this.timeout(360000); // 6 minutes + + // Test configuration (paths) + const CLI_BIN = path.resolve(__dirname, '../../../bin/run.js'); + const CARTRIDGES_DIR = path.resolve(__dirname, '../fixtures/cartridges'); + + // Test state + let sandboxId: string; + let serverHostname: string; + + before(function () { + // Check required environment variables + if (!process.env.SFCC_CLIENT_ID || !process.env.SFCC_CLIENT_SECRET || !process.env.TEST_REALM) { + this.skip(); + } + }); + + /** + * Helper function to run CLI commands with proper environment. + * Uses process.env directly to get credentials from GitHub secrets. + */ + async function runCLI(args: string[]) { + const result = await execa('node', [CLI_BIN, ...args], { + env: { + ...process.env, + SFCC_LOG_LEVEL: 'silent', + }, + reject: false, + }); + + return result; + } + + /** + * Helper function to get current sandbox state (for verification only) + */ + async function getSandboxState(sandboxId: string): Promise { + const result = await runCLI(['ods', 'get', sandboxId, '--json']); + if (result.exitCode === 0) { + const sandbox = parseJson(result.stdout); + return sandbox.state; + } + return null; + } + + /** + * Helper function to parse JSON response from CLI + */ + function parseJson(output: string): any { + try { + // Try to parse the entire output as JSON first + return JSON.parse(output); + } catch { + // If that fails, look for JSON in the output + const lines = output.split('\n'); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith('{') || trimmed.startsWith('[')) { + try { + return JSON.parse(trimmed); + } catch { + } + } + } + throw new Error(`No valid JSON found in output: ${output}`); + } + } + + describe('Step 1: Create Sandbox', function () { + it('should create a new sandbox with permissions and wait for readiness', async function () { + // --wait can take 5-10 minutes, so increase timeout for this test + this.timeout(600000); // 10 minutes + + const result = await runCLI([ + 'ods', 'create', + '--realm', process.env.TEST_REALM!, + '--ttl', '24', + '--wait', + '--set-permissions', + '--json' + ]); + + expect(result.exitCode).to.equal(0, `Create command failed: ${result.stderr}`); + expect(result.stdout, 'Create command should return JSON output').to.not.be.empty; + + const response = parseJson(result.stdout); + expect(response, 'Create response should be a valid object').to.be.an('object'); + expect(response.id, 'Create response should contain a sandbox ID').to.be.a('string').and.not.be.empty; + expect(response.hostName, 'Create response should contain a hostname').to.be.a('string').and.not.be.empty; + expect(response.state, `Sandbox state should be 'started' after --wait, but got '${response.state}'`).to.equal('started'); + + // Store for subsequent tests + sandboxId = response.id; + serverHostname = response.hostName; + + // Debug output to verify values are set + console.log(`Created sandbox: ${sandboxId} on ${serverHostname}`); + }); + }); + + describe('Step 2: List Sandboxes', function () { + it('should list sandboxes and verify the created one is present', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI([ + 'ods', 'list', + '--realm', process.env.TEST_REALM!, + '--json' + ]); + + expect(result.exitCode).to.equal(0, `List command failed: ${result.stderr}`); + expect(result.stdout, 'List command should return JSON output').to.not.be.empty; + + const response = parseJson(result.stdout); + expect(response, 'List response should be a valid object').to.be.an('object'); + expect(response.data, 'List response should contain data array').to.be.an('array'); + + // Find our sandbox in the list + const foundSandbox = response.data.find((sandbox: any) => sandbox.id === sandboxId); + expect(foundSandbox, `Sandbox '${sandboxId}' not found in list.`).to.exist; + expect(foundSandbox.id).to.equal(sandboxId); + }); + }); + + describe('Step 3: Deploy Code', function () { + it('should deploy test cartridge to the sandbox', async function () { + // Skip deploy if we don't have a valid sandbox + if (!sandboxId || !serverHostname) { + this.skip(); + } + + const result = await runCLI([ + 'code', 'deploy', + CARTRIDGES_DIR, + '--cartridge', 'plugin_example', + '--server', serverHostname, + '--account-manager-host', process.env.SFCC_ACCOUNT_MANAGER_HOST || 'account-pod5.demandware.net', + '--json' + ]); + + expect(result.exitCode).to.equal(0, `Deploy command failed: ${result.stderr}`); + expect(result.stdout, 'Deploy command should return JSON output').to.not.be.empty; + + const response = parseJson(result.stdout); + expect(response, 'Deploy response should be a valid object').to.be.an('object'); + expect(response.cartridges, 'Deploy response should contain cartridges array').to.be.an('array').with.length.greaterThan(0); + expect(response.codeVersion, 'Deploy response should contain code version').to.be.a('string').and.not.be.empty; + }); + }); + + describe('Step 4: Stop Sandbox', function () { + it('should stop the sandbox', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI([ + 'ods', 'stop', + sandboxId, + '--json' + ]); + + expect(result.exitCode).to.equal(0, `Stop command failed: ${result.stderr}`); + + const state = await getSandboxState(sandboxId); + if (state) { + expect(['stopped', 'stopping'], `Sandbox state should be 'stopped' or 'stopping' after stop command`).to.include(state); + } + }); + }); + + describe('Step 5: Start Sandbox', function () { + it('should start the sandbox', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI([ + 'ods', 'start', + sandboxId, + '--json' + ]); + + expect(result.exitCode).to.equal(0, `Start command failed: ${result.stderr}`); + const state = await getSandboxState(sandboxId); + if (state) { + expect(['started', 'starting']).to.include(state); + } + }); + }); + + describe('Step 6: Restart Sandbox', function () { + it('should restart the sandbox', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI([ + 'ods', 'restart', + sandboxId, + '--json' + ]); + + expect(result.exitCode).to.equal(0, `Restart command failed: ${result.stderr}`); + + const state = await getSandboxState(sandboxId); + if (state) { + expect(['started', 'starting', 'restarting'], `Sandbox state should be 'started', 'starting', or 'restarting' after restart command, but got '${state}'`).to.include(state); + } + }); + }); + + describe('Step 7: Get Sandbox Status', function () { + it('should retrieve sandbox status', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI([ + 'ods', 'get', + sandboxId, + '--json' + ]); + + expect(result.exitCode).to.equal(0, `Get command failed: ${result.stderr}`); + expect(result.stdout, 'Get command should return JSON output').to.not.be.empty; + + const response = parseJson(result.stdout); + expect(response, 'Get response should be a valid object').to.be.an('object'); + expect(response.id, `Get response ID '${response.id}' should match requested sandbox '${sandboxId}'`).to.equal(sandboxId); + expect(response.state, 'Get response should contain sandbox state').to.be.a('string').and.not.be.empty; + }); + }); + + describe('Step 8: Delete Sandbox', function () { + it('should delete the sandbox', async function () { + // Skip if we don't have a valid sandbox ID + if (!sandboxId) { + this.skip(); + } + + const result = await runCLI([ + 'ods', 'delete', + sandboxId, '--force', + '--json' + ]); + + expect(result.exitCode).to.equal(0, `Delete command failed: ${result.stderr}`); + }); + }); + + describe('Additional Test Cases', function () { + describe('Error Handling', function () { + it('should handle invalid realm gracefully', async function () { + const result = await runCLI([ + 'ods', 'list', + '--realm', 'invalid-realm-xyz', + '--json' + ]); + + // Command should either succeed with empty list or fail with error + expect(result.exitCode, `Invalid realm command should either succeed (0) or fail (1), but got ${result.exitCode}`).to.be.oneOf([0, 1]); + }); + + it('should handle missing sandbox ID gracefully', async function () { + const result = await runCLI([ + 'ods', 'get', + 'non-existent-sandbox-id', + '--json' + ]); + + expect(result.exitCode, `Missing sandbox command should fail, but got exit code ${result.exitCode}`).to.not.equal(0); + expect(result.stderr, 'Missing sandbox command should return error message').to.not.be.empty; + }); + }); + + describe('Authentication', function () { + it('should fail with invalid credentials', async function () { + const result = await execa('node', [ + CLI_BIN, + 'ods', 'list', + '--realm', process.env.TEST_REALM!, + '--json' + ], { + env: { + ...process.env, + SFCC_CLIENT_ID: 'invalid-client-id', + SFCC_CLIENT_SECRET: 'invalid-client-secret', + SFCC_LOG_LEVEL: 'silent', + }, + reject: false, + }); + + expect(result.exitCode, `Invalid credentials should fail, but got exit code ${result.exitCode}`).to.not.equal(0); + expect(result.stderr, 'Invalid credentials should return authentication error').to.match(/401|unauthorized|invalid.*client/i); + }); + }); + }); + + after(function () { + + }); +}); diff --git a/packages/b2c-cli/test/functional/e2e_cli_test.sh b/packages/b2c-cli/test/functional/e2e_cli_test.sh deleted file mode 100755 index 3d09b482..00000000 --- a/packages/b2c-cli/test/functional/e2e_cli_test.sh +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env bash -# b2c-cli e2e cli tests -# required env vars -# SFCC_CLIENT_ID -# SFCC_CLIENT_SECRET -# SFCC_SHORTCODE -# TEST_REALM - -set -e - - -# Get the directory where this script is located -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Determine CLI binary to use -if [ -x "$(npm bin)/b2c-cli" ]; then - CLI_BIN="$(npm bin)/b2c-cli" -else - CLI_BIN="$SCRIPT_DIR/../../bin/run.js" -fi - -# Test fixtures directory -FIXTURES_DIR="$SCRIPT_DIR/fixtures" - -# Suppress logs for clean JSON output -export SFCC_LOG_LEVEL=silent - -echo "=== B2C CLI E2E Test ===" -echo "" - -# Check required environment variables -if [ -z "$SFCC_CLIENT_ID" ] || [ -z "$SFCC_CLIENT_SECRET" ] || [ -z "$TEST_REALM" ]; then - echo "[FAIL] Required environment variables missing:" - echo " SFCC_CLIENT_ID, SFCC_CLIENT_SECRET, TEST_REALM" - exit 1 -fi - -# Set Account Manager host for test environment (if not already set) -if [ -z "$SFCC_ACCOUNT_MANAGER_HOST" ]; then - export SFCC_ACCOUNT_MANAGER_HOST="account-pod5.demandware.net" - echo "[INFO] Using Account Manager host: $SFCC_ACCOUNT_MANAGER_HOST" -fi - -echo "Configuration:" -echo " Realm: $TEST_REALM" -echo " Client ID: ${SFCC_CLIENT_ID:0:10}..." -echo " Account Manager: $SFCC_ACCOUNT_MANAGER_HOST" -echo " CLI Path: $CLI_BIN" -echo "" - -# 1. Create on-demand sandbox with TTL=24 -echo "Step 1: Creating on-demand sandbox..." -ODS_CREATE_RESULT=$("$CLI_BIN" ods create --realm "$TEST_REALM" --ttl 24 --wait --set-permissions --json) - -# Debug: Show raw output if parsing fails -if ! echo "$ODS_CREATE_RESULT" | jq empty 2>/dev/null; then - echo "[DEBUG] Raw output:" - echo "$ODS_CREATE_RESULT" | head -c 500 - echo "" - echo "[FAIL] Invalid JSON response from ods create" - exit 1 -fi - -# Parse the result - could be array or object depending on implementation -ODS_ID=$(echo "$ODS_CREATE_RESULT" | jq -r 'if type == "array" then .[0].id else .id end') -SERVER=$(echo "$ODS_CREATE_RESULT" | jq -r 'if type == "array" then .[0].hostName else .hostName end') - -if [ -z "$ODS_ID" ] || [ "$ODS_ID" == "null" ]; then - echo "[FAIL] Failed to extract sandbox ID" - echo "[DEBUG] Response: $(echo "$ODS_CREATE_RESULT" | jq -c '.')" - exit 1 -fi - -if [ -z "$SERVER" ] || [ "$SERVER" == "null" ]; then - echo "[FAIL] Failed to extract server hostname" - echo "[DEBUG] Response: $(echo "$ODS_CREATE_RESULT" | jq -c '.')" - exit 1 -fi - -echo "[PASS] Created on demand sandbox with ID: $ODS_ID" -echo "[INFO] Server hostname: $SERVER" - -# 2. List on demand sandboxes and verify the created one is present -echo "Step 2: Verifying sandbox in list..." -ODS_LIST_RESULT=$("$CLI_BIN" ods list --realm "$TEST_REALM" --json) - -# Validate JSON -if ! echo "$ODS_LIST_RESULT" | jq empty 2>/dev/null; then - echo "[FAIL] Invalid JSON response from ods list" - exit 1 -fi - -# List returns {count, total, data: [...]} -ODS_PRESENT=$(echo "$ODS_LIST_RESULT" | jq -r --arg ODS_ID "$ODS_ID" '.data[]? | select(.id == $ODS_ID) | .id') - -if [ "$ODS_PRESENT" != "$ODS_ID" ]; then - echo "[FAIL] Created sandbox not found in list" - echo "Expected: $ODS_ID" - exit 1 -fi - -echo "[PASS] Sandbox found in list" -echo "" - -: <&1) - DEPLOY_EXIT=$? - set -e - - # Check exit code - if 0, deployment succeeded - if [ $DEPLOY_EXIT -eq 0 ]; then - echo "[PASS] Code deployment succeeded" - else - echo "[FAIL] Code deployment failed (exit code: $DEPLOY_EXIT)" - echo "[DEBUG] Output: $(echo "$DEPLOY_RESULT" | head -n 10)" - exit 1 - fi -else - echo "[WARN] Test cartridge not found at: $FIXTURES_DIR/cartridges/plugin_example" - echo "[INFO] Skipping deployment test" -fi -echo "" -COMMENT - -# 4. Test stop operation -echo "Step 4: Testing stop operation..." -set +e -STOP_RESULT=$("$CLI_BIN" ods stop "$ODS_ID" --json 2>&1) -STOP_EXIT=$? -set -e - -if [ $STOP_EXIT -eq 0 ]; then - echo "[PASS] Stop command succeeded" -else - echo "[WARN] Stop command failed with exit code: $STOP_EXIT" -fi -echo "" - -# 5. Test start operation -echo "Step 5: Testing start operation..." -set +e -START_RESULT=$("$CLI_BIN" ods start "$ODS_ID" --json 2>&1) -START_EXIT=$? -set -e - -if [ $START_EXIT -eq 0 ]; then - echo "[PASS] Start command succeeded" -else - echo "[WARN] Start command failed with exit code: $START_EXIT" -fi -echo "" - -# 6. Test restart operation -echo "Step 6: Testing restart operation..." -set +e -RESTART_RESULT=$("$CLI_BIN" ods restart "$ODS_ID" --json 2>&1) -RESTART_EXIT=$? -set -e - -if [ $RESTART_EXIT -eq 0 ]; then - echo "[PASS] Restart command succeeded" -else - echo "[WARN] Restart command failed with exit code: $RESTART_EXIT" -fi -echo "" - -# 7. Get sandbox status -echo "Step 7: Getting sandbox status..." -set +e -STATUS=$("$CLI_BIN" ods get "$ODS_ID" --json 2>&1) -STATUS_EXIT=$? -set -e - -if [ $STATUS_EXIT -eq 0 ]; then - echo "[PASS] Get command succeeded" -else - echo "[WARN] Get command failed with exit code: $STATUS_EXIT" -fi -echo "" - -# 8. Delete sandbox -echo "Step 8: Deleting sandbox..." -set +e -DELETE_RESULT=$("$CLI_BIN" ods delete "$ODS_ID" --force --json 2>&1) -DELETE_EXIT=$? -set -e - -if [ $DELETE_EXIT -eq 0 ]; then - echo "[PASS] Delete command succeeded" -else - echo "[WARN] Delete command failed with exit code: $DELETE_EXIT" -fi -echo "" - -echo "=== E2E Tests Completed ===" -echo "" -echo "Summary:" -echo " ✓ Step 1: Create sandbox (TTL=24, --wait)" -echo " ✓ Step 2: List sandboxes" -echo " ✓ Step 3: Deploy code" -echo " ✓ Step 4: Stop sandbox" -echo " ✓ Step 5: Start sandbox" -echo " ✓ Step 6: Restart sandbox" -echo " ✓ Step 7: Get sandbox status" -echo " ✓ Step 8: Delete sandbox" -echo "" diff --git a/packages/b2c-cli/test/functional/e2e_ods_example.test.ts b/packages/b2c-cli/test/functional/e2e_ods_example.test.ts deleted file mode 100644 index 4f26226d..00000000 --- a/packages/b2c-cli/test/functional/e2e_ods_example.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Example E2E test using Mocha + Chai + Execa - * - * This is a POC to compare with shell-based E2E tests. - * - * To run: pnpm mocha test/functional/e2e_ods_example.test.ts - */ - -import {expect} from 'chai'; -import {execa} from 'execa'; -import path from 'node:path'; -import {fileURLToPath} from 'node:url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -describe('E2E: ODS functionality', function () { - // Increase timeout for real API calls - this.timeout(30000); - - const CLI_PATH = path.resolve(__dirname, '../../bin/run.js'); - - // Helper to run CLI commands - async function runCli(command: string, args: string[] = [], options: {logLevel?: string} = {}) { - const result = await execa('node', [CLI_PATH, command, ...args], { - env: { - ...process.env, - SFCC_CLIENT_ID: process.env.SFCC_CLIENT_ID, - SFCC_CLIENT_SECRET: process.env.SFCC_CLIENT_SECRET, - SFCC_LOG_LEVEL: options.logLevel || process.env.SFCC_LOG_LEVEL || 'info', - }, - reject: false, // Don't throw on non-zero exit - }); - - return { - stdout: result.stdout, - stderr: result.stderr, - exitCode: result.exitCode, - parseJson: () => JSON.parse(result.stdout), - }; - } - - it('should list sandboxes with --json flag', async function () { - // Arrange - const realm = process.env.TEST_REALM; - if (!realm) { - this.skip(); - } - - // Act - test with normal logging to ensure JSON parsing works with mixed output - const result = await runCli('ods', ['list', '--realm', realm!, '--json'], {logLevel: 'info'}); - - // Assert - expect(result.exitCode).to.equal(0, 'Command should succeed'); - - // Parse JSON even with log messages present - const json = result.parseJson(); - - expect(json).to.have.property('count'); - expect(json).to.have.property('data'); - expect(json.data).to.be.an('array'); - - console.log(`✓ Found ${json.data.length} sandboxes (with logging)`); - }); - - it('should list sandboxes in table format (default)', async function () { - // Arrange - const realm = process.env.TEST_REALM; - if (!realm) { - this.skip(); - } - - // Act - const result = await runCli('ods', ['list', '--realm', realm!]); - - // Assert - expect(result.exitCode).to.equal(0); - expect(result.stdout).to.include('ID'); // Table header - expect(result.stdout).to.include('Realm'); // Table header - - console.log('✓ Table output looks correct'); - }); - - it('should handle authentication errors gracefully', async function () { - // Act - run with invalid credentials - const result = await execa('node', [CLI_PATH, 'ods', 'list', '--realm', 'zzzz'], { - env: { - SFCC_CLIENT_ID: 'invalid', - SFCC_CLIENT_SECRET: 'invalid', - }, - reject: false, - }); - - // Assert - expect(result.exitCode).to.not.equal(0); - expect(result.stderr).to.match(/auth|401|unauthorized/i); - - console.log('✓ Authentication error handled correctly'); - }); -}); - From f97c5fa3467d057bf73dfe0fafb6829c8482a125 Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Mon, 5 Jan 2026 22:10:37 +0530 Subject: [PATCH 3/6] minor refactors --- packages/b2c-cli/package.json | 2 +- packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/b2c-cli/package.json b/packages/b2c-cli/package.json index ea40c200..6760dd6e 100644 --- a/packages/b2c-cli/package.json +++ b/packages/b2c-cli/package.json @@ -32,7 +32,7 @@ "eslint-config-oclif": "^6", "eslint-config-prettier": "^10", "eslint-plugin-prettier": "^5.5.4", - "execa": "^9.5.2", + "execa": "^9.6.1", "mocha": "^10", "oclif": "^4", "prettier": "^3.6.2", diff --git a/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts index 8027bd9c..784f1176 100644 --- a/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts +++ b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts @@ -98,7 +98,7 @@ describe('ODS Lifecycle E2E Tests', function () { describe('Step 1: Create Sandbox', function () { it('should create a new sandbox with permissions and wait for readiness', async function () { // --wait can take 5-10 minutes, so increase timeout for this test - this.timeout(600000); // 10 minutes + this.timeout(600000); // 6 minutes const result = await runCLI([ 'ods', 'create', @@ -166,7 +166,8 @@ describe('ODS Lifecycle E2E Tests', function () { CARTRIDGES_DIR, '--cartridge', 'plugin_example', '--server', serverHostname, - '--account-manager-host', process.env.SFCC_ACCOUNT_MANAGER_HOST || 'account-pod5.demandware.net', + '--account-manager-host', + process.env.SFCC_ACCOUNT_MANAGER_HOST!, '--json' ]); @@ -218,7 +219,7 @@ describe('ODS Lifecycle E2E Tests', function () { expect(result.exitCode).to.equal(0, `Start command failed: ${result.stderr}`); const state = await getSandboxState(sandboxId); if (state) { - expect(['started', 'starting']).to.include(state); + expect(['started']).to.include(state); } }); }); From 36ad826bb400b436b070bc175fe54efd2097d4ac Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Mon, 5 Jan 2026 22:33:39 +0530 Subject: [PATCH 4/6] updated e2e test run flow --- .github/workflows/ci.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0ca6ceb..0bf07c06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,18 +65,6 @@ jobs: working-directory: packages/b2c-cli run: pnpm run test:ci && pnpm run lint - - name: Run E2E tests - id: e2e-test - if: always() && steps.cli-test.conclusion == 'success' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push') - working-directory: packages/b2c-cli - env: - SFCC_CLIENT_ID: ${{ secrets.SFCC_CLIENT_ID }} - SFCC_CLIENT_SECRET: ${{ secrets.SFCC_CLIENT_SECRET }} - TEST_REALM: ${{ secrets.TEST_REALM }} - SFCC_ACCOUNT_MANAGER_HOST: ${{ secrets.SFCC_ACCOUNT_MANAGER_HOST }} - SFCC_SANDBOX_API_HOST: ${{ secrets.SFCC_SANDBOX_API_HOST }} - run: pnpm --filter @salesforce/b2c-cli run test:e2e - - name: Test Report uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0 if: always() && steps.sdk-test.conclusion != 'cancelled' From 71366caebd1b3a02e79597491f81750d02841cbd Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Mon, 5 Jan 2026 22:40:13 +0530 Subject: [PATCH 5/6] Updated execa version and lockfile --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f56bc3a4..bfe26e8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -97,7 +97,7 @@ importers: specifier: ^5.5.4 version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.6.2) execa: - specifier: ^9.5.2 + specifier: ^9.6.1 version: 9.6.1 mocha: specifier: ^10 From bc78abd13ff977f3139748b0299f3a16fdcd6a28 Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Mon, 5 Jan 2026 22:49:34 +0530 Subject: [PATCH 6/6] resolved linting and formatting issues --- .../test/functional/e2e/ods-lifecycle.test.ts | 202 ++++++++---------- 1 file changed, 95 insertions(+), 107 deletions(-) diff --git a/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts index 784f1176..0d6cd80a 100644 --- a/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts +++ b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts @@ -4,17 +4,39 @@ * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0 */ -import { expect } from 'chai'; -import { execa } from 'execa'; +import {expect} from 'chai'; +import {execa} from 'execa'; import path from 'node:path'; -import { fileURLToPath } from 'node:url'; +import {fileURLToPath} from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +/** + * Helper function to parse JSON response from CLI + */ +function parseJson(output: string): Record { + try { + // Try to parse the entire output as JSON first + return JSON.parse(output); + } catch { + // If that fails, look for JSON in the output + const lines = output.split('\n'); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith('{') || trimmed.startsWith('[')) { + try { + return JSON.parse(trimmed); + } catch {} + } + } + throw new Error(`No valid JSON found in output: ${output}`); + } +} + /** * E2E Tests for ODS (On-Demand Sandbox) Lifecycle - * + * * This test suite covers the complete lifecycle of an ODS sandbox: * 1. Create sandbox with permissions * 2. List sandboxes and verify creation @@ -27,7 +49,7 @@ const __dirname = path.dirname(__filename); */ describe('ODS Lifecycle E2E Tests', function () { // Timeout for entire test suite - this.timeout(360000); // 6 minutes + this.timeout(360_000); // 6 minutes // Test configuration (paths) const CLI_BIN = path.resolve(__dirname, '../../../bin/run.js'); @@ -63,7 +85,7 @@ describe('ODS Lifecycle E2E Tests', function () { /** * Helper function to get current sandbox state (for verification only) */ - async function getSandboxState(sandboxId: string): Promise { + async function getSandboxState(sandboxId: string): Promise { const result = await runCLI(['ods', 'get', sandboxId, '--json']); if (result.exitCode === 0) { const sandbox = parseJson(result.stdout); @@ -72,41 +94,21 @@ describe('ODS Lifecycle E2E Tests', function () { return null; } - /** - * Helper function to parse JSON response from CLI - */ - function parseJson(output: string): any { - try { - // Try to parse the entire output as JSON first - return JSON.parse(output); - } catch { - // If that fails, look for JSON in the output - const lines = output.split('\n'); - for (const line of lines) { - const trimmed = line.trim(); - if (trimmed.startsWith('{') || trimmed.startsWith('[')) { - try { - return JSON.parse(trimmed); - } catch { - } - } - } - throw new Error(`No valid JSON found in output: ${output}`); - } - } - describe('Step 1: Create Sandbox', function () { it('should create a new sandbox with permissions and wait for readiness', async function () { // --wait can take 5-10 minutes, so increase timeout for this test - this.timeout(600000); // 6 minutes - + this.timeout(600_000); // 6 minutes + const result = await runCLI([ - 'ods', 'create', - '--realm', process.env.TEST_REALM!, - '--ttl', '24', + 'ods', + 'create', + '--realm', + process.env.TEST_REALM!, + '--ttl', + '24', '--wait', '--set-permissions', - '--json' + '--json', ]); expect(result.exitCode).to.equal(0, `Create command failed: ${result.stderr}`); @@ -116,12 +118,14 @@ describe('ODS Lifecycle E2E Tests', function () { expect(response, 'Create response should be a valid object').to.be.an('object'); expect(response.id, 'Create response should contain a sandbox ID').to.be.a('string').and.not.be.empty; expect(response.hostName, 'Create response should contain a hostname').to.be.a('string').and.not.be.empty; - expect(response.state, `Sandbox state should be 'started' after --wait, but got '${response.state}'`).to.equal('started'); + expect(response.state, `Sandbox state should be 'started' after --wait, but got '${response.state}'`).to.equal( + 'started', + ); // Store for subsequent tests sandboxId = response.id; serverHostname = response.hostName; - + // Debug output to verify values are set console.log(`Created sandbox: ${sandboxId} on ${serverHostname}`); }); @@ -133,12 +137,8 @@ describe('ODS Lifecycle E2E Tests', function () { if (!sandboxId) { this.skip(); } - - const result = await runCLI([ - 'ods', 'list', - '--realm', process.env.TEST_REALM!, - '--json' - ]); + + const result = await runCLI(['ods', 'list', '--realm', process.env.TEST_REALM!, '--json']); expect(result.exitCode).to.equal(0, `List command failed: ${result.stderr}`); expect(result.stdout, 'List command should return JSON output').to.not.be.empty; @@ -148,7 +148,7 @@ describe('ODS Lifecycle E2E Tests', function () { expect(response.data, 'List response should contain data array').to.be.an('array'); // Find our sandbox in the list - const foundSandbox = response.data.find((sandbox: any) => sandbox.id === sandboxId); + const foundSandbox = response.data.find((sandbox: Record) => sandbox.id === sandboxId); expect(foundSandbox, `Sandbox '${sandboxId}' not found in list.`).to.exist; expect(foundSandbox.id).to.equal(sandboxId); }); @@ -160,15 +160,18 @@ describe('ODS Lifecycle E2E Tests', function () { if (!sandboxId || !serverHostname) { this.skip(); } - + const result = await runCLI([ - 'code', 'deploy', + 'code', + 'deploy', CARTRIDGES_DIR, - '--cartridge', 'plugin_example', - '--server', serverHostname, + '--cartridge', + 'plugin_example', + '--server', + serverHostname, '--account-manager-host', process.env.SFCC_ACCOUNT_MANAGER_HOST!, - '--json' + '--json', ]); expect(result.exitCode).to.equal(0, `Deploy command failed: ${result.stderr}`); @@ -176,7 +179,9 @@ describe('ODS Lifecycle E2E Tests', function () { const response = parseJson(result.stdout); expect(response, 'Deploy response should be a valid object').to.be.an('object'); - expect(response.cartridges, 'Deploy response should contain cartridges array').to.be.an('array').with.length.greaterThan(0); + expect(response.cartridges, 'Deploy response should contain cartridges array') + .to.be.an('array') + .with.length.greaterThan(0); expect(response.codeVersion, 'Deploy response should contain code version').to.be.a('string').and.not.be.empty; }); }); @@ -187,18 +192,17 @@ describe('ODS Lifecycle E2E Tests', function () { if (!sandboxId) { this.skip(); } - - const result = await runCLI([ - 'ods', 'stop', - sandboxId, - '--json' - ]); + + const result = await runCLI(['ods', 'stop', sandboxId, '--json']); expect(result.exitCode).to.equal(0, `Stop command failed: ${result.stderr}`); const state = await getSandboxState(sandboxId); if (state) { - expect(['stopped', 'stopping'], `Sandbox state should be 'stopped' or 'stopping' after stop command`).to.include(state); + expect( + ['stopped', 'stopping'], + `Sandbox state should be 'stopped' or 'stopping' after stop command`, + ).to.include(state); } }); }); @@ -209,12 +213,8 @@ describe('ODS Lifecycle E2E Tests', function () { if (!sandboxId) { this.skip(); } - - const result = await runCLI([ - 'ods', 'start', - sandboxId, - '--json' - ]); + + const result = await runCLI(['ods', 'start', sandboxId, '--json']); expect(result.exitCode).to.equal(0, `Start command failed: ${result.stderr}`); const state = await getSandboxState(sandboxId); @@ -230,18 +230,17 @@ describe('ODS Lifecycle E2E Tests', function () { if (!sandboxId) { this.skip(); } - - const result = await runCLI([ - 'ods', 'restart', - sandboxId, - '--json' - ]); + + const result = await runCLI(['ods', 'restart', sandboxId, '--json']); expect(result.exitCode).to.equal(0, `Restart command failed: ${result.stderr}`); const state = await getSandboxState(sandboxId); if (state) { - expect(['started', 'starting', 'restarting'], `Sandbox state should be 'started', 'starting', or 'restarting' after restart command, but got '${state}'`).to.include(state); + expect( + ['started', 'starting', 'restarting'], + `Sandbox state should be 'started', 'starting', or 'restarting' after restart command, but got '${state}'`, + ).to.include(state); } }); }); @@ -252,19 +251,17 @@ describe('ODS Lifecycle E2E Tests', function () { if (!sandboxId) { this.skip(); } - - const result = await runCLI([ - 'ods', 'get', - sandboxId, - '--json' - ]); + + const result = await runCLI(['ods', 'get', sandboxId, '--json']); expect(result.exitCode).to.equal(0, `Get command failed: ${result.stderr}`); expect(result.stdout, 'Get command should return JSON output').to.not.be.empty; const response = parseJson(result.stdout); expect(response, 'Get response should be a valid object').to.be.an('object'); - expect(response.id, `Get response ID '${response.id}' should match requested sandbox '${sandboxId}'`).to.equal(sandboxId); + expect(response.id, `Get response ID '${response.id}' should match requested sandbox '${sandboxId}'`).to.equal( + sandboxId, + ); expect(response.state, 'Get response should contain sandbox state').to.be.a('string').and.not.be.empty; }); }); @@ -275,12 +272,8 @@ describe('ODS Lifecycle E2E Tests', function () { if (!sandboxId) { this.skip(); } - - const result = await runCLI([ - 'ods', 'delete', - sandboxId, '--force', - '--json' - ]); + + const result = await runCLI(['ods', 'delete', sandboxId, '--force', '--json']); expect(result.exitCode).to.equal(0, `Delete command failed: ${result.stderr}`); }); @@ -289,36 +282,29 @@ describe('ODS Lifecycle E2E Tests', function () { describe('Additional Test Cases', function () { describe('Error Handling', function () { it('should handle invalid realm gracefully', async function () { - const result = await runCLI([ - 'ods', 'list', - '--realm', 'invalid-realm-xyz', - '--json' - ]); - + const result = await runCLI(['ods', 'list', '--realm', 'invalid-realm-xyz', '--json']); + // Command should either succeed with empty list or fail with error - expect(result.exitCode, `Invalid realm command should either succeed (0) or fail (1), but got ${result.exitCode}`).to.be.oneOf([0, 1]); + expect( + result.exitCode, + `Invalid realm command should either succeed (0) or fail (1), but got ${result.exitCode}`, + ).to.be.oneOf([0, 1]); }); it('should handle missing sandbox ID gracefully', async function () { - const result = await runCLI([ - 'ods', 'get', - 'non-existent-sandbox-id', - '--json' - ]); + const result = await runCLI(['ods', 'get', 'non-existent-sandbox-id', '--json']); - expect(result.exitCode, `Missing sandbox command should fail, but got exit code ${result.exitCode}`).to.not.equal(0); + expect( + result.exitCode, + `Missing sandbox command should fail, but got exit code ${result.exitCode}`, + ).to.not.equal(0); expect(result.stderr, 'Missing sandbox command should return error message').to.not.be.empty; }); }); describe('Authentication', function () { it('should fail with invalid credentials', async function () { - const result = await execa('node', [ - CLI_BIN, - 'ods', 'list', - '--realm', process.env.TEST_REALM!, - '--json' - ], { + const result = await execa('node', [CLI_BIN, 'ods', 'list', '--realm', process.env.TEST_REALM!, '--json'], { env: { ...process.env, SFCC_CLIENT_ID: 'invalid-client-id', @@ -328,13 +314,15 @@ describe('ODS Lifecycle E2E Tests', function () { reject: false, }); - expect(result.exitCode, `Invalid credentials should fail, but got exit code ${result.exitCode}`).to.not.equal(0); - expect(result.stderr, 'Invalid credentials should return authentication error').to.match(/401|unauthorized|invalid.*client/i); + expect(result.exitCode, `Invalid credentials should fail, but got exit code ${result.exitCode}`).to.not.equal( + 0, + ); + expect(result.stderr, 'Invalid credentials should return authentication error').to.match( + /401|unauthorized|invalid.*client/i, + ); }); }); }); - after(function () { - - }); + after(function () {}); });