Initial commit: Next.js scaffold, Request entity, Prisma+Zod, Tailwind v4+shadcn
- Next.js 14 (TS, App Router) monolith - Request entity (Draft state) via Prisma + Postgres, validated with Zod - ESLint configured - Tailwind v4 + shadcn/ui (new-york-v4 style) - Vitest test suite (6 tests, runs against throwaway Postgres DB per file) - Multi-agent dev workflow docs in CLAUDE.md and .claude/agents/
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { GET, POST } from "@/app/api/requests/route";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
function postRequest(body: unknown): Request {
|
||||
return new Request("http://localhost/api/requests", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
// Each test starts from an empty table so listing/order assertions are
|
||||
// deterministic.
|
||||
await prisma.request.deleteMany();
|
||||
});
|
||||
|
||||
describe("POST /api/requests", () => {
|
||||
it("creates a request in Draft status on valid input", async () => {
|
||||
const res = await POST(
|
||||
postRequest({ employee_id: "emp-1", type: "asset", reason: "New laptop for onboarding" })
|
||||
);
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
const body = await res.json();
|
||||
|
||||
expect(body).toMatchObject({
|
||||
employee_id: "emp-1",
|
||||
type: "asset",
|
||||
reason: "New laptop for onboarding",
|
||||
status: "Draft",
|
||||
});
|
||||
expect(typeof body.id).toBe("string");
|
||||
expect(body.id.length).toBeGreaterThan(0);
|
||||
expect(typeof body.created_at).toBe("string");
|
||||
expect(Number.isNaN(Date.parse(body.created_at))).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects requests with missing required fields", async () => {
|
||||
const res = await POST(postRequest({ employee_id: "", type: "asset", reason: "" }));
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
const body = await res.json();
|
||||
expect(typeof body.error).toBe("string");
|
||||
expect(body.error.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("rejects requests missing the reason field entirely", async () => {
|
||||
const res = await POST(postRequest({ employee_id: "emp-1", type: "asset" }));
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
const body = await res.json();
|
||||
expect(typeof body.error).toBe("string");
|
||||
});
|
||||
|
||||
it("rejects an invalid type value", async () => {
|
||||
const res = await POST(
|
||||
postRequest({ employee_id: "emp-1", type: "not-a-real-type", reason: "Something" })
|
||||
);
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
const body = await res.json();
|
||||
expect(body.error).toMatch(/type/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/requests", () => {
|
||||
it("lists created requests, newest first", async () => {
|
||||
await POST(postRequest({ employee_id: "emp-1", type: "asset", reason: "First request" }));
|
||||
// Ensure a distinct created_at ordering even on fast filesystems/clocks.
|
||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||
await POST(postRequest({ employee_id: "emp-2", type: "license", reason: "Second request" }));
|
||||
|
||||
const res = await GET();
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
const body = await res.json();
|
||||
expect(Array.isArray(body)).toBe(true);
|
||||
expect(body).toHaveLength(2);
|
||||
|
||||
// newest first
|
||||
expect(body[0]).toMatchObject({ employee_id: "emp-2", type: "license", status: "Draft" });
|
||||
expect(body[1]).toMatchObject({ employee_id: "emp-1", type: "asset", status: "Draft" });
|
||||
});
|
||||
|
||||
it("returns an empty list when there are no requests", async () => {
|
||||
const res = await GET();
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user