86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
import { errorResponseSchema, routes } from "@deadhead/schema";
|
|
import Fastify from "fastify";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { buildApp } from "../src/app";
|
|
import { createRecordingQueue } from "../src/jobs/queue";
|
|
import { registerErrorHandler } from "../src/http/errors";
|
|
import { registerRoute } from "../src/http/route";
|
|
import { useTestDatabase } from "./db";
|
|
import { testConfig } from "./helpers";
|
|
|
|
const database = useTestDatabase();
|
|
|
|
describe("request validation from the route catalog", () => {
|
|
let app: ReturnType<typeof buildApp>;
|
|
beforeEach(() => {
|
|
app = buildApp(testConfig(), { database: database(), jobs: createRecordingQueue() });
|
|
});
|
|
afterEach(() => app.close());
|
|
|
|
const expectValidationError = (response: { statusCode: number; json(): unknown }) => {
|
|
expect(response.statusCode).toBe(400);
|
|
expect(errorResponseSchema.parse(response.json()).error.code).toBe("validation-error");
|
|
};
|
|
|
|
it("rejects unexpected fields on a route that takes none", async () => {
|
|
expectValidationError(
|
|
await app.inject({ method: "POST", url: "/api/guest", payload: { surprise: true } }),
|
|
);
|
|
});
|
|
|
|
it("rejects a body that is not valid JSON", async () => {
|
|
expectValidationError(
|
|
await app.inject({
|
|
method: "POST",
|
|
url: "/api/guest",
|
|
headers: { "content-type": "application/json" },
|
|
payload: "{not json",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("rejects unexpected query parameters on an authenticated route before checking the session", async () => {
|
|
expectValidationError(await app.inject({ method: "GET", url: "/api/state?evil=1" }));
|
|
});
|
|
|
|
it("still answers valid requests", async () => {
|
|
expect((await app.inject({ method: "GET", url: "/api/time" })).statusCode).toBe(200);
|
|
});
|
|
|
|
it("returns not-found in the error format for unknown endpoints", async () => {
|
|
const response = await app.inject({ method: "GET", url: "/api/nope" });
|
|
expect(response.statusCode).toBe(404);
|
|
expect(errorResponseSchema.parse(response.json()).error.code).toBe("not-found");
|
|
});
|
|
});
|
|
|
|
describe("response validation", () => {
|
|
const build = (validateResponses: boolean) => {
|
|
const app = Fastify({ logger: false });
|
|
registerErrorHandler(app);
|
|
// A handler that breaks the server-time response schema.
|
|
registerRoute(
|
|
app,
|
|
routes.getServerTime,
|
|
{ requirePlayer: async () => "unused", validateResponses },
|
|
async () => ({ realTime: "not a number" }) as never,
|
|
);
|
|
return app;
|
|
};
|
|
|
|
it("fails a response that violates its schema in test mode", async () => {
|
|
const app = build(true);
|
|
const response = await app.inject({ method: "GET", url: "/api/time" });
|
|
expect(response.statusCode).toBe(500);
|
|
expect(errorResponseSchema.parse(response.json()).error.code).toBe("internal-error");
|
|
await app.close();
|
|
});
|
|
|
|
it("does not check responses when validation is off (production)", async () => {
|
|
const app = build(false);
|
|
const response = await app.inject({ method: "GET", url: "/api/time" });
|
|
expect(response.statusCode).toBe(200);
|
|
await app.close();
|
|
});
|
|
});
|