// Verificacao de consistencia documental, nao validacao funcional do portal. import test from 'node:test'; import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; const root = new URL('../', import.meta.url); async function csv(name) { const text = await readFile(new URL(name, root), 'utf8'); const lines = text.trim().split(/\r?\n/); const parse = (line) => [...line.matchAll(/"((?:[^"]|"")*)"(?:,|$)/g)].map((match) => match[1].replaceAll('""', '"'), ); const header = parse(lines.shift()); return lines.map((line) => { const cells = parse(line); assert.equal(cells.length, header.length, name + ': numero de colunas'); return Object.fromEntries(header.map((key, i) => [key, cells[i]])); }); } await test('33 requirements retain IDs and resolve entities, rules, risks and acceptance tests', async () => { const rows = await csv('requirements.csv'); const entities = new Set( (await csv('entities.csv')).map((e) => e.entidade), ); const spec = await readFile(new URL('specification.md', root), 'utf8'); assert.equal(rows.length, 33); assert.equal(new Set(rows.map((r) => r.id)).size, rows.length); for (const [index, row] of rows.entries()) { assert.equal(row.id, 'RF-EDU-' + String(index + 1).padStart(3, '0')); for (const entity of row.dados_entidades.split(';')) assert.ok(entities.has(entity), entity); for (const rule of row.regra_negocio.split(';')) assert.ok(spec.includes(rule), rule); assert.ok(spec.includes(row.risco), row.risco); assert.ok(row.criterio_aceitacao.length > 20); assert.ok(row.endpoint_proposto.startsWith('/api/v1/education/')); assert.equal(row.estado, 'Proposta'); } }); await test('entities, RNF, indicators and conformity matrices are complete and explicitly proposed', async () => { assert.equal((await csv('entities.csv')).length, 44); assert.equal((await csv('nonfunctional.csv')).length, 18); const indicators = await csv('indicators.csv'); assert.equal(indicators.length, 25); for (const indicator of indicators) assert.match(indicator.meta, /Pendente/); const conformity = await csv('conformity.csv'); assert.equal( conformity.filter((r) => r.categoria.startsWith('Módulo ')).length, 20, ); for (const item of conformity) assert.ok( [ 'Conforme', 'Parcialmente conforme', 'Não conforme', 'Pendente de validação', ].includes(item.classificacao), ); }); await test('OpenAPI proposal resolves local refs and guards all modifying operations', async () => { const api = JSON.parse( await readFile(new URL('openapi.proposed.json', root), 'utf8'), ); assert.equal(api.openapi, '3.1.0'); assert.equal(api['x-artifact-status'], 'Proposta'); const operations = new Set(); function walk(value) { if (!value || typeof value !== 'object') return; if (value.$ref) { assert.ok(value.$ref.startsWith('#/')); let target = api; for (const part of value.$ref.slice(2).split('/')) target = target?.[part.replaceAll('~1', '/').replaceAll('~0', '~')]; assert.ok(target, value.$ref); } for (const nested of Object.values(value)) walk(nested); } walk(api); for (const [path, methods] of Object.entries(api.paths)) { assert.ok(path.startsWith('/api/v1/education/')); for (const method of ['get', 'post', 'patch']) { const operation = methods[method]; if (!operation) continue; assert.ok(!operations.has(operation.operationId)); operations.add(operation.operationId); assert.ok(operation.responses['401']); if (method !== 'get') assert.ok( operation.parameters.some((p) => p.$ref.endsWith('/Idempotency'), ), ); if (method === 'patch' || path.endsWith('/decisions')) assert.ok( operation.parameters.some((p) => p.$ref.endsWith('/Match')), ); } } }); await test('all thirty requested numbered sections exist in order', async () => { const text = await readFile(new URL('specification.md', root), 'utf8'); const numbers = [...text.matchAll(/^## (\d+)\. /gm)].map((m) => Number(m[1]), ); assert.deepEqual( numbers, Array.from({ length: 30 }, (_, i) => i + 1), ); });