Skip to content

Commit

Permalink
Implement headers matching feature.
Browse files Browse the repository at this point in the history
  • Loading branch information
SachaCR committed May 30, 2020
1 parent 7adae06 commit a7d0a52
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 48 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ denock({
method: 'POST',
protocol: 'https',
host: 'jsonplaceholder.typicode.com',
headers: [{
header: 'content-type', value: 'application/json'
}],
path: '/todos',
requestBody: {
userId: 2,
Expand All @@ -32,6 +35,9 @@ const urlObject = new URL('https://jsonplaceholder.typicode.com/todos');

const response = await fetch(urlObject, {
method: 'POST',
headers: new Headers({
'content-type': 'application/json'
}),
body: JSON.stringify({
userId: 2,
id: 23024,
Expand All @@ -54,7 +60,7 @@ console.log(body) // ==> { test: '5' } instead of the real response.
- `port`: optional port to intercept
- `path`: optional, is the path of the query
- `queryParams`?: optional, object that contains URL query parameters
- `headers`: optional, Not implemented yet.
- `headers`: optional. Is an array of objects representing headers
- `requestBody`: optional, this is the body that the request must contains to be intercepted
- `replyStatus`?: optional, default 200, this is the status code that will be returned on interception
- `responseBody`: this is the body that will be returned on interception;
Expand All @@ -67,6 +73,5 @@ console.log(body) // ==> { test: '5' } instead of the real response.
- [x] Implement intercept HTTP calls made with fetch with a Request object
- [x] Implement intercept HTTP calls made with fetch with a URL object
- [x] Implement interception number
- [ ] Implement matching on headers
- [x] Implement matching on headers
- [ ] Implement basic authentication
- [ ] Implement a mode where if there is no match the request is not intercepted.
5 changes: 5 additions & 0 deletions src/extractMethodAndBodyFromRequestInitObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ export function extractMethodAndBodyFromRequestInitObject(
): {
originalMethod: string;
originalBody: string;
originalHeaders: Headers | string[][] | Record<string, string> | undefined;
} {
let originalMethod = "GET";
let originalBody = "{}";
let originalHeaders;

if (init) {
originalMethod = init.method ? init.method.toUpperCase() : originalMethod;
Expand All @@ -27,10 +29,13 @@ export function extractMethodAndBodyFromRequestInitObject(
originalBody = init.body;
}
}

originalHeaders = init.headers;
}

return {
originalBody,
originalMethod,
originalHeaders,
};
}
91 changes: 46 additions & 45 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { formatTargetUrlFromOptions } from "./formatTargetUrlFromOptions.ts";
import { extractMethodAndBodyFromRequestInitObject } from "./extractMethodAndBodyFromRequestInitObject.ts";
import { extractBodyFromRequest } from "./extractBodyFromRequest.ts";
import { verifyMatch } from "./verifyMatch.ts";
import { TypeGuardResult, determineInputType } from "./typeguard.ts";

function denock(options: DenockOptions): Interceptor {
const originalFetch = window.fetch;
Expand All @@ -23,53 +24,53 @@ function denock(options: DenockOptions): Interceptor {
callCounter++;

try {
if (typeof input === "string") {
const {
originalBody,
originalMethod,
} = extractMethodAndBodyFromRequestInitObject(init);

verifyMatch(targetURl, options, {
originalUrl: input,
originalBody,
originalMethod,
});
const inputTypeResult = determineInputType(input);

let originalUrl: string;

let {
originalBody,
originalMethod,
originalHeaders,
} = extractMethodAndBodyFromRequestInitObject(init);

switch (inputTypeResult.type) {
case "string":
originalUrl = inputTypeResult.stringURL;
break;

case "url":
originalUrl = inputTypeResult.url.toString();
break;

case "request":
const request = inputTypeResult.request;
originalUrl = inputTypeResult.request.url;
originalMethod = inputTypeResult.request.method.toUpperCase();

if (request.body) {
const readableStreamReader = request.body?.getReader();
const extractedBody = await extractBodyFromRequest(
readableStreamReader,
);
originalBody = extractedBody ? extractedBody : originalBody;
}

originalHeaders = request.headers;
break;

default:
const unknownType: never = inputTypeResult;
throw new TypeError(`Denock: Unknown type for input: ${unknownType}`);
break;
}

if ((input as URL).toJSON !== undefined) {
const url = input as URL;
const {
originalBody,
originalMethod,
} = extractMethodAndBodyFromRequestInitObject(init);

verifyMatch(targetURl, options, {
originalUrl: url.toString(),
originalBody,
originalMethod,
});
}

if ((input as Request).clone !== undefined) {
const request = input as Request;
const originalUrl = request.url;
const originalMethod = request.method.toUpperCase();
let originalBody = "{}";

if (request.body) {
const readableStreamReader = request.body?.getReader();
const extractedBody = await extractBodyFromRequest(
readableStreamReader,
);
originalBody = extractedBody ? extractedBody : originalBody;
}

verifyMatch(targetURl, options, {
originalUrl,
originalMethod,
originalBody,
});
}
verifyMatch(targetURl, options, {
originalUrl,
originalBody,
originalMethod,
originalHeaders,
});
} catch (err) {
window.fetch = originalFetch;
throw err;
Expand Down
1 change: 1 addition & 0 deletions src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ export interface RequestData {
originalUrl: string;
originalMethod: string;
originalBody: string;
originalHeaders: Headers | string[][] | Record<string, string> | undefined;
}
42 changes: 42 additions & 0 deletions src/typeguard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export type TypeGuardResult =
| {
type: "string";
stringURL: string;
}
| {
type: "url";
url: URL;
}
| {
type: "request";
request: Request;
};

export function determineInputType(
input: string | Request | URL,
): TypeGuardResult {
if (typeof input === "string") {
return {
type: "string",
stringURL: input,
};
}

if ((input as URL).toJSON !== undefined) {
return {
type: "url",
url: input as URL,
};
}

if ((input as Request).clone !== undefined) {
return {
type: "request",
request: input as Request,
};
}

throw new Error(
"Denock: does not handle that kind of input for fetch invocation: Use a string a Request object or a URL object",
);
}
18 changes: 18 additions & 0 deletions src/verifyMatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,22 @@ export function verifyMatch(
);
}
}

if (options.headers) {
if (requestData.originalHeaders === undefined) {
throw new Error(
`Denock: headers does not match: ${requestData.originalHeaders}`,
);
}

const originalHeaders = new Headers(requestData.originalHeaders);

const allHeaderMatches = options.headers.every((h) => {
return originalHeaders.get(h.header) === h.value;
});

if (!allHeaderMatches) {
throw new Error(`Denock: headers does not match`);
}
}
}
Loading

0 comments on commit a7d0a52

Please sign in to comment.