Phase 3 Completed

This commit is contained in:
Rayyan
2026-08-02 17:44:50 +07:00
parent 7ce0de4e91
commit ffc5ecd259
68 changed files with 3132 additions and 163 deletions
+65
View File
@@ -0,0 +1,65 @@
import { RoleSyncService } from '../src/modules/auth/role-sync.service';
describe('RoleSyncService', () => {
const mockPrisma: any = {};
const mockPermissionCache: any = { invalidate: jest.fn() };
const mockEvents: any = { publish: jest.fn() };
const mockGroupHash: any = { compute: (g: any) => require('crypto').createHash('sha256').update((g||[]).slice().sort().join(','), 'utf8').digest('hex') };
let service: RoleSyncService;
beforeEach(() => {
mockPrisma.user = { findUnique: jest.fn() };
mockPrisma.authGroupRoleMapping = { findMany: jest.fn() };
mockPrisma.userRole = { findMany: jest.fn() };
mockPrisma.auditLog = { create: jest.fn() };
mockPrisma.user = mockPrisma.user;
mockPrisma.$transaction = jest.fn(async (cb: any) => {
// simulate transaction by calling provided callback with tx = mockPrisma
await cb(mockPrisma);
});
service = new RoleSyncService(mockPrisma as any, mockGroupHash as any, mockPermissionCache as any, mockEvents as any);
});
test('computeGroupHash consistent and order independent', () => {
const a = ['b', 'a', 'c'];
const h1 = service.computeGroupHash(a);
const h2 = service.computeGroupHash(['a', 'b', 'c']);
expect(h1).toBe(h2);
});
test('skips sync when group hash unchanged', async () => {
const groups = ['RL-Owner'];
const hash = service.computeGroupHash(groups);
mockPrisma.user.findUnique.mockResolvedValue({ id: 'uid', lastGroupHash: hash });
const res = await service.syncUserRolesFromAuthentik('uid', groups);
expect(res.skipped).toBe(true);
expect(mockPrisma.$transaction).not.toHaveBeenCalled();
});
test('performs transaction and writes audit when changed', async () => {
const groups = ['RL-Owner'];
mockPrisma.user.findUnique.mockResolvedValue({ id: 'uid', lastGroupHash: 'old' });
mockPrisma.authGroupRoleMapping.findMany.mockResolvedValue([{ roleId: 'r1' }]);
mockPrisma.userRole.findMany.mockResolvedValue([{ roleId: 'r_old' }]);
// spy on tx ops
mockPrisma.userRole.deleteMany = jest.fn();
mockPrisma.userRole.createMany = jest.fn();
mockPrisma.user.update = jest.fn();
mockPrisma.auditLog.create = jest.fn();
const res = await service.syncUserRolesFromAuthentik('uid', groups);
expect(mockPrisma.$transaction).toHaveBeenCalled();
expect(mockPrisma.userRole.deleteMany).toHaveBeenCalledWith({ where: { userId: 'uid', source: 'AUTHENTIK' } });
expect(mockPrisma.userRole.createMany).toHaveBeenCalled();
expect(mockPrisma.user.update).toHaveBeenCalled();
expect(mockPermissionCache.invalidate).toHaveBeenCalledWith('uid');
expect(mockEvents.publish).toHaveBeenCalledWith(expect.objectContaining({ type: 'RolesSynchronized' }));
expect(res.skipped).toBe(false);
expect(res.assignedRoleIds).toEqual(['r1']);
});
});