Skip to content

Commit b324de6

Browse files
@W-20937015: Adding windows test support (#359)
1 parent 3c1f20e commit b324de6

16 files changed

Lines changed: 2367 additions & 2044 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: setup-windows
2+
description: 'Set up a Windows CI runner for the B2C developer tooling monorepo (Node, pnpm, dependency install, pnpm-store cache).'
3+
4+
# Mirrors the SalesforceCommerceCloud/pwa-kit `setup_windows` composite action.
5+
# Windows runs everything under Git Bash (`shell: bash`) so the inline `env VAR=value ...`
6+
# style used in the package.json test scripts keeps working on windows-latest.
7+
8+
inputs:
9+
node-version:
10+
description: 'Node.js version to install (matches `actions/setup-node` input).'
11+
required: false
12+
default: '22.x'
13+
14+
runs:
15+
using: composite
16+
steps:
17+
- name: Setup Node.js ${{ inputs.node-version }}
18+
uses: actions/setup-node@v6
19+
with:
20+
node-version: ${{ inputs.node-version }}
21+
22+
- name: Setup pnpm
23+
uses: pnpm/action-setup@v5
24+
25+
- name: Get pnpm store directory
26+
shell: bash
27+
run: |
28+
echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV"
29+
30+
- name: Setup pnpm cache
31+
uses: actions/cache@v5
32+
with:
33+
path: ${{ env.STORE_PATH }}
34+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
35+
restore-keys: |
36+
${{ runner.os }}-pnpm-store-
37+
38+
- name: Install dependencies
39+
shell: bash
40+
run: pnpm install --frozen-lockfile

.github/workflows/ci.yml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,105 @@ jobs:
124124
packages/b2c-tooling-sdk/coverage/
125125
packages/b2c-cli/coverage/
126126
retention-days: 30
127+
128+
test-windows:
129+
runs-on: windows-latest
130+
# Advisory rollout: Windows coverage is new; surface failures without
131+
# blocking Linux CI while remaining Windows-specific test issues are
132+
# addressed in follow-up PRs. Remove once Windows tests are green.
133+
continue-on-error: true
134+
defaults:
135+
run:
136+
shell: bash
137+
strategy:
138+
fail-fast: false
139+
matrix:
140+
node-version: [22.x, 24.x]
141+
steps:
142+
- name: Checkout code
143+
uses: actions/checkout@v6
144+
- name: Setup Windows machine
145+
uses: ./.github/actions/setup-windows
146+
with:
147+
node-version: ${{ matrix.node-version }}
148+
- name: Build packages
149+
run: pnpm -r run build
150+
- name: Run SDK tests
151+
id: sdk-test
152+
working-directory: packages/b2c-tooling-sdk
153+
run: pnpm run pretest && pnpm run test:ci && pnpm run lint
154+
- name: Run MCP tests
155+
id: mcp-test
156+
if: always() && steps.sdk-test.conclusion != 'cancelled'
157+
working-directory: packages/b2c-dx-mcp
158+
run: pnpm run pretest && pnpm run test:ci && pnpm run lint
159+
- name: Run CLI tests
160+
id: cli-test
161+
if: always() && steps.mcp-test.conclusion != 'cancelled'
162+
working-directory: packages/b2c-cli
163+
# Use the Windows-specific script (test:ci:win) to bypass c8's
164+
# coverage threshold check. V8 coverage on Windows double-counts
165+
# some TS source files (distinct URL casing emitted by the tsx
166+
# loader), which drops the reported function-coverage number below
167+
# the Linux-calibrated 70% threshold even though the tests pass.
168+
# Coverage is still generated and uploaded for inspection.
169+
run: pnpm run pretest && pnpm run test:ci:win && pnpm run lint
170+
- name: Run VS Extension checks
171+
if: always() && steps.cli-test.conclusion != 'cancelled'
172+
working-directory: packages/b2c-vs-extension
173+
run: pnpm run typecheck:agent && pnpm run lint
174+
- name: Print Windows test failures
175+
# Mocha's JSON reporter swallows stdout, so Windows-only failures are
176+
# invisible in the step log. Parse the per-package test-results.json
177+
# and echo failing test titles + error messages to the job log for triage.
178+
if: always() && steps.sdk-test.conclusion != 'cancelled'
179+
shell: bash
180+
run: |
181+
node -e "
182+
const fs = require('node:fs');
183+
const path = require('node:path');
184+
const reports = [
185+
'packages/b2c-tooling-sdk/test-results.json',
186+
'packages/b2c-dx-mcp/test-results.json',
187+
'packages/b2c-cli/test-results.json',
188+
];
189+
let totalFailures = 0;
190+
for (const report of reports) {
191+
if (!fs.existsSync(report)) continue;
192+
const data = JSON.parse(fs.readFileSync(report, 'utf8'));
193+
const failures = data.failures || [];
194+
if (failures.length === 0) continue;
195+
totalFailures += failures.length;
196+
console.log('\n=== ' + report + ' (' + failures.length + ' failures) ===');
197+
for (const f of failures) {
198+
console.log('\n✖ ' + f.fullTitle);
199+
if (f.err && f.err.message) console.log(' message: ' + f.err.message.split('\n')[0]);
200+
if (f.err && f.err.stack) console.log(' stack: ' + f.err.stack.split('\n').slice(0, 5).join('\n '));
201+
}
202+
}
203+
if (totalFailures === 0) console.log('No Windows test failures reported.');
204+
else console.log('\nTotal Windows failures: ' + totalFailures);
205+
"
206+
- name: Test Report
207+
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0
208+
if: always() && steps.sdk-test.conclusion != 'cancelled'
209+
with:
210+
name: Test Results (Windows Node ${{ matrix.node-version }})
211+
path: 'packages/*/test-results.json'
212+
reporter: mocha-json
213+
- name: Upload test results (for Windows triage)
214+
if: always() && steps.sdk-test.conclusion != 'cancelled'
215+
uses: actions/upload-artifact@v7
216+
with:
217+
name: test-results-windows-node-${{ matrix.node-version }}
218+
path: 'packages/*/test-results.json'
219+
retention-days: 30
220+
- name: Upload coverage reports
221+
if: always() && steps.sdk-test.conclusion != 'cancelled'
222+
uses: actions/upload-artifact@v7
223+
with:
224+
name: coverage-reports-windows-node-${{ matrix.node-version }}
225+
path: |
226+
packages/b2c-tooling-sdk/coverage/
227+
packages/b2c-cli/coverage/
228+
retention-days: 30

.github/workflows/e2e-shell-tests.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,67 @@ jobs:
8787
echo "Running E2E shell tests with realm: ${TEST_REALM}"
8888
cd packages/b2c-cli
8989
./test/functional/e2e_cli_test.sh
90+
91+
e2e-shell-tests-windows:
92+
runs-on: windows-latest
93+
# Advisory rollout: surface Windows shell-test failures without blocking
94+
# the Linux shell-test job while Windows-specific issues are triaged.
95+
continue-on-error: true
96+
environment: e2e-dev
97+
timeout-minutes: 45
98+
defaults:
99+
run:
100+
shell: bash
101+
steps:
102+
- uses: actions/checkout@v6
103+
104+
- name: Check for required secrets and vars
105+
id: check-secrets
106+
env:
107+
SFCC_CLIENT_ID: ${{ vars.SFCC_CLIENT_ID }}
108+
SFCC_CLIENT_SECRET: ${{ secrets.SFCC_CLIENT_SECRET }}
109+
TEST_REALM: ${{ vars.TEST_REALM }}
110+
SFCC_ACCOUNT_MANAGER_HOST: ${{ vars.SFCC_ACCOUNT_MANAGER_HOST }}
111+
SFCC_SANDBOX_API_HOST: ${{ vars.SFCC_SANDBOX_API_HOST }}
112+
SFCC_SHORTCODE: ${{ vars.SFCC_SHORTCODE }}
113+
run: |
114+
missing=""
115+
[ -z "$SFCC_CLIENT_ID" ] && missing="$missing SFCC_CLIENT_ID"
116+
[ -z "$SFCC_CLIENT_SECRET" ] && missing="$missing SFCC_CLIENT_SECRET"
117+
[ -z "$TEST_REALM" ] && missing="$missing TEST_REALM"
118+
[ -z "$SFCC_ACCOUNT_MANAGER_HOST" ] && missing="$missing SFCC_ACCOUNT_MANAGER_HOST"
119+
[ -z "$SFCC_SANDBOX_API_HOST" ] && missing="$missing SFCC_SANDBOX_API_HOST"
120+
[ -z "$SFCC_SHORTCODE" ] && missing="$missing SFCC_SHORTCODE"
121+
122+
if [ -z "$missing" ]; then
123+
echo "has-secrets=true" >> "$GITHUB_OUTPUT"
124+
else
125+
echo "has-secrets=false" >> "$GITHUB_OUTPUT"
126+
echo "Windows E2E shell tests skipped - missing required variables:$missing" >> "$GITHUB_STEP_SUMMARY"
127+
fi
128+
129+
- name: Setup Windows machine
130+
if: steps.check-secrets.outputs.has-secrets == 'true'
131+
uses: ./.github/actions/setup-windows
132+
with:
133+
node-version: '24'
134+
135+
- name: Build packages
136+
if: steps.check-secrets.outputs.has-secrets == 'true'
137+
run: pnpm -r run build
138+
139+
- name: Run E2E Shell Tests
140+
if: steps.check-secrets.outputs.has-secrets == 'true'
141+
env:
142+
SFCC_CLIENT_ID: ${{ vars.SFCC_CLIENT_ID }}
143+
SFCC_CLIENT_SECRET: ${{ secrets.SFCC_CLIENT_SECRET }}
144+
SFCC_ACCOUNT_MANAGER_HOST: ${{ vars.SFCC_ACCOUNT_MANAGER_HOST }}
145+
SFCC_SANDBOX_API_HOST: ${{ vars.SFCC_SANDBOX_API_HOST }}
146+
SFCC_SHORTCODE: ${{ vars.SFCC_SHORTCODE }}
147+
TEST_REALM: ${{ vars.TEST_REALM }}
148+
SFCC_EXTRA_HEADERS: ${{ secrets.SFCC_EXTRA_HEADERS }}
149+
CURL_EXTRA_HEADERS: ${{ secrets.CURL_EXTRA_HEADERS }}
150+
run: |
151+
echo "Running Windows E2E shell tests with realm: ${TEST_REALM}"
152+
cd packages/b2c-cli
153+
./test/functional/e2e_cli_test.sh

.github/workflows/e2e-tests.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,83 @@ jobs:
165165
github.rest.issues.create(issue);
166166
}
167167
168+
e2e-tests-windows:
169+
name: e2e-tests (windows)
170+
# Advisory rollout: Windows E2E coverage is new; surface failures without
171+
# blocking Linux E2E while remaining Windows-specific test issues are
172+
# addressed in follow-up PRs. Remove once Windows E2E is green.
173+
continue-on-error: true
174+
strategy:
175+
fail-fast: false
176+
matrix:
177+
node-version: [22.x]
178+
runs-on: windows-latest
179+
environment: e2e-dev
180+
timeout-minutes: 60
181+
defaults:
182+
run:
183+
shell: bash
184+
steps:
185+
- uses: actions/checkout@v6
186+
- name: Check for required secrets and vars
187+
id: check-secrets
188+
env:
189+
SFCC_CLIENT_ID: ${{ vars.SFCC_CLIENT_ID }}
190+
SFCC_CLIENT_SECRET: ${{ secrets.SFCC_CLIENT_SECRET }}
191+
TEST_REALM: ${{ vars.TEST_REALM }}
192+
SFCC_ACCOUNT_MANAGER_HOST: ${{ vars.SFCC_ACCOUNT_MANAGER_HOST }}
193+
SFCC_SANDBOX_API_HOST: ${{ vars.SFCC_SANDBOX_API_HOST }}
194+
SFCC_SHORTCODE: ${{ vars.SFCC_SHORTCODE }}
195+
run: |
196+
if [ -n "$SFCC_CLIENT_ID" ] && [ -n "$SFCC_CLIENT_SECRET" ] && [ -n "$TEST_REALM" ] && [ -n "$SFCC_ACCOUNT_MANAGER_HOST" ] && [ -n "$SFCC_SANDBOX_API_HOST" ] && [ -n "$SFCC_SHORTCODE" ]; then
197+
echo "has-secrets=true" >> "$GITHUB_OUTPUT"
198+
else
199+
echo "has-secrets=false" >> "$GITHUB_OUTPUT"
200+
echo "Windows E2E tests skipped - missing required variables" >> "$GITHUB_STEP_SUMMARY"
201+
fi
202+
- name: Setup Windows machine
203+
if: steps.check-secrets.outputs.has-secrets == 'true'
204+
uses: ./.github/actions/setup-windows
205+
with:
206+
node-version: ${{ matrix.node-version }}
207+
- name: Build package
208+
if: steps.check-secrets.outputs.has-secrets == 'true'
209+
run: pnpm -r run build
210+
- name: Run E2E Tests
211+
if: steps.check-secrets.outputs.has-secrets == 'true'
212+
id: e2e-test
213+
working-directory: packages/b2c-cli
214+
env:
215+
SFCC_CLIENT_ID: ${{ inputs.sfcc_client_id || vars.SFCC_CLIENT_ID }}
216+
SFCC_CLIENT_SECRET: ${{ inputs.sfcc_client_secret || secrets.SFCC_CLIENT_SECRET }}
217+
SFCC_ACCOUNT_MANAGER_HOST: ${{ inputs.sfcc_account_manager_host || vars.SFCC_ACCOUNT_MANAGER_HOST }}
218+
SFCC_SANDBOX_API_HOST: ${{ inputs.sfcc_sandbox_api_host || vars.SFCC_SANDBOX_API_HOST }}
219+
TEST_REALM: ${{ inputs.test_realm || vars.TEST_REALM }}
220+
SFCC_SHORTCODE: ${{ vars.SFCC_SHORTCODE }}
221+
SFCC_EXTRA_HEADERS: ${{ secrets.SFCC_EXTRA_HEADERS }}
222+
SFCC_MRT_CLOUD_ORIGIN: ${{ vars.SFCC_MRT_CLOUD_ORIGIN }}
223+
SFCC_MRT_API_KEY: ${{ secrets.SFCC_MRT_API_KEY }}
224+
NODE_ENV: test
225+
SFCC_LOG_LEVEL: silent
226+
run: |
227+
echo "Running Windows E2E tests with realm: $TEST_REALM"
228+
echo "Node version: $(node --version)"
229+
pnpm run test:e2e:ci && pnpm run lint
230+
- name: E2E Test Report
231+
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0
232+
if: always() && steps.e2e-test.conclusion != 'cancelled' && steps.check-secrets.outputs.has-secrets == 'true'
233+
with:
234+
name: E2E Test Results (Windows Node ${{ matrix.node-version }})
235+
path: 'packages/b2c-cli/test-results.json'
236+
reporter: mocha-json
237+
- name: Upload E2E Test Results
238+
if: always() && steps.e2e-test.conclusion != 'cancelled' && steps.check-secrets.outputs.has-secrets == 'true'
239+
uses: actions/upload-artifact@v7
240+
with:
241+
name: e2e-test-results-windows-node-${{ matrix.node-version }}-${{ github.run_number }}
242+
path: packages/b2c-cli/test-results.json
243+
retention-days: 30
244+
168245
mcp-e2e-tests:
169246
name: MCP E2E
170247
runs-on: ubuntu-latest

