Skip to content

Commit c12e2e1

Browse files
authored
Merge pull request #88 from jaredwray/feat-adding-function-for-tap-injections
feat: adding function for tap injections
2 parents ca08f45 + d5c64f4 commit c12e2e1

4 files changed

Lines changed: 219 additions & 12 deletions

File tree

src/mock-http.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,13 @@ export class MockHttp extends Hookified {
276276
this._server.addHook("onRequest", async (request, reply) => {
277277
const matchedTap = this._taps.matchRequest(request);
278278
if (matchedTap) {
279-
const { response, statusCode = 200, headers } = matchedTap.response;
279+
// Handle both static response objects and response functions
280+
const injectionResponse =
281+
typeof matchedTap.response === "function"
282+
? matchedTap.response(request)
283+
: matchedTap.response;
284+
285+
const { response, statusCode = 200, headers } = injectionResponse;
280286

281287
// Set status code
282288
reply.code(statusCode);

src/tap-manager.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ export type InjectionMatcher = {
4848
headers?: Record<string, string>;
4949
};
5050

51+
/**
52+
* Function that returns an injection response
53+
*/
54+
export type InjectionResponseFunction = (
55+
request: FastifyRequest,
56+
) => InjectionResponse;
57+
5158
/**
5259
* A tap represents an active injection that can be removed later
5360
*/
@@ -57,9 +64,9 @@ export type InjectionTap = {
5764
*/
5865
id: string;
5966
/**
60-
* The response configuration
67+
* The response configuration or a function that returns it
6168
*/
62-
response: InjectionResponse;
69+
response: InjectionResponse | InjectionResponseFunction;
6370
/**
6471
* The request matcher configuration
6572
*/
@@ -92,12 +99,12 @@ export class TapManager {
9299

93100
/**
94101
* Inject a custom response for requests matching the given criteria
95-
* @param response - The response configuration
102+
* @param response - The response configuration or a function that returns it
96103
* @param matcher - Optional criteria to match requests
97104
* @returns A tap object that can be used to remove the injection
98105
*/
99106
public inject(
100-
response: InjectionResponse,
107+
response: InjectionResponse | InjectionResponseFunction,
101108
matcher?: InjectionMatcher,
102109
): InjectionTap {
103110
const id = randomUUID();

test/mock-http.test.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,5 +321,116 @@ describe("MockHttp", () => {
321321

322322
await mock.close();
323323
});
324+
325+
test("should support function response", async () => {
326+
const mock = new MockHttp();
327+
await mock.start();
328+
329+
mock.taps.inject(
330+
(request) => ({
331+
response: `Hello from ${request.url}`,
332+
statusCode: 200,
333+
}),
334+
{ url: "/api/dynamic" },
335+
);
336+
337+
const response = await mock.server.inject({
338+
method: "GET",
339+
url: "/api/dynamic",
340+
});
341+
342+
expect(response.statusCode).toBe(200);
343+
expect(response.body).toBe("Hello from /api/dynamic");
344+
345+
await mock.close();
346+
});
347+
348+
test("should support function response with dynamic status codes", async () => {
349+
const mock = new MockHttp();
350+
await mock.start();
351+
352+
mock.taps.inject((request) => {
353+
const statusCode = request.url.includes("error") ? 500 : 200;
354+
return {
355+
response: { status: statusCode === 500 ? "error" : "success" },
356+
statusCode,
357+
};
358+
});
359+
360+
const successResponse = await mock.server.inject({
361+
method: "GET",
362+
url: "/api/success",
363+
});
364+
365+
const errorResponse = await mock.server.inject({
366+
method: "GET",
367+
url: "/api/error",
368+
});
369+
370+
expect(successResponse.statusCode).toBe(200);
371+
expect(successResponse.json()).toEqual({ status: "success" });
372+
373+
expect(errorResponse.statusCode).toBe(500);
374+
expect(errorResponse.json()).toEqual({ status: "error" });
375+
376+
await mock.close();
377+
});
378+
379+
test("should support function response with dynamic headers", async () => {
380+
const mock = new MockHttp();
381+
await mock.start();
382+
383+
mock.taps.inject(
384+
(request) => ({
385+
response: "OK",
386+
statusCode: 200,
387+
headers: {
388+
"X-Request-Method": request.method,
389+
"X-Request-URL": request.url,
390+
},
391+
}),
392+
{ url: "/api/headers" },
393+
);
394+
395+
const response = await mock.server.inject({
396+
method: "POST",
397+
url: "/api/headers",
398+
});
399+
400+
expect(response.statusCode).toBe(200);
401+
expect(response.headers["x-request-method"]).toBe("POST");
402+
expect(response.headers["x-request-url"]).toBe("/api/headers");
403+
404+
await mock.close();
405+
});
406+
407+
test("should support function response with request body inspection", async () => {
408+
const mock = new MockHttp();
409+
await mock.start();
410+
411+
mock.taps.inject(
412+
(request) => ({
413+
response: {
414+
receivedMethod: request.method,
415+
receivedUrl: request.url,
416+
},
417+
statusCode: 200,
418+
}),
419+
{ url: "/api/echo" },
420+
);
421+
422+
const response = await mock.server.inject({
423+
method: "POST",
424+
url: "/api/echo",
425+
payload: { test: "data" },
426+
});
427+
428+
expect(response.statusCode).toBe(200);
429+
const body = response.json();
430+
expect(body.receivedMethod).toBe("POST");
431+
expect(body.receivedUrl).toBe("/api/echo");
432+
433+
await mock.close();
434+
});
324435
});
325436
});

test/tap.test.ts

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ describe("Tap", () => {
371371
};
372372
const tap = tapManager.inject(response);
373373

374-
expect(tap.response.response).toBe("Hello World");
374+
expect(tap.response).toEqual(response);
375375
});
376376

377377
it("should support object response", () => {
@@ -380,7 +380,7 @@ describe("Tap", () => {
380380
};
381381
const tap = tapManager.inject(response);
382382

383-
expect(tap.response.response).toEqual({ message: "Hello", code: 200 });
383+
expect(tap.response).toEqual(response);
384384
});
385385

386386
it("should support custom status code", () => {
@@ -390,7 +390,7 @@ describe("Tap", () => {
390390
};
391391
const tap = tapManager.inject(response);
392392

393-
expect(tap.response.statusCode).toBe(404);
393+
expect(tap.response).toEqual(response);
394394
});
395395

396396
it("should support custom headers", () => {
@@ -403,10 +403,93 @@ describe("Tap", () => {
403403
};
404404
const tap = tapManager.inject(response);
405405

406-
expect(tap.response.headers).toEqual({
407-
"Content-Type": "application/json",
408-
"X-Custom-Header": "value",
409-
});
406+
expect(tap.response).toEqual(response);
407+
});
408+
409+
it("should support function response", () => {
410+
const responseFunction = (request: FastifyRequest): InjectionResponse => {
411+
return {
412+
response: `Hello from ${request.url}`,
413+
statusCode: 200,
414+
};
415+
};
416+
417+
const tap = tapManager.inject(responseFunction);
418+
419+
expect(tap.response).toBe(responseFunction);
420+
expect(typeof tap.response).toBe("function");
421+
});
422+
423+
it("should call function response with request object", () => {
424+
const responseFunction = (request: FastifyRequest): InjectionResponse => {
425+
return {
426+
response: `Method: ${request.method}, URL: ${request.url}`,
427+
statusCode: 200,
428+
};
429+
};
430+
431+
const tap = tapManager.inject(responseFunction, { url: "/api/test" });
432+
const mockRequest = createMockRequest("/api/test", "GET");
433+
434+
// Verify the tap was created with the function
435+
expect(typeof tap.response).toBe("function");
436+
437+
// Call the function to verify it works correctly
438+
if (typeof tap.response === "function") {
439+
const result = tap.response(mockRequest);
440+
expect(result.response).toBe("Method: GET, URL: /api/test");
441+
expect(result.statusCode).toBe(200);
442+
}
443+
});
444+
445+
it("should support function response with dynamic status codes", () => {
446+
const responseFunction = (request: FastifyRequest): InjectionResponse => {
447+
const statusCode = request.url.includes("error") ? 500 : 200;
448+
return {
449+
response: { status: statusCode === 500 ? "error" : "success" },
450+
statusCode,
451+
};
452+
};
453+
454+
const tap = tapManager.inject(responseFunction);
455+
456+
// Test success case
457+
if (typeof tap.response === "function") {
458+
const successRequest = createMockRequest("/api/success", "GET");
459+
const successResult = tap.response(successRequest);
460+
expect(successResult.statusCode).toBe(200);
461+
expect(successResult.response).toEqual({ status: "success" });
462+
463+
// Test error case
464+
const errorRequest = createMockRequest("/api/error", "GET");
465+
const errorResult = tap.response(errorRequest);
466+
expect(errorResult.statusCode).toBe(500);
467+
expect(errorResult.response).toEqual({ status: "error" });
468+
}
469+
});
470+
471+
it("should support function response with dynamic headers", () => {
472+
const responseFunction = (request: FastifyRequest): InjectionResponse => {
473+
return {
474+
response: "OK",
475+
statusCode: 200,
476+
headers: {
477+
"X-Request-Method": request.method,
478+
"X-Request-URL": request.url,
479+
},
480+
};
481+
};
482+
483+
const tap = tapManager.inject(responseFunction);
484+
const mockRequest = createMockRequest("/api/test", "POST");
485+
486+
if (typeof tap.response === "function") {
487+
const result = tap.response(mockRequest);
488+
expect(result.headers).toEqual({
489+
"X-Request-Method": "POST",
490+
"X-Request-URL": "/api/test",
491+
});
492+
}
410493
});
411494
});
412495
});

0 commit comments

Comments
 (0)