import { test, describe, it, beforeEach } from 'node:test'; import assert from 'node:assert/strict'; import { Arbiter } from '../../src/index.js'; /** * Performance Regression Tests * * Tests that performance optimizations don't regress and that * authorization remains fast even with large datasets. */ describe.skip('Performance Regression Tests', () => { let arbiter; beforeEach(() => { arbiter = new Arbiter({ embeddingDimensions: 256, directCheckCacheSize: 1000, directCheckCacheTTL: 60000 }); }); describe('Large Dataset Performance', () => { it('maintains performance with large number of nodes', () => { const nodeCount = 1000; const startTime = Date.now(); // Create many nodes for (let i = 0; i < nodeCount; i++) { arbiter.addNode(`user:user-${i}`, 'user'); arbiter.addNode(`group:group-${i}`, 'group'); arbiter.addNode(`document:doc-${i}`, 'document'); } const creationTime = Date.now() - startTime; if (process.env.TEST_DEBUG === '1') console.log(`Created ${nodeCount * 3} nodes in ${creationTime}ms`); // Set up relations arbiter.setRelationConfig('member_of', { type: 'direct' }); arbiter.setRelationConfig('owner', { type: 'direct' }); const relationStartTime = Date.now(); for (let i = 0; i < nodeCount; i++) { arbiter.addRelation(`user:user-${i}`, 'member_of', `group:group-${i}`); arbiter.addRelation(`document:doc-${i}`, 'owner', `group:group-${i}`); } const relationTime = Date.now() - relationStartTime; if (process.env.TEST_DEBUG === '1') console.log(`Created ${nodeCount * 2} relations in ${relationTime}ms`); // Test authorization performance const authStartTime = Date.now(); for (let i = 0; i < 100; i++) { const result = arbiter.check(`user:user-${i}`, 'member_of', `group:group-${i}`); assert.equal(result.possibility, 1.0); } const authTime = Date.now() - authStartTime; if (process.env.TEST_DEBUG === '1') console.log(`Performed 100 authorization checks in ${authTime}ms`); // Performance assertions assert.ok(creationTime < 5000, `Node creation too slow: ${creationTime}ms`); assert.ok(relationTime < 3000, `Relation creation too slow: ${relationTime}ms`); assert.ok(authTime < 1000, `Authorization too slow: ${authTime}ms`); }); it('maintains performance with complex tuple-to-userset rules', () => { const groupCount = 100; const userCount = 50; // Create groups and users for (let i = 0; i < groupCount; i++) { arbiter.addNode(`group:group-${i}`, 'group'); } for (let i = 0; i < userCount; i++) { arbiter.addNode(`user:user-${i}`, 'user'); } arbiter.setRelationConfig('member_of', { type: 'direct' }); arbiter.setRelationConfig('owner', { type: 'direct' }); // Create complex membership patterns for (let i = 0; i < userCount; i++) { // Each user is member of multiple groups for (let j = 0; j < 5; j++) { const groupIndex = (i + j) % groupCount; arbiter.addRelation(`user:user-${i}`, 'member_of', `group:group-${groupIndex}`); } } // Create document ownership for (let i = 0; i < groupCount; i++) { arbiter.addRelation(`document:doc-${i}`, 'owner', `group:group-${i}`); } // Set up tuple-to-userset rule arbiter.setRelationConfig('group_access', { type: 'tuple_to_userset', tuplesetRelation: 'owner', computedRelation: 'member_of' }); const startTime = Date.now(); // Test authorization performance for (let i = 0; i < 50; i++) { const result = arbiter.check(`user:user-${i}`, 'group_access', `document:doc-${i}`); assert.ok(result.possibility >= 0); } const duration = Date.now() - startTime; if (process.env.TEST_DEBUG === '1') console.log(`Complex tuple-to-userset authorization: ${duration}ms for 50 checks`); // Should complete within reasonable time assert.ok(duration < 2000, `Complex authorization too slow: ${duration}ms`); }); it('maintains performance with deep chain rules', () => { const depth = 10; const nodeCount = depth + 1; // Create chain: user → group1 → group2 → ... → groupN → document for (let i = 0; i < nodeCount; i++) { arbiter.addNode(`entity:entity-${i}`, 'entity'); } arbiter.setRelationConfig('next', { type: 'direct' }); // Create chain relations for (let i = 0; i < depth; i++) { arbiter.addRelation(`entity:entity-${i}`, 'next', `entity:entity-${i + 1}`); } // Set up deep chain rule const steps = []; for (let i = 0; i < depth; i++) { steps.push({ relation: 'next', direction: 'out' }); } arbiter.setRelationConfig('deep_chain', { type: 'chain', steps: steps }); const startTime = Date.now(); // Test deep chain authorization const result = arbiter.check('entity:entity-0', 'deep_chain', `entity:entity-${depth}`); const duration = Date.now() - startTime; if (process.env.TEST_DEBUG === '1') console.log(`Deep chain authorization (depth ${depth}): ${duration}ms`); assert.equal(result.possibility, 1.0); assert.ok(duration < 1000, `Deep chain too slow: ${duration}ms`); }); }); describe('Cache Performance', () => { it('demonstrates cache effectiveness with repeated queries', () => { // Set up simple authorization arbiter.addNode('user:alice', 'user'); arbiter.addNode('document:spec', 'document'); arbiter.addRelation('user:alice', 'can_read', 'document:spec', { possibility: 1.0 }); arbiter.setRelationConfig('can_read', { type: 'direct' }); // First query (cache miss) const startTime1 = Date.now(); const result1 = arbiter.check('user:alice', 'can_read', 'document:spec'); const time1 = Date.now() - startTime1; // Second query (cache hit) const startTime2 = Date.now(); const result2 = arbiter.check('user:alice', 'can_read', 'document:spec'); const time2 = Date.now() - startTime2; if (process.env.TEST_DEBUG === '1') console.log(`First query: ${time1}ms, Second query: ${time2}ms`); assert.equal(result1.possibility, 1.0); assert.equal(result2.possibility, 1.0); // Cache should make second query faster (though this might be too fast to measure) assert.ok(time2 <= time1, 'Cache should not make queries slower'); }); it('handles cache eviction under memory pressure', () => { // Set up cache with small size const smallArbiter = new Arbiter({ directCheckCacheSize: 10, // Very small cache directCheckCacheTTL: 60000 }); smallArbiter.setRelationConfig('can_read', { type: 'direct' }); // Create many users and documents for (let i = 0; i < 50; i++) { smallArbiter.addNode(`user:user-${i}`, 'user'); smallArbiter.addNode(`document:doc-${i}`, 'document'); smallArbiter.addRelation(`user:user-${i}`, 'can_read', `document:doc-${i}`, { possibility: 1.0 }); } const startTime = Date.now(); // Perform many authorization checks to trigger cache eviction for (let i = 0; i < 100; i++) { const userIndex = i % 50; const docIndex = (i + 1) % 50; const result = smallArbiter.check(`user:user-${userIndex}`, 'can_read', `document:doc-${docIndex}`); assert.ok(result.possibility >= 0); } const duration = Date.now() - startTime; if (process.env.TEST_DEBUG === '1') console.log(`Cache eviction test: ${duration}ms for 100 checks`); // Should complete within reasonable time even with cache eviction assert.ok(duration < 3000, `Cache eviction too slow: ${duration}ms`); }); }); describe('Binary Mode Performance', () => { it('demonstrates binary mode performance benefits', () => { // Set up authorization scenario arbiter.addNode('user:alice', 'user'); arbiter.addNode('document:spec', 'document'); arbiter.addRelation('user:alice', 'can_read', 'document:spec', { possibility: 1.0 }); arbiter.setRelationConfig('can_read', { type: 'direct' }); // Test normal mode const normalStartTime = Date.now(); for (let i = 0; i < 1000; i++) { const result = arbiter.check('user:alice', 'can_read', 'document:spec'); assert.equal(result.possibility, 1.0); } const normalTime = Date.now() - normalStartTime; // Test binary mode const binaryStartTime = Date.now(); for (let i = 0; i < 1000; i++) { const result = arbiter.check('user:alice', 'can_read', 'document:spec', { binary: true }); assert.equal(result.possibility, 1.0); } const binaryTime = Date.now() - binaryStartTime; if (process.env.TEST_DEBUG === '1') console.log(`Normal mode: ${normalTime}ms, Binary mode: ${binaryTime}ms`); // Binary mode should be faster for simple cases assert.ok(binaryTime <= normalTime, 'Binary mode should not be slower than normal mode'); }); it('maintains binary mode performance with complex rules', () => { // Set up complex tuple-to-userset scenario for (let i = 0; i < 20; i++) { arbiter.addNode(`user:user-${i}`, 'user'); arbiter.addNode(`group:group-${i}`, 'group'); arbiter.addNode(`document:doc-${i}`, 'document'); arbiter.addRelation(`user:user-${i}`, 'member_of', `group:group-${i}`); arbiter.addRelation(`document:doc-${i}`, 'owner', `group:group-${i}`); } arbiter.setRelationConfig('member_of', { type: 'direct' }); arbiter.setRelationConfig('owner', { type: 'direct' }); arbiter.setRelationConfig('group_access', { type: 'tuple_to_userset', tuplesetRelation: 'owner', computedRelation: 'member_of' }); const startTime = Date.now(); // Test binary mode with complex rules for (let i = 0; i < 100; i++) { const userIndex = i % 20; const docIndex = i % 20; const result = arbiter.check(`user:user-${userIndex}`, 'group_access', `document:doc-${docIndex}`, { binary: true }); assert.ok(result.possibility >= 0); } const duration = Date.now() - startTime; if (process.env.TEST_DEBUG === '1') console.log(`Binary mode complex rules: ${duration}ms for 100 checks`); // Should complete within reasonable time assert.ok(duration < 2000, `Binary mode too slow: ${duration}ms`); }); }); describe('Memory Usage Performance', () => { it('tracks memory usage during large operations', () => { const initialMemory = process.memoryUsage(); // Create large dataset const nodeCount = 500; for (let i = 0; i < nodeCount; i++) { arbiter.addNode(`user:user-${i}`, 'user'); arbiter.addNode(`group:group-${i}`, 'group'); arbiter.addNode(`document:doc-${i}`, 'document'); } const afterNodesMemory = process.memoryUsage(); // Add relations arbiter.setRelationConfig('member_of', { type: 'direct' }); arbiter.setRelationConfig('owner', { type: 'direct' }); for (let i = 0; i < nodeCount; i++) { arbiter.addRelation(`user:user-${i}`, 'member_of', `group:group-${i}`); arbiter.addRelation(`document:doc-${i}`, 'owner', `group:group-${i}`); } const afterRelationsMemory = process.memoryUsage(); // Perform authorization checks for (let i = 0; i < 100; i++) { arbiter.check(`user:user-${i % nodeCount}`, 'member_of', `group:group-${i % nodeCount}`); } const finalMemory = process.memoryUsage(); if (process.env.TEST_DEBUG === '1') console.log('Memory usage:'); if (process.env.TEST_DEBUG === '1') console.log(` Initial: ${Math.round(initialMemory.heapUsed / 1024 / 1024)}MB`); if (process.env.TEST_DEBUG === '1') console.log(` After nodes: ${Math.round(afterNodesMemory.heapUsed / 1024 / 1024)}MB`); if (process.env.TEST_DEBUG === '1') console.log(` After relations: ${Math.round(afterRelationsMemory.heapUsed / 1024 / 1024)}MB`); if (process.env.TEST_DEBUG === '1') console.log(` Final: ${Math.round(finalMemory.heapUsed / 1024 / 1024)}MB`); // Memory usage should be reasonable const memoryIncrease = finalMemory.heapUsed - initialMemory.heapUsed; const memoryIncreaseMB = memoryIncrease / 1024 / 1024; assert.ok(memoryIncreaseMB < 100, `Memory usage too high: ${memoryIncreaseMB}MB increase`); }); }); describe('Concurrent Performance', () => { it('handles concurrent authorization requests efficiently', async () => { // Set up test data for (let i = 0; i < 100; i++) { arbiter.addNode(`user:user-${i}`, 'user'); arbiter.addNode(`document:doc-${i}`, 'document'); arbiter.addRelation(`user:user-${i}`, 'can_read', `document:doc-${i}`, { possibility: 1.0 }); } arbiter.setRelationConfig('can_read', { type: 'direct' }); const startTime = Date.now(); // Create concurrent authorization requests const promises = []; for (let i = 0; i < 50; i++) { promises.push( new Promise((resolve) => { const result = arbiter.check(`user:user-${i}`, 'can_read', `document:doc-${i}`); resolve(result); }) ); } const results = await Promise.all(promises); const duration = Date.now() - startTime; if (process.env.TEST_DEBUG === '1') console.log(`Concurrent authorization: ${duration}ms for 50 requests`); // All results should be successful for (const result of results) { assert.equal(result.possibility, 1.0); } // Should complete within reasonable time assert.ok(duration < 1000, `Concurrent authorization too slow: ${duration}ms`); }); }); });