From ce5047c5ed5ad693d7d88cb3069ce3b351bdfaa5 Mon Sep 17 00:00:00 2001 From: Krzysztof Platis Date: Thu, 3 Oct 2024 13:23:34 +0200 Subject: [PATCH 01/26] Revert of Revert "test(ssr-tests): print SSR Logs for errors in SSR tests, ALSO when a test times out (#19302) (#19326) Original PR: https://github.com/SAP/spartacus/pull/19302 Revert of original PR: https://github.com/SAP/spartacus/pull/19319 becasue of errors on the `develop`'s pipeline https://github.com/SAP/spartacus/actions/runs/11142085195/job/30964328791 Now bringing back the contents of the original PR, but with ensuring SSR Tests don't encounter the following error again on the pipeline: ``` node:internal/event_target:1099 process.nextTick(() => { throw err; }); ^ Error: Unknown worker message type message This is caused by either a bug in Node.js or incorrect usage of Node.js internals. Please open an issue with this stack trace at https://github.com/nodejs/node/issues at Function.fail (node:internal/assert:20:9) at Worker.[kOnMessage] (node:internal/worker:354:12) at MessagePort. (node:internal/worker:232:57) at _ZoneDelegate.Object.._ZoneDelegate.invokeTask (//node_modules/zone.js/bundles/zone.umd.js:445:35) (... rest of stacktrace ...) at MessagePort. (node:internal/per_context/messageport:23:28) { code: 'ERR_INTERNAL_ASSERTION' ``` **How fixed the problem:** Removed `jest-preset-angular` from the Jest setup of SSR Tests. Used simply `ts-jest` instead. **Why it helped** I only deduce that the new custom `testEvironment` introduced in the Original PR that extends from `NodeEnvironment` from `jest-environment-node` **conflicted** with the `jest-preset-angular` which under the hood assumes simulating Browser environment (not Node). **Why we really don't need or want `jest-preset-angular` in SSR Tests project** - it enforces `jsdom` testEnvironment (see https://github.com/thymikee/jest-preset-angular/blob/ff0895f4b8dfa561c4f4bc9779016d9d4d7213c7/src/presets/index.ts#L6) - it enforces importing zone.js (see https://github.com/thymikee/jest-preset-angular/blob/4c2b674634b64eed267e7c0ddb715f52e7839cba/setup-jest.js#L1-L2) - it enforces initializing Jest test environment with Angular's `BrowserDynamicTestingModule` (see https://github.com/thymikee/jest-preset-angular/blob/4c2b674634b64eed267e7c0ddb715f52e7839cba/setup-jest.js#L18) - in SSR Tests we want to simply run NodeJS processes, but not simulate Angular components in a browser Note: Finally I could reproduce it on local too, while using JS `testEnvironment` file instead of TS. Moreover, I could reproduce it only on the first run of tests on local. On the second run, everything would pass. Only when I renamed the file with `testEvironment`, on the first run of tests, the error could be reproduced again. related to https://jira.tools.sap/browse/CXSPA-8564 --- package-lock.json | 2 + package.json | 4 +- projects/schematics/src/dependencies.json | 4 +- projects/ssr-tests/jest.config.js | 12 +- projects/ssr-tests/package.json | 4 +- projects/ssr-tests/setup-jest.ts | 9 -- .../environments/custom-test-environment.ts | 61 +++++++++ projects/ssr-tests/src/ssr-testing.spec.ts | 122 ++++++++---------- projects/ssr-tests/src/utils/log.utils.ts | 40 ------ 9 files changed, 129 insertions(+), 129 deletions(-) delete mode 100644 projects/ssr-tests/setup-jest.ts create mode 100644 projects/ssr-tests/src/environments/custom-test-environment.ts diff --git a/package-lock.json b/package-lock.json index 52acb6055dd..d9ea25a3f8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,6 +98,8 @@ "jasmine-core": "~5.1.2", "jasmine-marbles": "^0.9.2", "jest": "^29.0.0", + "jest-circus": "^29.0.0", + "jest-environment-node": "^29.0.0", "jest-preset-angular": "13.1.6", "jsonc-parser": "~3.2.1", "karma": "~6.4.1", diff --git a/package.json b/package.json index 5c04662578f..e07d1a77683 100644 --- a/package.json +++ b/package.json @@ -209,6 +209,8 @@ "jasmine-core": "~5.1.2", "jasmine-marbles": "^0.9.2", "jest": "^29.0.0", + "jest-circus": "^29.0.0", + "jest-environment-node": "^29.0.0", "jest-preset-angular": "13.1.6", "jsonc-parser": "~3.2.1", "karma": "~6.4.1", @@ -238,4 +240,4 @@ "webpack": "~5.94.0", "webpack-cli": "^5.0.0" } -} \ No newline at end of file +} diff --git a/projects/schematics/src/dependencies.json b/projects/schematics/src/dependencies.json index 868cdd0b9b1..ff4286ae720 100644 --- a/projects/schematics/src/dependencies.json +++ b/projects/schematics/src/dependencies.json @@ -25,7 +25,9 @@ }, "ssr-tests": { "@spartacus/core": "2211.29.0-2", - "http-proxy": "^1.18.1" + "http-proxy": "^1.18.1", + "jest-circus": "^29.0.0", + "jest-environment-node": "^29.0.0" }, "storefrontapp-e2e-cypress": {}, "@spartacus/storefront": { diff --git a/projects/ssr-tests/jest.config.js b/projects/ssr-tests/jest.config.js index 5753f3804ac..f0aea45398b 100644 --- a/projects/ssr-tests/jest.config.js +++ b/projects/ssr-tests/jest.config.js @@ -1,23 +1,14 @@ const { pathsToModuleNameMapper } = require('ts-jest'); const { compilerOptions } = require('./tsconfig.json'); -const { defaultTransformerOptions } = require('jest-preset-angular/presets'); /** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ module.exports = { - preset: 'jest-preset-angular', moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths || {}, { prefix: '/', }), - setupFilesAfterEnv: ['/setup-jest.ts'], testMatch: ['**/+(*.)+(spec).+(ts)'], transform: { - '^.+\\.(ts|js|mjs|html|svg)$': [ - 'jest-preset-angular', - { - ...defaultTransformerOptions, - tsconfig: '/tsconfig.json', - }, - ], + '^.+\\.(ts|js|mjs)$': ['ts-jest'], }, collectCoverage: false, @@ -31,4 +22,5 @@ module.exports = { lines: 90, }, }, + testEnvironment: './src/environments/custom-test-environment.ts', }; diff --git a/projects/ssr-tests/package.json b/projects/ssr-tests/package.json index 76829d320bd..d44b5aa712c 100644 --- a/projects/ssr-tests/package.json +++ b/projects/ssr-tests/package.json @@ -18,6 +18,8 @@ }, "peerDependencies": { "@spartacus/core": "2211.29.0-2", - "http-proxy": "^1.18.1" + "http-proxy": "^1.18.1", + "jest-circus": "^29.0.0", + "jest-environment-node": "^29.0.0" } } diff --git a/projects/ssr-tests/setup-jest.ts b/projects/ssr-tests/setup-jest.ts deleted file mode 100644 index 2b340f94b88..00000000000 --- a/projects/ssr-tests/setup-jest.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 SAP Spartacus team - * SPDX-FileCopyrightText: 2024 SAP Spartacus team - * - * SPDX-License-Identifier: Apache-2.0 - */ - -import 'jest-preset-angular/setup-jest'; -import 'zone.js'; diff --git a/projects/ssr-tests/src/environments/custom-test-environment.ts b/projects/ssr-tests/src/environments/custom-test-environment.ts new file mode 100644 index 00000000000..22c6a7adf69 --- /dev/null +++ b/projects/ssr-tests/src/environments/custom-test-environment.ts @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2024 SAP Spartacus team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Event, State } from 'jest-circus'; +import { TestEnvironment as NodeEnvironment } from 'jest-environment-node'; +import { getRawLogsPretty } from '../utils/log.utils'; + +/** + * This is a custom Jest environment that adds the SSR logs to the test errors. + * The logs are added to the error `cause` property for failed tests, + * so those logs will be printed by Jest along with the test error. + */ +export default class CustomTestEnvironment extends NodeEnvironment { + // For comparison, see the original source code of Jest-Circus: + // https://github.com/jestjs/jest/blob/bd1c6db7c15c23788ca3e09c919138e48dd3b28a/packages/jest-circus/src/formatNodeAssertErrors.ts#L45 + async handleTestEvent(event: Event, _state: State) { + if (event.name === 'test_done') { + event.test.errors = event.test.errors.map((errors) => { + if (Array.isArray(errors)) { + const [originalError, asyncError] = errors; + + const ssrLogs = getSsrLogs(); + // Error's `cause` property is the only property printed by Jest + // besides `message` that we can utilize for attaching logs. + // No other custom properties are printed by Jest. + // See their source code of their function `formatExecError`: + // https://github.com/jestjs/jest/blob/bd1c6db7c15c23788ca3e09c919138e48dd3b28a/packages/jest-message-util/src/index.ts#L436 + addCauseToError({ + error: originalError, // e.g. error when an expect() fails + cause: ssrLogs, + }); + addCauseToError({ + error: asyncError, // e.g. error when a test timeout happens + cause: ssrLogs, + }); + } + return errors; + }); + } + } +} + +function addCauseToError({ error, cause }: { error: any; cause: any }) { + // in some cases, the error might be a string, not an object + if ( + typeof error === 'object' && + error !== null && + 'message' in error && + 'stack' in error + ) { + error.cause = cause; + } +} + +function getSsrLogs() { + const readableLogs = getRawLogsPretty().join('\n'); + return `(more context below)\n--- SSR LOGS (with JSONs pretty-printed) ---\n${readableLogs}\n--- SSR LOGS END ---`; +} diff --git a/projects/ssr-tests/src/ssr-testing.spec.ts b/projects/ssr-tests/src/ssr-testing.spec.ts index 335d1514185..d503bb2091a 100644 --- a/projects/ssr-tests/src/ssr-testing.spec.ts +++ b/projects/ssr-tests/src/ssr-testing.spec.ts @@ -23,73 +23,61 @@ describe('SSR E2E', () => { await SsrUtils.startSsrServer(); }); - it( - 'should receive success response with request', - LogUtils.attachLogsToErrors(async () => { - backendProxy = await ProxyUtils.startBackendProxyServer({ - target: BACKEND_BASE_URL, - }); - const response: any = await HttpUtils.sendRequestToSsrServer({ - path: REQUEST_PATH, - }); - expect(response.statusCode).toEqual(200); + it('should receive success response with request', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + }); + const response: any = await HttpUtils.sendRequestToSsrServer({ + path: REQUEST_PATH, + }); + expect(response.statusCode).toEqual(200); - const logsMessages = LogUtils.getLogsMessages(); - expect(logsMessages).toContain(`Rendering started (${REQUEST_PATH})`); - expect(logsMessages).toContain( - `Request is waiting for the SSR rendering to complete (${REQUEST_PATH})` - ); - }) - ); + const logsMessages = LogUtils.getLogsMessages(); + expect(logsMessages).toContain(`Rendering started (${REQUEST_PATH})`); + expect(logsMessages).toContain( + `Request is waiting for the SSR rendering to complete (${REQUEST_PATH})` + ); + }); - it( - 'should receive response with 404 when page does not exist', - LogUtils.attachLogsToErrors(async () => { - backendProxy = await ProxyUtils.startBackendProxyServer({ - target: BACKEND_BASE_URL, - }); - const response = await HttpUtils.sendRequestToSsrServer({ - path: REQUEST_PATH + 'not-existing-page', - }); - expect(response.statusCode).toEqual(404); - }) - ); + it('should receive response with 404 when page does not exist', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + }); + const response = await HttpUtils.sendRequestToSsrServer({ + path: REQUEST_PATH + 'not-existing-page', + }); + expect(response.statusCode).toEqual(404); + }); - it( - 'should receive response with status 404 if HTTP error occurred when calling cms/pages API URL', - LogUtils.attachLogsToErrors(async () => { - backendProxy = await ProxyUtils.startBackendProxyServer({ - target: BACKEND_BASE_URL, - callback: (proxyRes, req) => { - if (req.url?.includes('cms/pages')) { - proxyRes.statusCode = 404; - } - }, - }); - const response = await HttpUtils.sendRequestToSsrServer({ - path: REQUEST_PATH, - }); - expect(response.statusCode).toEqual(404); - }) - ); + it('should receive response with status 404 if HTTP error occurred when calling cms/pages API URL', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + callback: (proxyRes, req) => { + if (req.url?.includes('cms/pages')) { + proxyRes.statusCode = 404; + } + }, + }); + const response = await HttpUtils.sendRequestToSsrServer({ + path: REQUEST_PATH, + }); + expect(response.statusCode).toEqual(404); + }); - it.skip( - 'should receive response with status 500 if HTTP error occurred when calling other than cms/pages API URL', - LogUtils.attachLogsToErrors(async () => { - backendProxy = await ProxyUtils.startBackendProxyServer({ - target: BACKEND_BASE_URL, - callback: (proxyRes, req) => { - if (req.url?.includes('cms/components')) { - proxyRes.statusCode = 404; - } - }, - }); - const response = await HttpUtils.sendRequestToSsrServer({ - path: REQUEST_PATH, - }); - expect(response.statusCode).toEqual(500); - }) - ); + it.skip('should receive response with status 500 if HTTP error occurred when calling other than cms/pages API URL', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + callback: (proxyRes, req) => { + if (req.url?.includes('cms/components')) { + proxyRes.statusCode = 404; + } + }, + }); + const response = await HttpUtils.sendRequestToSsrServer({ + path: REQUEST_PATH, + }); + expect(response.statusCode).toEqual(500); + }); }); describe('With caching enabled', () => { @@ -99,7 +87,7 @@ describe('SSR E2E', () => { it( 'should take the response from cache for the next request if previous render succeeded', - LogUtils.attachLogsToErrors(async () => { + async () => { backendProxy = await ProxyUtils.startBackendProxyServer({ target: BACKEND_BASE_URL, }); @@ -123,13 +111,13 @@ describe('SSR E2E', () => { expect(logsMessages2).toContain( `Render from cache (${REQUEST_PATH})` ); - }), + }, 2 * SsrUtils.DEFAULT_SSR_TIMEOUT // increase timeout for this test as it calls the SSR server twice ); it( 'should render for the next request if previous render failed', - LogUtils.attachLogsToErrors(async () => { + async () => { backendProxy = await ProxyUtils.startBackendProxyServer({ target: BACKEND_BASE_URL, callback: (proxyRes, req) => { @@ -152,7 +140,7 @@ describe('SSR E2E', () => { expect(logsMessages).not.toContain( `Render from cache (${REQUEST_PATH})` ); - }), + }, 2 * SsrUtils.DEFAULT_SSR_TIMEOUT // increase timeout for this test as it calls the SSR server twice ); }); diff --git a/projects/ssr-tests/src/utils/log.utils.ts b/projects/ssr-tests/src/utils/log.utils.ts index 9ddc2c3a6cb..654acff728d 100644 --- a/projects/ssr-tests/src/utils/log.utils.ts +++ b/projects/ssr-tests/src/utils/log.utils.ts @@ -126,43 +126,3 @@ export async function waitUntilLogContainsText( ); }); } - -/** - * A higher-order function that wraps a test callback and includes SSR logs - * in any error thrown during the test execution. The logs are put into the `cause` - * property of the Error. - * - * @param testFn - The original test function to be wrapped. - * @returns A new function that can be passed to Jest's `it()` or `test()`. - * - * @example - * it('should perform SSR correctly', attachLogsToErrors(async () => { - * // Your test code here - * })); - */ -export function attachLogsToErrors( - testFn: () => Promise | void -): () => Promise { - return async () => { - try { - await testFn(); - } catch (error: unknown) { - const readableLogs = getRawLogsPretty().join('\n'); - const ssrLogs = `(more context below)\n--- SSR LOGS (with JSONs pretty-printed) ---\n${readableLogs}\n--- SSR LOGS END ---`; - - if (error instanceof Error) { - // Error's `cause` property is the only property printed by Jest - // besides `message` that we can utilize for attaching logs. - // No other custom properties are printed by Jest. - // See their source code of their function `formatExecError`: - // https://github.com/jestjs/jest/blob/bd1c6db7c15c23788ca3e09c919138e48dd3b28a/packages/jest-message-util/src/index.ts#L436 - - error.cause = ssrLogs; - } else { - throw new Error(error as string, { cause: ssrLogs }); - } - - throw error; - } - }; -} From 23b72bb0749577b194d3a53c18fedcb334c8b0f5 Mon Sep 17 00:00:00 2001 From: Krzysztof Platis Date: Thu, 3 Oct 2024 14:00:26 +0200 Subject: [PATCH 02/26] test(SSR e2e): case when a call to Backend timeouts (#19307) Added a test case: when a call to Backend timeouts btw. changes: - replaced `callback` with `responseInterceptor` in ProxyUtils fixes https://jira.tools.sap/browse/CXSPA-8386 --- projects/ssr-tests/src/ssr-testing.spec.ts | 153 +++++++++++++------- projects/ssr-tests/src/utils/proxy.utils.ts | 75 +++++++--- 2 files changed, 155 insertions(+), 73 deletions(-) diff --git a/projects/ssr-tests/src/ssr-testing.spec.ts b/projects/ssr-tests/src/ssr-testing.spec.ts index d503bb2091a..b891d6bc25c 100644 --- a/projects/ssr-tests/src/ssr-testing.spec.ts +++ b/projects/ssr-tests/src/ssr-testing.spec.ts @@ -19,64 +19,115 @@ describe('SSR E2E', () => { describe('With SSR error handling', () => { describe('Common behavior', () => { - beforeEach(async () => { - await SsrUtils.startSsrServer(); - }); - - it('should receive success response with request', async () => { - backendProxy = await ProxyUtils.startBackendProxyServer({ - target: BACKEND_BASE_URL, + describe('With default SSR request timeout', () => { + beforeEach(async () => { + await SsrUtils.startSsrServer(); }); - const response: any = await HttpUtils.sendRequestToSsrServer({ - path: REQUEST_PATH, - }); - expect(response.statusCode).toEqual(200); - const logsMessages = LogUtils.getLogsMessages(); - expect(logsMessages).toContain(`Rendering started (${REQUEST_PATH})`); - expect(logsMessages).toContain( - `Request is waiting for the SSR rendering to complete (${REQUEST_PATH})` - ); - }); + it('should receive success response with request', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + }); + const response: any = await HttpUtils.sendRequestToSsrServer({ + path: REQUEST_PATH, + }); + expect(response.statusCode).toEqual(200); - it('should receive response with 404 when page does not exist', async () => { - backendProxy = await ProxyUtils.startBackendProxyServer({ - target: BACKEND_BASE_URL, + const logsMessages = LogUtils.getLogsMessages(); + expect(logsMessages).toContain(`Rendering started (${REQUEST_PATH})`); + expect(logsMessages).toContain( + `Request is waiting for the SSR rendering to complete (${REQUEST_PATH})` + ); }); - const response = await HttpUtils.sendRequestToSsrServer({ - path: REQUEST_PATH + 'not-existing-page', + + it('should receive response with 404 when page does not exist', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + }); + const response = await HttpUtils.sendRequestToSsrServer({ + path: REQUEST_PATH + 'not-existing-page', + }); + expect(response.statusCode).toEqual(404); }); - expect(response.statusCode).toEqual(404); - }); - it('should receive response with status 404 if HTTP error occurred when calling cms/pages API URL', async () => { - backendProxy = await ProxyUtils.startBackendProxyServer({ - target: BACKEND_BASE_URL, - callback: (proxyRes, req) => { - if (req.url?.includes('cms/pages')) { - proxyRes.statusCode = 404; - } - }, + it('should receive response with status 404 if HTTP error occurred when calling cms/pages API URL', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + responseInterceptor: ({ res, req, body }) => { + if (req.url?.includes('cms/pages')) { + res.statusCode = 404; + } + res.end(body); + }, + }); + const response = await HttpUtils.sendRequestToSsrServer({ + path: REQUEST_PATH, + }); + expect(response.statusCode).toEqual(404); }); - const response = await HttpUtils.sendRequestToSsrServer({ - path: REQUEST_PATH, + + it.skip('should receive response with status 500 if HTTP error occurred when calling other than cms/pages API URL', async () => { + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + responseInterceptor: ({ res, req, body }) => { + if (req.url?.includes('cms/pages')) { + res.statusCode = 404; + } + res.end(body); + }, + }); + const response = await HttpUtils.sendRequestToSsrServer({ + path: REQUEST_PATH, + }); + expect(response.statusCode).toEqual(500); }); - expect(response.statusCode).toEqual(404); }); - it.skip('should receive response with status 500 if HTTP error occurred when calling other than cms/pages API URL', async () => { - backendProxy = await ProxyUtils.startBackendProxyServer({ - target: BACKEND_BASE_URL, - callback: (proxyRes, req) => { - if (req.url?.includes('cms/components')) { - proxyRes.statusCode = 404; - } - }, + describe('With long SSR request timeout', () => { + beforeEach(async () => { + // The SSR in the following tests might take longer than the default SSR request timeout. + // So to avoid getting a "CSR fallback" response, we're increasing the SSR request timeout to 20 seconds. + await SsrUtils.startSsrServer({ timeout: 20_000 }); }); - const response = await HttpUtils.sendRequestToSsrServer({ - path: REQUEST_PATH, + + it('should receive response with status 500 if HTTP call to backend timeouted', async () => { + /** + * We're configuring a custom time limit for the Backend API calls in Spartacus. + * This is to speed up the test. Otherwise, we would need to delay the call to /languages + * for 20 seconds (which is the default Backend Timeout in Spartacus) to get an error. + */ + const BACKEND_TIMEOUT_TIME_LIMIT = 4000; + const API_DELAY = BACKEND_TIMEOUT_TIME_LIMIT + 1; + + backendProxy = await ProxyUtils.startBackendProxyServer({ + target: BACKEND_BASE_URL, + responseInterceptor: ({ res, req, body }) => { + // Delay the response from Backend API, but only for for the /languages endpoint. + // We want Spartacus to consider this Backend API request as Timeouted (therefore failed). + if (req.url?.includes('languages')) { + setTimeout(() => res.end(body), API_DELAY); + } else { + res.end(body); + } + }, + }); + + const response = await HttpUtils.sendRequestToSsrServer({ + path: REQUEST_PATH, + testConfig: { + backend: { timeout: { server: BACKEND_TIMEOUT_TIME_LIMIT } }, + }, + }); + + expect(response.statusCode).toEqual(500); + + const logsMessages = LogUtils.getLogsMessages(); + expect(logsMessages.join('\n')).toMatch( + new RegExp( + `Error: Request to URL '[^']*\/languages' exceeded expected time of ${BACKEND_TIMEOUT_TIME_LIMIT}ms and was aborted` + ) + ); }); - expect(response.statusCode).toEqual(500); }); }); @@ -120,10 +171,11 @@ describe('SSR E2E', () => { async () => { backendProxy = await ProxyUtils.startBackendProxyServer({ target: BACKEND_BASE_URL, - callback: (proxyRes, req) => { + responseInterceptor: ({ res, req, body }) => { if (req.url?.includes('cms/pages')) { - proxyRes.statusCode = 404; + res.statusCode = 404; } + res.end(body); }, }); let response: HttpUtils.SsrResponse; @@ -193,10 +245,11 @@ describe('SSR E2E', () => { it('should receive response with status 500 even if HTTP error occurred when calling backend API URL', async () => { backendProxy = await ProxyUtils.startBackendProxyServer({ target: BACKEND_BASE_URL, - callback: (proxyRes, req) => { + responseInterceptor: ({ res, req, body }) => { if (req.url?.includes('cms/components')) { - proxyRes.statusCode = 400; + res.statusCode = 400; } + res.end(body); }, }); const response = await HttpUtils.sendRequestToSsrServer({ diff --git a/projects/ssr-tests/src/utils/proxy.utils.ts b/projects/ssr-tests/src/utils/proxy.utils.ts index 931ef6ccfd0..29d8be7d82a 100644 --- a/projects/ssr-tests/src/utils/proxy.utils.ts +++ b/projects/ssr-tests/src/utils/proxy.utils.ts @@ -16,14 +16,31 @@ interface ProxyOptions { * The url to reroute requests to. */ target: string; - /** - * Number of seconds to delay requests before sending. - */ - delay?: number; - /** - * Callback to be executed if the request to the target got a response. - */ - callback?: httpProxy.ProxyResCallback; + + responseInterceptor?: (params: { + /** + * The body of the response from the upstream server. + * + * Note: we're exposing separately from the `proxyRes` object for convenience + * (because extracting it manually from `proxyRes` is very cumbersome) + */ + body: string; + + /** + * The response from the upstream server. + */ + proxyRes: http.IncomingMessage; + + /** + * The request that was sent to the upstream server. + */ + req: http.IncomingMessage; + + /** + * The response that will be sent to the client. + */ + res: http.ServerResponse; + }) => void; } /** @@ -34,24 +51,36 @@ export async function startBackendProxyServer( ): Promise { const proxy = httpProxy.createProxyServer({ secure: false, + selfHandleResponse: !!options.responseInterceptor, }); + if (options.responseInterceptor) { + proxy.on('proxyRes', (proxyRes, req, res) => { + // We have to buffer the response body before passing it to the interceptor + const bodyBuffer: Buffer[] = []; + proxyRes.on('data', (chunk) => { + bodyBuffer.push(chunk); + }); + proxyRes.on('end', () => { + const body = Buffer.concat(bodyBuffer).toString(); + + // Pass the body to the interceptor + if (options.responseInterceptor) { + options.responseInterceptor({ + body, + proxyRes, + res, + req, + }); + } else { + res.end(body); + } + }); + }); + } + return new Promise((resolve) => { const server = http.createServer((req, res) => { - const forwardRequest = () => - proxy.web(req, res, { target: options.target }); - - if (options.callback) { - // Add one-time listener for the proxy response that stays until `proxyRes` event is triggered next time. - proxy.once('proxyRes', options.callback); - } - - if (options.delay) { - setTimeout(() => { - forwardRequest(); - }, options.delay); - } else { - forwardRequest(); - } + proxy.web(req, res, { target: options.target }); }); server.listen(9002, () => { From cbff3319dcf5746199002e5ab895da042af93fc0 Mon Sep 17 00:00:00 2001 From: PioBar <72926984+Pio-Bar@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:52:45 +0200 Subject: [PATCH 03/26] fix: (CXSPA-7958) - Dialog trigger refocus (#19240) --- .../add-to-cart/add-to-cart.component.html | 1 + .../add-to-cart/add-to-cart.component.ts | 14 +++- .../added-to-cart-dialog-event.listener.ts | 2 +- .../cart/base/root/events/cart.events.ts | 6 ++ ...dp-pickup-options-container.component.html | 2 +- ...pickup-options-container.component.spec.ts | 18 ++++- .../pdp-pickup-options-container.component.ts | 73 +++++++++++++++---- .../pickup-options.component.html | 4 + .../pickup-options.component.spec.ts | 16 +++- .../pickup-options.component.ts | 33 +++++++-- .../feature-toggles/config/feature-toggles.ts | 7 ++ .../accessibility/focus-management.e2e.cy.ts | 18 +++++ .../spartacus/spartacus-features.module.ts | 1 + 13 files changed, 167 insertions(+), 28 deletions(-) diff --git a/feature-libs/cart/base/components/add-to-cart/add-to-cart.component.html b/feature-libs/cart/base/components/add-to-cart/add-to-cart.component.html index 57060104fee..cea53d5837f 100644 --- a/feature-libs/cart/base/components/add-to-cart/add-to-cart.component.html +++ b/feature-libs/cart/base/components/add-to-cart/add-to-cart.component.html @@ -27,6 +27,7 @@ + +