Skip to content

Commit

Permalink
Add test for withValidation
Browse files Browse the repository at this point in the history
  • Loading branch information
mpppk committed Dec 13, 2024
1 parent e88bf92 commit 2a2097d
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 1 deletion.
7 changes: 6 additions & 1 deletion pkgs/typed-api-spec/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ const depRules = [
},
{
module: "src/zod",
allowReferenceFrom: [...dRef, "src/express/zod", "src/fastify/zod"],
allowReferenceFrom: [
...dRef,
"src/express/zod",
"src/fastify/zod",
"**/*.test.ts",
],
allowSameModule: true,
},
];
Expand Down
105 changes: 105 additions & 0 deletions pkgs/typed-api-spec/src/fetch/validation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { withValidation } from "./validation";
import { describe, it, expect, vi } from "vitest";
import { newZodValidator, ZodApiEndpoints } from "../zod";
import { z } from "zod";
import { toLCObj } from "../utils";

const newFetch = (
body: Record<string, string>,
headers: Record<string, string>,
) => {
return vi.fn(
() => new Response(JSON.stringify(body), { headers: toLCObj(headers) }),
) as unknown as typeof fetch;
};
describe("withValidation", () => {
const pathMap = {
"/:paramsName": {
get: {
query: z.object({ queryName: z.string() }),
headers: z.object(toLCObj({ headersName: z.string() })),
params: z.object({ paramsName: z.string() }),
responses: {
200: {
body: z.object({ bodyNameRes: z.string() }),
headers: z.object(toLCObj({ headersNameRes: z.string() })),
},
400: { body: z.object({ message: z.string() }) },
},
},
post: {
body: z.object({ bodyName: z.string() }),
responses: { 200: { body: z.object({ bodyNameRes: z.string() }) } },
},
},
} satisfies ZodApiEndpoints;
const path = "/p?queryName=q";
describe("invalid request", () => {
const ft = newFetch({ bodyNameRes: "b" }, { headersNameRes: "h" });
const { req, res } = newZodValidator(pathMap);
const fetchV = withValidation(ft, pathMap, req, res);
it("query", async () => {
await expect(() =>
fetchV("/p", { headers: { headersName: "h" } }),
).rejects.toThrow(
'{"reason":"query","issues":[{"code":"invalid_type","expected":"string","received":"undefined","path":["queryName"],"message":"Required"}],"name":"ZodError"}',
);
});
it("headers", async () => {
await expect(() => fetchV(path, { headers: {} })).rejects.toThrow(
'{"reason":"headers","issues":[{"code":"invalid_type","expected":"string","received":"undefined","path":["headersname"],"message":"Required"}],"name":"ZodError"}',
);
});
it("body", async () => {
await expect(() =>
fetchV(path, {
method: "post",
body: "{}",
headers: { headersName: "h" },
}),
).rejects.toThrow(
'{"reason":"body","issues":[{"code":"invalid_type","expected":"string","received":"undefined","path":["bodyName"],"message":"Required"}],"name":"ZodError"}',
);
});
});
describe("status code 200", () => {
it("valid response", async () => {
const ft = newFetch({ bodyNameRes: "b" }, { headersNameRes: "h" });
const { req, res } = newZodValidator(pathMap);
const fetchV = withValidation(ft, pathMap, req, res);
const r = await fetchV(path, { headers: { headersName: "h" } });
expect(await r.json()).toEqual({ bodyNameRes: "b" });
});
describe("invalid response", () => {
it("body", async () => {
const ft = newFetch({ invalid: "invalid" }, { headersNameRes: "h" });
const { req, res } = newZodValidator(pathMap);
const fetchV = withValidation(ft, pathMap, req, res);
await expect(() =>
fetchV(path, { headers: { headersName: "h" } }),
).rejects.toThrow(
'{"reason":"body","issues":[{"code":"invalid_type","expected":"string","received":"undefined","path":["bodyNameRes"],"message":"Required"}],"name":"ZodError"}',
);
});
it("headers", async () => {
const ft = newFetch({ bodyNameRes: "b" }, { invalid: "invalid" });
const { req, res } = newZodValidator(pathMap);
const fetchV = withValidation(ft, pathMap, req, res);
await expect(() =>
fetchV(path, { headers: { headersName: "h" } }),
).rejects.toThrow(
'{"reason":"headers","issues":[{"code":"invalid_type","expected":"string","received":"undefined","path":["headersnameres"],"message":"Required"}],"name":"ZodError"}',
);
});
});
});
describe("status code 400", () => {
it("valid response", async () => {
const ft = newFetch({ message: "m" }, { headersNameRes: "h" });
const { req, res } = newZodValidator(pathMap);
const fetchV = withValidation(ft, pathMap, req, res);
const r = await fetchV(path, { headers: { headersName: "h" } });
expect(await r.json()).toEqual({ message: "m" });
});
});
});
2 changes: 2 additions & 0 deletions pkgs/typed-api-spec/src/fetch/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ const toInput =
method: init?.method?.toLowerCase() ?? "get",
headers: headersToRecord(init?.headers ?? {}),
params: cp.params,
// FIXME: JSON APIじゃない時どうするか
body: init?.body ? JSON.parse(init.body.toString()) : undefined,
query,
};
};
Expand Down
11 changes: 11 additions & 0 deletions pkgs/typed-api-spec/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,14 @@ export function tupleIteratorToObject<T extends string | number | symbol, U>(
}
return result;
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const toLCObj = <Keys extends string, Values>(
obj: Record<Keys, Values>,
): Record<Lowercase<Keys>, Values> => {
const newObj: Partial<Record<Lowercase<Keys>, Values>> = {};
for (const key in obj) {
newObj[key.toLowerCase() as Lowercase<Keys>] = obj[key];
}
return newObj as Record<Lowercase<Keys>, Values>;
};

0 comments on commit 2a2097d

Please sign in to comment.