feat(db): phase 4 group 1 — lib/ settings + notifications to Drizzle

Convert lib/settings.ts, lib/notifications/{in-app,email,capa-escalation,
effectiveness-recheck}.ts from Supabase PostgREST to Drizzle asAdmin queries.
Drop supabase arg from all call sites in app/api/ and cron routes. Rewrite
notification unit tests to mock @/lib/db/with-user instead of SupabaseClient.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 16:37:51 +08:00
co-authored by Claude Sonnet 4.6
parent d18d29168a
commit 25f923f530
19 changed files with 296 additions and 300 deletions
+27 -32
View File
@@ -1,8 +1,8 @@
// @vitest-environment node
import { describe, it, expect, vi, beforeEach } from 'vitest'
const { mockSend, mockFrom } = vi.hoisted(() => ({
const { mockSend } = vi.hoisted(() => ({
mockSend: vi.fn(),
mockFrom: vi.fn(),
}))
vi.mock('resend', () => {
@@ -11,42 +11,36 @@ vi.mock('resend', () => {
return { Resend: MockResend }
})
vi.mock('@/lib/supabase/server', () => ({
createClient: vi.fn().mockResolvedValue({ from: mockFrom }),
// Mock asAdmin: first call returns recipients, second returns incident+join rows
let asAdminCallCount = 0
const mockRecipients = [
{ email: 'supervisor@setiacorp.com' },
{ email: 'hse@setiacorp.com' },
]
const mockIncidentRows = [{
reportedAt: new Date('2026-07-10T09:00:00Z'),
siteName: 'SCW1',
reporterName: 'John Doe',
}]
vi.mock('@/lib/db/with-user', () => ({
asAdmin: vi.fn().mockImplementation((fn: (db: unknown) => unknown) => {
asAdminCallCount++
if (asAdminCallCount % 2 === 1) {
// first call: recipients query
return Promise.resolve(mockRecipients)
}
// second call: incident join query
return Promise.resolve(mockIncidentRows)
}),
}))
import { sendNewIncidentEmail } from '@/lib/notifications/email'
const mockIncidentRow = {
reported_at: '2026-07-10T09:00:00Z',
sites: { name: 'SCW1' },
reporter: { name: 'John Doe' },
}
function setupMocks(recipientData: object[]) {
mockFrom.mockImplementation((table: string) => {
if (table === 'users') {
return {
select: vi.fn().mockReturnThis(),
in: vi.fn().mockReturnThis(),
eq: vi.fn().mockResolvedValue({ data: recipientData, error: null }),
}
}
return {
select: vi.fn().mockReturnThis(),
eq: vi.fn().mockReturnThis(),
single: vi.fn().mockResolvedValue({ data: mockIncidentRow, error: null }),
}
})
}
beforeEach(() => {
vi.clearAllMocks()
asAdminCallCount = 0
mockSend.mockResolvedValue({ data: { id: 'email-123' }, error: null })
setupMocks([
{ email: 'supervisor@setiacorp.com', name: 'Ahmad', role: 'supervisor' },
{ email: 'hse@setiacorp.com', name: 'Priya', role: 'hse' },
])
})
describe('sendNewIncidentEmail', () => {
@@ -59,7 +53,8 @@ describe('sendNewIncidentEmail', () => {
})
it('does not send if no recipients', async () => {
setupMocks([])
const { asAdmin } = await import('@/lib/db/with-user')
vi.mocked(asAdmin).mockResolvedValueOnce([])
await sendNewIncidentEmail('inc-001', 'site-001', 'SCW1-202607-0001', 'near_miss')
expect(mockSend).not.toHaveBeenCalled()
})