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 createAssetUseCase: typeof import("@/use-cases/asset.use-cases").createAssetUseCase let updateAssetUseCase: typeof import("@/use-cases/asset.use-cases").updateAssetUseCase beforeAll(async () => { await startIntegrationTestDatabase() const prismaModule = await import("@/lib/prisma") const assetUseCases = await import("@/use-cases/asset.use-cases") prisma = prismaModule.prisma createAssetUseCase = assetUseCases.createAssetUseCase updateAssetUseCase = assetUseCases.updateAssetUseCase }) beforeEach(async () => { await resetIntegrationTestDatabase(prisma) }) afterAll(async () => { await prisma?.$disconnect() await stopIntegrationTestDatabase() }) describe("asset use-cases", () => { it("creates an available asset, increments item stock, and records an IN movement", async () => { const actor = await createTestUser(prisma) const item = await createTestItem(prisma, { stock: 0 }) const result = await createAssetUseCase({ actorId: actor.id, itemId: item.id, serialNumber: "ASSET-AVAILABLE-001", deliveryNote: "DN-001", status: "AVAILABLE", notes: "Ready to assign", }) expect(result.success).toBe(true) if (!result.success) throw new Error("Expected asset creation success") const [asset, updatedItem, movements] = await Promise.all([ prisma.asset.findUniqueOrThrow({ where: { id: result.assetId } }), prisma.item.findUniqueOrThrow({ where: { id: item.id } }), prisma.movement.findMany({ orderBy: [{ createdAt: "asc" }, { id: "asc" }], }), ]) expect(asset).toMatchObject({ itemId: item.id, serialNumber: "ASSET-AVAILABLE-001", deliveryNote: "DN-001", status: "AVAILABLE", notes: "Ready to assign", }) expect(updatedItem.stock).toBe(1) expect(movements).toHaveLength(1) expect(movements[0]).toMatchObject({ type: "IN", itemId: item.id, assetId: result.assetId, quantity: 1, userId: actor.id, }) }) it("creates an assigned asset with assignment and ASSIGNMENT movement", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 0 }) const result = await createAssetUseCase({ actorId: actor.id, itemId: item.id, serialNumber: "ASSET-ASSIGNED-001", status: "ASSIGNED", personId: person.id, }) expect(result.success).toBe(true) if (!result.success) throw new Error("Expected asset creation success") const [asset, updatedItem, assignment, movements] = await Promise.all([ prisma.asset.findUniqueOrThrow({ where: { id: result.assetId } }), prisma.item.findUniqueOrThrow({ where: { id: item.id } }), prisma.assignment.findFirstOrThrow({ where: { assetId: result.assetId, returnDate: null }, }), prisma.movement.findMany({ orderBy: [{ createdAt: "asc" }, { id: "asc" }], }), ]) expect(asset).toMatchObject({ itemId: item.id, serialNumber: "ASSET-ASSIGNED-001", status: "ASSIGNED", }) expect(updatedItem.stock).toBe(0) expect(assignment).toMatchObject({ itemId: item.id, assetId: result.assetId, personId: person.id, quantity: 1, createdBy: actor.id, returnDate: null, }) expect(movements).toHaveLength(1) expect(movements[0]).toMatchObject({ type: "ASSIGNMENT", itemId: item.id, assetId: result.assetId, personId: person.id, assignmentId: assignment.id, quantity: 1, userId: actor.id, }) }) it("moves an available asset to assigned and back to available", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 0 }) const created = await createAssetUseCase({ actorId: actor.id, itemId: item.id, serialNumber: "ASSET-LIFECYCLE-001", status: "AVAILABLE", }) expect(created.success).toBe(true) if (!created.success) throw new Error("Expected asset creation success") await expect( updateAssetUseCase({ actorId: actor.id, id: created.assetId, itemId: item.id, serialNumber: "ASSET-LIFECYCLE-001", status: "ASSIGNED", personId: person.id, }), ).resolves.toEqual({ success: true }) const [assignedAsset, assignedItem, activeAssignment] = await Promise.all([ prisma.asset.findUniqueOrThrow({ where: { id: created.assetId } }), prisma.item.findUniqueOrThrow({ where: { id: item.id } }), prisma.assignment.findFirstOrThrow({ where: { assetId: created.assetId, returnDate: null }, }), ]) expect(assignedAsset.status).toBe("ASSIGNED") expect(assignedItem.stock).toBe(0) expect(activeAssignment).toMatchObject({ itemId: item.id, assetId: created.assetId, personId: person.id, quantity: 1, }) await expect( updateAssetUseCase({ actorId: actor.id, id: created.assetId, itemId: item.id, serialNumber: "ASSET-LIFECYCLE-001", status: "AVAILABLE", }), ).resolves.toEqual({ success: true }) const [availableAsset, availableItem, returnedAssignment, movements] = await Promise.all([ prisma.asset.findUniqueOrThrow({ where: { id: created.assetId } }), prisma.item.findUniqueOrThrow({ where: { id: item.id } }), prisma.assignment.findUniqueOrThrow({ where: { id: activeAssignment.id }, }), prisma.movement.findMany({ orderBy: [{ createdAt: "asc" }, { id: "asc" }], }), ]) expect(availableAsset.status).toBe("AVAILABLE") expect(availableItem.stock).toBe(1) expect(returnedAssignment.returnDate).toBeInstanceOf(Date) expect(returnedAssignment).toMatchObject({ itemId: null, assetId: null, personId: null, quantity: null, }) expect(movements).toHaveLength(3) expect(movements.map((movement) => movement.type)).toEqual([ "IN", "ASSIGNMENT", "RETURN", ]) expect(movements[1]).toMatchObject({ itemId: item.id, assetId: created.assetId, personId: person.id, assignmentId: activeAssignment.id, quantity: 1, userId: actor.id, }) expect(movements[2]).toMatchObject({ itemId: item.id, assetId: created.assetId, assignmentId: activeAssignment.id, quantity: 1, userId: actor.id, }) }) it("returns an active assignment without restoring stock when an assigned asset moves to a terminal status", async () => { const actor = await createTestUser(prisma) const person = await createTestPerson(prisma) const item = await createTestItem(prisma, { stock: 0 }) const created = await createAssetUseCase({ actorId: actor.id, itemId: item.id, serialNumber: "ASSET-BROKEN-001", status: "ASSIGNED", personId: person.id, }) expect(created.success).toBe(true) if (!created.success) throw new Error("Expected asset creation success") const activeAssignment = await prisma.assignment.findFirstOrThrow({ where: { assetId: created.assetId, returnDate: null }, }) await expect( updateAssetUseCase({ actorId: actor.id, id: created.assetId, itemId: item.id, serialNumber: "ASSET-BROKEN-001", status: "BROKEN", }), ).resolves.toEqual({ success: true }) const [asset, itemAfterUpdate, returnedAssignment, movements] = await Promise.all([ prisma.asset.findUniqueOrThrow({ where: { id: created.assetId } }), prisma.item.findUniqueOrThrow({ where: { id: item.id } }), prisma.assignment.findUniqueOrThrow({ where: { id: activeAssignment.id }, }), prisma.movement.findMany({ orderBy: [{ createdAt: "asc" }, { id: "asc" }], }), ]) expect(asset.status).toBe("BROKEN") expect(itemAfterUpdate.stock).toBe(0) expect(returnedAssignment.returnDate).toBeInstanceOf(Date) expect(movements).toHaveLength(2) expect(movements.map((movement) => movement.type)).toEqual([ "ASSIGNMENT", "RETURN", ]) expect(movements[1]).toMatchObject({ type: "RETURN", itemId: item.id, assetId: created.assetId, personId: person.id, assignmentId: activeAssignment.id, quantity: 1, userId: actor.id, }) }) })