/** * i18n PR9–10 — public residential inquiry endpoint. * * Validates the server-side phone normalization that the public inquiry * route runs when the website posts a raw international format (older * Nuxt builds), and that pre-normalized payloads pass through unchanged. */ import { describe, it, expect, vi, beforeAll } from 'vitest'; import { eq } from 'drizzle-orm'; import { db } from '@/lib/db'; import { residentialClients } from '@/lib/db/schema/residential'; import { makePort } from '../helpers/factories'; import { makeMockRequest } from '../helpers/route-tester'; vi.mock('@/lib/socket/server', () => ({ emitToRoom: vi.fn() })); vi.mock('@/lib/email', () => ({ sendEmail: vi.fn().mockResolvedValue(undefined) })); // Randomize per-run prefix so leftover redis sliding-window entries from a // previous run don't 429 the new run (publicForm limiter pexpires after 1h). const IP_PREFIX = `10.${Math.floor(Math.random() * 200) + 10}`; let ipCounter = 1; function uniqueIp(): string { ipCounter += 1; return `${IP_PREFIX}.${Math.floor(ipCounter / 255) % 255}.${ipCounter % 255}`; } describe('POST /api/public/residential-inquiries', () => { let POST: typeof import('@/app/api/public/residential-inquiries/route').POST; beforeAll(async () => { const mod = await import('@/app/api/public/residential-inquiries/route'); POST = mod.POST; }); it('parses a raw international phone string into E.164 + country', async () => { const port = await makePort(); const email = `res-${Math.random().toString(36).slice(2, 8)}@test.local`; const req = makeMockRequest( 'POST', `http://localhost/api/public/residential-inquiries?portId=${port.id}`, { headers: { 'x-forwarded-for': uniqueIp() }, body: { firstName: 'Anna', lastName: 'Nowak', email, // Raw international format — server should normalize. phone: '+44 20 7946 0958', placeOfResidence: 'Warsaw', }, }, ); const res = await POST(req); expect(res.status).toBe(201); const [row] = await db .select() .from(residentialClients) .where(eq(residentialClients.email, email)); expect(row).toBeDefined(); expect(row?.phoneE164).toBe('+442079460958'); expect(row?.phoneCountry).toBe('GB'); // Free-text legacy column preserved verbatim for backfill. expect(row?.phone).toBe('+44 20 7946 0958'); }); it('passes pre-normalized E.164 + country through unchanged', async () => { const port = await makePort(); const email = `res-${Math.random().toString(36).slice(2, 8)}@test.local`; const req = makeMockRequest( 'POST', `http://localhost/api/public/residential-inquiries?portId=${port.id}`, { headers: { 'x-forwarded-for': uniqueIp() }, body: { firstName: 'Jan', lastName: 'Kowalski', email, phone: '+48 22 555 0100', phoneE164: '+48225550100', phoneCountry: 'PL', nationalityIso: 'PL', timezone: 'Europe/Warsaw', placeOfResidence: 'Warsaw', placeOfResidenceCountryIso: 'PL', }, }, ); const res = await POST(req); expect(res.status).toBe(201); const [row] = await db .select() .from(residentialClients) .where(eq(residentialClients.email, email)); expect(row?.phoneE164).toBe('+48225550100'); expect(row?.phoneCountry).toBe('PL'); expect(row?.nationalityIso).toBe('PL'); expect(row?.timezone).toBe('Europe/Warsaw'); expect(row?.placeOfResidenceCountryIso).toBe('PL'); }); it('persists a national-format phone when the website only sends a country hint', async () => { const port = await makePort(); const email = `res-${Math.random().toString(36).slice(2, 8)}@test.local`; const req = makeMockRequest( 'POST', `http://localhost/api/public/residential-inquiries?portId=${port.id}`, { headers: { 'x-forwarded-for': uniqueIp() }, body: { firstName: 'Marta', lastName: 'Lewandowska', email, phone: '22 555 0200', // National-format phoneCountry: 'PL', // Hint only — no E.164 yet. }, }, ); const res = await POST(req); expect(res.status).toBe(201); const [row] = await db .select() .from(residentialClients) .where(eq(residentialClients.email, email)); expect(row?.phoneE164).toBe('+48225550200'); expect(row?.phoneCountry).toBe('PL'); }); });