Files
Moheeb ZaraandClaude Opus 4.8 b1edb5c460 types: clear remaining tsc errors across 41 files (76→14, behavior-preserving)
Per-file type-only fixes surfaced after the Supabase client was correctly
typed. All fixes preserve runtime behavior; no any/ts-ignore/ts-expect-error.

- lib/supabase/client.ts: annotate the browser client as SupabaseClient<Database>
  (same ssr/js drift as server.ts; fixes it centrally for all callers).
- Supabase embedded-select joins whose FK metadata is missing from the
  generated types are bridged with `as unknown as <target>` + a TODO to remove
  after regenerating types/database.ts.
- setState optimistic-update variance and raw <select> enum values cast to the
  precise element/union type; .insert payload fields (Json, enums) given the
  precise column type via satisfies/field casts.
- Added narrowing guards where a value was legitimately nullable (comms channel,
  public form page) without changing the intended path.

The 14 remaining errors are confined to the four files fixed in Phase B (real
bugs + the tautological actions.test rewrite). 639 tests still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 18:56:11 -07:00

78 lines
2.5 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { sendEmail } from '@/lib/email/send'
const OLD = { ...process.env }
function mockFetch(status: number, body: unknown) {
return vi.fn(async (_input?: Request | string | URL, _init?: RequestInit) =>
new Response(JSON.stringify(body), {
status,
headers: { 'content-type': 'application/json' },
}),
)
}
beforeEach(() => {
process.env.RESEND_API_KEY = 're_test'
process.env.EMAIL_FROM = 'Acme <[email protected]>'
})
afterEach(() => {
process.env = { ...OLD }
vi.restoreAllMocks()
})
const msg = { to: '[email protected]', subject: 's', html: '<p>h</p>', text: 't' }
describe('sendEmail', () => {
it('non-retryable when transport is not configured', async () => {
delete process.env.RESEND_API_KEY
const r = await sendEmail(msg)
expect(r).toEqual({ ok: false, error: expect.stringContaining('not configured'), retryable: false })
})
it('non-retryable on an invalid recipient (never calls fetch)', async () => {
const f = vi.fn()
vi.stubGlobal('fetch', f)
const r = await sendEmail({ ...msg, to: 'nope' })
expect(r.ok).toBe(false)
if (!r.ok) expect(r.retryable).toBe(false)
expect(f).not.toHaveBeenCalled()
})
it('ok with the returned id on 200', async () => {
vi.stubGlobal('fetch', mockFetch(200, { id: 'e_123' }))
const r = await sendEmail(msg)
expect(r).toEqual({ ok: true, id: 'e_123' })
})
it('passes the Idempotency-Key header when given', async () => {
const f = mockFetch(200, { id: 'e_1' })
vi.stubGlobal('fetch', f)
await sendEmail({ ...msg, idempotencyKey: 'row-42' })
const headers = (f.mock.calls[0][1] as RequestInit).headers as Record<string, string>
expect(headers['Idempotency-Key']).toBe('row-42')
})
it('429 is retryable; 422 is not', async () => {
vi.stubGlobal('fetch', mockFetch(429, { name: 'rate_limit_exceeded', message: 'slow down' }))
const a = await sendEmail(msg)
expect(a.ok).toBe(false)
if (!a.ok) expect(a.retryable).toBe(true)
vi.stubGlobal('fetch', mockFetch(422, { name: 'validation_error', message: 'bad from' }))
const b = await sendEmail(msg)
expect(b.ok).toBe(false)
if (!b.ok) {
expect(b.retryable).toBe(false)
expect(b.error).toContain('422')
}
})
it('network throw is retryable', async () => {
vi.stubGlobal('fetch', vi.fn(async () => { throw new Error('ECONNRESET') }))
const r = await sendEmail(msg)
expect(r.ok).toBe(false)
if (!r.ok) expect(r.retryable).toBe(true)
})
})