import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest" import type { PrismaClient } from "@/generated/prisma/client" import { createTestItem, createTestPerson, createTestUser, } from "../helpers/factories" import { resetIntegrationTestDatabase, startIntegrationTestDatabase, stopIntegrationTestDatabase, } from "../helpers/test-db" let prisma: PrismaClient let createAssignmentUseCase: typeof import("@/use-cases/assignment.use-cases").createAssignmentUseCase let returnAssignmentUseCase: typeof import("@/use-cases/assignment.use-cases").returnAssignmentUseCase beforeAll(async () => { await startIntegrationTestDatabase() const prismaModule = await import("@/lib/prisma") const assignmentUseCases = await import("@/use-cases/assignment.use-cases") prisma = prismaModule.prisma createAssignmentUseCase = assignmentUseCases.createAssignmentUseCase returnAssignmentUseCase = assignmentUseCases.returnAssignmentUseCase }) beforeEach(async () => { await resetIntegrationTestDatabase(prisma) }) afterAll(async () => { await prisma?.$disconnect() await stopIntegrationTestDatabase() }) describe("assignment use-cases", () => { it("creates an assignment, decrements stock, and records an ASSIGNMENT movement", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 5 }) const assignmentDate = new Date("2026-01-01T00:00:00.000Z") const result = await createAssignmentUseCase({ actorId: actor.id, itemId: item.id, personId: person.id, quantity: 2, assignmentDate, notes: "Initial assignment", }) expect(result.success).toBe(true) if (!result.success) throw new Error("Expected assignment creation success") const [updatedItem, assignment, movements] = await Promise.all([ prisma.item.findUniqueOrThrow({ where: { id: item.id } }), prisma.assignment.findUniqueOrThrow({ where: { id: result.assignmentId }, include: { stockLines: true }, }), prisma.inventoryMovement.findMany({ include: { stockLines: true }, orderBy: [{ createdAt: "asc" }, { id: "asc" }], }), ]) expect(updatedItem.stock).toBe(3) expect(assignment).toMatchObject({ personId: person.id, notes: "Initial assignment", createdById: actor.id, closedAt: null, }) expect(assignment.assignedAt).toEqual(assignmentDate) expect(assignment.stockLines[0]).toMatchObject({ itemId: item.id, quantity: 2, returnedQuantity: 0, }) expect(movements).toHaveLength(1) expect(movements[0]).toMatchObject({ type: "ASSIGNMENT", assignmentId: result.assignmentId, performedById: actor.id, }) expect(movements[0].stockLines[0]).toMatchObject({ itemId: item.id, stockDelta: -2, }) }) it("rejects assignment creation when item stock is insufficient", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 1 }) const result = await createAssignmentUseCase({ actorId: actor.id, itemId: item.id, personId: person.id, quantity: 2, }) expect(result).toEqual({ success: false, errors: { quantity: ["Item does not have enough stock"], }, }) await expect( prisma.item.findUniqueOrThrow({ where: { id: item.id } }), ).resolves.toMatchObject({ stock: 1 }) await expect(prisma.assignment.count()).resolves.toBe(0) await expect(prisma.inventoryMovement.count()).resolves.toBe(0) }) it("returns an assignment, restores stock, closes it, and records a RETURN movement", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 4 }) const created = await createAssignmentUseCase({ actorId: actor.id, itemId: item.id, personId: person.id, quantity: 3, }) expect(created.success).toBe(true) if (!created.success) throw new Error("Expected assignment creation success") const returned = await returnAssignmentUseCase({ id: created.assignmentId, actorId: actor.id, }) expect(returned).toEqual({ success: true }) const [updatedItem, assignment, movements] = await Promise.all([ prisma.item.findUniqueOrThrow({ where: { id: item.id } }), prisma.assignment.findUniqueOrThrow({ where: { id: created.assignmentId }, include: { stockLines: true }, }), prisma.inventoryMovement.findMany({ include: { stockLines: true }, orderBy: [{ createdAt: "asc" }, { id: "asc" }], }), ]) expect(updatedItem.stock).toBe(4) expect(assignment.closedAt).toBeInstanceOf(Date) expect(assignment).toMatchObject({ personId: person.id, status: "RETURNED", }) expect(assignment.stockLines[0]).toMatchObject({ itemId: item.id, quantity: 3, returnedQuantity: 3, }) expect(movements).toHaveLength(2) expect(movements[0]).toMatchObject({ type: "ASSIGNMENT", assignmentId: created.assignmentId, performedById: actor.id, }) expect(movements[0].stockLines[0]).toMatchObject({ itemId: item.id, stockDelta: -3, }) expect(movements[1]).toMatchObject({ type: "RETURN", assignmentId: created.assignmentId, performedById: actor.id, }) expect(movements[1].stockLines[0]).toMatchObject({ itemId: item.id, stockDelta: 3, }) }) it("rejects returning the same assignment twice", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 2 }) const created = await createAssignmentUseCase({ actorId: actor.id, itemId: item.id, personId: person.id, quantity: 1, }) expect(created.success).toBe(true) if (!created.success) throw new Error("Expected assignment creation success") await expect( returnAssignmentUseCase({ id: created.assignmentId, actorId: actor.id }), ).resolves.toEqual({ success: true }) await expect( returnAssignmentUseCase({ id: created.assignmentId, actorId: actor.id }), ).resolves.toEqual({ success: false, errors: { id: ["Assignment already returned"] }, }) await expect(prisma.inventoryMovement.count()).resolves.toBe(2) }) })