fix architecture and write new tests

This commit is contained in:
root
2026-06-10 00:40:19 -04:00
parent 560da1cadf
commit 80a597bc10
377 changed files with 84020 additions and 1337 deletions
@@ -0,0 +1,71 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('../../lib/prisma', () => ({
prisma: {
complaint: { findMany: vi.fn(), count: vi.fn(), findFirst: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn() },
},
}))
import { prisma } from '../../lib/prisma'
import * as repo from './complaint.repo'
describe('complaint.repo edge behavior', () => {
beforeEach(() => vi.clearAllMocks())
it('lists complaints with company-scoped filters and deterministic ordering', async () => {
vi.mocked(prisma.complaint.findMany).mockResolvedValue([] as never)
vi.mocked(prisma.complaint.count).mockResolvedValue(0 as never)
await repo.findMany('company_1', { status: 'OPEN', severity: 'LEVEL_2' }, 40, 20)
const where = { companyId: 'company_1', status: 'OPEN', severity: 'LEVEL_2' }
expect(prisma.complaint.findMany).toHaveBeenCalledWith(expect.objectContaining({
where,
skip: 40,
take: 20,
orderBy: { createdAt: 'desc' },
}))
expect(prisma.complaint.count).toHaveBeenCalledWith({ where })
})
it('creates complaints with linked reservation, review and customer ids preserved', async () => {
await repo.create({
companyId: 'company_1',
reservationId: 'reservation_1',
reviewId: 'review_1',
customerId: 'customer_1',
severity: 'LEVEL_3',
category: 'DAMAGE_CLAIM',
subject: 'Damage dispute',
assignedTo: 'employee_1',
})
expect(prisma.complaint.create).toHaveBeenCalledWith(expect.objectContaining({
data: {
companyId: 'company_1',
reservationId: 'reservation_1',
reviewId: 'review_1',
customerId: 'customer_1',
severity: 'LEVEL_3',
category: 'DAMAGE_CLAIM',
subject: 'Damage dispute',
assignedTo: 'employee_1',
},
}))
})
it('updates by complaint id without accepting a company id bypass from caller data', async () => {
await repo.updateById('complaint_1', { status: 'RESOLVED', resolution: 'Refunded deposit' })
expect(prisma.complaint.update).toHaveBeenCalledWith(expect.objectContaining({
where: { id: 'complaint_1' },
data: { status: 'RESOLVED', resolution: 'Refunded deposit' },
}))
})
it('deletes by primary id', async () => {
await repo.deleteById('complaint_1')
expect(prisma.complaint.delete).toHaveBeenCalledWith({ where: { id: 'complaint_1' } })
})
})
@@ -0,0 +1,35 @@
import { describe, expect, it } from 'vitest'
import { createSchema, updateSchema, listQuerySchema } from './complaint.schemas'
describe('complaint schemas', () => {
it('defaults new complaints to the lowest severity when not provided', () => {
expect(createSchema.parse({ category: 'BILLING', subject: 'Incorrect invoice' })).toMatchObject({
category: 'BILLING',
subject: 'Incorrect invoice',
severity: 'LEVEL_1',
})
})
it('rejects empty complaint subjects', () => {
expect(() => createSchema.parse({ category: 'BILLING', subject: '' })).toThrow()
})
it('accepts lifecycle updates without requiring immutable creation fields', () => {
expect(updateSchema.parse({ status: 'RESOLVED', resolution: 'Refund issued' })).toEqual({
status: 'RESOLVED',
resolution: 'Refund issued',
})
})
it('coerces pagination and applies default page size for list queries', () => {
expect(listQuerySchema.parse({ page: '2', severity: 'LEVEL_3' })).toEqual({
page: 2,
pageSize: 20,
severity: 'LEVEL_3',
})
})
it('rejects pathological page sizes before they hit the database', () => {
expect(() => listQuerySchema.parse({ pageSize: '101' })).toThrow()
})
})
@@ -0,0 +1,86 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('./complaint.repo', () => ({
findMany: vi.fn(),
findById: vi.fn(),
create: vi.fn(),
updateById: vi.fn(),
deleteById: vi.fn(),
}))
import { NotFoundError } from '../../http/errors'
import * as repo from './complaint.repo'
import { createComplaint, deleteComplaint, getComplaint, listComplaints, updateComplaint } from './complaint.service'
describe('complaint.service', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.setSystemTime(new Date('2026-06-08T12:00:00.000Z'))
})
it('builds filtered paginated complaint queries without leaking transport concerns into the repo', async () => {
vi.mocked(repo.findMany).mockResolvedValue([[{ id: 'complaint_1' }], 11] as never)
const result = await listComplaints('company_1', {
status: 'OPEN',
severity: 'HIGH',
category: 'BILLING',
page: 3,
pageSize: 5,
})
expect(repo.findMany).toHaveBeenCalledWith('company_1', {
status: 'OPEN',
severity: 'HIGH',
category: 'BILLING',
}, 10, 5)
expect(result).toEqual({
data: [{ id: 'complaint_1' }],
meta: { total: 11, page: 3, pageSize: 5, totalPages: 3 },
})
})
it('throws NotFoundError when a complaint cannot be found in the company tenant', async () => {
vi.mocked(repo.findById).mockResolvedValue(null as never)
await expect(getComplaint('complaint_1', 'company_1')).rejects.toBeInstanceOf(NotFoundError)
})
it('sets resolvedAt exactly when the complaint transitions into RESOLVED', async () => {
vi.mocked(repo.findById).mockResolvedValue({ id: 'complaint_1', status: 'OPEN' } as never)
vi.mocked(repo.updateById).mockResolvedValue({ id: 'complaint_1', status: 'RESOLVED' } as never)
await updateComplaint('complaint_1', 'company_1', { status: 'RESOLVED', resolution: 'Refund issued' })
expect(repo.updateById).toHaveBeenCalledWith('complaint_1', {
status: 'RESOLVED',
resolution: 'Refund issued',
resolvedAt: new Date('2026-06-08T12:00:00.000Z'),
})
})
it('does not overwrite resolvedAt when an already resolved complaint is edited', async () => {
vi.mocked(repo.findById).mockResolvedValue({ id: 'complaint_1', status: 'RESOLVED' } as never)
await updateComplaint('complaint_1', 'company_1', { notes: 'Follow-up call logged' })
expect(repo.updateById).toHaveBeenCalledWith('complaint_1', { notes: 'Follow-up call logged' })
})
it('creates and deletes complaints through tenant-scoped repository calls', async () => {
vi.mocked(repo.create).mockResolvedValue({ id: 'complaint_1' } as never)
vi.mocked(repo.findById).mockResolvedValue({ id: 'complaint_1' } as never)
await createComplaint('company_1', {
reservationId: 'reservation_1',
severity: 'MEDIUM',
category: 'SERVICE',
subject: 'Late pickup',
})
const deleted = await deleteComplaint('complaint_1', 'company_1')
expect(repo.create).toHaveBeenCalledWith(expect.objectContaining({ companyId: 'company_1', reservationId: 'reservation_1' }))
expect(repo.deleteById).toHaveBeenCalledWith('complaint_1')
expect(deleted).toEqual({ success: true })
})
})