with-api-auth-document-lock.test.ts 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. /**
  2. * A locked document surfacing through the technician API.
  3. *
  4. * The mobile app cannot read a stack trace: a lock refusal that came back as
  5. * a generic 500 told the technician to retry a request that can never
  6. * succeed. It must arrive as a 409 with a code the app can react to and a
  7. * message that names the rule.
  8. */
  9. import { describe, it, expect, vi, beforeEach } from 'vitest'
  10. vi.mock('@/lib/auth', () => ({ auth: { api: { getSession: vi.fn() } } }))
  11. vi.mock('@/lib/rate-limit', () => ({ rateLimit: vi.fn(() => null) }))
  12. vi.mock('@/lib/db', () => ({
  13. db: {
  14. user: { findUnique: vi.fn() },
  15. organizationMember: { findFirst: vi.fn() },
  16. technician: { findMany: vi.fn() },
  17. },
  18. }))
  19. import { auth } from '@/lib/auth'
  20. import { db } from '@/lib/db'
  21. import { withApiAuth, apiOk } from '@/lib/with-api-auth'
  22. import { DocumentLockedError } from '@/lib/document-lock'
  23. function apiRequest() {
  24. return new Request('https://app.test/api/v1/tech/jobs/rec-1/labor', {
  25. method: 'POST',
  26. headers: { authorization: 'Bearer token-1' },
  27. })
  28. }
  29. beforeEach(() => {
  30. vi.resetAllMocks()
  31. vi.mocked(auth.api.getSession).mockResolvedValue({ user: { id: 'user-1' } } as any)
  32. vi.mocked(db.user.findUnique).mockResolvedValue({ isSuperAdmin: false } as any)
  33. vi.mocked(db.organizationMember.findFirst).mockResolvedValue({
  34. organizationId: 'org-1',
  35. role: 'member',
  36. roleId: null,
  37. customRole: null,
  38. } as any)
  39. vi.mocked(db.technician.findMany).mockResolvedValue([{ id: 'tech-1' }] as any)
  40. })
  41. describe('a handler that hits a locked document', () => {
  42. it('returns 409 document_locked with the reason, not a 500', async () => {
  43. const response = await withApiAuth(apiRequest(), async () => {
  44. throw new DocumentLockedError('paid')
  45. })
  46. expect(response.status).toBe(409)
  47. const body = await response.json()
  48. expect(body.error.code).toBe('document_locked')
  49. expect(body.error.message).toMatch(/locked/i)
  50. expect(body.error.message).toMatch(/paid/i)
  51. })
  52. it('leaves ordinary handlers untouched', async () => {
  53. const response = await withApiAuth(apiRequest(), async () => apiOk({ ok: true }))
  54. expect(response.status).toBe(200)
  55. })
  56. })