bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
141 lines
4.4 KiB
JavaScript
141 lines
4.4 KiB
JavaScript
/**
|
|
* test/tenant-isolation.test.mjs — Tenant isolation tests
|
|
*/
|
|
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert';
|
|
|
|
// Mock pool that tracks tenant isolation
|
|
function createMockPool() {
|
|
const executedQueries = [];
|
|
return {
|
|
executedQueries,
|
|
query: async (sql, params) => {
|
|
executedQueries.push({ sql, params });
|
|
// Verify tenant_id is first param for most queries
|
|
return { rows: [] };
|
|
},
|
|
connect: async () => ({
|
|
query: async (sql, params) => {
|
|
executedQueries.push({ sql, params, client: true });
|
|
return { rows: [] };
|
|
},
|
|
release: () => {}
|
|
})
|
|
};
|
|
}
|
|
|
|
// Tenant context builder (same as in index.mjs)
|
|
function buildCtx(req, entityType = null, entityId = null, decisionSource = 'user') {
|
|
return {
|
|
trace_id: req.headers?.['x-trace-id'] || 'test-trace',
|
|
correlation_id: req.headers?.['x-correlation-id'] || 'test-correlation',
|
|
tenant_id: req.headers?.['x-tenant-id'] || req.body?.tenant_id || 'wavult-group',
|
|
user_id: req.headers?.['x-user-id'] || req.body?.user_id || 'system',
|
|
entity_type: entityType,
|
|
entity_id: entityId,
|
|
decision_source: decisionSource,
|
|
};
|
|
}
|
|
|
|
describe('Tenant isolation', () => {
|
|
it('should use tenant_id from headers', () => {
|
|
const req = {
|
|
headers: { 'x-tenant-id': 'tenant-a' },
|
|
body: {}
|
|
};
|
|
const ctx = buildCtx(req);
|
|
assert.strictEqual(ctx.tenant_id, 'tenant-a');
|
|
});
|
|
|
|
it('should fallback to body tenant_id if header missing', () => {
|
|
const req = {
|
|
headers: {},
|
|
body: { tenant_id: 'tenant-b' }
|
|
};
|
|
const ctx = buildCtx(req);
|
|
assert.strictEqual(ctx.tenant_id, 'tenant-b');
|
|
});
|
|
|
|
it('should use default tenant when no tenant specified', () => {
|
|
const req = { headers: {}, body: {} };
|
|
const ctx = buildCtx(req);
|
|
assert.strictEqual(ctx.tenant_id, 'wavult-group');
|
|
});
|
|
|
|
it('should include tenant_id in SQL queries', async () => {
|
|
const pool = createMockPool();
|
|
const tenantId = 'tenant-test';
|
|
|
|
// Simulate a query that should include tenant_id
|
|
await pool.query(
|
|
'SELECT * FROM ledger_accounts WHERE tenant_id = $1',
|
|
[tenantId]
|
|
);
|
|
|
|
const query = pool.executedQueries[0];
|
|
assert.strictEqual(query.params[0], tenantId);
|
|
assert.ok(query.sql.includes('tenant_id'));
|
|
});
|
|
|
|
it('should prevent cross-tenant data access', () => {
|
|
const tenantA = 'tenant-a';
|
|
const tenantB = 'tenant-b';
|
|
|
|
// Simulate query for tenant A
|
|
const queryA = {
|
|
sql: 'SELECT * FROM ledger_journal_entries WHERE tenant_id = $1',
|
|
params: [tenantA]
|
|
};
|
|
|
|
// Ensure tenant B cannot access tenant A's data
|
|
const isIsolated = queryA.params[0] !== tenantB;
|
|
assert.strictEqual(isIsolated, true);
|
|
});
|
|
|
|
it('should include tenant_id in all journal entry operations', () => {
|
|
const operations = [
|
|
'SELECT * FROM ledger_journal_entries WHERE tenant_id = $1',
|
|
'INSERT INTO ledger_journal_entries (tenant_id, ...) VALUES ($1, ...)',
|
|
'UPDATE ledger_journal_entries SET ... WHERE id = $2 AND tenant_id = $1',
|
|
'DELETE FROM ledger_journal_entries WHERE id = $1 AND tenant_id = $2'
|
|
];
|
|
|
|
for (const sql of operations) {
|
|
assert.ok(sql.includes('tenant_id'), `Missing tenant_id in: ${sql}`);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Multi-tenant scenarios', () => {
|
|
it('should maintain separate contexts for concurrent requests', () => {
|
|
const req1 = { headers: { 'x-tenant-id': 'tenant-1' }, body: {} };
|
|
const req2 = { headers: { 'x-tenant-id': 'tenant-2' }, body: {} };
|
|
|
|
const ctx1 = buildCtx(req1);
|
|
const ctx2 = buildCtx(req2);
|
|
|
|
assert.strictEqual(ctx1.tenant_id, 'tenant-1');
|
|
assert.strictEqual(ctx2.tenant_id, 'tenant-2');
|
|
assert.notStrictEqual(ctx1.tenant_id, ctx2.tenant_id);
|
|
});
|
|
|
|
it('should propagate trace context across operations', () => {
|
|
const req = {
|
|
headers: {
|
|
'x-trace-id': 'trace-123',
|
|
'x-correlation-id': 'corr-456',
|
|
'x-tenant-id': 'tenant-x'
|
|
},
|
|
body: {}
|
|
};
|
|
|
|
const ctx = buildCtx(req, 'journal_entry', 'entry-1');
|
|
|
|
assert.strictEqual(ctx.trace_id, 'trace-123');
|
|
assert.strictEqual(ctx.correlation_id, 'corr-456');
|
|
assert.strictEqual(ctx.tenant_id, 'tenant-x');
|
|
assert.strictEqual(ctx.entity_type, 'journal_entry');
|
|
assert.strictEqual(ctx.entity_id, 'entry-1');
|
|
});
|
|
});
|