Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
Copied from penx/remix@b7606a4
  • Loading branch information
penx committed Jan 24, 2023
0 parents commit 39156a0
Show file tree
Hide file tree
Showing 10 changed files with 462 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/node_modules
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Welcome to Remix!

[Remix](https://remix.run) is a web framework that helps you build better websites with React.

To get started, open a new shell and run:

```sh
npx create-remix@latest
```

Then follow the prompts you see in your terminal.

For more information about Remix, [visit remix.run](https://remix.run)!
272 changes: 272 additions & 0 deletions __tests__/server-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
import supertest from "supertest";
import { createRequest } from "node-mocks-http";
import {
createRequestHandler as createRemixRequestHandler,
Response as NodeResponse,
} from "@remix-run/node";
import { Readable } from "stream";
import { http } from "@google-cloud/functions-framework";
// @ts-ignore
import { getTestServer } from "@google-cloud/functions-framework/testing";

import {
createRemixHeaders,
createRemixRequest,
createRequestHandler,
} from "../server";

// We don't want to test that the remix server works here (that's what the
// puppetteer tests do), we just want to test the express adapter
jest.mock("@remix-run/node", () => {
let original = jest.requireActual("@remix-run/node");
return {
...original,
createRequestHandler: jest.fn(),
};
});
let mockedCreateRequestHandler =
createRemixRequestHandler as jest.MockedFunction<
typeof createRemixRequestHandler
>;

function createApp() {
http(
"remixServer",
createRequestHandler({
// We don't have a real app to test, but it doesn't matter. We
// won't ever call through to the real createRequestHandler
build: undefined,
})
);
return getTestServer("remixServer");
}

describe("express createRequestHandler", () => {
describe("basic requests", () => {
afterEach(() => {
mockedCreateRequestHandler.mockReset();
});

afterAll(() => {
jest.restoreAllMocks();
});

it("handles requests", async () => {
mockedCreateRequestHandler.mockImplementation(() => async (req) => {
return new Response(`URL: ${new URL(req.url).pathname}`);
});

let request = supertest(createApp());
let res = await request.get("/foo/bar");

expect(res.status).toBe(200);
expect(res.text).toBe("URL: /foo/bar");
expect(res.headers["x-powered-by"]).toBe(undefined);
});

it("handles null body", async () => {
mockedCreateRequestHandler.mockImplementation(() => async () => {
return new Response(null, { status: 200 });
});

let request = supertest(createApp());
let res = await request.get("/");

expect(res.status).toBe(200);
});

// https://github.com/node-fetch/node-fetch/blob/4ae35388b078bddda238277142bf091898ce6fda/test/response.js#L142-L148
it("handles body as stream", async () => {
mockedCreateRequestHandler.mockImplementation(() => async () => {
let stream = Readable.from("hello world");
return new NodeResponse(stream, { status: 200 }) as unknown as Response;
});

let request = supertest(createApp());
// note: vercel's createServerWithHelpers requires a x-now-bridge-request-id
let res = await request.get("/").set({ "x-now-bridge-request-id": "2" });

expect(res.status).toBe(200);
expect(res.text).toBe("hello world");
});

it("handles status codes", async () => {
mockedCreateRequestHandler.mockImplementation(() => async () => {
return new Response(null, { status: 204 });
});

let request = supertest(createApp());
let res = await request.get("/");

expect(res.status).toBe(204);
});

it("sets headers", async () => {
mockedCreateRequestHandler.mockImplementation(() => async () => {
let headers = new Headers({ "X-Time-Of-Year": "most wonderful" });
headers.append(
"Set-Cookie",
"first=one; Expires=0; Path=/; HttpOnly; Secure; SameSite=Lax"
);
headers.append(
"Set-Cookie",
"second=two; MaxAge=1209600; Path=/; HttpOnly; Secure; SameSite=Lax"
);
headers.append(
"Set-Cookie",
"third=three; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Path=/; HttpOnly; Secure; SameSite=Lax"
);
return new Response(null, { headers });
});

let request = supertest(createApp());
let res = await request.get("/");

expect(res.headers["x-time-of-year"]).toBe("most wonderful");
expect(res.headers["set-cookie"]).toEqual([
"first=one; Expires=0; Path=/; HttpOnly; Secure; SameSite=Lax",
"second=two; MaxAge=1209600; Path=/; HttpOnly; Secure; SameSite=Lax",
"third=three; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Path=/; HttpOnly; Secure; SameSite=Lax",
]);
});
});
});

describe("express createRemixHeaders", () => {
describe("creates fetch headers from express headers", () => {
it("handles empty headers", () => {
expect(createRemixHeaders({})).toMatchInlineSnapshot(`
Headers {
Symbol(query): Array [],
Symbol(context): null,
}
`);
});

it("handles simple headers", () => {
expect(createRemixHeaders({ "x-foo": "bar" })).toMatchInlineSnapshot(`
Headers {
Symbol(query): Array [
"x-foo",
"bar",
],
Symbol(context): null,
}
`);
});

it("handles multiple headers", () => {
expect(createRemixHeaders({ "x-foo": "bar", "x-bar": "baz" }))
.toMatchInlineSnapshot(`
Headers {
Symbol(query): Array [
"x-foo",
"bar",
"x-bar",
"baz",
],
Symbol(context): null,
}
`);
});

it("handles headers with multiple values", () => {
expect(createRemixHeaders({ "x-foo": "bar, baz" }))
.toMatchInlineSnapshot(`
Headers {
Symbol(query): Array [
"x-foo",
"bar, baz",
],
Symbol(context): null,
}
`);
});

it("handles headers with multiple values and multiple headers", () => {
expect(createRemixHeaders({ "x-foo": "bar, baz", "x-bar": "baz" }))
.toMatchInlineSnapshot(`
Headers {
Symbol(query): Array [
"x-foo",
"bar, baz",
"x-bar",
"baz",
],
Symbol(context): null,
}
`);
});

it("handles multiple set-cookie headers", () => {
expect(
createRemixHeaders({
"set-cookie": [
"__session=some_value; Path=/; Secure; HttpOnly; MaxAge=7200; SameSite=Lax",
"__other=some_other_value; Path=/; Secure; HttpOnly; MaxAge=3600; SameSite=Lax",
],
})
).toMatchInlineSnapshot(`
Headers {
Symbol(query): Array [
"set-cookie",
"__session=some_value; Path=/; Secure; HttpOnly; MaxAge=7200; SameSite=Lax",
"set-cookie",
"__other=some_other_value; Path=/; Secure; HttpOnly; MaxAge=3600; SameSite=Lax",
],
Symbol(context): null,
}
`);
});
});
});

describe("express createRemixRequest", () => {
it("creates a request with the correct headers", async () => {
let expressRequest = createRequest({
url: "/foo/bar",
method: "GET",
protocol: "http",
hostname: "localhost",
headers: {
"Cache-Control": "max-age=300, s-maxage=3600",
Host: "localhost:3000",
},
});

expect(createRemixRequest(expressRequest)).toMatchInlineSnapshot(`
NodeRequest {
"agent": undefined,
"compress": true,
"counter": 0,
"follow": 20,
"highWaterMark": 16384,
"insecureHTTPParser": false,
"size": 0,
Symbol(Body internals): Object {
"body": null,
"boundary": null,
"disturbed": false,
"error": null,
"size": 0,
"type": null,
},
Symbol(Request internals): Object {
"headers": Headers {
Symbol(query): Array [
"cache-control",
"max-age=300, s-maxage=3600",
"host",
"localhost:3000",
],
Symbol(context): null,
},
"method": "GET",
"parsedURL": "http://localhost:3000/foo/bar",
"redirect": "follow",
"signal": AbortSignal {},
},
}
`);
});
});
2 changes: 2 additions & 0 deletions __tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { installGlobals } from "@remix-run/node";
installGlobals();
2 changes: 2 additions & 0 deletions globals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { installGlobals } from "@remix-run/node";
installGlobals();
4 changes: 4 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import "./globals";

export type { GetLoadContextFunction, RequestHandler } from "./server";
export { createRequestHandler } from "./server";
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
...require("../../jest/jest.config.shared"),
displayName: "google-cloud-functions",
};
27 changes: 27 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "remix-google-cloud-functions",
"description": "Google Cloud functions request handler for Remix",
"version": "0.0.1",
"license": "MIT",
"main": "build/index.js",
"repository": {
"type": "git",
"url": "https://github.com/penx/remix",
"directory": "packages/remix-google-cloud-functions"
},
"bugs": {
"url": "https://github.com/penx/remix/issues"
},
"scripts": {
"build": "rm -rf build && tsc -b"
},
"dependencies": {
"@google-cloud/functions-framework": "^3.1.1",
"@remix-run/node": "1.5.1"
},
"devDependencies": {
"@types/supertest": "^2.0.10",
"node-mocks-http": "^1.10.1",
"supertest": "^6.0.1"
}
}
Loading

0 comments on commit 39156a0

Please sign in to comment.