Files
SGF/scripts/b1-http-proof.mjs

48 lines
5.0 KiB
JavaScript

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 {
async request(path, method = 'GET', body, key = randomUUID(), token = context) {
const headers = { Accept: 'application/json', 'Content-Type': 'application/json', Cookie: [...cookies].map(([k,v]) => `${k}=${v}`).join('; '), 'X-CSRF-TOKEN': csrf, 'X-SGF-Context': token, 'Idempotency-Key': key };
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); await this.select(); },
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'); await reader.login('consulta@example.test');
async function create(area, data) { const result = await manager.request('/api/a/' + area, 'POST', data); assert.equal(result.status, 201); return result.data.id; }
const customer = await create('clients', { name: 'Cliente fotografia B1 HTTP' });
const service = await create('services', { name: 'Serviço fotografia B1 HTTP' });
const source = await create('service_drafts', { client_id: customer, lines: [{ service_id: service, quantity: '1.25', unit_price: '0.00000001' }] });
const draft = await create('billing_drafts', { source_id: source });
const endpoint = `/api/b1/billing-drafts/${draft}/confirm-internal`, record = `/api/a/billing_drafts/${draft}`;
assert.equal((await reader.request(endpoint, 'POST', { version: 1 })).status, 403);
assert.equal((await manager.request(endpoint, 'POST', { version: 2 })).status, 409);
const oldToken = await manager.select(); await manager.select(null);
assert.equal((await manager.request(endpoint, 'POST', { version: 1 }, randomUUID(), oldToken)).status, 409); await manager.select();
const key = randomUUID(), first = await manager.request(endpoint, 'POST', { version: 1 }, key); assert.equal(first.status, 200);
const replay = await manager.request(endpoint, 'POST', { version: 1 }, key); assert.equal(replay.status, 200); assert.equal(replay.data.id, first.data.id); assert.equal(replay.data.replayed, true);
assert.equal((await manager.request(endpoint, 'POST', { version: 2 }, key)).status, 409);
const before = (await manager.request(record)).data.confirmation;
assert.equal(before.snapshot.data.currency, null); assert.equal(before.snapshot.base_amount, '0.0000000125');
for (const pending of ['currency_missing', 'tax_rule_pending', 'accounting_rule_pending', 'accounts_pending', 'recognition_pending']) assert.ok(before.snapshot.pending[pending]);
assert.equal((await manager.request(`/api/a/clients/${customer}`, 'PATCH', { name: 'Cliente alterado após confirmação HTTP', version: 1 })).status, 200);
assert.equal((await manager.request(`/api/a/services/${service}`, 'PATCH', { name: 'Serviço alterado após confirmação HTTP', version: 1 })).status, 200);
assert.deepEqual((await manager.request(record)).data.confirmation, before);
assert.equal((await manager.request(record, 'PATCH', { version: 1, reload_source: true })).status, 409);
assert.equal((await reader.request(record)).status, 200);
assert.equal(before.event.type, 'b1.billing_draft.confirmed_internal'); assert.equal(before.event.state, 'captured'); assert.equal(before.snapshot.fiscal_status, 'not_issued'); assert.equal(before.snapshot.accounting_status, 'blocked_pending');
await manager.request('/api/logout', 'POST'); await reader.request('/api/logout', 'POST');
await fs.writeFile('docs/proofs/b1-http-results.json', JSON.stringify({ database: 'sgf_tempo_test', real_synthetic_login: true, results: ['Confirmation authorized; reader denied 403.', 'Old context and version denied 409.', 'Identical command recovers confirmation; different content conflicts 409.', 'Snapshot unchanged after client/service edits.', 'Confirmed draft edit denied 409; reader consultation 200.', 'Currency remains unknown, tax/accounts/recognition pending; captured, not issued or accounted.'] }, null, 2));
console.log('B1 isolated HTTP proof passed; no credentials displayed.');