From 4ba72c94aab55d96486d7b3e53d1087a77136b6d Mon Sep 17 00:00:00 2001 From: Ryan Manuel Date: Tue, 22 Oct 2024 08:21:44 -0500 Subject: [PATCH] fix: ensure that requests to the same resource will be properly intercepted even if they are sent in rapid succession (#30435) * fix: ensure that requests to the same resource will be properly intercepted even if they are sent in rapid succession - run ci * add changelog * Update packages/net-stubbing/lib/server/middleware/request.ts * PR comments --- cli/CHANGELOG.md | 1 + .../lib/server/intercepted-request.ts | 8 ++- .../lib/server/middleware/request.ts | 5 ++ .../test/unit/intercepted-request-spec.ts | 63 ++++++++++++++++++- 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 3ab6f48f4221..7747c11e8a79 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -6,6 +6,7 @@ _Released 10/1/2024 (PENDING)_ **Bugfixes:** - Patched [find-process](https://github.com/yibn2008/find-process) to fix an issue where trying to clean up browser profiles can throw an error on Windows. Addresses [#30378](https://github.com/cypress-io/cypress/issues/30378). +- Fixed an issue where requests to the same resource in rapid succession may not have the appropriate static response intercept applied if there are multiple intercepts that apply for that resource. Addresses [#30375](https://github.com/cypress-io/cypress/issues/30375). **Misc:** diff --git a/packages/net-stubbing/lib/server/intercepted-request.ts b/packages/net-stubbing/lib/server/intercepted-request.ts index 98665939acb6..9b80816f54bf 100644 --- a/packages/net-stubbing/lib/server/intercepted-request.ts +++ b/packages/net-stubbing/lib/server/intercepted-request.ts @@ -51,8 +51,6 @@ export class InterceptedRequest { this._onResponse = opts.onResponse this.state = opts.state this.socket = opts.socket - - this.addDefaultSubscriptions() } onResponse = (incomingRes: IncomingMessage, resStream: Readable) => { @@ -65,7 +63,7 @@ export class InterceptedRequest { this._onResponse(incomingRes, resStream) } - private addDefaultSubscriptions () { + addDefaultSubscriptions () { if (this.subscriptionsByRoute.length) { throw new Error('cannot add default subscriptions to non-empty array') } @@ -75,6 +73,10 @@ export class InterceptedRequest { } for (const route of this.req.matchingRoutes) { + if (route.disabled) { + continue + } + const subscriptionsByRoute = { routeId: route.id, immediateStaticResponse: route.staticResponse, diff --git a/packages/net-stubbing/lib/server/middleware/request.ts b/packages/net-stubbing/lib/server/middleware/request.ts index 00dd34561fff..132d060522cf 100644 --- a/packages/net-stubbing/lib/server/middleware/request.ts +++ b/packages/net-stubbing/lib/server/middleware/request.ts @@ -138,6 +138,11 @@ export const InterceptRequest: RequestMiddleware = async function () { await ensureBody() + // Note that this needs to happen after the `ensureBody` call to ensure that we proceed synchronously to + // where we update the state of the routes: + // https://github.com/cypress-io/cypress/blob/aafac6a6104b689a118f4c4f29f948d7d8a35aef/packages/net-stubbing/lib/server/intercepted-request.ts#L167-L169 + request.addDefaultSubscriptions() + if (!_.isString(req.body) && !_.isBuffer(req.body)) { throw new Error('req.body must be a string or a Buffer') } diff --git a/packages/net-stubbing/test/unit/intercepted-request-spec.ts b/packages/net-stubbing/test/unit/intercepted-request-spec.ts index 84de5e468ecb..2807b8a04689 100644 --- a/packages/net-stubbing/test/unit/intercepted-request-spec.ts +++ b/packages/net-stubbing/test/unit/intercepted-request-spec.ts @@ -1,9 +1,12 @@ -import { expect } from 'chai' +import chai, { expect } from 'chai' import _ from 'lodash' import sinon from 'sinon' +import sinonChai from 'sinon-chai' import { InterceptedRequest } from '../../lib/server/intercepted-request' import { state as NetStubbingState } from '../../lib/server/state' +chai.use(sinonChai) + describe('InterceptedRequest', () => { context('handleSubscriptions', () => { it('handles subscriptions as expected', async () => { @@ -32,6 +35,8 @@ describe('InterceptedRequest', () => { socket, }) + interceptedRequest.addDefaultSubscriptions() + interceptedRequest.addSubscription({ routeId: '1', eventName: 'before:response', @@ -59,6 +64,62 @@ describe('InterceptedRequest', () => { data, mergeChanges: _.merge, }) + + expect(socket.toDriver).to.be.calledTwice + }) + + it('ignores disabled subscriptions', async () => { + const socket = { + toDriver: sinon.stub(), + } + const state = NetStubbingState() + const interceptedRequest = new InterceptedRequest({ + req: { + matchingRoutes: [ + // @ts-ignore + { + id: '1', + hasInterceptor: true, + routeMatcher: {}, + disabled: true, + }, + // @ts-ignore + { + id: '2', + hasInterceptor: true, + routeMatcher: {}, + }, + ], + }, + state, + socket, + }) + + interceptedRequest.addDefaultSubscriptions() + + const data = { foo: 'bar' } + + socket.toDriver.callsFake((eventName, subEventName, frame) => { + expect(eventName).to.eq('net:stubbing:event') + expect(subEventName).to.eq('before:request') + expect(frame).to.deep.include({ + subscription: { + eventName: 'before:request', + await: true, + routeId: frame.subscription.routeId, + }, + }) + + state.pendingEventHandlers[frame.eventId](frame.data) + }) + + await interceptedRequest.handleSubscriptions({ + eventName: 'before:request', + data, + mergeChanges: _.merge, + }) + + expect(socket.toDriver).to.be.calledOnce }) }) })