import fs from 'node:fs/promises'; import assert from 'node:assert/strict'; import { randomUUID } from 'node:crypto'; const fixture = JSON.parse(await fs.readFile('C:/laragon/data/sgf-tempo/secrets/browser-fixture.json','utf8')); const base = 'http://127.0.0.1:8001'; function client() { const cookies = new Map(); let csrf = ''; let context = ''; return { cookies, async request(path, method='GET', body, {key=randomUUID(), token=context, useCsrf=true}={}) { const headers={Accept:'application/json','Content-Type':'application/json', Cookie:[...cookies].map(([k,v])=>`${k}=${v}`).join('; '),'X-SGF-Context':token,'Idempotency-Key':key}; if(useCsrf) headers['X-CSRF-TOKEN']=csrf; const response=await fetch(base+path,{method,headers,...(body===undefined?{}:{body:JSON.stringify(body)})}); for(const cookie of response.headers.getSetCookie()){const pair=cookie.split(';',1)[0], split=pair.indexOf('=');cookies.set(pair.slice(0,split),pair.slice(split+1));} const data=await response.json().catch(()=>({})); if(data.csrf)csrf=data.csrf;if(data.context?.token)context=data.context.token; return {status:response.status,data}; }, async login(email){await this.request('/api/session');assert.equal((await this.request('/api/login','POST',{email,password:fixture.password})).status,200);}, async select(unit=fixture.unit){const result=await this.request('/api/context','POST',{tenant_id:fixture.tenant,company_id:fixture.company,unit_id:unit});assert.equal(result.status,200);return result.data.context.token;} }; } const manager=client(),reader=client(); await manager.login('gestor@example.test');const oldToken=await manager.select();await reader.login('consulta@example.test');await reader.select(); const results=[]; const key=randomUUID();const created=await manager.request('/api/a/clients','POST',{name:'Cliente sintético HTTP A'},{key});assert.equal(created.status,201); const replay=await manager.request('/api/a/clients','POST',{name:'Cliente sintético HTTP A'},{key});assert.equal(replay.status,201);assert.equal(replay.data.id,created.data.id);assert.equal(replay.data.event_id,created.data.event_id);assert.equal(replay.data.replayed,true); assert.equal((await manager.request('/api/a/clients','POST',{name:'Conteúdo alterado'},{key})).status,409); results.push('Criação repetida recupera o mesmo registo/evento; conteúdo diferente com a mesma chave é recusado (409).'); const path=`/api/a/clients/${created.data.id}`; assert.equal((await reader.request(path)).status,200); assert.equal((await reader.request(path,'PATCH',{name:'Não autorizado',version:1})).status,403); assert.equal((await reader.request('/api/a/clients/access')).status,403); results.push('Consulta por credenciais reais sintéticas: leitura 200, edição e gestão 403.'); const grant={email:'consulta@example.test',action:'edit',grant:true,reason:'Prova HTTP A, exclusivamente sintética'}; assert.equal((await manager.request('/api/a/clients/access','POST',grant)).status,200); assert.equal((await reader.request(path,'PATCH',{name:'Edição sintética autorizada',version:1})).status,200); assert.equal((await manager.request('/api/a/clients/access','POST',{...grant,grant:false})).status,200); assert.equal((await reader.request(path,'PATCH',{name:'Direito revogado',version:2})).status,403); results.push('Concessão/revogação de edição têm efeito no pedido seguinte, mantendo a mesma sessão.'); assert.equal((await manager.request(path,'PATCH',{name:'Versão antiga',version:1})).status,409); await manager.select(null); assert.equal((await manager.request(path,'PATCH',{name:'Contexto antigo',version:2},{token:oldToken})).status,409); assert.equal((await manager.request('/api/a/clients','POST',{name:'Sem CSRF'},{useCsrf:false})).status,419); results.push('Versão antiga e contexto antigo recusados (409); alteração sem protecção CSRF/origem recusada (419).'); const previousCookie=new Map(reader.cookies);assert.equal((await reader.request('/api/logout','POST')).status,200);reader.cookies.clear();for(const [k,v] of previousCookie)reader.cookies.set(k,v); assert.equal((await reader.request('/api/a/clients')).status,401);await manager.request('/api/logout','POST'); results.push('Cookie anterior à saída não recupera acesso aos dados de A (401).'); await fs.writeFile(new URL('../docs/proofs/a-http-results.json',import.meta.url),JSON.stringify({environment:'browser-testing / sgf_tempo_test only',results},null,2)); console.log(results.join('\n'));