rbac-engine
Version:
Role-based access control engine with policy-based permissions
306 lines • 14.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const statement_builder_1 = require("../../builders/statement-builder");
const policy_builder_1 = require("../../builders/policy-builder");
const types_1 = require("../../builders/types");
const models_1 = require("../../models");
describe('StatementBuilder', () => {
describe('Basic statement building', () => {
it('should build a simple allow statement', () => {
const statement = new statement_builder_1.StatementBuilder()
.allow(['read', 'write'])
.on(['document/*'])
.build();
expect(statement.Effect).toBe(models_1.Effect.Allow);
expect(statement.Action).toEqual(['read', 'write']);
expect(statement.Resource).toEqual(['document/*']);
expect(statement.Condition).toBeUndefined();
expect(statement.StartDate).toBeUndefined();
expect(statement.EndDate).toBeUndefined();
});
it('should build a simple deny statement', () => {
const statement = new statement_builder_1.StatementBuilder()
.deny(['delete'])
.on(['document/confidential/*'])
.build();
expect(statement.Effect).toBe(models_1.Effect.Deny);
expect(statement.Action).toEqual(['delete']);
expect(statement.Resource).toEqual(['document/confidential/*']);
});
it('should build a statement with conditions', () => {
const statement = new statement_builder_1.StatementBuilder()
.allow(['read'])
.on(['document/*'])
.when({ department: 'engineering', role: 'developer' })
.build();
expect(statement.Condition).toEqual({
department: 'engineering',
role: 'developer'
});
});
it('should build a statement with time constraints', () => {
const startDate = '2025-01-01T00:00:00Z';
const endDate = '2025-12-31T23:59:59Z';
const statement = new statement_builder_1.StatementBuilder()
.allow(['read', 'write'])
.on(['project/*'])
.activeFrom(startDate)
.activeUntil(endDate)
.build();
expect(statement.StartDate).toBe(startDate);
expect(statement.EndDate).toBe(endDate);
});
});
describe('Validation', () => {
it('should throw error when no effect is set', () => {
expect(() => {
new statement_builder_1.StatementBuilder()
.on(['document/*'])
.build();
}).toThrow(types_1.BuilderValidationError);
});
it('should throw error when no actions are specified', () => {
expect(() => {
new statement_builder_1.StatementBuilder()
.allow([])
.on(['document/*'])
.build();
}).toThrow(types_1.BuilderValidationError);
});
it('should throw error when no resources are specified', () => {
expect(() => {
new statement_builder_1.StatementBuilder()
.allow(['read'])
.build();
}).toThrow(types_1.BuilderValidationError);
});
it('should throw error for invalid date formats', () => {
expect(() => {
new statement_builder_1.StatementBuilder()
.allow(['read'])
.on(['document/*'])
.activeFrom('invalid-date')
.build();
}).toThrow(types_1.BuilderValidationError);
});
it('should throw error when start date is after end date', () => {
expect(() => {
new statement_builder_1.StatementBuilder()
.allow(['read'])
.on(['document/*'])
.activeFrom('2025-12-31T23:59:59Z')
.activeUntil('2025-01-01T00:00:00Z')
.build();
}).toThrow(types_1.BuilderValidationError);
});
it('should throw error for empty actions', () => {
expect(() => {
new statement_builder_1.StatementBuilder()
.allow(['read', '', 'write'])
.on(['document/*'])
.build();
}).toThrow(types_1.BuilderValidationError);
});
it('should throw error for empty resources', () => {
expect(() => {
new statement_builder_1.StatementBuilder()
.allow(['read'])
.on(['document/*', '', 'other/*'])
.build();
}).toThrow(types_1.BuilderValidationError);
});
});
describe('Method chaining', () => {
it('should allow fluent method chaining', () => {
const builder = new statement_builder_1.StatementBuilder();
expect(builder.allow(['read'])).toBe(builder);
expect(builder.on(['document/*'])).toBe(builder);
expect(builder.when({ dept: 'eng' })).toBe(builder);
expect(builder.activeFrom('2025-01-01T00:00:00Z')).toBe(builder);
expect(builder.activeUntil('2025-12-31T23:59:59Z')).toBe(builder);
});
it('should override effect when called multiple times', () => {
const statement = new statement_builder_1.StatementBuilder()
.allow(['read'])
.deny(['write'])
.on(['document/*'])
.build();
expect(statement.Effect).toBe(models_1.Effect.Deny);
expect(statement.Action).toEqual(['write']);
});
});
});
describe('PolicyBuilder', () => {
describe('Simple policy building', () => {
it('should build a simple policy with single statement', () => {
const policy = new policy_builder_1.PolicyBuilder('policy-123')
.version('2023-11-15')
.allow(['read', 'write'])
.on(['document/*'])
.build();
expect(policy.id).toBe('policy-123');
expect(policy.document.Version).toBe('2023-11-15');
expect(policy.document.Statement).toHaveLength(1);
expect(policy.document.Statement[0].Effect).toBe(models_1.Effect.Allow);
expect(policy.document.Statement[0].Action).toEqual(['read', 'write']);
expect(policy.document.Statement[0].Resource).toEqual(['document/*']);
});
it('should use default version when not specified', () => {
const policy = new policy_builder_1.PolicyBuilder('policy-123')
.allow(['read'])
.on(['document/*'])
.build();
expect(policy.document.Version).toBe('2023-11-15');
});
it('should build a simple policy with conditions and time constraints', () => {
const policy = new policy_builder_1.PolicyBuilder('policy-456')
.deny(['delete'])
.on(['document/confidential/*'])
.when({ clearance: 'secret' })
.activeFrom('2025-01-01T00:00:00Z')
.activeUntil('2025-12-31T23:59:59Z')
.build();
const statement = policy.document.Statement[0];
expect(statement.Effect).toBe(models_1.Effect.Deny);
expect(statement.Condition).toEqual({ clearance: 'secret' });
expect(statement.StartDate).toBe('2025-01-01T00:00:00Z');
expect(statement.EndDate).toBe('2025-12-31T23:59:59Z');
});
});
describe('Complex policy building', () => {
it('should build a policy with multiple statements', () => {
const policy = new policy_builder_1.PolicyBuilder('policy-789')
.statement(new statement_builder_1.StatementBuilder()
.allow(['read', 'write'])
.on(['document/*'])
.when({ department: 'engineering' }))
.statement(new statement_builder_1.StatementBuilder()
.deny(['delete'])
.on(['document/confidential/*']))
.build();
expect(policy.document.Statement).toHaveLength(2);
expect(policy.document.Statement[0].Effect).toBe(models_1.Effect.Allow);
expect(policy.document.Statement[1].Effect).toBe(models_1.Effect.Deny);
});
it('should accept pre-built PolicyStatement objects', () => {
const preBuiltStatement = {
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document/*']
};
const policy = new policy_builder_1.PolicyBuilder('policy-pre')
.statement(preBuiltStatement)
.build();
expect(policy.document.Statement).toHaveLength(1);
expect(policy.document.Statement[0]).toEqual(preBuiltStatement);
});
it('should add multiple statements at once', () => {
const statements = [
new statement_builder_1.StatementBuilder().allow(['read']).on(['doc1/*']),
new statement_builder_1.StatementBuilder().deny(['write']).on(['doc2/*'])
];
const policy = new policy_builder_1.PolicyBuilder('policy-multi')
.addStatements(statements)
.build();
expect(policy.document.Statement).toHaveLength(2);
});
});
describe('Validation and error handling', () => {
it('should throw error for empty policy ID', () => {
expect(() => {
new policy_builder_1.PolicyBuilder('')
.allow(['read'])
.on(['document/*'])
.build();
}).toThrow(types_1.BuilderValidationError);
});
it('should throw error when no statements are defined', () => {
expect(() => {
new policy_builder_1.PolicyBuilder('policy-empty')
.build();
}).toThrow(types_1.BuilderValidationError);
});
it('should throw error when mixing simple and complex modes', () => {
expect(() => {
new policy_builder_1.PolicyBuilder('policy-mixed')
.allow(['read'])
.statement(new statement_builder_1.StatementBuilder().deny(['write']).on(['doc/*']))
.build();
}).toThrow(types_1.BuilderValidationError);
});
it('should throw error when using simple methods after statement()', () => {
expect(() => {
new policy_builder_1.PolicyBuilder('policy-mixed2')
.statement(new statement_builder_1.StatementBuilder().allow(['read']).on(['doc/*']))
.deny(['write'])
.build();
}).toThrow(types_1.BuilderValidationError);
});
it('should propagate validation errors from simple statement', () => {
expect(() => {
new policy_builder_1.PolicyBuilder('policy-invalid')
.allow(['read'])
.build();
}).toThrow(types_1.BuilderValidationError);
});
});
describe('Static factory method', () => {
it('should create instance using static create method', () => {
const policy = policy_builder_1.PolicyBuilder.create('factory-policy')
.allow(['read'])
.on(['document/*'])
.build();
expect(policy.id).toBe('factory-policy');
});
});
describe('Method chaining', () => {
it('should allow fluent method chaining', () => {
const builder = new policy_builder_1.PolicyBuilder('chain-test');
expect(builder.version('1.0')).toBe(builder);
expect(builder.allow(['read'])).toBe(builder);
expect(builder.on(['doc/*'])).toBe(builder);
expect(builder.when({ dept: 'eng' })).toBe(builder);
expect(builder.activeFrom('2025-01-01T00:00:00Z')).toBe(builder);
expect(builder.activeUntil('2025-12-31T23:59:59Z')).toBe(builder);
});
});
});
describe('Builder Integration', () => {
it('should create equivalent policies using both simple and complex modes', () => {
const simplePolicy = new policy_builder_1.PolicyBuilder('policy-simple')
.allow(['read', 'write'])
.on(['document/*'])
.when({ department: 'engineering' })
.build();
const complexPolicy = new policy_builder_1.PolicyBuilder('policy-complex')
.statement(new statement_builder_1.StatementBuilder()
.allow(['read', 'write'])
.on(['document/*'])
.when({ department: 'engineering' }))
.build();
expect(simplePolicy.document.Statement[0]).toEqual(complexPolicy.document.Statement[0]);
});
it('should handle complex real-world scenarios', () => {
const policy = new policy_builder_1.PolicyBuilder('comprehensive-policy')
.version('2023-11-15')
.statement(new statement_builder_1.StatementBuilder()
.allow(['read', 'list'])
.on(['document/*', 'folder/*'])
.when({ department: 'engineering' }))
.statement(new statement_builder_1.StatementBuilder()
.allow(['write', 'update'])
.on(['document/*'])
.when({ role: 'senior-engineer' })
.activeFrom('2025-01-01T00:00:00Z')
.activeUntil('2025-12-31T23:59:59Z'))
.statement(new statement_builder_1.StatementBuilder()
.deny(['delete'])
.on(['document/production/*']))
.build();
expect(policy.document.Statement).toHaveLength(3);
expect(policy.document.Statement[0].Action).toEqual(['read', 'list']);
expect(policy.document.Statement[1].StartDate).toBe('2025-01-01T00:00:00Z');
expect(policy.document.Statement[2].Effect).toBe(models_1.Effect.Deny);
});
});
//# sourceMappingURL=builder.spec.js.map