import { test, describe, before, after } from 'node:test'; import assert from 'node:assert/strict'; import { Arbiter } from '../../src/index.js'; import { BigGraphGenerator } from '../helpers/big-graph-generator.js'; describe.skip('No-Cache Chain Query Benchmark', () => { let arbiter; let generator; let testArbiter; let graphData; let validPaths = []; before(() => { // Create arbiter with caching completely disabled arbiter = new Arbiter({ fastConstructionMode: false, disableCaching: true, disableChainCaching: true, disableDirectCaching: true }); generator = new BigGraphGenerator({ scale: 'medium', seed: 12345 }); // Generate a larger, more realistic graph graphData = generator.generateGraph('enterprise'); testArbiter = generator.loadIntoArbiter(graphData, { disableCaching: true, disableChainCaching: true, disableDirectCaching: true }); // Configure chain rules testArbiter.setRelationConfig('can_read_via_role', { type: 'chain', steps: [ { relation: 'member_of', direction: 'out' }, { relation: 'can_read', direction: 'out' } ] }); testArbiter.setRelationConfig('can_access_multi_hop', { type: 'chain', steps: [ { relation: 'member_of', direction: 'out' }, { relation: 'member_of', direction: 'out' }, { relation: 'member_of', direction: 'out' }, { relation: 'can_read', direction: 'out' } ] }); // Pre-find valid paths that actually exist in the graph if (process.env.TEST_DEBUG === '1') console.log('🔍 Finding valid chain paths (no cache)...'); validPaths = findValidChainPaths(testArbiter, graphData); if (process.env.TEST_DEBUG === '1') console.log(` Found ${validPaths.length} valid paths`); }); after(() => { arbiter = null; generator = null; testArbiter = null; graphData = null; validPaths = null; }); /** * Find valid chain paths that actually exist in the graph */ function findValidChainPaths(arbiter, graphData) { const validPaths = []; // Get all users and documents const users = graphData.users.map(u => u.id); const docs = graphData.documents.map(d => d.id); // Sample a reasonable number of combinations to test const maxTests = Math.min(1000, users.length * docs.length); let tested = 0; for (const user of users) { if (tested >= maxTests) break; for (const doc of docs) { if (tested >= maxTests) break; // Test if this path actually exists try { const result = arbiter.check(user, 'can_read_via_role', doc); if (result.possibility > 0) { validPaths.push({ user, doc, relation: 'can_read_via_role', result }); } } catch (error) { // Skip invalid paths } tested++; } } return validPaths; } /** * Measure QPS for a given operation with detailed analysis */ function measureQPSWithAnalysis(operation, duration = 3000, operationName = 'Operation') { const startTime = Date.now(); const endTime = startTime + duration; let operationCount = 0; const latencies = []; let successCount = 0; let failureCount = 0; // Verify caches are disabled if (testArbiter.directCheckCache) { throw new Error('Direct check cache should be disabled but is not null'); } if (testArbiter.relationManager && testArbiter.relationManager.chainRule) { if (testArbiter.relationManager.chainRule.chainResultCache) { throw new Error('Chain result cache should be disabled but is not null'); } if (testArbiter.relationManager.chainRule.chainPathCache) { throw new Error('Chain path cache should be disabled but is not null'); } } while (Date.now() < endTime) { const opStart = process.hrtime.bigint(); try { const result = operation(); operationCount++; if (result && result.possibility > 0) { successCount++; } else { failureCount++; } const opEnd = process.hrtime.bigint(); const latency = Number(opEnd - opStart) / 1000000; // Convert to milliseconds latencies.push(latency); } catch (error) { operationCount++; failureCount++; const opEnd = process.hrtime.bigint(); const latency = Number(opEnd - opStart) / 1000000; latencies.push(latency); } } const actualDuration = Date.now() - startTime; const qps = (operationCount / actualDuration) * 1000; const avgLatency = latencies.reduce((a, b) => a + b, 0) / latencies.length; // Sort once and reuse const sortedLatencies = latencies.sort((a, b) => a - b); const p95Latency = sortedLatencies[Math.floor(latencies.length * 0.95)]; const p99Latency = sortedLatencies[Math.floor(latencies.length * 0.99)]; return { qps, operationCount, duration: actualDuration, avgLatency, p95Latency, p99Latency, maxLatency: sortedLatencies[sortedLatencies.length - 1], minLatency: sortedLatencies[0], successCount, failureCount, successRate: successCount / operationCount }; } test('No-Cache Chain Query - True Cold Start', async () => { if (process.env.TEST_DEBUG === '1') console.log('📊 Testing No-Cache Chain Query QPS (True Cold Start)...'); if (validPaths.length === 0) { if (process.env.TEST_DEBUG === '1') console.log(' ⚠️ No valid paths found - skipping test'); return; } let queryIndex = 0; const result = measureQPSWithAnalysis(() => { const path = validPaths[queryIndex % validPaths.length]; return testArbiter.check(path.user, path.relation, path.doc); }, 3000, 'No-Cache Chain Query'); if (process.env.TEST_DEBUG === '1') console.log(` QPS: ${result.qps.toFixed(2)}`); if (process.env.TEST_DEBUG === '1') console.log(` Operations: ${result.operationCount}`); if (process.env.TEST_DEBUG === '1') console.log(` Duration: ${result.duration}ms`); if (process.env.TEST_DEBUG === '1') console.log(` Avg Latency: ${result.avgLatency.toFixed(3)}ms`); if (process.env.TEST_DEBUG === '1') console.log(` P95 Latency: ${result.p95Latency.toFixed(3)}ms`); if (process.env.TEST_DEBUG === '1') console.log(` P99 Latency: ${result.p99Latency.toFixed(3)}ms`); if (process.env.TEST_DEBUG === '1') console.log(` Success Rate: ${(result.successRate * 100).toFixed(1)}%`); if (process.env.TEST_DEBUG === '1') console.log(` Success Count: ${result.successCount}`); if (process.env.TEST_DEBUG === '1') console.log(` Failure Count: ${result.failureCount}`); // This should be the true performance without any caching assert.ok(result.qps > 10, `No-cache chain query QPS ${result.qps.toFixed(0)} below 10 threshold`); assert.ok(result.avgLatency < 200, `No-cache chain query latency ${result.avgLatency.toFixed(3)}ms too high`); assert.ok(result.successRate > 0.8, `Success rate ${(result.successRate * 100).toFixed(1)}% too low for valid paths`); }); test('No-Cache Multi-hop Chain Query - True Cold Start', async () => { if (process.env.TEST_DEBUG === '1') console.log('📊 Testing No-Cache Multi-hop Chain Query QPS (True Cold Start)...'); // Find valid multi-hop paths const multiHopPaths = []; const users = graphData.users.map(u => u.id); const docs = graphData.documents.map(d => d.id); let tested = 0; const maxTests = Math.min(500, users.length * docs.length); for (const user of users) { if (tested >= maxTests) break; for (const doc of docs) { if (tested >= maxTests) break; try { const result = testArbiter.check(user, 'can_access_multi_hop', doc); if (result.possibility > 0) { multiHopPaths.push({ user, doc, relation: 'can_access_multi_hop', result }); } } catch (error) { // Skip invalid paths } tested++; } } if (process.env.TEST_DEBUG === '1') console.log(` Found ${multiHopPaths.length} valid multi-hop paths`); if (multiHopPaths.length === 0) { if (process.env.TEST_DEBUG === '1') console.log(' ⚠️ No valid multi-hop paths found - skipping test'); return; } let queryIndex = 0; const result = measureQPSWithAnalysis(() => { const path = multiHopPaths[queryIndex % multiHopPaths.length]; return testArbiter.check(path.user, path.relation, path.doc); }, 3000, 'No-Cache Multi-hop Chain Query'); if (process.env.TEST_DEBUG === '1') console.log(` QPS: ${result.qps.toFixed(2)}`); if (process.env.TEST_DEBUG === '1') console.log(` Operations: ${result.operationCount}`); if (process.env.TEST_DEBUG === '1') console.log(` Duration: ${result.duration}ms`); if (process.env.TEST_DEBUG === '1') console.log(` Avg Latency: ${result.avgLatency.toFixed(3)}ms`); if (process.env.TEST_DEBUG === '1') console.log(` P95 Latency: ${result.p95Latency.toFixed(3)}ms`); if (process.env.TEST_DEBUG === '1') console.log(` P99 Latency: ${result.p99Latency.toFixed(3)}ms`); if (process.env.TEST_DEBUG === '1') console.log(` Success Rate: ${(result.successRate * 100).toFixed(1)}%`); if (process.env.TEST_DEBUG === '1') console.log(` Success Count: ${result.successCount}`); if (process.env.TEST_DEBUG === '1') console.log(` Failure Count: ${result.failureCount}`); // Multi-hop should be slower than 2-hop, especially without caching assert.ok(result.qps > 5, `No-cache multi-hop chain query QPS ${result.qps.toFixed(0)} below 5 threshold`); assert.ok(result.avgLatency < 500, `No-cache multi-hop chain query latency ${result.avgLatency.toFixed(3)}ms too high`); assert.ok(result.successRate > 0.7, `Success rate ${(result.successRate * 100).toFixed(1)}% too low for valid paths`); }); test('Cache Disable Verification', async () => { if (process.env.TEST_DEBUG === '1') console.log('🔍 Verifying cache disable functionality...'); // Verify that caches are actually disabled assert.strictEqual(testArbiter.directCheckCache, null, 'Direct check cache should be null when disabled'); assert.strictEqual(testArbiter.disableCaching, true, 'disableCaching should be true'); assert.strictEqual(testArbiter.disableChainCaching, true, 'disableChainCaching should be true'); assert.strictEqual(testArbiter.disableDirectCaching, true, 'disableDirectCaching should be true'); if (testArbiter.relationManager && testArbiter.relationManager.chainRule) { assert.strictEqual(testArbiter.relationManager.chainRule.chainResultCache, null, 'Chain result cache should be null when disabled'); assert.strictEqual(testArbiter.relationManager.chainRule.chainPathCache, null, 'Chain path cache should be null when disabled'); } if (process.env.TEST_DEBUG === '1') console.log(' ✅ All caches are properly disabled'); }); test('Graph Statistics', async () => { if (process.env.TEST_DEBUG === '1') console.log('📊 Graph Statistics:'); if (process.env.TEST_DEBUG === '1') console.log(` Total Relations: ${graphData.relations.length}`); if (process.env.TEST_DEBUG === '1') console.log(` Total Users: ${graphData.users.length}`); if (process.env.TEST_DEBUG === '1') console.log(` Total Documents: ${graphData.documents.length}`); const userCount = graphData.users.length; const docCount = graphData.documents.length; const groupCount = graphData.enterprises ? graphData.enterprises.length : 0; if (process.env.TEST_DEBUG === '1') console.log(` Users: ${userCount}`); if (process.env.TEST_DEBUG === '1') console.log(` Documents: ${docCount}`); if (process.env.TEST_DEBUG === '1') console.log(` Groups: ${groupCount}`); const relationTypes = [...new Set(graphData.relations.map(r => r.relation))]; if (process.env.TEST_DEBUG === '1') console.log(` Relation Types: ${relationTypes.length} (${relationTypes.join(', ')})`); if (process.env.TEST_DEBUG === '1') console.log(` Valid Chain Paths Found: ${validPaths.length}`); // Verify we have a reasonable graph size assert.ok(graphData.relations.length > 1000, `Graph too small: ${graphData.relations.length} relations`); assert.ok(graphData.users.length > 50, `Graph too small: ${graphData.users.length} users`); }); });