diff --git a/packages/core/src/Matchers.js b/packages/core/src/Matchers.js index d0abd8fc..5c6cbbc2 100644 --- a/packages/core/src/Matchers.js +++ b/packages/core/src/Matchers.js @@ -101,6 +101,23 @@ const getHeaderMatcher = ({ headers: expectedHeaders }) => { ); }; }; +/** + * @type {MatcherGenerator} + */ +const getMissingHeaderMatcher = ({ + missingHeaders: expectedMissingHeaders, +}) => { + if (!expectedMissingHeaders) { + return; + } + const expectation = expectedMissingHeaders.map((header) => + header.toLowerCase(), + ); + return ({ options: { headers = {} } }) => { + const lowerCaseHeaders = normalizeHeaders(headers); + return expectation.every((headerName) => !(headerName in lowerCaseHeaders)); + }; +}; /** * @type {MatcherGenerator} */ @@ -306,6 +323,7 @@ export const builtInMatchers = [ { name: 'query', matcher: getQueryParamsMatcher }, { name: 'method', matcher: getMethodMatcher }, { name: 'headers', matcher: getHeaderMatcher }, + { name: 'missingHeaders', matcher: getMissingHeaderMatcher }, { name: 'params', matcher: getExpressParamsMatcher }, { name: 'body', matcher: getBodyMatcher, usesBody: true }, { name: 'matcherFunction', matcher: getFunctionMatcher }, diff --git a/packages/core/src/Route.js b/packages/core/src/Route.js index 53001141..6ba62445 100644 --- a/packages/core/src/Route.js +++ b/packages/core/src/Route.js @@ -15,6 +15,7 @@ import statusTextMap from './StatusTextMap.js'; * @property {RouteName} [name] * @property {string} [method] * @property {{ [key: string]: string | number }} [headers] + * @property {string[]} [missingHeaders] * @property {{ [key: string]: string }} [query] * @property {{ [key: string]: string }} [params] * @property {object} [body] diff --git a/packages/core/src/__tests__/Matchers/headers.test.js b/packages/core/src/__tests__/Matchers/headers.test.js index a4965690..9d6218bd 100644 --- a/packages/core/src/__tests__/Matchers/headers.test.js +++ b/packages/core/src/__tests__/Matchers/headers.test.js @@ -45,6 +45,50 @@ describe('header matching', () => { ).toBe(true); }); + it('match missing headers', () => { + const route = new Route({ + missingHeaders: ['a'], + response: 200, + }); + expect( + route.matcher({ + url: 'http://a.com/', + options: { + headers: { b: 'c' }, + }, + }), + ).toBe(true); + }); + + it('not match present missing header', () => { + const route = new Route({ + missingHeaders: ['a'], + response: 200, + }); + expect( + route.matcher({ + url: 'http://a.com/', + options: { + headers: { a: 'b' }, + }, + }), + ).toBe(false); + }); + + it('not error when request sent without headers', () => { + const route = new Route({ + missingHeaders: ['a'], + response: 200, + }); + + expect( + route.matcher({ + url: 'http://a.com/', + options: {}, + }), + ).toBe(true); + }); + it('be case insensitive', () => { const route = new Route({ headers: { a: 'b' }, diff --git a/packages/core/types/Route.d.ts b/packages/core/types/Route.d.ts index 812ea10e..6f3d25e0 100644 --- a/packages/core/types/Route.d.ts +++ b/packages/core/types/Route.d.ts @@ -12,6 +12,7 @@ export type UserRouteSpecificConfig = { headers?: { [key: string]: string | number; }; + missingHeaders?: string[]; query?: { [key: string]: string; };