teqo/tests/int/contactPhoneNormalization.int.spec.ts
Francisco Ignácio Fontoura Solla 0e4e6855dc Add the tooling gate: knip, CI, zero-warning lint, as-never ban
- knip (files/deps at error level; unused exports ledgered as warn) with
  config for Payload/Next entries; deleted the dead modules it confirmed:
  RecentlyVisited + UpcomingActionPlansCard + actionPlanUpcomingPreview,
  lazy-dialog helpers, tseZone, federalCandidateTotalsAggregate (test-only),
  unused ui kit files, one-shot rename script; pruned unused radix deps and
  typescript-eslint; added pg/@types/pg (pinned to drizzle's 8.10.2)
- GitHub Actions CI on the mirror: lint, typecheck, knip, unit, migrate, int
  against a Postgres 17 service
- eslint: unused-vars with ^_ ignore patterns (migrations args exempt),
  repo-wide ban on `as never` (no-restricted-syntax), lint gate at
  --max-warnings=0; fixed all 54 pre-existing warnings (dead imports,
  hooks deps, tracking-pixel img disable with impeccable config exception)
- tsconfig: include consolidated from 219 lines to 5 (wildcard covers all
  .next dist variants), noUnusedLocals on
- typed replacements for every as-never: hookFilledCreateData (actions
  create data completed by collection hooks), drizzle scopeColumn narrowing,
  tests' stub<T> helper (property-level checking kept, completeness waived)
- test infra: leased-consent generalization used by onda0 spec (shared
  leases over the stable consent keys; window race ledgered), purge fix
- docs: IMPROVE-CODE-QUALITY-PLAN.md tracker, TECH-DEBT.md ledger,
  TESTING.md safety-net map (package.json also gains the Phase 2
  build:election-aggregates script entry; the script lands next commit)
2026-07-23 23:06:12 -03:00

64 lines
2.2 KiB
TypeScript

// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from 'vitest'
const phoneMocks = vi.hoisted(() => ({
normalizeBrazilianPhone: vi.fn<(value: string) => string | null>(),
}))
vi.mock('@/utilities/phone', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/utilities/phone')>()
phoneMocks.normalizeBrazilianPhone.mockImplementation(actual.normalizeBrazilianPhone)
return {
...actual,
normalizeBrazilianPhone: phoneMocks.normalizeBrazilianPhone,
}
})
import { Contact } from '@/collections/Contact'
import { leadershipCreateSchema } from '@/lib/schemas/leadership'
import { stub } from '../helpers/stub'
const contactPhoneHook = Contact.hooks?.beforeValidate?.[0]
type ContactPhoneHookArgs = Parameters<NonNullable<typeof contactPhoneHook>>[0]
const runContactPhoneHook = async (phone: string) => {
if (typeof contactPhoneHook !== 'function') {
throw new Error('Expected Contact.beforeValidate phone hook.')
}
const data = { phone }
await contactPhoneHook(stub<ContactPhoneHookArgs>({ data }))
return data.phone
}
describe('Contact phone normalization boundary', () => {
beforeEach(() => {
phoneMocks.normalizeBrazilianPhone.mockClear()
})
it('normalizes once before leadership dedupe and skips canonical Contact input', async () => {
const parsed = leadershipCreateSchema.parse({
municipalities: [1],
name: 'Liderança normalizada',
phone: '+55 (71) 99999-1234',
})
expect(parsed.phone).toBe('71999991234')
expect(await runContactPhoneHook(parsed.phone)).toBe('71999991234')
expect(phoneMocks.normalizeBrazilianPhone).toHaveBeenCalledTimes(1)
})
it('normalizes a formatted phone sent directly to the Contact API boundary', async () => {
expect(await runContactPhoneHook('+55 (71) 99999-1234')).toBe('71999991234')
expect(phoneMocks.normalizeBrazilianPhone).toHaveBeenCalledTimes(1)
})
it('rejects an invalid canonical-length direct API phone without re-normalizing it', async () => {
await expect(runContactPhoneHook('00000000000')).rejects.toThrow('Celular brasileiro inválido.')
expect(phoneMocks.normalizeBrazilianPhone).not.toHaveBeenCalled()
})
})