import assert from 'node:assert/strict'; import test from 'node:test'; import { PortalApiError, fieldErrors, interestPayload, newSubmissionKey, money, portalRequest, searchUrl, toFeaturedOpportunity, toOpportunity, } from '../../resources/js/lib/portal-api.ts'; await test('submission keys are distinct UUIDv4 values', () => { const first = newSubmissionKey(); assert.match( first, /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, ); assert.notEqual(first, newSubmissionKey()); }); await test('filters preserve one-based maturity and pagination and escape query text', () => { const url = new URL( searchUrl( { query: ' energia & solar ', sector: 'energia', province: 'Zambézia', maturity: '4', }, 2, ), 'http://localhost', ); assert.equal(url.searchParams.get('q'), 'energia & solar'); assert.equal(url.searchParams.get('maturity_level'), '4'); assert.equal(url.searchParams.get('page'), '2'); assert.equal(url.searchParams.get('province'), 'Zambézia'); }); await test('empty filters do not restrict results', () => { const url = searchUrl( { query: '', sector: '', province: '', maturity: '' }, 1, ); assert.equal(url, '/api/v1/opportunities/search?page=1&per_page=12'); }); await test('request uses fullName and transmits consent without inventing a reference', () => { const payload = interestPayload({ name: ' Teste fictício ', email: 'teste@example.invalid', phone: '', investorType: 'empresa', country: 'Moçambique', sectorInterest: 'Energia', opportunityReference: 'TEST-1', investmentRange: '', message: 'Mensagem fictícia para teste.', privacyConsent: true, }); assert.equal(payload.fullName, 'Teste fictício'); assert.equal(payload.privacyConsent, true); assert.equal(payload.opportunityReference, 'TEST-1'); assert.equal('reference' in payload, false); assert.equal('name' in payload, false); }); await test('server validation maps fullName back to the name field', () => { assert.deepEqual( fieldErrors( new PortalApiError(422, { fullName: ['Nome obrigatório'], email: ['Email inválido'], unexpected: ['ignored'], }), ), { name: 'Nome obrigatório', email: 'Email inválido' }, ); }); await test('monetary rendering preserves large decimal values and the API currency', () => { const amount = money('123456789012345678.90', 'MZN'); assert.match(amount, /MZN|MTn?/); assert.equal(amount.replace(/\D/g, ''), '12345678901234567890'); assert.notEqual(money('1000.00', 'EUR'), money('1000.00', 'USD')); assert.equal(money(null, 'USD'), 'Por indicar'); assert.notEqual(money('0.00', 'USD'), 'Por indicar'); }); await test('database records map without changing maturity or amount precision', () => { const item = toOpportunity({ slug: 'test', reference: 'TEST', title: 'Teste', sector: null, province: null, maturity_label: 'Projecto estruturado', estimated_investment_amount: '1000.25', estimated_investment_currency: 'MZN', financing_modality: null, expected_impact: null, summary: null, description: 'Descrição completa', is_featured: false, }); assert.equal(item.sectorName, 'Sector não indicado'); assert.equal(item.maturity, 'Projecto estruturado'); assert.equal(item.amount, '1000.25'); assert.equal(item.description, 'Descrição completa'); }); await test('home cards retain the slug needed to fetch the full detail', () => { const item = toFeaturedOpportunity({ reference: 'TEST', title: 'Teste', sector: 'Energia', province: null, maturity: null, estimatedInvestment: { amount: null, currency: 'USD' }, url: '/oportunidades/projecto-teste', }); assert.equal(item.slug, 'projecto-teste'); assert.equal(item.description, null); }); await test('fetch accepts a real server confirmation', async (t) => { t.mock.method(globalThis, 'fetch', async (_url, options) => { assert.equal(options.headers.get('Accept'), 'application/json'); assert.equal(options.credentials, 'same-origin'); return new Response( JSON.stringify({ success: true, data: { reference: 'IR-SERVER-001' }, }), { status: 201 }, ); }); const response = await portalRequest('/api/v1/interest-requests', { method: 'POST', }); assert.equal(response.data.reference, 'IR-SERVER-001'); }); await test('browser writes preserve the CSRF cookie header', async (t) => { const previous = Object.getOwnPropertyDescriptor(globalThis, 'document'); Object.defineProperty(globalThis, 'document', { configurable: true, value: { cookie: 'other=x; XSRF-TOKEN=qa%2Ftoken' } }); try { t.mock.method(globalThis, 'fetch', async (_url, options) => { assert.equal(options.headers.get('X-XSRF-TOKEN'), 'qa/token'); return new Response('{"success":true}', { status: 201 }); }); await portalRequest('/api/v1/interest-requests', { method: 'POST' }); } finally { if (previous) Object.defineProperty(globalThis, 'document', previous); else delete globalThis.document; } }); for (const status of [422, 429, 500]) { await test(`HTTP ${status} never becomes a successful confirmation`, async (t) => { t.mock.method( globalThis, 'fetch', async () => new Response( JSON.stringify({ errors: { email: ['Inválido'] }, message: 'Internal diagnostic', }), { status }, ), ); await assert.rejects( portalRequest('/api/v1/interest-requests'), (error) => { assert.equal(error.status, status); assert.doesNotMatch(error.message, /Internal diagnostic/); if (status === 422) assert.equal(error.errors.email[0], 'Inválido'); return true; }, ); }); } await test('invalid JSON and network errors remain failures', async (t) => { t.mock.method( globalThis, 'fetch', async () => new Response('Error', { status: 200 }), ); await assert.rejects(portalRequest('/api/v1/home'), PortalApiError); t.mock.method(globalThis, 'fetch', async () => { throw new TypeError('Failed to fetch'); }); await assert.rejects(portalRequest('/api/v1/home'), TypeError); });