packages/b2c-cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@
332332
"pretest": "tsc --noEmit -p test",
333333
"test": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" \"test/**/*.test.ts\"",
334334
"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\"",
335+
"test:ci:win": "c8 --check-coverage=false env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" --reporter json --reporter-option output=test-results.json \"test/**/*.test.ts\"",
335336
"test:unit": "env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" \"test/**/*.test.ts\"",
336337
"test:agent": "env OCLIF_TEST_ROOT=. mocha --forbid-only --reporter min --exclude \"test/functional/e2e/**\" \"test/**/*.test.ts\"",
337338
"test:e2e": "env TEST_USE_SHARED_SANDBOX=true OCLIF_TEST_ROOT=. mocha --forbid-only --require test/functional/e2e/hooks.ts --node-option import=tsx --timeout 30000 --retries 2 --reporter spec \"test/functional/e2e/**/*.test.ts\"",

packages/b2c-cli/test/commands/mrt/save-credentials.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,14 @@ describe('mrt save-credentials', () => {
5555
expect(content).to.deep.equal({username: 'user@example.com', api_key: 'abc123'});
5656
});
5757

58-
it('sets file permissions to 0o600', async () => {
58+
it('sets file permissions to 0o600', async function () {
59+
// NTFS does not honor POSIX permission bits: fs.chmod(0o600) silently
60+
// leaves the mode as 0o666, so the assertion can only be validated on
61+
// POSIX platforms. Credential files on Windows are protected via ACLs,
62+
// not mode bits.
63+
if (process.platform === 'win32') {
64+
this.skip();
65+
}
5966
const credFile = path.join(tempDir, '.mobify');
6067
const command = createCommand({
6168
user: 'user@example.com',

0 commit comments

Comments
 (0)