mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
O escopo A/C do gate (PR #1370) é superado por decisão do dono (2026-10-01, aval jurídico declarado): o índice vira anônimo (archivePhotoFace, foto + descritor 128-d, sem nome/vínculo), o enrollment por pessoa morre (faceSubject/faces:enroll removidos) e qualquer visitante encontra as próprias fotos aprovadas. - faces:index persiste os descritores por foto (transação substitui as linhas + marker = modelo); desaprovar/remover/deletar foto apaga os descritores (hooks + FK cascade) - POST /api/fotos/selfie casa o vetor da consulta contra o índice em memória (TTL 60 s) e devolve só a interseção com o read aprovado do C233, sem score/nome de terceiro; leave-index apaga os descritores que casarem - Consentimentos seguem fail-closed: busca-selfie-fotos (consulta) e busca-selfie-indice (aviso público do índice), textos versionados em scripts/lib/faceConsentTexts.mjs e provisionados por pnpm seed:face-consents - pnpm archive:publish aprova o acervo em lote (draft -> approved; removed nunca) com guardas e recibo - C243: seção condicional SelfieSearchHomeSection (port do design hi-fi, cenas 390/1280) entre a Central e a newsletter; sem engine/captura na home e sem CTA morto quando fechada - docs: planos C242/C243, AGENTS-public, runbook §C242, changelog Provas: gate:fast 457 arquivos/5.055 testes; int full 109/1.075; e2e frontendFotosSelfie 9/9; knip verde.
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import {
|
|
buildConsentRichText,
|
|
FACE_INDEX_CONSENT_PARAGRAPHS,
|
|
FACE_INDEX_CONSENT_TEXT,
|
|
FACE_SEARCH_CONSENT_PARAGRAPHS,
|
|
FACE_SEARCH_CONSENT_TEXT,
|
|
} from '../../scripts/lib/faceConsentTexts.mjs'
|
|
|
|
// C242 — the versioned legal texts of the selfie search: the Lexical shape the
|
|
// Consent collection stores and the honest content the visitor reads.
|
|
|
|
describe('buildConsentRichText', () => {
|
|
it('builds the Lexical root with one paragraph per text', () => {
|
|
const rich = buildConsentRichText(['Um.', 'Dois.'])
|
|
|
|
expect(rich.root.type).toBe('root')
|
|
expect(rich.root.children).toHaveLength(2)
|
|
expect(rich.root.children[0].children[0].text).toBe('Um.')
|
|
expect(rich.root.children[1].children[0].text).toBe('Dois.')
|
|
})
|
|
|
|
it('produces an empty root for no paragraphs (fail-closed content)', () => {
|
|
expect(buildConsentRichText([]).root.children).toEqual([])
|
|
})
|
|
})
|
|
|
|
const flatten = (text: unknown): string =>
|
|
(text as { root: { children: { children: { text: string }[] }[] } }).root.children
|
|
.map((paragraph) => paragraph.children.map((child) => child.text).join(''))
|
|
.join('\n')
|
|
|
|
describe('face consent texts', () => {
|
|
it('has substantive paragraphs in both texts', () => {
|
|
expect(FACE_SEARCH_CONSENT_PARAGRAPHS.length).toBeGreaterThanOrEqual(4)
|
|
expect(FACE_INDEX_CONSENT_PARAGRAPHS.length).toBeGreaterThanOrEqual(4)
|
|
for (const paragraph of [...FACE_SEARCH_CONSENT_PARAGRAPHS, ...FACE_INDEX_CONSENT_PARAGRAPHS]) {
|
|
expect(paragraph.trim().length).toBeGreaterThan(40)
|
|
}
|
|
})
|
|
|
|
it('states the on-device processing and the LGPD basis in the query consent', () => {
|
|
const text = flatten(FACE_SEARCH_CONSENT_TEXT)
|
|
expect(text).toMatch(/aparelho/i)
|
|
expect(text).toMatch(/não é enviada nem armazenada/i)
|
|
expect(text).toMatch(/LGPD/)
|
|
expect(text).toMatch(/retirar este consentimento/i)
|
|
})
|
|
|
|
it('states the anonymous index and the self-service opt-out in the notice', () => {
|
|
const text = flatten(FACE_INDEX_CONSENT_TEXT)
|
|
expect(text).toMatch(/índice biométrico anônimo/i)
|
|
expect(text).toMatch(/sem nome/i)
|
|
expect(text).toMatch(/Minha presença/)
|
|
expect(text).toMatch(/remoção de uma foto/i)
|
|
})
|
|
})
|