// @requirements REQ-PRODUCT-SERVICE-CATALOG REQ-PRODUCT-A11Y REQ-PRODUCT-MOBILE REQ-PRODUCT-SAFE-CONTENT import {test} from 'node:test'; import assert from 'node:assert/strict'; import {JSDOM} from 'jsdom'; import {createElement} from 'react'; import {renderToStaticMarkup} from 'react-dom/server'; import {build} from 'esbuild'; import {createServiceInventory,createServiceDirectory,renderServiceDirectory,renderServiceDirectoryPage,mountServiceDirectory} from 'notabenda/services'; import {ServiceDirectory} from 'notabenda/react/documents'; import {createLegalDocument} from 'notabenda'; import {compileLegalDocuments} from 'notabenda/content'; import {fixture} from './helpers.mjs'; import {serviceExampleConfig} from '../../../examples/services/build.mjs'; const settings={idPrefix:'test-services'}; const config=async()=>serviceExampleConfig(await fixture({reviewed:true})); test('directory and privacy use detached declared service facts, including custom services, with selected-only public data',async()=>{ const c=await config();c.services[0].resourceOrigins=['https://internal-scope.example.test'];c.private={note:'PRIVATE_SENTINEL'}; const model=createServiceDirectory(c);assert.equal(model.services.length,4);assert.equal(model.totalServices,4);assert.equal(model.policies.privacy.version,c.documents.privacy.version); const inventory=createServiceInventory(c),privacy=createLegalDocument(c,'privacy').sections.find(s=>s.id==='providers');for(const service of inventory){assert.ok(privacy.items.some(item=>item.includes(service.name)&&item.includes(service.provider)&&item.includes(service.location)));} assert.doesNotMatch(JSON.stringify(model),/PRIVATE_SENTINEL|internal-scope|resourceOrigins|scopeGuards|googleBasic|catalogRevision/); model.services[0].purposes[0].dataCategories.push('Changed');model.services[0].provider='Changed';assert.notEqual(c.services[0].provider,'Changed');assert.ok(!c.purposes[0].dataCategories.includes('Changed')); const pinned=compileLegalDocuments(c).artifact,old=renderServiceDirectory(pinned.publicConfig,settings);c.services[0].name='Later inventory revision';assert.equal(renderServiceDirectory(pinned.publicConfig,settings),old);assert.notEqual(renderServiceDirectory(c,settings),old); }); test('directory filters are literal bounded searches with declared-purpose validation and clear partial counts',async()=>{ const c=await config();assert.equal(createServiceDirectory(c,{query:'brookpost'}).services[0].id,'mail');assert.equal(createServiceDirectory(c,{query:'IP address'}).services[0].id,'hosting');assert.equal(createServiceDirectory(c,{query:'readership_id',purposeId:'measurement'}).services[0].id,'measurement');assert.equal(createServiceDirectory(c,{query:'(a+)+'}).services.length,0); assert.match(renderServiceDirectory(c,{...settings,purposeId:'measurement'}),/Showing 1 of 4 declared services/); for(const bad of [{query:null},{query:'a'.repeat(121)},{query:'\u001b[2J'},{purposeId:'unknown'},{purposeId:['measurement']}])assert.throws(()=>createServiceDirectory(c,bad)); }); test('native directory is script-free, escaped, keyboard-readable and makes missing facts explicit',async()=>{ const c=await config();c.services[0].name='';c.services[0].provider='Provider '; const html=renderServiceDirectory(c,{...settings,headingLevel:2});const dom=new JSDOM(html),doc=dom.window.document; assert.equal(doc.querySelectorAll('article').length,4);assert.equal(doc.querySelectorAll('details > summary').length,4);assert.equal(doc.querySelectorAll('input,select,button,script,img').length,0);assert.equal(doc.querySelector('article h3').textContent,c.services[0].name);assert.equal(doc.querySelectorAll('details section h4').length,8); assert.match(html,/A provider privacy link is not declared/);assert.match(html,/No browser storage is declared for this service/);assert.match(html,/<script>/);for(const node of doc.querySelectorAll('[aria-labelledby]'))assert.ok(doc.getElementById(node.getAttribute('aria-labelledby'))); assert.equal(new Set([...doc.querySelectorAll('[id]')].map(n=>n.id)).size,doc.querySelectorAll('[id]').length); for(const bad of [{idPrefix:'bad id'},{...settings,headingLevel:6},{...settings,title:'\u202eunsafe'},{...settings,description:'x'.repeat(1001)}])assert.throws(()=>renderServiceDirectory(c,bad)); c.services[0].privacyUrl='javascript:alert(1)';assert.throws(()=>renderServiceDirectory(c,settings));dom.window.close(); }); test('production review, empty inventories, page link validation and exact React server parity are enforced',async()=>{ const c=await config();assert.equal(renderToStaticMarkup(createElement(ServiceDirectory,{config:c,...settings,className:'host'})),`
${renderServiceDirectory(c,settings)}
`); const html=renderServiceDirectoryPage(c,{...settings,stylesheetUrl:'/assets/services.css'});assert.doesNotMatch(html,/