fix: update Request validation/tests for Employee FK relation

- createRequestSchema now requires employee_id to be a UUID-format
  string (was a free-text non-empty check), matching the new FK to
  Employee in schema.prisma.
- createRequest() now returns a CreateRequestResult discriminated
  union and catches Prisma P2003 (FK violation), returning a friendly
  ok:false error instead of a raw 500 when employee_id does not
  reference an existing Employee. POST /api/requests branches on
  created.ok and returns 400 in that case.
- tests/requests.test.ts: every test that used to POST a fake string
  employee_id (emp-1, emp-2) now creates a real Employee first via
  createTestEmployee() and uses its id, since employee_id is now a
  real FK. Added coverage for a syntactically invalid UUID and for a
  well-formed but non-existent UUID (expects the friendly 400 from
  the P2003 catch, not a raw 500) - the exact case this retrofit was
  meant to protect against.
This commit is contained in:
2026-07-31 15:14:55 +07:00
parent 49d96b77e8
commit 7e69efa4a8
3 changed files with 118 additions and 22 deletions
+5 -1
View File
@@ -32,7 +32,11 @@ export async function POST(request: Request): Promise<NextResponse> {
}
const created = await createRequest(validation.value);
return NextResponse.json(created, { status: 201 });
if (!created.ok) {
return NextResponse.json({ error: created.error }, { status: 400 });
}
return NextResponse.json(created.value, { status: 201 });
}
export async function GET(): Promise<NextResponse> {
+36 -10
View File
@@ -1,4 +1,5 @@
import { z } from "zod";
import { Prisma } from "@prisma/client";
import { prisma } from "./prisma";
export type RequestType = "asset" | "license";
@@ -40,7 +41,14 @@ function nonEmptyString(message: string) {
}
const createRequestSchema = z.object({
employee_id: nonEmptyString("employee_id is required and must be a non-empty string"),
// Now a real FK to Employee (see schema.prisma) — only a UUID *format*
// check here — whether it references a real employee is left to Prisma's
// own FK constraint at insert time (see createRequest()'s P2003 handling
// below), rather than an extra pre-check query. Mirrors the same pattern
// used for Employee.managerId in src/lib/employees.ts.
employee_id: z
.string({ required_error: "employee_id is required and must be a valid UUID string" })
.uuid("employee_id is required and must be a valid UUID string"),
reason: nonEmptyString("reason is required and must be a non-empty string"),
type: z.enum(["asset", "license"], {
errorMap: () => ({
@@ -85,15 +93,33 @@ function toRecord(row: {
};
}
export async function createRequest(input: CreateRequestInput): Promise<RequestRecord> {
const created = await prisma.request.create({
data: {
employeeId: input.employee_id,
type: input.type,
reason: input.reason,
},
});
return toRecord(created);
export type CreateRequestResult =
| { ok: true; value: RequestRecord }
| { ok: false; error: string };
/**
* Creates a Request. If employee_id doesn't reference an existing Employee,
* Prisma raises a known error (code P2003, FK violation) — caught here and
* translated into the same `{ok:false,error}` shape as input validation
* failures, rather than letting a raw Prisma error leak to the API layer.
* Mirrors src/lib/employees.ts's createEmployee() pattern for managerId.
*/
export async function createRequest(input: CreateRequestInput): Promise<CreateRequestResult> {
try {
const created = await prisma.request.create({
data: {
employeeId: input.employee_id,
type: input.type,
reason: input.reason,
},
});
return { ok: true, value: toRecord(created) };
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2003") {
return { ok: false, error: "employee_id does not reference an existing employee" };
}
throw error;
}
}
export async function listRequests(): Promise<RequestRecord[]> {