Skip to content

Commit

Permalink
Add 100% coverage on server-side code (sergiodxa#8)
Browse files Browse the repository at this point in the history
* Move redirectBack test to server/responses.test.ts

* Add tests for all Response helpers

* Change CSRF token verification error messages

* Add tests for CSRF server-side code

* Create tests for bodyParser methods

* Add test for typed JSON function

* Remove file with old tests

* Change import paths in tests

* Ignore coverage folder

* Update describe title to use function to get the name
  • Loading branch information
sergiodxa authored Nov 12, 2021
1 parent 5b646f5 commit 3565b34
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 45 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
node_modules

/browser
/build
/build
/coverage
4 changes: 2 additions & 2 deletions src/server/csrf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ export async function verifyAuthenticityToken(
// if the session doesn't have a csrf token, throw an error
if (!session.has(sessionKey)) {
throw unprocessableEntity({
message: "Can't verify CSRF token authenticity.",
message: "Can't find CSRF token in session.",
});
}

// if the body doesn't have a csrf token, throw an error
if (!params.get(sessionKey)) {
throw unprocessableEntity({
message: "Can't verify CSRF token authenticity.",
message: "Can't find CSRF token in body.",
});
}

Expand Down
39 changes: 0 additions & 39 deletions test/server.test.ts

This file was deleted.

43 changes: 43 additions & 0 deletions test/server/body-parser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { bodyParser } from "../../src";

describe("Body Parser", () => {
describe(bodyParser.toString, () => {
test("should return string", async () => {
let request = new Request("/", {
method: "POST",
body: new URLSearchParams("a=1&b=2"),
});

let body = await bodyParser.toString(request);

expect(body).toBe("a=1&b=2");
});
});

describe(bodyParser.toSearchParams, () => {
test("should return URLSearchParams", async () => {
let request = new Request("/", {
method: "POST",
body: new URLSearchParams("a=1&b=2"),
});

let params = await bodyParser.toSearchParams(request);

expect(params.get("a")).toBe("1");
expect(params.get("b")).toBe("2");
});
});

describe(bodyParser.toJSON, () => {
test("should return JS object", async () => {
let request = new Request("/", {
method: "POST",
body: new URLSearchParams("a=1&b=2"),
});

let body = (await bodyParser.toJSON(request)) as { a: 1; b: 2 };

expect(body).toEqual({ a: "1", b: "2" });
});
});
});
91 changes: 91 additions & 0 deletions test/server/csrf.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { createCookieSessionStorage } from "remix";
import { createAuthenticityToken, verifyAuthenticityToken } from "../../src/";

describe("CSRF Server", () => {
let sessionStorage = createCookieSessionStorage({
cookie: { name: "session", secrets: ["s3cr3t"] },
});

describe(createAuthenticityToken, () => {
test("should return a random string", async () => {
let session = await sessionStorage.getSession();
const token = createAuthenticityToken(session);
expect(token).toBeDefined();
expect(token).toHaveLength(136);
});

test("the returned token should be stored in the session as csrf", async () => {
let session = await sessionStorage.getSession();
const token = createAuthenticityToken(session);
expect(session.get("csrf")).toBe(token);
});

test("should be able to change the key used to store the token in the session", async () => {
let session = await sessionStorage.getSession();
const token = createAuthenticityToken(session, "newKey");
expect(session.get("newKey")).toBe(token);
});
});

describe(verifyAuthenticityToken, () => {
test("should throw Unprocessable Entity if the csrf is not in the session", async () => {
let session = await sessionStorage.getSession();
let cookie = await sessionStorage.commitSession(session);
let request = new Request("/", {
method: "POST",
headers: { cookie },
});

try {
await verifyAuthenticityToken(request, session);
} catch (error) {
if (!(error instanceof Response)) throw error;
expect(error.status).toBe(422);
expect(await error.json()).toEqual({
message: "Can't find CSRF token in session.",
});
}
});

test("should throw Unprocessable Entity if csrf is not in the body", async () => {
let session = await sessionStorage.getSession();
session.set("csrf", "token");
let cookie = await sessionStorage.commitSession(session);
let request = new Request("/", {
method: "POST",
headers: { cookie },
});

try {
await verifyAuthenticityToken(request, session);
} catch (error) {
if (!(error instanceof Response)) throw error;
expect(error.status).toBe(422);
expect(await error.json()).toEqual({
message: "Can't find CSRF token in body.",
});
}
});

test("should throw Unprocessable Entity if session and body csrf don't match", async () => {
let session = await sessionStorage.getSession();
session.set("csrf", "token");
let cookie = await sessionStorage.commitSession(session);
let request = new Request("/", {
method: "POST",
headers: { cookie },
body: new URLSearchParams({ csrf: "wrong token" }),
});

try {
await verifyAuthenticityToken(request, session);
} catch (error) {
if (!(error instanceof Response)) throw error;
expect(error.status).toBe(422);
expect(await error.json()).toEqual({
message: "Can't verify CSRF token authenticity.",
});
}
});
});
});
177 changes: 174 additions & 3 deletions test/server/responses.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
import { notModified } from "../../src/server";
import {
badRequest,
forbidden,
json,
notFound,
notModified,
redirectBack,
serverError,
unauthorized,
unprocessableEntity,
} from "../../src";

let jsonContentType = "application/json; charset=utf-8";

describe("Responses", () => {
describe("Not Modified", () => {
test("Shoudl return Response with status 304", () => {
describe(json, () => {
it("returns a response with the JSON data", async () => {
const response = json({ framework: "Remix" } as const);
const body = await response.json();
expect(body.framework).toBe("Remix");
});
});

describe(redirectBack, () => {
it("uses the referer if available", () => {
const request = new Request("/", {
headers: { Referer: "/referer" },
});
const response = redirectBack(request, { fallback: "/fallback" });
expect(response.headers.get("Location")).toBe("/referer");
});

it("uses the fallback if referer is not available", () => {
const request = new Request("/");
const response = redirectBack(request, { fallback: "/fallback" });
expect(response.headers.get("Location")).toBe("/fallback");
});
});

describe(notModified, () => {
test("Should return Response with status 304", () => {
let response = notModified();
expect(response).toEqual(new Response("", { status: 304 }));
});
Expand All @@ -19,4 +55,139 @@ describe("Responses", () => {
);
});
});

describe(badRequest, () => {
test("Should return Response with status 404", () => {
let response = badRequest({});
expect(response).toEqual(
new Response("{}", {
status: 400,
headers: { "Content-Type": jsonContentType },
})
);
});

test("Should allow changing the Response headers", () => {
let response = badRequest({}, { headers: { "X-Test": "it worked" } });
expect(response).toEqual(
new Response("{}", {
status: 400,
headers: { "X-Test": "it worked", "Content-Type": jsonContentType },
})
);
});
});

describe(unauthorized, () => {
test("Should return Response with status 401", () => {
let response = unauthorized({});
expect(response).toEqual(
new Response("{}", {
status: 401,
headers: { "Content-Type": jsonContentType },
})
);
});

test("Should allow changing the Response headers", () => {
let response = unauthorized({}, { headers: { "X-Test": "it worked" } });
expect(response).toEqual(
new Response("{}", {
status: 401,
headers: { "X-Test": "it worked", "Content-Type": jsonContentType },
})
);
});
});

describe(forbidden, () => {
test("Should return Response with status 403", () => {
let response = forbidden({});
expect(response).toEqual(
new Response("{}", {
status: 403,
headers: { "Content-Type": jsonContentType },
})
);
});

test("Should allow changing the Response headers", () => {
let response = forbidden({}, { headers: { "X-Test": "it worked" } });
expect(response).toEqual(
new Response("{}", {
status: 403,
headers: { "X-Test": "it worked", "Content-Type": jsonContentType },
})
);
});
});

describe(notFound, () => {
test("Should return Response with status 404", () => {
let response = notFound({});
expect(response).toEqual(
new Response("{}", {
status: 404,
headers: { "Content-Type": jsonContentType },
})
);
});

test("Should allow changing the Response headers", () => {
let response = notFound({}, { headers: { "X-Test": "it worked" } });
expect(response).toEqual(
new Response("{}", {
status: 404,
headers: { "X-Test": "it worked", "Content-Type": jsonContentType },
})
);
});
});

describe(unprocessableEntity, () => {
test("Should return Response with status 422", () => {
let response = unprocessableEntity({});
expect(response).toEqual(
new Response("{}", {
status: 422,
headers: { "Content-Type": jsonContentType },
})
);
});

test("Should allow changing the Response headers", () => {
let response = unprocessableEntity(
{},
{ headers: { "X-Test": "it worked" } }
);
expect(response).toEqual(
new Response("{}", {
status: 422,
headers: { "X-Test": "it worked", "Content-Type": jsonContentType },
})
);
});
});

describe(serverError, () => {
test("Should return Response with status 500", () => {
let response = serverError({});
expect(response).toEqual(
new Response("{}", {
status: 500,
headers: { "Content-Type": jsonContentType },
})
);
});

test("Should allow changing the Response headers", () => {
let response = serverError({}, { headers: { "X-Test": "it worked" } });
expect(response).toEqual(
new Response("{}", {
status: 500,
headers: { "X-Test": "it worked", "Content-Type": jsonContentType },
})
);
});
});
});

0 comments on commit 3565b34

Please sign in to comment.