diff --git a/src/response.ts b/src/response.ts index 0eec6ec..330f016 100644 --- a/src/response.ts +++ b/src/response.ts @@ -6,9 +6,10 @@ import createStream from "./stream"; export interface ServerlessResponse { buffer?: string; // Raw http body base64 encoded - status: number; - statusMessage: string; - headers: Record; + body?: string; // Response body returned as string. Either this or `buffer` is used. + status?: number; + statusMessage?: string; + headers?: Record; } class Response extends ServerResponse { diff --git a/src/utils/callback.spec.ts b/src/utils/callback.spec.ts new file mode 100644 index 0000000..5abaa48 --- /dev/null +++ b/src/utils/callback.spec.ts @@ -0,0 +1,90 @@ +import * as http from "node:http"; +import { handleApi } from "./callbacks"; + +jest.mock("./filesys", () => ({ + walkTree: () => { + return []; + }, + matchPath: () => { + return { path: "/path/to/", name: "api-file.ts" }; + }, +})); + +describe("utils/callback.ts", () => { + describe("handleApi", () => { + const exampleRequest = { + method: "GET", + url: "/", + path: "/", + body: "", + headers: {}, + }; + + afterEach(() => { + jest.resetModules(); + }); + + describe("with returned object", () => { + beforeEach(() => { + jest.mock( + "/path/to/api-file.ts", + () => ({ + default: () => { + return { + body: "Hello world", + status: 201, + headers: { + "X-Custom-Header": "Sample Project", + }, + }; + }, + }), + { virtual: true } + ); + }); + + test("should handle returning a response body", async () => { + const response = await handleApi(exampleRequest, "/"); + + expect(response).toEqual({ + body: "Hello world", + status: 201, + headers: { + "X-Custom-Header": "Sample Project", + }, + }); + }); + }); + + describe("with response.end", () => { + beforeEach(() => { + jest.mock( + "/path/to/api-file.ts", + () => ({ + default: (_: http.IncomingMessage, res: http.ServerResponse) => { + res.setHeader("X-Custom-Header", "Sample Project"); + res.write("Hi world"); + res.end(); + }, + }), + { virtual: true } + ); + }); + + test("should handle returning a response body", async () => { + const response = await handleApi(exampleRequest, "/"); + + expect(response).toEqual({ + buffer: "SGkgd29ybGQ=", + status: 200, + statusMessage: "OK", + headers: { + connection: "close", + date: expect.any(String), + "x-custom-header": "Sample Project", + }, + }); + }); + }); + }); +}); diff --git a/src/utils/callbacks.ts b/src/utils/callbacks.ts index a91815c..1a96afb 100644 --- a/src/utils/callbacks.ts +++ b/src/utils/callbacks.ts @@ -32,6 +32,13 @@ export const handleError = (callback: AwsCallback) => (e: Error) => { let cachedFiles: WalkFile[]; +interface AlternativeSyntax { + body?: string; + headers?: Record; + statusCode?: number; + status?: number; // Alias for statusCode +} + export const handleApi = ( event: RequestEvent, apiDir: string @@ -54,7 +61,20 @@ export const handleApi = ( if (file) { try { const mod = require(path.join(file.path, file.name)); - return mod.default ? mod.default(req, res) : mod(req, res); + const ret = mod.default ? mod.default(req, res) : mod(req, res); + + // Allow function to return a value instead of using `response.end` + Promise.resolve(ret).then((r: AlternativeSyntax) => { + if (typeof r !== "undefined" && typeof r === "object") { + resolve({ + body: r.body, + headers: r.headers, + status: r.statusCode || r.status, + }); + } + }); + + return; } catch (e) { console.error(e); } diff --git a/tests/unit/app-express.spec.ts b/tests/unit/app-express.spec.ts index 5c2f15a..74c90f9 100644 --- a/tests/unit/app-express.spec.ts +++ b/tests/unit/app-express.spec.ts @@ -264,7 +264,7 @@ describe("express", () => { {}, (e: Error | null, parsed: ServerlessResponse) => { expect(e).toBe(null); - expect(parsed.headers["x-custom-proxy-header"]).toBe("1"); + expect(parsed.headers?.["x-custom-proxy-header"]).toBe("1"); expect(JSON.parse(decodeString(parsed.buffer))).toEqual({ user: "robin", email: "robin@stormkit.io", diff --git a/tests/unit/handlers/stormkit.spec.ts b/tests/unit/handlers/stormkit.spec.ts index af152be..086ad19 100644 --- a/tests/unit/handlers/stormkit.spec.ts +++ b/tests/unit/handlers/stormkit.spec.ts @@ -126,7 +126,7 @@ describe("handlers/stormkit.ts", () => { expect(decodeString(parsed.buffer)).toEqual( "Written a text\r\n\r\nWrite something elsemy-data" ); - expect(parsed.headers["transfer-encoding"]).toBeUndefined(); + expect(parsed.headers?.["transfer-encoding"]).toBeUndefined(); expect(parsed).toEqual( expect.objectContaining({ status: 200,