Skip to content

Commit

Permalink
wip: changes for req.on events, middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
flotwig committed Mar 3, 2021
1 parent a7655d3 commit a8f51b2
Show file tree
Hide file tree
Showing 10 changed files with 531 additions and 30 deletions.
453 changes: 435 additions & 18 deletions packages/driver/cypress/integration/commands/net_stubbing_spec.ts

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions packages/driver/src/cy/net-stubbing/add-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function annotateMatcherOptionsTypes (options: RouteMatcherOptions) {
}
})

const noAnnotationRequiredFields: (keyof RouteMatcherOptions)[] = ['https', 'port', 'matchUrlAgainstPath']
const noAnnotationRequiredFields: (keyof RouteMatcherOptions)[] = ['https', 'port', 'matchUrlAgainstPath', 'middleware']

_.extend(ret, _.pick(options, noAnnotationRequiredFields))

Expand Down Expand Up @@ -113,7 +113,7 @@ function validateRouteMatcherOptions (routeMatcher: RouteMatcherOptions): { isVa
}
}

const booleanProps = ['https', 'matchUrlAgainstPath']
const booleanProps = ['https', 'matchUrlAgainstPath', 'middleware']

for (const prop of booleanProps) {
if (_.has(routeMatcher, prop) && !_.isBoolean(routeMatcher[prop])) {
Expand Down Expand Up @@ -246,6 +246,10 @@ export function addCommand (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy,
routeMatcher.headers = lowercaseFieldNames(routeMatcher.headers)
}

if (routeMatcher.middleware && !hasInterceptor) {
return $errUtils.throwErrByPath('net_stubbing.intercept.invalid_middleware_handler', { args: { handler } })
}

const frame: NetEvent.ToServer.AddRoute = {
handlerId,
hasInterceptor,
Expand Down
6 changes: 6 additions & 0 deletions packages/driver/src/cypress/error_messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,12 @@ module.exports = {
You passed: ${format(handler)}`
},
invalid_middleware_handler: ({ handler }) => {
return stripIndent`\
${cmd('intercept')}'s \`handler\` argument must be an HttpController function when \`middleware\` is set to \`true\`.
You passed: ${format(handler)}`
},
invalid_route_matcher: ({ message, matcher }) => {
return stripIndent`\
An invalid RouteMatcher was supplied to ${cmd('intercept')}. ${message}
Expand Down
4 changes: 2 additions & 2 deletions packages/driver/src/cypress/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Bluebird from 'bluebird'

const log = Debug('cypress:driver')

const proxyFunctions = ['emit', 'emitThen', 'emitMap']
const proxyFunctions = ['emit', 'emitThen', 'emitThenSeries', 'emitMap']

const withoutFunctions = (arr) => {
return _.reject(arr, _.isFunction)
Expand All @@ -20,7 +20,7 @@ type CyEvents = {
emitThenSeries: (eventName: string, ...args: any[]) => Bluebird<any[]>
}

type Events = EventEmitter2 & CyEvents
export type Events = EventEmitter2 & CyEvents

export function extend (obj): Events {
const events: EventEmitter2 & Partial<CyEvents> = new EventEmitter2()
Expand Down
9 changes: 6 additions & 3 deletions packages/net-stubbing/lib/external-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,8 @@ export interface Interception {
subscription: Subscription
handler: (data: any) => Promise<void> | void
}>
/* @internal */
on(eventName: 'request', cb: () => void): Interception
/* @internal */
on(eventName: 'before-response', cb: (res: CyHttpMessages.IncomingHttpResponse) => void): Interception
/* @internal */
on(eventName: 'response', cb: (res: CyHttpMessages.IncomingHttpResponse) => void): Interception
}

Expand Down Expand Up @@ -286,6 +283,12 @@ export interface RouteMatcherOptionsGeneric<S> {
* @default '*'
*/
method?: S
/**
* If `true`, this will pass the request on to the next `RouteMatcher` after the request handler completes.
* Can only be used with a dynamic request handler.
* @default false
*/
middleware?: boolean
/**
* Match on request path after the hostname, including query params.
*/
Expand Down
4 changes: 4 additions & 0 deletions packages/net-stubbing/lib/internal-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export declare namespace NetEvent {
eventId: string
data: D
}

export interface Request extends Event<CyHttpMessages.IncomingRequest> {}

export interface Response extends Event<CyHttpMessages.IncomingResponse> {}
}

export namespace ToServer {
Expand Down
4 changes: 3 additions & 1 deletion packages/net-stubbing/lib/server/driver-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
sendStaticResponse as _sendStaticResponse,
setResponseFromFixture,
} from './util'
import CyServer from '@packages/server'

const debug = Debug('cypress:net-stubbing:server:driver-events')

Expand Down Expand Up @@ -105,7 +106,7 @@ export function _restoreMatcherOptionsTypes (options: AnnotatedRouteMatcherOptio
_.set(ret, field, value)
})

const noAnnotationRequiredFields: (keyof AnnotatedRouteMatcherOptions)[] = ['https', 'port', 'matchUrlAgainstPath']
const noAnnotationRequiredFields: (keyof AnnotatedRouteMatcherOptions)[] = ['https', 'port', 'matchUrlAgainstPath', 'middleware']

_.extend(ret, _.pick(options, noAnnotationRequiredFields))

Expand All @@ -115,6 +116,7 @@ export function _restoreMatcherOptionsTypes (options: AnnotatedRouteMatcherOptio
type OnNetEventOpts = {
eventName: string
state: NetStubbingState
socket: CyServer.Socket
getFixture: GetFixtureFn
args: any[]
frame: NetEvent.ToServer.AddRoute | NetEvent.ToServer.EventHandlerResolved | NetEvent.ToServer.Subscribe | NetEvent.ToServer.SendStaticResponse
Expand Down
21 changes: 17 additions & 4 deletions packages/net-stubbing/lib/server/route-matching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,24 @@ export function _getMatchableForRequest (req: CypressIncomingRequest) {
* Try to match a `BackendRoute` to a request, optionally starting after `prevRoute`.
*/
export function getRouteForRequest (routes: BackendRoute[], req: CypressIncomingRequest, prevRoute?: BackendRoute) {
const possibleRoutes = prevRoute ? routes.slice(_.findIndex(routes, prevRoute) + 1) : routes
// if (prevRoute && !prevRoute.routeMatcher.middleware) {
// // Terminates after one matching handler.
// return
// }

const [middleware, handlers] = _.partition(routes, (route) => route.routeMatcher.middleware === true)
// First, match the oldest matching route handler with `middleware: true`.
// Then, match the newest matching route handler.
const orderedRoutes = middleware.concat(handlers.reverse())
const possibleRoutes = prevRoute ? orderedRoutes.slice(_.findIndex(orderedRoutes, prevRoute) + 1) : orderedRoutes

for (const route of possibleRoutes) {
if (_doesRouteMatch(route.routeMatcher, req)) {
return route
}
}

return _.find(possibleRoutes, (route) => {
return _doesRouteMatch(route.routeMatcher, req)
})
return
}

function isPreflightRequest (req: CypressIncomingRequest) {
Expand Down
51 changes: 51 additions & 0 deletions packages/net-stubbing/test/unit/route-matching-spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
_doesRouteMatch,
_getMatchableForRequest,
getRouteForRequest,
} from '../../lib/server/route-matching'
import { RouteMatcherOptions } from '../../lib/types'
import { expect } from 'chai'
import { CypressIncomingRequest } from '@packages/proxy'
import { BackendRoute } from '../../lib/server/types'

describe('intercept-request', function () {
context('._getMatchableForRequest', function () {
Expand Down Expand Up @@ -185,4 +187,53 @@ describe('intercept-request', function () {
})
})
})

context('.getRouteForRequest', function () {
it('matches middleware, then handlers', function () {
const routes: Partial<BackendRoute>[] = [
{
handlerId: '1',
routeMatcher: {
middleware: true,
pathname: '/foo',
},
},
{
handlerId: '2',
routeMatcher: {
pathname: '/foo',
},
},
{
handlerId: '3',
routeMatcher: {
middleware: true,
pathname: '/foo',
},
},
{
handlerId: '4',
routeMatcher: {
pathname: '/foo',
},
},
]

const req: Partial<CypressIncomingRequest> = {
method: 'GET',
headers: {},
proxiedUrl: 'http://bar.baz/foo?_',
}

let prevRoute: BackendRoute
let e: string[] = []

// @ts-ignore
while ((prevRoute = getRouteForRequest(routes, req, prevRoute))) {
e.push(prevRoute.handlerId)
}

expect(e).to.deep.eq(['1', '3', '4'])
})
})
})
1 change: 1 addition & 0 deletions packages/server/lib/socket-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ export class SocketBase {
eventName: args[0],
frame: args[1],
state: options.netStubbingState,
socket: this,
getFixture,
args,
})
Expand Down

0 comments on commit a8f51b2

Please sign in to comment.