Files
stock-manager/tests/integration/use-cases/item.use-cases.test.ts
T
aferrer f2b9239d82 test: add initial unit integration and e2e coverage
Adds the initial testing baseline for the project:

   Unit coverage:
   - Zod schemas for items, assignments, movements, categories, auth, recipients, users, and assets
   - password hashing and verification helpers
   - auth role helper functions

   Integration coverage with PostgreSQL Testcontainers:
   - item use-cases: create, duplicate names, delete constraints
   - assignment use-cases: create, insufficient stock, return, double return
   - asset use-cases: available/assigned creation and lifecycle transitions
   - user use-cases: create/update, uniqueness, admin safeguards, password reset
   - category use-cases: create/update/delete constraints
   - recipient use-cases: create/update and uniqueness constraints

   E2E smoke coverage with Playwright:
   - unauthenticated redirect to login
   - seeded admin login
   - dashboard load
   - admin users page
   - inventory items page
   - assignments page

   Also configures:
   - Vitest
   - Playwright
   - PostgreSQL Testcontainers helpers
   - deterministic E2E admin bootstrap
   - test artifact ignores

   Validation:
   - bun run test: 9 files / 37 tests passed
   - bun run test:e2e: 3 passed
   - bunx tsc --noEmit: passed
   - bunx prisma validate: passed
2026-06-07 04:14:01 +02:00

135 lines
3.5 KiB
TypeScript

import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"
import type { PrismaClient } from "@/generated/prisma/client"
import { createTestCategory, createTestUser } from "../helpers/factories"
import {
resetIntegrationTestDatabase,
startIntegrationTestDatabase,
stopIntegrationTestDatabase,
} from "../helpers/test-db"
let prisma: PrismaClient
let createItemUseCase: typeof import("@/use-cases/item.use-cases").createItemUseCase
let deleteItemUseCase: typeof import("@/use-cases/item.use-cases").deleteItemUseCase
beforeAll(async () => {
await startIntegrationTestDatabase()
const prismaModule = await import("@/lib/prisma")
const itemUseCases = await import("@/use-cases/item.use-cases")
prisma = prismaModule.prisma
createItemUseCase = itemUseCases.createItemUseCase
deleteItemUseCase = itemUseCases.deleteItemUseCase
})
beforeEach(async () => {
await resetIntegrationTestDatabase(prisma)
})
afterAll(async () => {
await prisma?.$disconnect()
await stopIntegrationTestDatabase()
})
describe("item use-cases", () => {
it("creates an item with initial stock and records an IN movement", async () => {
const actor = await createTestUser(prisma)
const category = await createTestCategory(prisma)
const result = await createItemUseCase({
actorId: actor.id,
name: "Laptop",
categoryId: category.id,
stock: 3,
})
expect(result).toEqual({ success: true })
const item = await prisma.item.findUnique({
where: { name: "Laptop" },
include: { movements: true },
})
expect(item).toMatchObject({
name: "Laptop",
categoryId: category.id,
stock: 3,
deletedAt: null,
})
expect(item?.movements).toHaveLength(1)
expect(item?.movements[0]).toMatchObject({
type: "IN",
quantity: 3,
userId: actor.id,
})
})
it("rejects duplicate item names", async () => {
const actor = await createTestUser(prisma)
const category = await createTestCategory(prisma)
await createItemUseCase({
actorId: actor.id,
name: "Monitor",
categoryId: category.id,
stock: 0,
})
const duplicate = await createItemUseCase({
actorId: actor.id,
name: "Monitor",
categoryId: category.id,
stock: 0,
})
expect(duplicate).toEqual({
success: false,
errors: {
name: ["An item with this name already exists"],
},
})
})
it("blocks deleting items with stock and soft deletes empty items", async () => {
const actor = await createTestUser(prisma)
const category = await createTestCategory(prisma)
await createItemUseCase({
actorId: actor.id,
name: "Keyboard",
categoryId: category.id,
stock: 2,
})
const stockedItem = await prisma.item.findUniqueOrThrow({
where: { name: "Keyboard" },
})
await expect(deleteItemUseCase(stockedItem.id)).resolves.toEqual({
success: false,
errors: { id: ["Item has stock, you cannot delete it"] },
})
await createItemUseCase({
actorId: actor.id,
name: "Mouse",
categoryId: category.id,
stock: 0,
})
const emptyItem = await prisma.item.findUniqueOrThrow({
where: { name: "Mouse" },
})
await expect(deleteItemUseCase(emptyItem.id)).resolves.toEqual({
success: true,
})
const deletedItem = await prisma.item.findUniqueOrThrow({
where: { id: emptyItem.id },
})
expect(deletedItem.deletedAt).toBeInstanceOf(Date)
})
})