diff --git a/.github/workflows/e2e-shell-tests.yml b/.github/workflows/e2e-shell-tests.yml new file mode 100644 index 00000000..8b62449f --- /dev/null +++ b/.github/workflows/e2e-shell-tests.yml @@ -0,0 +1,86 @@ +name: E2E Shell Tests + +on: + schedule: + - cron: '0 3 * * *' # Run at 3 AM UTC daily + workflow_dispatch: + +jobs: + e2e-shell-tests: + runs-on: ubuntu-latest + environment: e2e-dev + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '24' + + - 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 }} + SFCC_SHORTCODE: ${{ vars.SFCC_SHORTCODE }} + run: | + missing="" + [ -z "$SFCC_CLIENT_ID" ] && missing="$missing SFCC_CLIENT_ID" + [ -z "$SFCC_CLIENT_SECRET" ] && missing="$missing SFCC_CLIENT_SECRET" + [ -z "$TEST_REALM" ] && missing="$missing TEST_REALM" + [ -z "$SFCC_ACCOUNT_MANAGER_HOST" ] && missing="$missing SFCC_ACCOUNT_MANAGER_HOST" + [ -z "$SFCC_SANDBOX_API_HOST" ] && missing="$missing SFCC_SANDBOX_API_HOST" + [ -z "$SFCC_SHORTCODE" ] && missing="$missing SFCC_SHORTCODE" + + if [ -z "$missing" ]; then + echo "has-secrets=true" >> $GITHUB_OUTPUT + else + echo "has-secrets=false" >> $GITHUB_OUTPUT + echo "E2E shell tests skipped - missing required variables:$missing" >> $GITHUB_STEP_SUMMARY + fi + + - name: Setup pnpm + if: steps.check-secrets.outputs.has-secrets == 'true' + uses: pnpm/action-setup@v4 + with: + version: 10.17.1 + + - name: Get pnpm store directory + if: steps.check-secrets.outputs.has-secrets == 'true' + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Setup pnpm cache + if: steps.check-secrets.outputs.has-secrets == 'true' + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + if: steps.check-secrets.outputs.has-secrets == 'true' + run: pnpm install --frozen-lockfile + + - name: Build packages + if: steps.check-secrets.outputs.has-secrets == 'true' + run: pnpm -r run build + + - name: Run E2E Shell Tests + if: steps.check-secrets.outputs.has-secrets == 'true' + env: + SFCC_CLIENT_ID: ${{ vars.SFCC_CLIENT_ID }} + SFCC_CLIENT_SECRET: ${{ secrets.SFCC_CLIENT_SECRET }} + SFCC_ACCOUNT_MANAGER_HOST: ${{ vars.SFCC_ACCOUNT_MANAGER_HOST }} + SFCC_SANDBOX_API_HOST: ${{ vars.SFCC_SANDBOX_API_HOST }} + SFCC_SHORTCODE: ${{ vars.SFCC_SHORTCODE }} + TEST_REALM: ${{ vars.TEST_REALM }} + run: | + echo "Running E2E shell tests with realm: ${TEST_REALM}" + cd packages/b2c-cli + ./test/functional/e2e_cli_test.sh diff --git a/docs/cli/jobs.md b/docs/cli/jobs.md index 4b425bd1..bfd7dd03 100644 --- a/docs/cli/jobs.md +++ b/docs/cli/jobs.md @@ -27,9 +27,12 @@ In addition to [global flags](./index#global-flags): | `--wait`, `-w` | Wait for job to complete | `false` | | `--timeout`, `-t` | Timeout in seconds when waiting | No timeout | | `--param`, `-P` | Job parameter in format "name=value" (repeatable) | | +| `--body`, `-B` | Raw JSON request body (for system jobs with non-standard schemas) | | | `--no-wait-running` | Do not wait for running job to finish before starting | `false` | | `--show-log` | Show job log on failure | `true` | +Note: `--param` and `--body` are mutually exclusive. + ### Examples ```bash @@ -42,13 +45,25 @@ b2c job run my-custom-job --wait # Execute with timeout b2c job run my-custom-job --wait --timeout 600 -# Execute with parameters +# Execute with parameters (standard jobs) b2c job run my-custom-job -P "SiteScope={\"all_storefront_sites\":true}" -P OtherParam=value # Output as JSON b2c job run my-custom-job --wait --json ``` +### System Jobs with Custom Request Bodies + +Some system jobs (like search indexing) use non-standard request schemas that don't follow the `parameters` array format. Use `--body` to provide a raw JSON request body: + +```bash +# Run search index job for specific sites +b2c job run sfcc-search-index-product-full-update --wait --body '{"site_scope":["RefArch","SiteGenesis"]}' + +# Run search index job for a single site +b2c job run sfcc-search-index-product-full-update --wait --body '{"site_scope":["RefArch"]}' +``` + ### Authentication This command requires OAuth authentication with OCAPI permissions for the `/jobs` resource. diff --git a/docs/cli/slas.md b/docs/cli/slas.md index 64f915c0..4a450df9 100644 --- a/docs/cli/slas.md +++ b/docs/cli/slas.md @@ -102,17 +102,18 @@ b2c slas client create [CLIENTID] --tenant-id --channels ### Flags -| Flag | Description | Required | -|------|-------------|----------| -| `--tenant-id` | SLAS tenant ID (organization ID) | Yes | -| `--channels` | Site IDs/channels (comma-separated) | Yes | -| `--redirect-uri` | Redirect URIs (comma-separated) | Yes | -| `--name` | Display name for the client | No | -| `--scopes` | OAuth scopes for the client (comma-separated) | No | -| `--default-scopes` | Use default shopper scopes | No | -| `--callback-uri` | Callback URIs for passwordless login | No | -| `--secret` | Client secret (generated if omitted) | No | -| `--public` | Create a public client (default is private) | No | +| Flag | Description | Default | +|------|-------------|---------| +| `--tenant-id` | SLAS tenant ID (organization ID) | Required | +| `--channels` | Site IDs/channels (comma-separated) | Required | +| `--redirect-uri` | Redirect URIs (comma-separated) | Required | +| `--name` | Display name for the client | Auto-generated | +| `--scopes` | OAuth scopes for the client (comma-separated) | | +| `--default-scopes` | Use default shopper scopes | `false` | +| `--callback-uri` | Callback URIs for passwordless login | | +| `--secret` | Client secret (generated if omitted) | Auto-generated | +| `--public` | Create a public client (default is private) | `false` | +| `--[no-]create-tenant` | Automatically create tenant if it doesn't exist | `true` | ### Examples @@ -150,6 +151,7 @@ b2c slas client create --tenant-id abcd_123 \ - If `--secret` is not provided for a private client, one will be generated - The generated secret is only shown once during creation - Use `--default-scopes` for common shopper API access scopes +- By default, the tenant is automatically created if it doesn't exist. Use `--no-create-tenant` to disable this behavior if you prefer to manage tenants separately --- diff --git a/packages/b2c-cli/src/commands/job/run.ts b/packages/b2c-cli/src/commands/job/run.ts index a1b337c4..b6d15038 100644 --- a/packages/b2c-cli/src/commands/job/run.ts +++ b/packages/b2c-cli/src/commands/job/run.ts @@ -30,6 +30,7 @@ export default class JobRun extends JobCommand { '<%= config.bin %> <%= command.id %> my-custom-job --wait', String.raw`<%= config.bin %> <%= command.id %> my-custom-job -P "SiteScope={\"all_storefront_sites\":true}" -P OtherParam=value`, '<%= config.bin %> <%= command.id %> my-custom-job --wait --timeout 600', + String.raw`<%= config.bin %> <%= command.id %> sfcc-search-index-product-full-update --body '{"site_scope":{"named_sites":["RefArch"]}}'`, ]; static flags = { @@ -48,6 +49,12 @@ export default class JobRun extends JobCommand { description: 'Job parameter in format "name=value" (use -P multiple times for multiple params)', multiple: true, multipleNonGreedy: true, + exclusive: ['body'], + }), + body: Flags.string({ + char: 'B', + description: 'Raw JSON request body (for system jobs with non-standard schemas)', + exclusive: ['param'], }), 'no-wait-running': Flags.boolean({ description: 'Do not wait for running job to finish before starting', @@ -63,10 +70,11 @@ export default class JobRun extends JobCommand { this.requireOAuthCredentials(); const {jobId} = this.args; - const {wait, timeout, param, 'no-wait-running': noWaitRunning, 'show-log': showLog} = this.flags; + const {wait, timeout, param, body, 'no-wait-running': noWaitRunning, 'show-log': showLog} = this.flags; - // Parse parameters + // Parse parameters or body const parameters = this.parseParameters(param || []); + const rawBody = body ? this.parseBody(body) : undefined; this.log( t('commands.job.run.executing', 'Executing job {{jobId}} on {{hostname}}...', { @@ -78,7 +86,8 @@ export default class JobRun extends JobCommand { let execution: JobExecution; try { execution = await executeJob(this.instance, jobId, { - parameters, + parameters: rawBody ? undefined : parameters, + body: rawBody, waitForRunning: !noWaitRunning, }); } catch (error) { @@ -147,6 +156,14 @@ export default class JobRun extends JobCommand { return execution; } + private parseBody(body: string): Record { + try { + return JSON.parse(body) as Record; + } catch { + this.error(t('commands.job.run.invalidBody', 'Invalid JSON body: {{body}}', {body})); + } + } + private parseParameters(params: string[]): Array<{name: string; value: string}> { return params.map((p) => { const eqIndex = p.indexOf('='); diff --git a/packages/b2c-cli/src/commands/ods/create.ts b/packages/b2c-cli/src/commands/ods/create.ts index 4cde1523..5fa0e2f0 100644 --- a/packages/b2c-cli/src/commands/ods/create.ts +++ b/packages/b2c-cli/src/commands/ods/create.ts @@ -152,7 +152,7 @@ export default class OdsCreate extends OdsCommand { let sandbox = result.data.data; this.log(''); - this.log(t('commands.ods.create.success', 'Sandbox created successfully!')); + this.logger.info({sandboxId: sandbox.id}, t('commands.ods.create.success', 'Sandbox created successfully')); if (wait && sandbox.id) { this.log(''); @@ -256,6 +256,9 @@ export default class OdsCreate extends OdsCommand { this.log(t('commands.ods.create.waiting', 'Waiting for sandbox to be ready...')); + // Initial delay before first poll to allow the sandbox to be registered in the API + await this.sleep(2000); + while (true) { // Check for timeout if (timeoutSeconds > 0 && Date.now() - startTime > timeoutMs) { @@ -286,12 +289,8 @@ export default class OdsCreate extends OdsCommand { // Log current state on each poll const elapsed = Math.round((Date.now() - startTime) / 1000); - this.log( - t('commands.ods.create.stateChange', '[{{elapsed}}s] State: {{state}}', { - elapsed: String(elapsed), - state: currentState || 'unknown', - }), - ); + const state = currentState || 'unknown'; + this.logger.info({sandboxId, elapsed, state}, `[${elapsed}s] State: ${state}`); // Check for terminal states if (currentState && TERMINAL_STATES.has(currentState)) { @@ -306,7 +305,7 @@ export default class OdsCreate extends OdsCommand { } case 'started': { this.log(''); - this.log(t('commands.ods.create.ready', 'Sandbox is now ready!')); + this.logger.info({sandboxId}, t('commands.ods.create.ready', 'Sandbox is now ready')); break; } } diff --git a/packages/b2c-cli/src/commands/slas/client/create.ts b/packages/b2c-cli/src/commands/slas/client/create.ts index a8009078..f57e233c 100644 --- a/packages/b2c-cli/src/commands/slas/client/create.ts +++ b/packages/b2c-cli/src/commands/slas/client/create.ts @@ -98,6 +98,11 @@ export default class SlasClientCreate extends SlasClientCommand { @@ -113,6 +118,7 @@ export default class SlasClientCreate extends SlasClientCommand = { clientId, diff --git a/packages/b2c-cli/src/utils/slas/client.ts b/packages/b2c-cli/src/utils/slas/client.ts index 15c0f0e8..70e5ed19 100644 --- a/packages/b2c-cli/src/utils/slas/client.ts +++ b/packages/b2c-cli/src/utils/slas/client.ts @@ -109,6 +109,73 @@ export abstract class SlasClientCommand extends OAuthC }), }; + /** + * Ensure tenant exists, creating it if necessary. + * This is required before creating SLAS clients. + */ + protected async ensureTenantExists(slasClient: SlasClient, tenantId: string): Promise { + // Try to get the tenant first + const {error, response} = await slasClient.GET('/tenants/{tenantId}', { + params: { + path: {tenantId}, + }, + }); + + // If tenant exists, we're done + if (!error) { + return; + } + + // Check if this is a "tenant not found" error (SLAS returns 400 with TenantNotFoundException) + const isTenantNotFound = + response.status === 404 || + (response.status === 400 && + typeof error === 'object' && + error !== null && + 'exception_name' in error && + (error as {exception_name?: string}).exception_name === 'TenantNotFoundException'); + + // If it's not a tenant-not-found error, something else went wrong + if (!isTenantNotFound) { + this.error( + t('commands.slas.client.create.tenantError', 'Failed to check tenant: {{message}}', { + message: formatApiError(error), + }), + ); + } + + // Tenant doesn't exist, create it with placeholder values + if (!this.jsonEnabled()) { + this.log(t('commands.slas.client.create.creatingTenant', 'Creating SLAS tenant {{tenantId}}...', {tenantId})); + } + + const {error: createError} = await slasClient.PUT('/tenants/{tenantId}', { + params: { + path: {tenantId}, + }, + body: { + tenantId, + merchantName: 'B2C CLI Tenant', + description: 'Auto-created by b2c-cli', + contact: 'B2C CLI', + emailAddress: 'noreply@example.com', + phoneNo: '+1 000-000-0000', + }, + }); + + if (createError) { + this.error( + t('commands.slas.client.create.tenantCreateError', 'Failed to create tenant: {{message}}', { + message: formatApiError(createError), + }), + ); + } + + if (!this.jsonEnabled()) { + this.log(t('commands.slas.client.create.tenantCreated', 'SLAS tenant created successfully.')); + } + } + /** * Get the SLAS client, ensuring short code is configured. */ diff --git a/packages/b2c-cli/test/functional/e2e_cli_test.sh b/packages/b2c-cli/test/functional/e2e_cli_test.sh index f5002e35..f9494429 100755 --- a/packages/b2c-cli/test/functional/e2e_cli_test.sh +++ b/packages/b2c-cli/test/functional/e2e_cli_test.sh @@ -1,40 +1,268 @@ #!/usr/bin/env bash # b2c-cli e2e cli tests -# required env vars -# SFCC_CLIENT_ID -# SFCC_CLIENT_SECRET -# SFCC_SHORTCODE -# TEST_REALM +# +# Required environment variables: +# SFCC_CLIENT_ID - Account Manager API client ID +# SFCC_CLIENT_SECRET - Account Manager API client secret +# SFCC_SHORTCODE - SCAPI short code +# TEST_REALM - Realm ID for sandbox creation (four-letter ID) +# +# Optional environment variables: +# SFCC_ACCOUNT_MANAGER_HOST - Account Manager hostname (default: account.demandware.com) +# SFCC_SANDBOX_API_HOST - Sandbox API hostname (default: admin.dx.commercecloud.salesforce.com) set -e +# Script directory for relative paths +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CLI="$SCRIPT_DIR/../../bin/run.js" -# 1. Create on demand sandbox +# Fixtures paths +CARTRIDGE_PATH="$SCRIPT_DIR/fixtures/cartridges" +SITE_ARCHIVE_PATH="$SCRIPT_DIR/fixtures/site_archive" -# 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) +# Test configuration +SITE_ID="TestSite" +TTL_HOURS=4 # 4 hours in case test fails and needs manual cleanup -ODS_ID=$(echo "$ODS_CREATE_RESULT" | jq -r '.[0].id') +# Cleanup function for error handling +cleanup() { + local exit_code=$? + echo "" + echo "=========================================" + echo "Cleanup (exit code: $exit_code)" + echo "=========================================" + + # Delete SLAS client if it was created + if [ -n "$SLAS_CLIENT_ID" ] && [ -n "$TENANT_ID" ]; then + echo "Deleting SLAS client: $SLAS_CLIENT_ID" + $CLI slas client delete "$SLAS_CLIENT_ID" --tenant-id "$TENANT_ID" || true + fi + + # Delete sandbox if it was created + if [ -n "$ODS_ID" ]; then + echo "Deleting sandbox: $ODS_ID" + $CLI ods delete "$ODS_ID" --force || true + fi + + exit $exit_code +} + +# Set up cleanup trap +trap cleanup EXIT + +echo "=========================================" +echo "B2C CLI E2E Test Suite" +echo "=========================================" +echo "Realm: $TEST_REALM" +echo "Short Code: $SFCC_SHORTCODE" +echo "" + +################################################################################ +# 1. Create On-Demand Sandbox +################################################################################ +echo "Step 1: Creating on-demand sandbox..." + +ODS_CREATE_RESULT=$($CLI ods create \ + --realm "$TEST_REALM" \ + --ttl "$TTL_HOURS" \ + --wait \ + --json) + +echo "DEBUG: ODS create result:" +echo "$ODS_CREATE_RESULT" | jq . + +# The JSON output is the sandbox object directly (not an array) +ODS_ID=$(echo "$ODS_CREATE_RESULT" | jq -r '.id') +SERVER=$(echo "$ODS_CREATE_RESULT" | jq -r '.hostName') +INSTANCE_NUM=$(echo "$ODS_CREATE_RESULT" | jq -r '.instance') if [ -z "$ODS_ID" ] || [ "$ODS_ID" == "null" ]; then - echo "Failed to create on demand sandbox" - exit 1 + echo "FAILED: Could not create on-demand sandbox" + exit 1 fi -echo "Created on demand sandbox with ID: $ODS_ID" +echo "SUCCESS: Created sandbox" +echo " ID: $ODS_ID" +echo " Server: $SERVER" +echo " Instance: $INSTANCE_NUM" +echo "" + +################################################################################ +# 2. List and Verify Sandbox +################################################################################ +echo "Step 2: Verifying sandbox in list..." -# 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') +ODS_LIST_RESULT=$($CLI ods list --realm "$TEST_REALM" --json) + +echo "DEBUG: ODS list result:" +echo "$ODS_LIST_RESULT" | jq . + +# The JSON output is { count: number, data: SandboxModel[] } +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" - exit 1 + echo "FAILED: Created sandbox not found in list" + exit 1 +fi + +echo "SUCCESS: Sandbox verified in list" +echo "" + +################################################################################ +# 3. Deploy Code +################################################################################ +echo "Step 3: Deploying code to sandbox..." + +$CLI code deploy "$CARTRIDGE_PATH" \ + --server "$SERVER" \ + --code-version "e2e-test-version" --log-level debug --json + +echo "SUCCESS: Code deployed" +echo "" + +################################################################################ +# 4. Import Site Data +################################################################################ +echo "Step 4: Importing site data..." + +$CLI job import "$SITE_ARCHIVE_PATH" \ + --server "$SERVER" \ + --timeout 300 + +echo "SUCCESS: Site data imported" +echo "" + +################################################################################ +# 5. Run Search Index Job +################################################################################ +sleep 4 +echo "Step 5: Running search index job..." + +$CLI job run sfcc-search-index-product-full-update \ + --server "$SERVER" \ + --wait \ + --timeout 300 \ + --body "{\"site_scope\":[\"$SITE_ID\"]}" + +echo "SUCCESS: Search index job completed" +echo "" + +################################################################################ +# 6. Create SLAS Client +################################################################################ +echo "Step 6: Creating SLAS client..." + +# Construct tenant ID from realm and instance number +TENANT_ID="${TEST_REALM}_${INSTANCE_NUM}" + +# Let the CLI auto-generate a UUID4 client ID +SLAS_CREATE_RESULT=$($CLI slas client create \ + --tenant-id "$TENANT_ID" \ + --channels "$SITE_ID" \ + --default-scopes \ + --redirect-uri "http://localhost:3000/callback" \ + --json) + +echo "DEBUG: SLAS create result:" +echo "$SLAS_CREATE_RESULT" | jq . + +# Extract client ID and secret from response +SLAS_CLIENT_ID=$(echo "$SLAS_CREATE_RESULT" | jq -r '.clientId') +SLAS_SECRET=$(echo "$SLAS_CREATE_RESULT" | jq -r '.secret') + +if [ -z "$SLAS_SECRET" ] || [ "$SLAS_SECRET" == "null" ]; then + echo "FAILED: Could not create SLAS client" + echo "$SLAS_CREATE_RESULT" + exit 1 +fi + +echo "SUCCESS: SLAS client created" +echo " Client ID: $SLAS_CLIENT_ID" +echo " Tenant ID: $TENANT_ID" +echo "" + +################################################################################ +# 7. Test SLAS Guest Login +################################################################################ +echo "Step 7: Testing SLAS guest login..." + +# Wait a moment for SLAS client to be ready and search index to be available +sleep 10 + +# Organization ID format for SLAS +ORG_ID="f_ecom_${TEST_REALM}_${INSTANCE_NUM}" +SLAS_BASE="https://${SFCC_SHORTCODE}.api.commercecloud.salesforce.com" + +echo " ORG ID: $ORG_ID" +echo " SLAS Base: $SLAS_BASE" + +# Get shopper token via client credentials (guest login) +TOKEN_RESPONSE=$(curl -s "${SLAS_BASE}/shopper/auth/v1/organizations/${ORG_ID}/oauth2/token" \ + -u "${SLAS_CLIENT_ID}:${SLAS_SECRET}" \ + -d "grant_type=client_credentials&channel_id=${SITE_ID}") + +SHOPPER_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token') + +if [ -z "$SHOPPER_TOKEN" ] || [ "$SHOPPER_TOKEN" == "null" ]; then + echo "FAILED: Could not obtain shopper token" + echo "$TOKEN_RESPONSE" | jq + exit 1 fi -SERVER=$(echo "$ODS_CREATE_RESULT" | jq -r '.[0].server') +echo "SUCCESS: Obtained shopper access token" +echo "" + +################################################################################ +# 8. Test Shopper Search +################################################################################ +echo "Step 8: Testing shopper product search..." + +SEARCH_RESPONSE=$(curl -s "${SLAS_BASE}/search/shopper-search/v1/organizations/${ORG_ID}/product-search?siteId=${SITE_ID}&limit=5&q=sample" \ + -H "Authorization: Bearer ${SHOPPER_TOKEN}") + +# Check if we got a valid response (should have a 'hits' array or 'total' field) +SEARCH_TOTAL=$(echo "$SEARCH_RESPONSE" | jq -r '.total // .hits | length // 0') + +if [ "$SEARCH_TOTAL" == "null" ]; then + echo "WARNING: Search returned unexpected response format" + echo "$SEARCH_RESPONSE" | jq + # Don't fail - the product might not be indexed yet +else + echo "SUCCESS: Shopper search returned results" + echo " Total results: $SEARCH_TOTAL" +fi +echo "" + +################################################################################ +# 9. Delete SLAS Client +################################################################################ +echo "Step 9: Deleting SLAS client..." + +$CLI slas client delete "$SLAS_CLIENT_ID" --tenant-id "$TENANT_ID" + +# Clear SLAS_CLIENT_ID so cleanup doesn't try to delete again +SLAS_CLIENT_ID="" + +echo "SUCCESS: SLAS client deleted" +echo "" + +################################################################################ +# 10. Delete Sandbox +################################################################################ +echo "Step 10: Deleting sandbox..." + +$CLI ods delete "$ODS_ID" --force + +# Clear ODS_ID so cleanup doesn't try to delete again +ODS_ID="" -# 3. Import code into the created sandbox +echo "SUCCESS: Sandbox deleted" +echo "" -IMPORT_RESULT=$(../../bin/run.js code deploy --server --sandbox "$ODS_ID" --source ./test/functional/sample_code --wait --json) +################################################################################ +# Complete +################################################################################ +echo "=========================================" +echo "All E2E tests passed!" +echo "=========================================" diff --git a/packages/b2c-cli/test/functional/fixtures/site_archive/catalogs/TestCatalog/catalog.xml b/packages/b2c-cli/test/functional/fixtures/site_archive/catalogs/TestCatalog/catalog.xml new file mode 100644 index 00000000..db403560 --- /dev/null +++ b/packages/b2c-cli/test/functional/fixtures/site_archive/catalogs/TestCatalog/catalog.xml @@ -0,0 +1,47 @@ + + +
+ + + + large + medium + small + + +
+ + + Root Category + true +