rbac-engine
Version:
Role-based access control engine with policy-based permissions
268 lines • 11.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("../../core");
const builders_1 = require("../../builders");
const models_1 = require("../../models");
jest.mock('../../policy/evaluator', () => ({
evaluate: jest.fn()
}));
jest.mock('../../db/factory', () => ({
createRepository: jest.fn()
}));
describe('AccessControl Builder Integration', () => {
let accessControl;
let mockRepository;
beforeEach(() => {
mockRepository = {
setupTables: jest.fn().mockResolvedValue(undefined),
createUser: jest.fn(),
getUser: jest.fn(),
createRole: jest.fn(),
getRole: jest.fn(),
assignRoleToUser: jest.fn(),
createPolicy: jest.fn(),
updatePolicy: jest.fn(),
updateRole: jest.fn(),
deletePolicy: jest.fn(),
deleteRole: jest.fn(),
attachPolicyToRole: jest.fn(),
attachPolicyToUser: jest.fn(),
getUserPolicies: jest.fn(),
getRolePolicies: jest.fn(),
detachPolicyFromRole: jest.fn(),
detachPolicyFromUser: jest.fn(),
removeRoleFromUser: jest.fn()
};
const { createRepository } = require('../../db/factory');
createRepository.mockReturnValue(mockRepository);
accessControl = new core_1.AccessControl({}, jest.fn());
});
describe('Backward Compatibility', () => {
it('should accept traditional Policy objects', async () => {
const traditionalPolicy = {
id: 'traditional-policy',
document: {
Version: '2023-11-15',
Statement: [
{
Effect: models_1.Effect.Allow,
Action: ['read', 'write'],
Resource: ['document/*'],
Condition: { department: 'engineering' }
}
]
}
};
mockRepository.createPolicy.mockResolvedValue(traditionalPolicy);
const result = await accessControl.createPolicy(traditionalPolicy);
expect(mockRepository.createPolicy).toHaveBeenCalledWith(traditionalPolicy);
expect(result).toEqual(traditionalPolicy);
});
it('should handle complex traditional policies with multiple statements', async () => {
const complexTraditionalPolicy = {
id: 'complex-traditional',
document: {
Version: '2023-11-15',
Statement: [
{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document/*']
},
{
Effect: models_1.Effect.Deny,
Action: ['delete'],
Resource: ['document/confidential/*']
}
]
}
};
mockRepository.createPolicy.mockResolvedValue(complexTraditionalPolicy);
const result = await accessControl.createPolicy(complexTraditionalPolicy);
expect(mockRepository.createPolicy).toHaveBeenCalledWith(complexTraditionalPolicy);
expect(result).toEqual(complexTraditionalPolicy);
});
});
describe('Builder Pattern Integration', () => {
it('should accept PolicyBuilder instances and convert them to Policy objects', async () => {
const policyBuilder = new builders_1.PolicyBuilder('builder-policy')
.version('2023-11-15')
.allow(['read', 'write'])
.on(['document/*'])
.when({ department: 'engineering' });
const expectedPolicy = {
id: 'builder-policy',
document: {
Version: '2023-11-15',
Statement: [
{
Effect: models_1.Effect.Allow,
Action: ['read', 'write'],
Resource: ['document/*'],
Condition: { department: 'engineering' }
}
]
}
};
mockRepository.createPolicy.mockResolvedValue(expectedPolicy);
const result = await accessControl.createPolicy(policyBuilder);
expect(mockRepository.createPolicy).toHaveBeenCalledWith(expectedPolicy);
expect(result).toEqual(expectedPolicy);
});
it('should handle complex builder policies with multiple statements', async () => {
const policyBuilder = new builders_1.PolicyBuilder('complex-builder')
.version('2023-11-15')
.statement(new builders_1.StatementBuilder()
.allow(['read', 'list'])
.on(['document/*'])
.when({ department: 'engineering' }))
.statement(new builders_1.StatementBuilder()
.deny(['delete'])
.on(['document/confidential/*']));
const expectedPolicy = {
id: 'complex-builder',
document: {
Version: '2023-11-15',
Statement: [
{
Effect: models_1.Effect.Allow,
Action: ['read', 'list'],
Resource: ['document/*'],
Condition: { department: 'engineering' }
},
{
Effect: models_1.Effect.Deny,
Action: ['delete'],
Resource: ['document/confidential/*']
}
]
}
};
mockRepository.createPolicy.mockResolvedValue(expectedPolicy);
const result = await accessControl.createPolicy(policyBuilder);
expect(mockRepository.createPolicy).toHaveBeenCalledWith(expectedPolicy);
expect(result).toEqual(expectedPolicy);
});
it('should handle time-based builder policies', async () => {
const policyBuilder = new builders_1.PolicyBuilder('time-based-policy')
.allow(['read', 'write'])
.on(['project/*'])
.activeFrom('2025-01-01T00:00:00Z')
.activeUntil('2025-12-31T23:59:59Z');
const expectedPolicy = {
id: 'time-based-policy',
document: {
Version: '2023-11-15',
Statement: [
{
Effect: models_1.Effect.Allow,
Action: ['read', 'write'],
Resource: ['project/*'],
StartDate: '2025-01-01T00:00:00Z',
EndDate: '2025-12-31T23:59:59Z'
}
]
}
};
mockRepository.createPolicy.mockResolvedValue(expectedPolicy);
const result = await accessControl.createPolicy(policyBuilder);
expect(mockRepository.createPolicy).toHaveBeenCalledWith(expectedPolicy);
expect(result).toEqual(expectedPolicy);
});
});
describe('Error Handling', () => {
it('should propagate builder validation errors', async () => {
const invalidBuilder = new builders_1.PolicyBuilder('invalid-policy')
.allow(['read']);
await expect(accessControl.createPolicy(invalidBuilder))
.rejects
.toThrow('Invalid policy configuration');
});
it('should handle repository errors for both traditional and builder approaches', async () => {
const repositoryError = new Error('Database connection failed');
mockRepository.createPolicy.mockRejectedValue(repositoryError);
const traditionalPolicy = {
id: 'test-policy',
document: {
Version: '2023-11-15',
Statement: [
{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document/*']
}
]
}
};
await expect(accessControl.createPolicy(traditionalPolicy))
.rejects
.toThrow('Database connection failed');
const builderPolicy = new builders_1.PolicyBuilder('test-policy-2')
.allow(['read'])
.on(['document/*']);
await expect(accessControl.createPolicy(builderPolicy))
.rejects
.toThrow('Database connection failed');
});
});
describe('Type Safety', () => {
it('should provide proper TypeScript types for both approaches', async () => {
const traditionalPolicy = {
id: 'type-test-1',
document: {
Version: '2023-11-15',
Statement: []
}
};
const builderPolicy = new builders_1.PolicyBuilder('type-test-2')
.allow(['read'])
.on(['document/*']);
mockRepository.createPolicy.mockResolvedValue(traditionalPolicy);
const result1 = accessControl.createPolicy(traditionalPolicy);
const result2 = accessControl.createPolicy(builderPolicy);
expect(result1).toBeDefined();
expect(result2).toBeDefined();
});
});
describe('Real-world Usage Patterns', () => {
it('should support creating policies with both approaches in the same application', async () => {
const simplePolicy = {
id: 'simple-policy',
document: {
Version: '2023-11-15',
Statement: [
{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['public/*']
}
]
}
};
const complexPolicy = new builders_1.PolicyBuilder('complex-policy')
.statement(new builders_1.StatementBuilder()
.allow(['read', 'write'])
.on(['document/*'])
.when({ department: 'engineering' })
.activeFrom('2025-01-01T00:00:00Z'))
.statement(new builders_1.StatementBuilder()
.deny(['delete'])
.on(['document/production/*']));
mockRepository.createPolicy
.mockResolvedValueOnce(simplePolicy)
.mockResolvedValueOnce({
id: 'complex-policy',
document: {
Version: '2023-11-15',
Statement: complexPolicy.build().document.Statement
}
});
const result1 = await accessControl.createPolicy(simplePolicy);
const result2 = await accessControl.createPolicy(complexPolicy);
expect(result1.id).toBe('simple-policy');
expect(result2.id).toBe('complex-policy');
expect(mockRepository.createPolicy).toHaveBeenCalledTimes(2);
});
});
});
//# sourceMappingURL=integration.spec.js.map