test: add Area, InventoryScan and bot API tests
369/369 tests pass (35 new), tsc --noEmit clean, next lint clean. Not yet run through the repos code-reviewer subagent gate (this session operated via SSH, cwd was not the project dir so .claude/agents/ subagents were not recognized) -- see CLAUDE.md note. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
77dbd15450
commit
84e6db7406
@@ -0,0 +1,118 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { Mock } from "vitest";
|
||||
import type { Session } from "next-auth";
|
||||
|
||||
// Mirrors tests/employees.test.ts's @/auth mocking pattern.
|
||||
vi.mock("@/auth", () => ({ auth: vi.fn() }));
|
||||
|
||||
import { GET, POST } from "@/app/api/areas/route";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
const mockedAuth = auth as unknown as Mock<() => Promise<Session | null>>;
|
||||
|
||||
const testSession: Session = {
|
||||
user: { id: "test-user-id" },
|
||||
expires: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
||||
};
|
||||
|
||||
function postRequest(body: unknown): Request {
|
||||
return new Request("http://localhost/api/areas", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
await prisma.area.deleteMany();
|
||||
mockedAuth.mockResolvedValue(testSession);
|
||||
});
|
||||
|
||||
describe("POST /api/areas", () => {
|
||||
it("creates an area with no team on valid input", async () => {
|
||||
const res = await POST(postRequest({ name: "Phong hop A" }));
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
const body = await res.json();
|
||||
expect(body).toMatchObject({ name: "Phong hop A", team: null });
|
||||
expect(typeof body.id).toBe("string");
|
||||
});
|
||||
|
||||
it("creates an area with a team", async () => {
|
||||
const res = await POST(postRequest({ name: "Seat 12", team: "Backend" }));
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
const body = await res.json();
|
||||
expect(body).toMatchObject({ name: "Seat 12", team: "Backend" });
|
||||
});
|
||||
|
||||
it("rejects an empty name", async () => {
|
||||
const res = await POST(postRequest({ name: "" }));
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
const body = await res.json();
|
||||
expect(typeof body.error).toBe("string");
|
||||
});
|
||||
|
||||
// See src/lib/areas.ts's createOrGetArea() comment: this is deliberate,
|
||||
// not accidental idempotency — the bot's "pick from list or type new"
|
||||
// flow must be able to re-submit a name it just showed as an existing
|
||||
// option without erroring.
|
||||
it("returns the existing row (200-equivalent 201, same id) instead of erroring on a duplicate name", async () => {
|
||||
const first = await POST(postRequest({ name: "Kho tang 2" }));
|
||||
const firstBody = await first.json();
|
||||
|
||||
const second = await POST(postRequest({ name: "Kho tang 2" }));
|
||||
expect(second.status).toBe(201);
|
||||
const secondBody = await second.json();
|
||||
|
||||
expect(secondBody.id).toBe(firstBody.id);
|
||||
|
||||
const all = await prisma.area.findMany();
|
||||
expect(all).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("rejects an invalid JSON body", async () => {
|
||||
const req = new Request("http://localhost/api/areas", {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: "not json",
|
||||
});
|
||||
const res = await POST(req);
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /api/areas", () => {
|
||||
it("lists areas alphabetically by name", async () => {
|
||||
await POST(postRequest({ name: "Zeta" }));
|
||||
await POST(postRequest({ name: "Alpha" }));
|
||||
|
||||
const res = await GET();
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body.map((a: { name: string }) => a.name)).toEqual(["Alpha", "Zeta"]);
|
||||
});
|
||||
|
||||
it("returns an empty list when there are no areas", async () => {
|
||||
const res = await GET();
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.json()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("auth gating on /api/areas", () => {
|
||||
it("POST /api/areas returns 401 without a session", async () => {
|
||||
mockedAuth.mockResolvedValue(null);
|
||||
const res = await POST(postRequest({ name: "Phong hop A" }));
|
||||
expect(res.status).toBe(401);
|
||||
expect(await prisma.area.findMany()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("GET /api/areas returns 401 without a session", async () => {
|
||||
mockedAuth.mockResolvedValue(null);
|
||||
const res = await GET();
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user