import { test, describe } from 'node:test'; import assert from 'node:assert/strict'; import { Arbiter } from '../../src/index.js'; describe('AuthorizationChecker Basic Functionality', () => { let arbiter; let authChecker; test.beforeEach(() => { arbiter = new Arbiter(); authChecker = arbiter.authChecker; }); test('performs basic authorization checks', () => { arbiter.addNode('user1', 'user'); arbiter.addNode('project1', 'project'); arbiter.addRelation('user1', 'can_read', 'project1', 1.0); arbiter.setRelationConfig('can_read', { type: 'direct' }); const result = authChecker.check('user1', 'can_read', 'project1', { includeMeta: true }); assert.strictEqual(result.possibility, 1); // CI-001 fix: fast path → 'direct_match' assert.strictEqual(result.reason, 'direct_match'); }); test('handles missing nodes', () => { arbiter.addNode('user1', 'user'); const result = authChecker.check('user1', 'can_read', 'nonexistent'); assert.strictEqual(result.possibility, 0); assert.strictEqual(result.reason, 'missing_node'); }); test('handles missing relations', () => { arbiter.addNode('user1', 'user'); arbiter.addNode('project1', 'project'); arbiter.setRelationConfig('can_read', { type: 'direct' }); const result = authChecker.check('user1', 'can_read', 'project1', { includeMeta: true }); assert.strictEqual(result.possibility, 0); // CI-001 fix: fast path → 'no_relation' assert.strictEqual(result.reason, 'no_relation'); }); test('handles missing relation configuration', () => { arbiter.addNode('user1', 'user'); arbiter.addNode('project1', 'project'); arbiter.addRelation('user1', 'can_read', 'project1', 1.0); const result = authChecker.check('user1', 'can_read', 'project1', { includeMeta: true }); assert.strictEqual(result.possibility, 0); assert.strictEqual(result.reason, 'no_config'); }); test('supports binary mode with high threshold', () => { arbiter.addNode('user1', 'user'); arbiter.addNode('project1', 'project'); arbiter.addRelation('user1', 'can_read', 'project1', 0.9); arbiter.setRelationConfig('can_read', { type: 'direct' }); const result = authChecker.check('user1', 'can_read', 'project1', { binary: true, minAllowPossibility: 0.95 }); assert.strictEqual(result.possibility, 0.9); assert.strictEqual(result.allow, false); assert.strictEqual(result.deny, false); }); test('supports binary mode with low threshold', () => { arbiter.addNode('user1', 'user'); arbiter.addNode('project1', 'project'); arbiter.addRelation('user1', 'can_read', 'project1', 0.9); arbiter.setRelationConfig('can_read', { type: 'direct' }); const result = authChecker.check('user1', 'can_read', 'project1', { binary: true, minAllowPossibility: 0.8 }); assert.strictEqual(result.possibility, 0.9); assert.strictEqual(result.allow, true); assert.strictEqual(result.deny, false); }); test('handles visited set for cycle prevention', () => { arbiter.addNode('user1', 'user'); arbiter.addNode('project1', 'project'); arbiter.addRelation('user1', 'can_read', 'project1', 1.0); arbiter.setRelationConfig('can_read', { type: 'direct' }); const visited = new Set(); visited.add({ userKey: 'user1', relation: 'can_read', objectKey: 'project1' }); const result = authChecker.check('user1', 'can_read', 'project1', { _visited: visited }); assert.strictEqual(result.possibility, 0); assert.strictEqual(result.reason, 'cycle'); }); test('handles backward compatibility with Set parameter', () => { arbiter.addNode('user1', 'user'); arbiter.addNode('project1', 'project'); arbiter.addRelation('user1', 'can_read', 'project1', 1.0); arbiter.setRelationConfig('can_read', { type: 'direct' }); const visited = new Set(); const result = authChecker.check('user1', 'can_read', 'project1', visited, 'can_read'); assert.strictEqual(result.possibility, 1); // CI-001 fix: fast path → 'direct_match' assert.strictEqual(result.reason, 'direct_match'); }); test('handles edge cases gracefully', () => { // Test with empty strings const result1 = authChecker.check('', 'can_read', ''); assert.strictEqual(result1.possibility, 0); assert.strictEqual(result1.reason, 'missing_node'); // Test with null/undefined const result2 = authChecker.check(null, 'can_read', null); assert.strictEqual(result2.possibility, 0); assert.strictEqual(result2.reason, 'missing_node'); }); test('handles complex rule configurations', () => { arbiter.addNode('user1', 'user'); arbiter.addNode('group1', 'group'); arbiter.addNode('project1', 'project'); arbiter.addRelation('user1', 'member_of', 'group1', 1.0); arbiter.addRelation('group1', 'can_access', 'project1', 1.0); arbiter.setRelationConfig('member_of', { type: 'direct' }); arbiter.setRelationConfig('can_access', { type: 'chain', steps: [ { relation: 'member_of', direction: 'out' }, { relation: 'can_access', direction: 'out' } ] }); const result = authChecker.check('user1', 'can_access', 'project1'); assert.strictEqual(result.possibility, 1); assert.strictEqual(result.reason, 'allow_rule_matched'); }); test('returns proper meta information', () => { arbiter.addNode('user1', 'user'); arbiter.addNode('project1', 'project'); arbiter.addRelation('user1', 'can_read', 'project1', 1.0); arbiter.setRelationConfig('can_read', { type: 'direct' }); const result = authChecker.check('user1', 'can_read', 'project1', { includeMeta: true }); assert.ok(result.meta); // CI-001 fix: fast path exposes meta.allow.{ruleType,reason,...} // instead of the slow-path meta.{maxDeny,maxAllow} shape. assert.strictEqual(result.meta.allow.ruleType, 'direct'); assert.strictEqual(result.meta.allow.reason, 'direct'); }); });