mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-06-05 13:58:15 +02:00
230 lines
9.1 KiB
TypeScript
230 lines
9.1 KiB
TypeScript
/**
|
|
* Sprint 4A regression tests — MeshChat decomposition boundary checks.
|
|
*
|
|
* These tests validate the frozen contract:
|
|
* 1. High-privacy DM queueing lives in the controller
|
|
* 2. selectedGateAccessReady gating lives in the controller
|
|
* 3. DM polling trust-mutation code lives in the controller
|
|
* 4. Gate refresh is controller-owned via authenticated poll (SSE removed in S3A)
|
|
* 5. Identity persistence stays in meshIdentity.ts (not in presentational code)
|
|
* 6. No direct trust-mutating imports in presentational components
|
|
*/
|
|
import { describe, expect, it } from 'vitest';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
const MESH_CHAT_DIR = path.resolve(__dirname, '../../components/MeshChat');
|
|
|
|
function readFile(name: string): string {
|
|
return fs.readFileSync(path.join(MESH_CHAT_DIR, name), 'utf-8');
|
|
}
|
|
|
|
// ─── Trust-mutation isolation ───────────────────────────────────────────────
|
|
|
|
const TRUST_MUTATING_IMPORTS = [
|
|
'addContact',
|
|
'updateContact',
|
|
'blockContact',
|
|
'purgeBrowserSigningMaterial',
|
|
'purgeBrowserContactGraph',
|
|
'purgeBrowserDmState',
|
|
];
|
|
|
|
describe('MeshChat decomposition — trust mutation isolation', () => {
|
|
it('controller imports all trust-mutating functions', () => {
|
|
const controller = readFile('useMeshChatController.ts');
|
|
for (const fn of TRUST_MUTATING_IMPORTS) {
|
|
expect(controller).toContain(fn);
|
|
}
|
|
});
|
|
|
|
it('presentational index.tsx does NOT import trust-mutating functions directly', () => {
|
|
const index = readFile('index.tsx');
|
|
for (const fn of TRUST_MUTATING_IMPORTS) {
|
|
// Check that none of these appear in import statements
|
|
const importPattern = new RegExp(
|
|
`import\\s*\\{[^}]*\\b${fn}\\b[^}]*\\}\\s*from`,
|
|
);
|
|
expect(index).not.toMatch(importPattern);
|
|
}
|
|
});
|
|
|
|
it('presentational index.tsx does not import from meshIdentity', () => {
|
|
const index = readFile('index.tsx');
|
|
expect(index).not.toMatch(/from\s+['"]@\/mesh\/meshIdentity['"]/);
|
|
});
|
|
|
|
it('presentational index.tsx does not import from meshDmWorkerClient', () => {
|
|
const index = readFile('index.tsx');
|
|
expect(index).not.toMatch(/from\s+['"]@\/mesh\/meshDmWorkerClient['"]/);
|
|
});
|
|
});
|
|
|
|
// ─── Controller owns required-cohesion items ────────────────────────────────
|
|
|
|
describe('MeshChat decomposition — controller required-cohesion', () => {
|
|
const controller = readFile('useMeshChatController.ts');
|
|
|
|
it('controller exports enqueueDmSend (high-privacy DM queueing)', () => {
|
|
expect(controller).toMatch(/enqueueDmSend/);
|
|
// Also in the return block
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*enqueueDmSend[\s\S]*\}/);
|
|
});
|
|
|
|
it('controller exports flushDmQueue (high-privacy DM queueing)', () => {
|
|
expect(controller).toMatch(/flushDmQueue/);
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*flushDmQueue[\s\S]*\}/);
|
|
});
|
|
|
|
it('controller exports selectedGateAccessReady', () => {
|
|
expect(controller).toMatch(/selectedGateAccessReady/);
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*selectedGateAccessReady[\s\S]*\}/);
|
|
});
|
|
|
|
it('controller exports selectedGateKeyStatus', () => {
|
|
expect(controller).toMatch(/selectedGateKeyStatus/);
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*selectedGateKeyStatus[\s\S]*\}/);
|
|
});
|
|
|
|
it('controller exports native gate resync state and handler', () => {
|
|
expect(controller).toMatch(/gateResyncTarget/);
|
|
expect(controller).toMatch(/gateResyncBusy/);
|
|
expect(controller).toMatch(/handleResyncGateState/);
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*gateResyncTarget[\s\S]*\}/);
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*gateResyncBusy[\s\S]*\}/);
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*handleResyncGateState[\s\S]*\}/);
|
|
});
|
|
|
|
it('controller exports secureDmBlocked', () => {
|
|
expect(controller).toMatch(/secureDmBlocked/);
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*secureDmBlocked[\s\S]*\}/);
|
|
});
|
|
|
|
it('controller exports privacyProfile', () => {
|
|
expect(controller).toMatch(/privacyProfile/);
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*privacyProfile[\s\S]*\}/);
|
|
});
|
|
|
|
it('controller exports hasId and hasPublicLaneIdentity', () => {
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*hasId[\s\S]*\}/);
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*hasPublicLaneIdentity[\s\S]*\}/);
|
|
});
|
|
|
|
it('controller exports publicMeshBlockedByWormhole', () => {
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*publicMeshBlockedByWormhole[\s\S]*\}/);
|
|
});
|
|
|
|
it('controller exports anonymousPublicBlocked and anonymousDmBlocked', () => {
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*anonymousPublicBlocked[\s\S]*\}/);
|
|
expect(controller).toMatch(/return\s*\{[\s\S]*anonymousDmBlocked[\s\S]*\}/);
|
|
});
|
|
});
|
|
|
|
// ─── Gate refresh is controller-owned (SSE removed in S3A) ────────────────
|
|
|
|
describe('MeshChat decomposition — gate refresh ownership', () => {
|
|
it('controller does NOT import useGateSSE (removed in S3A)', () => {
|
|
const controller = readFile('useMeshChatController.ts');
|
|
expect(controller).not.toMatch(/import.*useGateSSE.*from/);
|
|
expect(controller).not.toMatch(/useGateSSE\(/);
|
|
});
|
|
|
|
it('controller owns gate message polling via authenticated fetch', () => {
|
|
const controller = readFile('useMeshChatController.ts');
|
|
// The controller polls /api/mesh/infonet/messages for gate refresh
|
|
expect(controller).toMatch(/\/api\/mesh\/infonet\/messages/);
|
|
expect(controller).toMatch(/setInterval\(poll/);
|
|
});
|
|
|
|
it('useGateSSE is NOT imported in the presentational shell', () => {
|
|
const index = readFile('index.tsx');
|
|
expect(index).not.toMatch(/useGateSSE/);
|
|
});
|
|
});
|
|
|
|
// ─── DM polling trust unit controller-owned ─────────────────────────────────
|
|
|
|
describe('MeshChat decomposition — DM poll sequence in controller', () => {
|
|
const controller = readFile('useMeshChatController.ts');
|
|
|
|
it('DM polling (pollDmMailboxes) is in the controller', () => {
|
|
expect(controller).toMatch(/pollDmMailboxes/);
|
|
});
|
|
|
|
it('decryptDM is called in the controller (DM decrypt)', () => {
|
|
expect(controller).toMatch(/decryptDM/);
|
|
});
|
|
|
|
it('ratchetDecryptDM is in the controller', () => {
|
|
expect(controller).toMatch(/ratchetDecryptDM/);
|
|
});
|
|
|
|
it('sender seal decryption is in the controller via storage import', () => {
|
|
expect(controller).toMatch(/decryptSenderSealForContact/);
|
|
});
|
|
|
|
it('contact mutation (addContact/updateContact) happens only in controller', () => {
|
|
const index = readFile('index.tsx');
|
|
// These should not appear as direct function calls in the view
|
|
expect(index).not.toMatch(/\baddContact\s*\(/);
|
|
expect(index).not.toMatch(/\bupdateContact\s*\(/);
|
|
expect(index).not.toMatch(/\bblockContact\s*\(/);
|
|
});
|
|
});
|
|
|
|
// ─── Identity persistence through meshIdentity.ts ───────────────────────────
|
|
|
|
describe('MeshChat decomposition — identity persistence', () => {
|
|
it('controller imports identity functions from meshIdentity', () => {
|
|
const controller = readFile('useMeshChatController.ts');
|
|
expect(controller).toMatch(/from\s+['"]@\/mesh\/meshIdentity['"]/);
|
|
expect(controller).toMatch(/getNodeIdentity/);
|
|
expect(controller).toMatch(/getStoredNodeDescriptor/);
|
|
expect(controller).toMatch(/nextSequence/);
|
|
expect(controller).toMatch(/verifyEventSignature/);
|
|
expect(controller).toMatch(/setSecureModeCached/);
|
|
});
|
|
|
|
it('storage module imports from meshIdentity for seal operations', () => {
|
|
const storage = readFile('storage.ts');
|
|
expect(storage).toMatch(/from\s+['"]@\/mesh\/meshIdentity['"]/);
|
|
});
|
|
|
|
it('types module re-exports Contact and NodeIdentity from meshIdentity', () => {
|
|
const types = readFile('types.ts');
|
|
expect(types).toMatch(/Contact/);
|
|
expect(types).toMatch(/NodeIdentity/);
|
|
});
|
|
});
|
|
|
|
// ─── Re-export stability ────────────────────────────────────────────────────
|
|
|
|
describe('MeshChat decomposition — export stability', () => {
|
|
it('MeshChat.tsx re-exports default from MeshChat/index', () => {
|
|
const reExport = fs.readFileSync(
|
|
path.resolve(MESH_CHAT_DIR, '../MeshChat.tsx'),
|
|
'utf-8',
|
|
);
|
|
expect(reExport).toMatch(/export\s*\{\s*default\s*\}\s*from\s+['"]\.\/MeshChat\/index['"]/);
|
|
});
|
|
|
|
it('MeshChat.tsx re-exports MeshChatProps type', () => {
|
|
const reExport = fs.readFileSync(
|
|
path.resolve(MESH_CHAT_DIR, '../MeshChat.tsx'),
|
|
'utf-8',
|
|
);
|
|
expect(reExport).toMatch(/export\s+type\s*\{\s*MeshChatProps\s*\}/);
|
|
});
|
|
|
|
it('index.tsx exports default MeshChat component', () => {
|
|
const index = readFile('index.tsx');
|
|
expect(index).toMatch(/export\s+default\s+MeshChat/);
|
|
});
|
|
|
|
it('presentational shell exposes the gate resync affordance', () => {
|
|
const index = readFile('index.tsx');
|
|
expect(index).toContain('RESYNC GATE STATE');
|
|
expect(index).toContain('handleResyncGateState(selectedGate)');
|
|
});
|
|
});
|