From a61b6f98885740524cdb4ff28c0aa0dcc09c26b0 Mon Sep 17 00:00:00 2001 From: charithaT07 Date: Mon, 5 Jan 2026 22:52:02 +0530 Subject: [PATCH 1/8] @W-20683414 initial ODS E2E tests (#1) * @W-20683414 initial ODS E2E tests * added end to end ODS lifecycle tests * minor refactors * updated e2e test run flow * Updated execa version and lockfile * resolved linting and formatting issues --- packages/b2c-cli/package.json | 8 +- .../test/functional/e2e/ods-lifecycle.test.ts | 328 ++++++++++++++++++ .../b2c-cli/test/functional/e2e_cli_test.sh | 40 --- .../cartridges/plugin_example/.project | 18 + .../cartridges/plugin_example/package.json | 7 + .../src/cli/instance-command.ts | 1 + pnpm-lock.yaml | 125 ++++++- 7 files changed, 476 insertions(+), 51 deletions(-) create mode 100644 packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts delete mode 100644 packages/b2c-cli/test/functional/e2e_cli_test.sh 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 20e232a3..535909a4 100644 --- a/packages/b2c-cli/package.json +++ b/packages/b2c-cli/package.json @@ -42,6 +42,7 @@ "eslint-config-prettier": "^10", "eslint-plugin-header": "^3.1.1", "eslint-plugin-prettier": "^5.5.4", + "execa": "^9.6.1", "mocha": "^10", "oclif": "^4", "prettier": "^3.6.2", @@ -127,9 +128,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": "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..0d6cd80a --- /dev/null +++ b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts @@ -0,0 +1,328 @@ +/* + * 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); + +/** + * 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 + * 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(360_000); // 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; + } + + 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(600_000); // 6 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: Record) => 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!, + '--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']).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 100644 index f5002e35..00000000 --- a/packages/b2c-cli/test/functional/e2e_cli_test.sh +++ /dev/null @@ -1,40 +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 - - -# 1. Create on demand sandbox - -# 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) - -ODS_ID=$(echo "$ODS_CREATE_RESULT" | jq -r '.[0].id') - -if [ -z "$ODS_ID" ] || [ "$ODS_ID" == "null" ]; then - echo "Failed to create on demand sandbox" - exit 1 -fi - -echo "Created on demand sandbox with ID: $ODS_ID" - -# 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') - -if [ "$ODS_PRESENT" != "$ODS_ID" ]; then - echo "Created on demand sandbox not found in list" - exit 1 -fi - -SERVER=$(echo "$ODS_CREATE_RESULT" | jq -r '.[0].server') - -# 3. Import code into the created sandbox - -IMPORT_RESULT=$(../../bin/run.js code deploy --server --sandbox "$ODS_ID" --source ./test/functional/sample_code --wait --json) 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..bfe26e8a 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.6.1 + 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 6a7dca8b33461e9a1386c06e358cb5c79a967221 Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Tue, 6 Jan 2026 15:06:17 +0530 Subject: [PATCH 2/8] improved json parsing --- .../test/functional/e2e/ods-lifecycle.test.ts | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 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 0d6cd80a..f24420d0 100644 --- a/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts +++ b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts @@ -15,19 +15,32 @@ const __dirname = path.dirname(__filename); /** * Helper function to parse JSON response from CLI */ +function extractJsonFromText(text: string): null | string { + const firstBrace = text.indexOf('{'); + const lastBrace = text.lastIndexOf('}'); + if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { + return text.slice(firstBrace, lastBrace + 1); + } + + const firstBracket = text.indexOf('['); + const lastBracket = text.lastIndexOf(']'); + if (firstBracket !== -1 && lastBracket !== -1 && lastBracket > firstBracket) { + return text.slice(firstBracket, lastBracket + 1); + } + + return null; +} + 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 {} + const jsonString = extractJsonFromText(output); + if (jsonString) { + try { + return JSON.parse(jsonString); + } catch { + // fallthrough to throw below } } throw new Error(`No valid JSON found in output: ${output}`); @@ -219,7 +232,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']).to.include(state); + expect(['started', 'starting']).to.include(state); } }); }); From 513bae8dde77a89e017cb23560d1db930c6fcf1e Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Tue, 6 Jan 2026 16:10:35 +0530 Subject: [PATCH 3/8] added non-null assertions --- .../test/functional/e2e/ods-lifecycle.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 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 f24420d0..cc5cc080 100644 --- a/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts +++ b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts @@ -102,7 +102,7 @@ describe('ODS Lifecycle E2E Tests', function () { const result = await runCLI(['ods', 'get', sandboxId, '--json']); if (result.exitCode === 0) { const sandbox = parseJson(result.stdout); - return sandbox.state; + return sandbox.state as null | string; } return null; } @@ -136,8 +136,8 @@ describe('ODS Lifecycle E2E Tests', function () { ); // Store for subsequent tests - sandboxId = response.id; - serverHostname = response.hostName; + sandboxId = response.id as string; + serverHostname = response.hostName as string; // Debug output to verify values are set console.log(`Created sandbox: ${sandboxId} on ${serverHostname}`); @@ -161,9 +161,11 @@ 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: Record) => sandbox.id === sandboxId); + const foundSandbox = (response.data as Record[]).find( + (sandbox: Record) => sandbox.id === sandboxId, + ); expect(foundSandbox, `Sandbox '${sandboxId}' not found in list.`).to.exist; - expect(foundSandbox.id).to.equal(sandboxId); + expect(foundSandbox!.id).to.equal(sandboxId); }); }); From 4f641e07b9e9032af4ac5454004e10a399fef0d7 Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Tue, 6 Jan 2026 22:55:04 +0530 Subject: [PATCH 4/8] added back shell file and remove json parsing --- .../test/functional/e2e/ods-lifecycle.test.ts | 45 +++---------------- .../b2c-cli/test/functional/e2e_cli_test.sh | 40 +++++++++++++++++ 2 files changed, 45 insertions(+), 40 deletions(-) create mode 100755 packages/b2c-cli/test/functional/e2e_cli_test.sh 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 cc5cc080..42d244e2 100644 --- a/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts +++ b/packages/b2c-cli/test/functional/e2e/ods-lifecycle.test.ts @@ -12,41 +12,6 @@ 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 extractJsonFromText(text: string): null | string { - const firstBrace = text.indexOf('{'); - const lastBrace = text.lastIndexOf('}'); - if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { - return text.slice(firstBrace, lastBrace + 1); - } - - const firstBracket = text.indexOf('['); - const lastBracket = text.lastIndexOf(']'); - if (firstBracket !== -1 && lastBracket !== -1 && lastBracket > firstBracket) { - return text.slice(firstBracket, lastBracket + 1); - } - - return null; -} - -function parseJson(output: string): Record { - try { - return JSON.parse(output); - } catch { - const jsonString = extractJsonFromText(output); - if (jsonString) { - try { - return JSON.parse(jsonString); - } catch { - // fallthrough to throw below - } - } - throw new Error(`No valid JSON found in output: ${output}`); - } -} - /** * E2E Tests for ODS (On-Demand Sandbox) Lifecycle * @@ -101,7 +66,7 @@ describe('ODS Lifecycle E2E Tests', function () { async function getSandboxState(sandboxId: string): Promise { const result = await runCLI(['ods', 'get', sandboxId, '--json']); if (result.exitCode === 0) { - const sandbox = parseJson(result.stdout); + const sandbox = JSON.parse(result.stdout); return sandbox.state as null | string; } return null; @@ -127,7 +92,7 @@ describe('ODS Lifecycle E2E Tests', function () { 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); + const response = JSON.parse(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; @@ -156,7 +121,7 @@ describe('ODS Lifecycle E2E Tests', function () { 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); + const response = JSON.parse(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'); @@ -192,7 +157,7 @@ describe('ODS Lifecycle E2E Tests', function () { 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); + const response = JSON.parse(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') @@ -272,7 +237,7 @@ describe('ODS Lifecycle E2E Tests', function () { 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); + const response = JSON.parse(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, diff --git a/packages/b2c-cli/test/functional/e2e_cli_test.sh b/packages/b2c-cli/test/functional/e2e_cli_test.sh new file mode 100755 index 00000000..f5002e35 --- /dev/null +++ b/packages/b2c-cli/test/functional/e2e_cli_test.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# b2c-cli e2e cli tests +# required env vars +# SFCC_CLIENT_ID +# SFCC_CLIENT_SECRET +# SFCC_SHORTCODE +# TEST_REALM + +set -e + + +# 1. Create on demand sandbox + +# 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) + +ODS_ID=$(echo "$ODS_CREATE_RESULT" | jq -r '.[0].id') + +if [ -z "$ODS_ID" ] || [ "$ODS_ID" == "null" ]; then + echo "Failed to create on demand sandbox" + exit 1 +fi + +echo "Created on demand sandbox with ID: $ODS_ID" + +# 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') + +if [ "$ODS_PRESENT" != "$ODS_ID" ]; then + echo "Created on demand sandbox not found in list" + exit 1 +fi + +SERVER=$(echo "$ODS_CREATE_RESULT" | jq -r '.[0].server') + +# 3. Import code into the created sandbox + +IMPORT_RESULT=$(../../bin/run.js code deploy --server --sandbox "$ODS_ID" --source ./test/functional/sample_code --wait --json) From a648d3d5d7a1bb93083d86bda776beccb5fa0721 Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Wed, 7 Jan 2026 00:31:19 +0530 Subject: [PATCH 5/8] testing the e2e temp workflow --- .../b2c-cli/.github/workflows/e2e-tests.yml | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 packages/b2c-cli/.github/workflows/e2e-tests.yml diff --git a/packages/b2c-cli/.github/workflows/e2e-tests.yml b/packages/b2c-cli/.github/workflows/e2e-tests.yml new file mode 100644 index 00000000..08dcd7fd --- /dev/null +++ b/packages/b2c-cli/.github/workflows/e2e-tests.yml @@ -0,0 +1,187 @@ +name: e2e tests + +on: + # Nightly workflow - Run E2E tests on a schedule (nightly) + schedule: + - cron: '0 2 * * *' # Run at 2 AM UTC daily + + # Post-merge - Run after changes are merged to main + push: + #branches: [main] + branches-ignore: [main] + + # Manual trigger - Support workflow_dispatch for on-demand runs + workflow_dispatch: + inputs: + test_realm: + description: 'Test realm to use (optional, defaults to var)' + required: false + type: string + sfcc_client_id: + description: 'SFCC Client ID (optional, defaults to var)' + required: false + type: string + sfcc_account_manager_host: + description: 'SFCC Account Manager Host (optional, defaults to var)' + required: false + type: string + sfcc_sandbox_api_host: + description: 'SFCC Sandbox API Host (optional, defaults to var)' + required: false + type: string + node_version: + description: 'Node.js version to test with' + required: false + default: 'lts/*' + type: choice + options: + - 'lts/-1' + - 'lts/*' + - 'latest' + os: + description: 'Operating system to test on' + required: false + default: 'ubuntu-latest' + type: choice + options: + - 'ubuntu-latest' + - 'windows-latest' + +jobs: + e2e-tests: + strategy: + matrix: + os: ['ubuntu-latest', 'windows-latest'] + node_version: ['lts/*'] + fail-fast: false + runs-on: ${{ inputs.os || matrix.os }} + environment: e2e-dev + timeout-minutes: 45 # E2E tests can take longer + + # Only run if we have the required secrets + if: > + (github.event_name == 'schedule' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'push' && github.ref == 'refs/heads/main')) + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version || matrix.node_version }} + cache: pnpm + + - name: Check for required secrets and vars + id: check-secrets + env: + SFCC_CLIENT_ID: ${{ vars.SFCC_CLIENT_ID }} + SFCC_CLIENT_SECRET: ${{ secrets.SFCC_CLIENT_SECRET }} + TEST_REALM: ${{ vars.TEST_REALM }} + SFCC_ACCOUNT_MANAGER_HOST: ${{ vars.SFCC_ACCOUNT_MANAGER_HOST }} + SFCC_SANDBOX_API_HOST: ${{ vars.SFCC_SANDBOX_API_HOST }} + run: | + if [ -n "$SFCC_CLIENT_ID" ] && [ -n "$SFCC_CLIENT_SECRET" ] && [ -n "$TEST_REALM" ]; then + echo "has-secrets=true" >> $GITHUB_OUTPUT + else + echo "has-secrets=false" >> $GITHUB_OUTPUT + echo "⚠️ E2E tests skipped - missing required variables:" >> $GITHUB_STEP_SUMMARY + echo " - SFCC_CLIENT_ID (var): ${SFCC_CLIENT_ID:+✓}" >> $GITHUB_STEP_SUMMARY + echo " - SFCC_CLIENT_SECRET (secret): ${SFCC_CLIENT_SECRET:+✓}" >> $GITHUB_STEP_SUMMARY + echo " - TEST_REALM (var): ${TEST_REALM:+✓}" >> $GITHUB_STEP_SUMMARY + echo " - SFCC_ACCOUNT_MANAGER_HOST (var): ${SFCC_ACCOUNT_MANAGER_HOST:+✓}" >> $GITHUB_STEP_SUMMARY + echo " - SFCC_SANDBOX_API_HOST (var): ${SFCC_SANDBOX_API_HOST:+✓}" >> $GITHUB_STEP_SUMMARY + fi + + - name: Install dependencies + if: steps.check-secrets.outputs.has-secrets == 'true' + run: pnpm install + + - name: Build package + if: steps.check-secrets.outputs.has-secrets == 'true' + run: pnpm run build + + - name: Run E2E Tests + if: steps.check-secrets.outputs.has-secrets == 'true' + id: e2e-test + env: + # Required environment variables for Commerce Cloud integration + SFCC_CLIENT_ID: ${{ inputs.sfcc_client_id || vars.SFCC_CLIENT_ID }} + SFCC_CLIENT_SECRET: ${{ secrets.SFCC_CLIENT_SECRET }} + SFCC_ACCOUNT_MANAGER_HOST: ${{ inputs.sfcc_account_manager_host || vars.SFCC_ACCOUNT_MANAGER_HOST }} + SFCC_SANDBOX_API_HOST: ${{ inputs.sfcc_sandbox_api_host || vars.SFCC_SANDBOX_API_HOST }} + TEST_REALM: ${{ inputs.test_realm || vars.TEST_REALM }} + # Test configuration + NODE_ENV: test + SFCC_LOG_LEVEL: silent + run: | + echo "Running E2E tests with realm: ${TEST_REALM}" + + # Run E2E tests with JSON output for parsing + pnpm mocha "test/functional/e2e/**/*.test.ts" --reporter json > e2e-results.json || true + + # Also run with spec reporter for readable output + echo "## E2E Test Results" >> $GITHUB_STEP_SUMMARY + pnpm mocha "test/functional/e2e/**/*.test.ts" --reporter spec + + - name: Parse E2E Results + if: always() && steps.e2e-test.conclusion != 'cancelled' && steps.check-secrets.outputs.has-secrets == 'true' + run: | + if [ -f "e2e-results.json" ]; then + # Extract test summary from JSON results + TESTS=$(cat e2e-results.json | jq -r '.stats.tests // 0') + PASSES=$(cat e2e-results.json | jq -r '.stats.passes // 0') + FAILURES=$(cat e2e-results.json | jq -r '.stats.failures // 0') + DURATION=$(cat e2e-results.json | jq -r '.stats.duration // 0') + + echo "## E2E Test Summary" >> $GITHUB_STEP_SUMMARY + echo "- **Total Tests:** $TESTS" >> $GITHUB_STEP_SUMMARY + echo "- **Passed:** $PASSES" >> $GITHUB_STEP_SUMMARY + echo "- **Failed:** $FAILURES" >> $GITHUB_STEP_SUMMARY + echo "- **Duration:** ${DURATION}ms" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Add failure details if any + if [ "$FAILURES" -gt "0" ]; then + echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat e2e-results.json | jq -r '.failures[]? | "❌ " + .fullTitle + "\n " + .err.message' >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + fi + fi + + - name: Upload E2E Test Results + if: always() && steps.e2e-test.conclusion != 'cancelled' && steps.check-secrets.outputs.has-secrets == 'true' + uses: actions/upload-artifact@v4 + with: + name: e2e-test-results-${{ matrix.os }}-node${{ matrix.node_version }}-${{ github.run_number }} + path: e2e-results.json + retention-days: 30 + + - name: Notify on Failure + if: failure() && (github.event_name == 'schedule' || github.event_name == 'push') && steps.check-secrets.outputs.has-secrets == 'true' + uses: actions/github-script@v7 + with: + script: | + const issue = { + owner: context.repo.owner, + repo: context.repo.repo, + title: `CLI E2E Tests Failed - ${new Date().toISOString().split('T')[0]}`, + body: `## CLI E2E Test Failure + + The CLI E2E tests have failed. Please investigate. + + **Workflow:** ${context.workflow} + **Run:** ${context.runNumber} + **Commit:** ${context.sha} + **Event:** ${context.eventName} + + [View workflow run](${context.payload.repository.html_url}/actions/runs/${context.runId}) + `, + labels: ['bug', 'e2e-failure', 'cli', 'needs-investigation'] + }; + + // Only create issue for scheduled runs to avoid spam + if (context.eventName === 'schedule') { + github.rest.issues.create(issue); + } From 3905cf6bb40322c53bc1c1880c83d3916c450d6c Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Wed, 7 Jan 2026 00:36:40 +0530 Subject: [PATCH 6/8] testing the e2e temp workflow --- packages/b2c-cli/.github/workflows/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/b2c-cli/.github/workflows/e2e-tests.yml b/packages/b2c-cli/.github/workflows/e2e-tests.yml index 08dcd7fd..29268bbb 100644 --- a/packages/b2c-cli/.github/workflows/e2e-tests.yml +++ b/packages/b2c-cli/.github/workflows/e2e-tests.yml @@ -62,7 +62,7 @@ jobs: if: > (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || - (github.event_name == 'push' && github.ref == 'refs/heads/main')) + (github.event_name == 'push')) steps: - uses: actions/checkout@v4 From 150cf5f39f8ecc0bc8180557443a3a2c0b24d324 Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Wed, 7 Jan 2026 00:56:10 +0530 Subject: [PATCH 7/8] testing the e2e temp workflow --- .../b2c-cli/.github/workflows/e2e-tests.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/b2c-cli/.github/workflows/e2e-tests.yml b/packages/b2c-cli/.github/workflows/e2e-tests.yml index 29268bbb..598ce431 100644 --- a/packages/b2c-cli/.github/workflows/e2e-tests.yml +++ b/packages/b2c-cli/.github/workflows/e2e-tests.yml @@ -7,8 +7,12 @@ on: # Post-merge - Run after changes are merged to main push: - #branches: [main] - branches-ignore: [main] + branches: [main] + # To be removed post testing + pull_request: + branches: + - main + # Manual trigger - Support workflow_dispatch for on-demand runs workflow_dispatch: @@ -56,7 +60,7 @@ jobs: fail-fast: false runs-on: ${{ inputs.os || matrix.os }} environment: e2e-dev - timeout-minutes: 45 # E2E tests can take longer + timeout-minutes: 10 # E2E tests can take longer # Only run if we have the required secrets if: > @@ -66,12 +70,10 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 with: node-version: ${{ inputs.node_version || matrix.node_version }} cache: pnpm - - name: Check for required secrets and vars id: check-secrets env: @@ -92,15 +94,12 @@ jobs: echo " - SFCC_ACCOUNT_MANAGER_HOST (var): ${SFCC_ACCOUNT_MANAGER_HOST:+✓}" >> $GITHUB_STEP_SUMMARY echo " - SFCC_SANDBOX_API_HOST (var): ${SFCC_SANDBOX_API_HOST:+✓}" >> $GITHUB_STEP_SUMMARY fi - - name: Install dependencies if: steps.check-secrets.outputs.has-secrets == 'true' run: pnpm install - - name: Build package if: steps.check-secrets.outputs.has-secrets == 'true' run: pnpm run build - - name: Run E2E Tests if: steps.check-secrets.outputs.has-secrets == 'true' id: e2e-test @@ -123,7 +122,6 @@ jobs: # Also run with spec reporter for readable output echo "## E2E Test Results" >> $GITHUB_STEP_SUMMARY pnpm mocha "test/functional/e2e/**/*.test.ts" --reporter spec - - name: Parse E2E Results if: always() && steps.e2e-test.conclusion != 'cancelled' && steps.check-secrets.outputs.has-secrets == 'true' run: | @@ -149,7 +147,6 @@ jobs: echo '```' >> $GITHUB_STEP_SUMMARY fi fi - - name: Upload E2E Test Results if: always() && steps.e2e-test.conclusion != 'cancelled' && steps.check-secrets.outputs.has-secrets == 'true' uses: actions/upload-artifact@v4 @@ -157,7 +154,6 @@ jobs: name: e2e-test-results-${{ matrix.os }}-node${{ matrix.node_version }}-${{ github.run_number }} path: e2e-results.json retention-days: 30 - - name: Notify on Failure if: failure() && (github.event_name == 'schedule' || github.event_name == 'push') && steps.check-secrets.outputs.has-secrets == 'true' uses: actions/github-script@v7 From 33a2a9788f982f6d4d3dff65a0be43e9e99217c1 Mon Sep 17 00:00:00 2001 From: CharithaT07 Date: Wed, 7 Jan 2026 01:22:43 +0530 Subject: [PATCH 8/8] changes to end to end test workflow --- .../b2c-cli/.github/workflows/e2e-tests.yml | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/packages/b2c-cli/.github/workflows/e2e-tests.yml b/packages/b2c-cli/.github/workflows/e2e-tests.yml index 598ce431..25bcc25b 100644 --- a/packages/b2c-cli/.github/workflows/e2e-tests.yml +++ b/packages/b2c-cli/.github/workflows/e2e-tests.yml @@ -1,19 +1,12 @@ name: e2e tests on: - # Nightly workflow - Run E2E tests on a schedule (nightly) + # Nightly workflow - Run E2E tests on a schedule schedule: - cron: '0 2 * * *' # Run at 2 AM UTC daily - # Post-merge - Run after changes are merged to main push: branches: [main] - # To be removed post testing - pull_request: - branches: - - main - - # Manual trigger - Support workflow_dispatch for on-demand runs workflow_dispatch: inputs: @@ -25,6 +18,10 @@ on: description: 'SFCC Client ID (optional, defaults to var)' required: false type: string + sfcc_client_secret: + description: 'SFCC Client Secret (optional, defaults to secret)' + required: false + type: string sfcc_account_manager_host: description: 'SFCC Account Manager Host (optional, defaults to var)' required: false @@ -50,29 +47,19 @@ on: options: - 'ubuntu-latest' - 'windows-latest' - jobs: e2e-tests: strategy: matrix: - os: ['ubuntu-latest', 'windows-latest'] - node_version: ['lts/*'] - fail-fast: false - runs-on: ${{ inputs.os || matrix.os }} + node_version: [22.x, 24.x] + runs-on: ubuntu-latest environment: e2e-dev timeout-minutes: 10 # E2E tests can take longer - - # Only run if we have the required secrets - if: > - (github.event_name == 'schedule' || - github.event_name == 'workflow_dispatch' || - (github.event_name == 'push')) - steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: ${{ inputs.node_version || matrix.node_version }} + node-version: ${{ matrix.node-version }} cache: pnpm - name: Check for required secrets and vars id: check-secrets @@ -83,13 +70,12 @@ jobs: SFCC_ACCOUNT_MANAGER_HOST: ${{ vars.SFCC_ACCOUNT_MANAGER_HOST }} SFCC_SANDBOX_API_HOST: ${{ vars.SFCC_SANDBOX_API_HOST }} run: | - if [ -n "$SFCC_CLIENT_ID" ] && [ -n "$SFCC_CLIENT_SECRET" ] && [ -n "$TEST_REALM" ]; then + if [ -n "$SFCC_CLIENT_ID" ] && [ -n "$SFCC_CLIENT_SECRET" ] && [ -n "$TEST_REALM" ] && [ -n "SFCC_ACCOUNT_MANAGER_HOST" ] && [ -n "SFCC_SANDBOX_API_HOST" ]; then echo "has-secrets=true" >> $GITHUB_OUTPUT else echo "has-secrets=false" >> $GITHUB_OUTPUT - echo "⚠️ E2E tests skipped - missing required variables:" >> $GITHUB_STEP_SUMMARY + echo "E2E tests skipped - missing required variables:" >> $GITHUB_STEP_SUMMARY echo " - SFCC_CLIENT_ID (var): ${SFCC_CLIENT_ID:+✓}" >> $GITHUB_STEP_SUMMARY - echo " - SFCC_CLIENT_SECRET (secret): ${SFCC_CLIENT_SECRET:+✓}" >> $GITHUB_STEP_SUMMARY echo " - TEST_REALM (var): ${TEST_REALM:+✓}" >> $GITHUB_STEP_SUMMARY echo " - SFCC_ACCOUNT_MANAGER_HOST (var): ${SFCC_ACCOUNT_MANAGER_HOST:+✓}" >> $GITHUB_STEP_SUMMARY echo " - SFCC_SANDBOX_API_HOST (var): ${SFCC_SANDBOX_API_HOST:+✓}" >> $GITHUB_STEP_SUMMARY @@ -106,7 +92,7 @@ jobs: env: # Required environment variables for Commerce Cloud integration SFCC_CLIENT_ID: ${{ inputs.sfcc_client_id || vars.SFCC_CLIENT_ID }} - SFCC_CLIENT_SECRET: ${{ secrets.SFCC_CLIENT_SECRET }} + SFCC_CLIENT_SECRET: ${{ inputs.sfcc_client_secret || secrets.SFCC_CLIENT_SECRET }} SFCC_ACCOUNT_MANAGER_HOST: ${{ inputs.sfcc_account_manager_host || vars.SFCC_ACCOUNT_MANAGER_HOST }} SFCC_SANDBOX_API_HOST: ${{ inputs.sfcc_sandbox_api_host || vars.SFCC_SANDBOX_API_HOST }} TEST_REALM: ${{ inputs.test_realm || vars.TEST_REALM }} @@ -143,7 +129,7 @@ jobs: if [ "$FAILURES" -gt "0" ]; then echo "### Failed Tests" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY - cat e2e-results.json | jq -r '.failures[]? | "❌ " + .fullTitle + "\n " + .err.message' >> $GITHUB_STEP_SUMMARY + cat e2e-results.json | jq -r '.failures[]? | "[X] " + .fullTitle + "\n " + .err.message' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY fi fi