mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
chore(core): Add timestamp fields to Role, and support counting role usages (#19171)
This commit is contained in:
@@ -59,6 +59,8 @@ describe('RoleController - Integration Tests', () => {
|
||||
roleType: role.roleType,
|
||||
scopes: role.scopes.map((scope) => scope.slug).sort(),
|
||||
licensed: expect.any(Boolean),
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -90,6 +92,8 @@ describe('RoleController - Integration Tests', () => {
|
||||
slug: expect.any(String),
|
||||
licensed: expect.any(Boolean),
|
||||
systemRole: false,
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -102,6 +106,8 @@ describe('RoleController - Integration Tests', () => {
|
||||
slug: response.body.data.slug,
|
||||
licensed: expect.any(Boolean),
|
||||
systemRole: false,
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -147,6 +153,8 @@ describe('RoleController - Integration Tests', () => {
|
||||
roleType: 'project',
|
||||
licensed: expect.any(Boolean),
|
||||
systemRole: false,
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -161,6 +169,8 @@ describe('RoleController - Integration Tests', () => {
|
||||
roleType: 'project',
|
||||
licensed: expect.any(Boolean),
|
||||
systemRole: false,
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,6 +106,251 @@ describe('RoleController', () => {
|
||||
expect(roleService.getAllRoles).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('GET /roles with withUsageCount parameter', () => {
|
||||
it('should pass withUsageCount=true to service and include usage counts in response', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const mockRolesWithUsage: Role[] = [
|
||||
{
|
||||
slug: 'global:admin',
|
||||
displayName: 'Global Admin',
|
||||
description: 'Global administrator',
|
||||
systemRole: true,
|
||||
roleType: 'global',
|
||||
scopes: ['user:manage', 'workflow:create'],
|
||||
licensed: true,
|
||||
usedByUsers: 5,
|
||||
},
|
||||
{
|
||||
slug: 'project:editor',
|
||||
displayName: 'Project Editor',
|
||||
description: 'Project editor role',
|
||||
systemRole: true,
|
||||
roleType: 'project',
|
||||
scopes: ['workflow:create', 'workflow:edit'],
|
||||
licensed: true,
|
||||
usedByUsers: 12,
|
||||
},
|
||||
];
|
||||
|
||||
roleService.getAllRoles.mockResolvedValue(mockRolesWithUsage);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const response = await memberAgent.get('/roles?withUsageCount=true').expect(200);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(response.body).toEqual({
|
||||
data: {
|
||||
global: [mockRolesWithUsage[0]], // global:admin with usedByUsers
|
||||
project: [mockRolesWithUsage[1]], // project:editor with usedByUsers
|
||||
credential: [],
|
||||
workflow: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(roleService.getAllRoles).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should pass withUsageCount=false to service and exclude usage counts', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const mockRolesWithoutUsage: Role[] = [
|
||||
{
|
||||
slug: 'global:admin',
|
||||
displayName: 'Global Admin',
|
||||
description: 'Global administrator',
|
||||
systemRole: true,
|
||||
roleType: 'global',
|
||||
scopes: ['user:manage', 'workflow:create'],
|
||||
licensed: true,
|
||||
},
|
||||
{
|
||||
slug: 'project:editor',
|
||||
displayName: 'Project Editor',
|
||||
description: 'Project editor role',
|
||||
systemRole: true,
|
||||
roleType: 'project',
|
||||
scopes: ['workflow:create', 'workflow:edit'],
|
||||
licensed: true,
|
||||
},
|
||||
];
|
||||
|
||||
roleService.getAllRoles.mockResolvedValue(mockRolesWithoutUsage);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const response = await memberAgent.get('/roles?withUsageCount=false').expect(200);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(response.body).toEqual({
|
||||
data: {
|
||||
global: [mockRolesWithoutUsage[0]], // global:admin without usedByUsers
|
||||
project: [mockRolesWithoutUsage[1]], // project:editor without usedByUsers
|
||||
credential: [],
|
||||
workflow: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(roleService.getAllRoles).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('should default to withUsageCount=false when parameter is omitted', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const mockRoles: Role[] = [
|
||||
{
|
||||
slug: 'global:admin',
|
||||
displayName: 'Global Admin',
|
||||
description: 'Global administrator',
|
||||
systemRole: true,
|
||||
roleType: 'global',
|
||||
scopes: ['user:manage', 'workflow:create'],
|
||||
licensed: true,
|
||||
},
|
||||
];
|
||||
|
||||
roleService.getAllRoles.mockResolvedValue(mockRoles);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const response = await memberAgent.get('/roles').expect(200);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(response.body).toEqual({
|
||||
data: {
|
||||
global: [mockRoles[0]], // global:admin without usedByUsers
|
||||
project: [],
|
||||
credential: [],
|
||||
workflow: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(roleService.getAllRoles).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('should maintain grouped response structure with usage counts for all role types', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const mockRolesWithUsageAllTypes: Role[] = [
|
||||
{
|
||||
slug: 'global:admin',
|
||||
displayName: 'Global Admin',
|
||||
description: 'Global administrator',
|
||||
systemRole: true,
|
||||
roleType: 'global',
|
||||
scopes: ['user:manage', 'workflow:create'],
|
||||
licensed: true,
|
||||
usedByUsers: 3,
|
||||
},
|
||||
{
|
||||
slug: 'project:editor',
|
||||
displayName: 'Project Editor',
|
||||
description: 'Project editor role',
|
||||
systemRole: true,
|
||||
roleType: 'project',
|
||||
scopes: ['workflow:create', 'workflow:edit'],
|
||||
licensed: true,
|
||||
usedByUsers: 8,
|
||||
},
|
||||
{
|
||||
slug: 'credential:owner',
|
||||
displayName: 'Credential Owner',
|
||||
description: 'Credential owner',
|
||||
systemRole: true,
|
||||
roleType: 'credential',
|
||||
scopes: ['credential:read', 'credential:write'],
|
||||
licensed: true,
|
||||
usedByUsers: 15,
|
||||
},
|
||||
{
|
||||
slug: 'workflow:editor',
|
||||
displayName: 'Workflow Editor',
|
||||
description: 'Workflow editor',
|
||||
systemRole: true,
|
||||
roleType: 'workflow',
|
||||
scopes: ['workflow:read', 'workflow:edit'],
|
||||
licensed: true,
|
||||
usedByUsers: 7,
|
||||
},
|
||||
];
|
||||
|
||||
roleService.getAllRoles.mockResolvedValue(mockRolesWithUsageAllTypes);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const response = await memberAgent.get('/roles?withUsageCount=true').expect(200);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(response.body).toEqual({
|
||||
data: {
|
||||
global: [mockRolesWithUsageAllTypes[0]], // global:admin with usedByUsers: 3
|
||||
project: [mockRolesWithUsageAllTypes[1]], // project:editor with usedByUsers: 8
|
||||
credential: [mockRolesWithUsageAllTypes[2]], // credential:owner with usedByUsers: 15
|
||||
workflow: [mockRolesWithUsageAllTypes[3]], // workflow:editor with usedByUsers: 7
|
||||
},
|
||||
});
|
||||
|
||||
expect(roleService.getAllRoles).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should handle invalid withUsageCount parameter values gracefully', async () => {
|
||||
//
|
||||
// ARRANGE & ACT & ASSERT
|
||||
//
|
||||
// Should return 400 for invalid parameter values due to DTO validation
|
||||
await memberAgent.get('/roles?withUsageCount=invalid').expect(400);
|
||||
|
||||
// Service should not be called when validation fails
|
||||
expect(roleService.getAllRoles).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should work with both member and owner agents when withUsageCount=true', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const mockRolesWithUsage: Role[] = [
|
||||
{
|
||||
slug: 'project:admin',
|
||||
displayName: 'Project Admin',
|
||||
description: 'Project administrator',
|
||||
systemRole: true,
|
||||
roleType: 'project',
|
||||
scopes: ['project:manage'],
|
||||
licensed: true,
|
||||
usedByUsers: 4,
|
||||
},
|
||||
];
|
||||
|
||||
roleService.getAllRoles.mockResolvedValue(mockRolesWithUsage);
|
||||
|
||||
//
|
||||
// ACT & ASSERT
|
||||
//
|
||||
await ownerAgent.get('/roles?withUsageCount=true').expect(200);
|
||||
await memberAgent.get('/roles?withUsageCount=true').expect(200);
|
||||
|
||||
expect(roleService.getAllRoles).toHaveBeenNthCalledWith(1, true);
|
||||
expect(roleService.getAllRoles).toHaveBeenNthCalledWith(2, true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty categories when no roles exist', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
@@ -250,6 +495,253 @@ describe('RoleController', () => {
|
||||
expect(response.body).toEqual({ data: mockRole });
|
||||
// Parameter verification skipped - test framework issue
|
||||
});
|
||||
|
||||
describe('GET /roles/:slug with withUsageCount parameter', () => {
|
||||
it('should pass withUsageCount=true to service and include usage count in response', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const roleSlug = 'project:admin';
|
||||
const mockRoleWithUsage: Role = {
|
||||
slug: roleSlug,
|
||||
displayName: 'Project Admin',
|
||||
description: 'Project administrator role',
|
||||
systemRole: true,
|
||||
roleType: 'project',
|
||||
scopes: ['project:manage', 'workflow:create'],
|
||||
licensed: true,
|
||||
usedByUsers: 8,
|
||||
};
|
||||
|
||||
roleService.getRole.mockResolvedValue(mockRoleWithUsage);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const response = await memberAgent
|
||||
.get(`/roles/${roleSlug}?withUsageCount=true`)
|
||||
.expect(200);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(response.body).toEqual({ data: mockRoleWithUsage });
|
||||
expect(roleService.getRole).toHaveBeenCalledTimes(1);
|
||||
expect(roleService.getRole).toHaveBeenCalledWith(roleSlug, true);
|
||||
});
|
||||
|
||||
it('should pass withUsageCount=false to service and exclude usage count', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const roleSlug = 'project:admin';
|
||||
const mockRoleWithoutUsage: Role = {
|
||||
slug: roleSlug,
|
||||
displayName: 'Project Admin',
|
||||
description: 'Project administrator role',
|
||||
systemRole: true,
|
||||
roleType: 'project',
|
||||
scopes: ['project:manage', 'workflow:create'],
|
||||
licensed: true,
|
||||
};
|
||||
|
||||
roleService.getRole.mockResolvedValue(mockRoleWithoutUsage);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const response = await memberAgent
|
||||
.get(`/roles/${roleSlug}?withUsageCount=false`)
|
||||
.expect(200);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(response.body).toEqual({ data: mockRoleWithoutUsage });
|
||||
expect(roleService.getRole).toHaveBeenCalledTimes(1);
|
||||
expect(roleService.getRole).toHaveBeenCalledWith(roleSlug, false);
|
||||
});
|
||||
|
||||
it('should default to withUsageCount=false when parameter is omitted', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const roleSlug = 'project:admin';
|
||||
const mockRoleWithoutUsage: Role = {
|
||||
slug: roleSlug,
|
||||
displayName: 'Project Admin',
|
||||
description: 'Project administrator role',
|
||||
systemRole: true,
|
||||
roleType: 'project',
|
||||
scopes: ['project:manage', 'workflow:create'],
|
||||
licensed: true,
|
||||
};
|
||||
|
||||
roleService.getRole.mockResolvedValue(mockRoleWithoutUsage);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const response = await memberAgent.get(`/roles/${roleSlug}`).expect(200);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(response.body).toEqual({ data: mockRoleWithoutUsage });
|
||||
expect(roleService.getRole).toHaveBeenCalledTimes(1);
|
||||
expect(roleService.getRole).toHaveBeenCalledWith(roleSlug, false);
|
||||
});
|
||||
|
||||
it('should include usage count in response when withUsageCount=true', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const roleSlug = 'project:editor';
|
||||
const mockRoleWithUsage: Role = {
|
||||
slug: roleSlug,
|
||||
displayName: 'Project Editor',
|
||||
description: 'Project editor role',
|
||||
systemRole: true,
|
||||
roleType: 'project',
|
||||
scopes: ['workflow:create', 'workflow:edit'],
|
||||
licensed: true,
|
||||
usedByUsers: 15,
|
||||
};
|
||||
|
||||
roleService.getRole.mockResolvedValue(mockRoleWithUsage);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const response = await memberAgent
|
||||
.get(`/roles/${roleSlug}?withUsageCount=true`)
|
||||
.expect(200);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(response.body).toEqual({ data: mockRoleWithUsage });
|
||||
expect(response.body.data.usedByUsers).toBe(15);
|
||||
expect(roleService.getRole).toHaveBeenCalledTimes(1);
|
||||
expect(roleService.getRole).toHaveBeenCalledWith(roleSlug, true);
|
||||
});
|
||||
|
||||
it('should work with URL-encoded slugs and withUsageCount parameter', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const roleSlug = 'project:custom-role';
|
||||
const encodedSlug = encodeURIComponent(roleSlug);
|
||||
const mockRoleWithUsage: Role = {
|
||||
slug: roleSlug,
|
||||
displayName: 'Custom Role',
|
||||
description: 'A custom project role',
|
||||
systemRole: false,
|
||||
roleType: 'project',
|
||||
scopes: ['workflow:read'],
|
||||
licensed: true,
|
||||
usedByUsers: 3,
|
||||
};
|
||||
|
||||
roleService.getRole.mockResolvedValue(mockRoleWithUsage);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const response = await memberAgent
|
||||
.get(`/roles/${encodedSlug}?withUsageCount=true`)
|
||||
.expect(200);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(response.body).toEqual({ data: mockRoleWithUsage });
|
||||
expect(response.body.data.usedByUsers).toBe(3);
|
||||
expect(roleService.getRole).toHaveBeenCalledTimes(1);
|
||||
expect(roleService.getRole).toHaveBeenCalledWith(roleSlug, true);
|
||||
});
|
||||
|
||||
it('should handle invalid withUsageCount parameter values gracefully', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const roleSlug = 'project:admin';
|
||||
|
||||
//
|
||||
// ACT & ASSERT
|
||||
//
|
||||
// Should return 400 for invalid parameter values due to DTO validation
|
||||
await memberAgent.get(`/roles/${roleSlug}?withUsageCount=invalid`).expect(400);
|
||||
|
||||
// Service should not be called when validation fails
|
||||
expect(roleService.getRole).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should work with both member and owner agents when withUsageCount=true', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const roleSlug = 'project:viewer';
|
||||
const mockRoleWithUsage: Role = {
|
||||
slug: roleSlug,
|
||||
displayName: 'Project Viewer',
|
||||
description: 'Project viewer role',
|
||||
systemRole: true,
|
||||
roleType: 'project',
|
||||
scopes: ['workflow:read'],
|
||||
licensed: true,
|
||||
usedByUsers: 22,
|
||||
};
|
||||
|
||||
roleService.getRole.mockResolvedValue(mockRoleWithUsage);
|
||||
|
||||
//
|
||||
// ACT & ASSERT
|
||||
//
|
||||
await ownerAgent.get(`/roles/${roleSlug}?withUsageCount=true`).expect(200);
|
||||
await memberAgent.get(`/roles/${roleSlug}?withUsageCount=true`).expect(200);
|
||||
|
||||
expect(roleService.getRole).toHaveBeenCalledTimes(2);
|
||||
expect(roleService.getRole).toHaveBeenNthCalledWith(1, roleSlug, true);
|
||||
expect(roleService.getRole).toHaveBeenNthCalledWith(2, roleSlug, true);
|
||||
});
|
||||
|
||||
it('should maintain single role response structure with usage count', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const roleSlug = 'credential:owner';
|
||||
const mockRoleWithUsage: Role = {
|
||||
slug: roleSlug,
|
||||
displayName: 'Credential Owner',
|
||||
description: 'Credential owner role',
|
||||
systemRole: true,
|
||||
roleType: 'credential',
|
||||
scopes: ['credential:read', 'credential:write'],
|
||||
licensed: true,
|
||||
usedByUsers: 7,
|
||||
};
|
||||
|
||||
roleService.getRole.mockResolvedValue(mockRoleWithUsage);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const response = await memberAgent
|
||||
.get(`/roles/${roleSlug}?withUsageCount=true`)
|
||||
.expect(200);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(response.body).toEqual({ data: mockRoleWithUsage });
|
||||
expect(response.body.data).toHaveProperty('slug', roleSlug);
|
||||
expect(response.body.data).toHaveProperty('displayName', 'Credential Owner');
|
||||
expect(response.body.data).toHaveProperty('usedByUsers', 7);
|
||||
expect(roleService.getRole).toHaveBeenCalledTimes(1);
|
||||
expect(roleService.getRole).toHaveBeenCalledWith(roleSlug, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /roles', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { testDb } from '@n8n/backend-test-utils';
|
||||
import { testDb, linkUserToProject, createTeamProject } from '@n8n/backend-test-utils';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { RoleRepository, ScopeRepository } from '@n8n/db';
|
||||
import { AuthRolesService, RoleRepository, ScopeRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
|
||||
import {
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
createCustomRoleWithScopes,
|
||||
createTestScopes,
|
||||
} from '../../shared/db/roles';
|
||||
import { createUser } from '../../shared/db/users';
|
||||
|
||||
describe('RoleRepository', () => {
|
||||
let roleRepository: RoleRepository;
|
||||
@@ -24,7 +25,7 @@ describe('RoleRepository', () => {
|
||||
// Truncate in the correct order to respect foreign key constraints
|
||||
// user table references role via roleSlug
|
||||
// ProjectRelation references role
|
||||
await testDb.truncate(['User', 'ProjectRelation', 'Role', 'Scope']);
|
||||
await testDb.truncate(['User', 'ProjectRelation', 'Project', 'Role', 'Scope']);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -641,4 +642,186 @@ describe('RoleRepository', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('countUsersWithRole()', () => {
|
||||
beforeEach(async () => {
|
||||
// make sure to initalize the default roles for user creation
|
||||
await Container.get(AuthRolesService).init();
|
||||
});
|
||||
|
||||
describe('global roles', () => {
|
||||
it('should return 0 when no users have the global role', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const globalRole = await createRole({
|
||||
slug: 'global-empty-role',
|
||||
displayName: 'Global Empty Role',
|
||||
roleType: 'global',
|
||||
});
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const count = await roleRepository.countUsersWithRole(globalRole);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
|
||||
it('should return correct count when multiple users have the global role', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const globalRole = await createRole({
|
||||
slug: 'global-multi-role',
|
||||
displayName: 'Global Multi Role',
|
||||
roleType: 'global',
|
||||
});
|
||||
|
||||
await createUser({ role: globalRole });
|
||||
await createUser({ role: globalRole });
|
||||
await createUser({ role: globalRole });
|
||||
|
||||
// Create user with different role to ensure isolation
|
||||
const otherRole = await createRole({
|
||||
slug: 'other-global-role',
|
||||
displayName: 'Other Global Role',
|
||||
roleType: 'global',
|
||||
});
|
||||
await createUser({ role: otherRole });
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const count = await roleRepository.countUsersWithRole(globalRole);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(count).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('project roles', () => {
|
||||
it('should return 0 when no project relations exist for the project role', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const projectRole = await createRole({
|
||||
slug: 'project-empty-role',
|
||||
displayName: 'Project Empty Role',
|
||||
roleType: 'project',
|
||||
});
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const count = await roleRepository.countUsersWithRole(projectRole);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
|
||||
it('should return correct count when multiple users have the project role', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const projectRole = await createRole({
|
||||
slug: 'project-multi-role',
|
||||
displayName: 'Project Multi Role',
|
||||
roleType: 'project',
|
||||
});
|
||||
|
||||
// Create users and projects
|
||||
const user1 = await createUser();
|
||||
const user2 = await createUser();
|
||||
const user3 = await createUser();
|
||||
const project1 = await createTeamProject('Test Project 1');
|
||||
const project2 = await createTeamProject('Test Project 2');
|
||||
|
||||
// Link users to projects with the target role
|
||||
await linkUserToProject(user1, project1, projectRole.slug);
|
||||
await linkUserToProject(user2, project1, projectRole.slug);
|
||||
await linkUserToProject(user3, project2, projectRole.slug);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const count = await roleRepository.countUsersWithRole(projectRole);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(count).toBe(3);
|
||||
});
|
||||
|
||||
it('should only count users with the specific project role slug', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const targetRole = await createRole({
|
||||
slug: 'project-target-role',
|
||||
displayName: 'Project Target Role',
|
||||
roleType: 'project',
|
||||
});
|
||||
|
||||
const otherRole = await createRole({
|
||||
slug: 'project-other-role',
|
||||
displayName: 'Project Other Role',
|
||||
roleType: 'project',
|
||||
});
|
||||
|
||||
const user1 = await createUser();
|
||||
const user2 = await createUser();
|
||||
const user3 = await createUser();
|
||||
const project = await createTeamProject('Test Project');
|
||||
|
||||
// Link users with different roles
|
||||
await linkUserToProject(user1, project, targetRole.slug as any);
|
||||
await linkUserToProject(user2, project, targetRole.slug as any);
|
||||
await linkUserToProject(user3, project, otherRole.slug as any);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const count = await roleRepository.countUsersWithRole(targetRole);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle project roles when query returns null count', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const projectRole = await createRole({
|
||||
slug: 'project-null-count-role',
|
||||
displayName: 'Project Null Count Role',
|
||||
roleType: 'project',
|
||||
});
|
||||
|
||||
// Create a project role but don't link any users to it
|
||||
// This ensures the query returns a row but with null/0 count
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const count = await roleRepository.countUsersWithRole(projectRole);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(count).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,11 @@ function checkForRole(role: Role, roles: Role[]) {
|
||||
role.scopes.sort();
|
||||
returnedRole!.scopes.sort();
|
||||
returnedRole!.licensed = role.licensed;
|
||||
expect(returnedRole).toEqual(role);
|
||||
expect(returnedRole).toEqual({
|
||||
...role,
|
||||
createdAt: expect.any(String),
|
||||
updatedAt: expect.any(String),
|
||||
});
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
|
||||
@@ -136,6 +136,307 @@ describe('RoleService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllRoles with usage counting', () => {
|
||||
it('should return roles without usage counts when withCount=false', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const customRole = await createCustomRoleWithScopes(
|
||||
[testScopes.readScope, testScopes.writeScope],
|
||||
{
|
||||
displayName: 'Custom Test Role',
|
||||
description: 'A custom role for testing',
|
||||
},
|
||||
);
|
||||
const systemRole = await createSystemRole({
|
||||
displayName: 'System Test Role',
|
||||
});
|
||||
|
||||
const mockfindAllRoleCounts = jest.spyOn(roleRepository, 'findAllRoleCounts');
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const roles = await roleService.getAllRoles(false);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(roles).toBeDefined();
|
||||
expect(Array.isArray(roles)).toBe(true);
|
||||
|
||||
// Find our test roles
|
||||
const returnedCustomRole = roles.find((r) => r.slug === customRole.slug);
|
||||
const returnedSystemRole = roles.find((r) => r.slug === systemRole.slug);
|
||||
|
||||
expect(returnedCustomRole).toBeDefined();
|
||||
expect(returnedSystemRole).toBeDefined();
|
||||
|
||||
// Verify usedByUsers is undefined when withCount=false
|
||||
expect(returnedCustomRole?.usedByUsers).toBeUndefined();
|
||||
expect(returnedSystemRole?.usedByUsers).toBeUndefined();
|
||||
|
||||
expect(mockfindAllRoleCounts).not.toHaveBeenCalled();
|
||||
mockfindAllRoleCounts.mockRestore();
|
||||
|
||||
// Verify other properties are correct
|
||||
expect(returnedCustomRole).toMatchObject({
|
||||
slug: customRole.slug,
|
||||
displayName: customRole.displayName,
|
||||
description: customRole.description,
|
||||
systemRole: false,
|
||||
roleType: customRole.roleType,
|
||||
scopes: expect.any(Array),
|
||||
licensed: expect.any(Boolean),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return roles with usage counts when withCount=true', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const customRole = await createCustomRoleWithScopes(
|
||||
[testScopes.readScope, testScopes.writeScope],
|
||||
{
|
||||
displayName: 'Custom Role With Usage',
|
||||
description: 'A custom role for usage testing',
|
||||
},
|
||||
);
|
||||
const systemRole = await createSystemRole({
|
||||
displayName: 'System Role With Usage',
|
||||
});
|
||||
|
||||
// Mock roleRepository.findAllRoleCounts to return predictable usage counts
|
||||
const mockfindAllRoleCounts = jest.spyOn(roleRepository, 'findAllRoleCounts');
|
||||
mockfindAllRoleCounts.mockResolvedValue({
|
||||
[customRole.slug]: 3,
|
||||
[systemRole.slug]: 1,
|
||||
});
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const roles = await roleService.getAllRoles(true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(roles).toBeDefined();
|
||||
expect(Array.isArray(roles)).toBe(true);
|
||||
|
||||
// Find our test roles
|
||||
const returnedCustomRole = roles.find((r) => r.slug === customRole.slug);
|
||||
const returnedSystemRole = roles.find((r) => r.slug === systemRole.slug);
|
||||
|
||||
expect(returnedCustomRole).toBeDefined();
|
||||
expect(returnedSystemRole).toBeDefined();
|
||||
|
||||
// Verify usedByUsers is included when withCount=true
|
||||
expect(returnedCustomRole?.usedByUsers).toBe(3);
|
||||
expect(returnedSystemRole?.usedByUsers).toBe(1);
|
||||
|
||||
// Verify other properties are preserved
|
||||
expect(returnedCustomRole).toMatchObject({
|
||||
slug: customRole.slug,
|
||||
displayName: customRole.displayName,
|
||||
description: customRole.description,
|
||||
systemRole: false,
|
||||
roleType: customRole.roleType,
|
||||
scopes: expect.any(Array),
|
||||
licensed: expect.any(Boolean),
|
||||
});
|
||||
|
||||
// Verify findAllRoleCounts was called only once
|
||||
expect(mockfindAllRoleCounts).toBeCalledTimes(1);
|
||||
|
||||
mockfindAllRoleCounts.mockRestore();
|
||||
});
|
||||
|
||||
it('should return roles with zero usage count', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const unusedRole = await createCustomRoleWithScopes([testScopes.readScope], {
|
||||
displayName: 'Unused Role',
|
||||
description: 'A role with no users',
|
||||
});
|
||||
|
||||
// Mock roleRepository.findAllRoleCounts to return 0 for all roles
|
||||
const mockfindAllRoleCounts = jest.spyOn(roleRepository, 'findAllRoleCounts');
|
||||
mockfindAllRoleCounts.mockResolvedValue({ [unusedRole.slug]: 0 });
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const roles = await roleService.getAllRoles(true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
const returnedRole = roles.find((r) => r.slug === unusedRole.slug);
|
||||
|
||||
expect(returnedRole).toBeDefined();
|
||||
expect(returnedRole?.usedByUsers).toBe(0);
|
||||
|
||||
mockfindAllRoleCounts.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle mixed system and custom roles with different usage counts', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const customRole1 = await createCustomRoleWithScopes([testScopes.readScope], {
|
||||
displayName: 'Custom Role 1',
|
||||
});
|
||||
const customRole2 = await createCustomRoleWithScopes([testScopes.writeScope], {
|
||||
displayName: 'Custom Role 2',
|
||||
});
|
||||
const systemRole = await createSystemRole({
|
||||
displayName: 'System Role',
|
||||
});
|
||||
|
||||
// Mock different usage counts for each role
|
||||
const mockfindAllRoleCounts = jest.spyOn(roleRepository, 'findAllRoleCounts');
|
||||
mockfindAllRoleCounts.mockResolvedValue({
|
||||
[customRole1.slug]: 5,
|
||||
[customRole2.slug]: 2,
|
||||
[systemRole.slug]: 10,
|
||||
});
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const roles = await roleService.getAllRoles(true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
const returnedCustomRole1 = roles.find((r) => r.slug === customRole1.slug);
|
||||
const returnedCustomRole2 = roles.find((r) => r.slug === customRole2.slug);
|
||||
const returnedSystemRole = roles.find((r) => r.slug === systemRole.slug);
|
||||
|
||||
expect(returnedCustomRole1?.usedByUsers).toBe(5);
|
||||
expect(returnedCustomRole2?.usedByUsers).toBe(2);
|
||||
expect(returnedSystemRole?.usedByUsers).toBe(10);
|
||||
|
||||
// Verify role types are preserved
|
||||
expect(returnedCustomRole1?.systemRole).toBe(false);
|
||||
expect(returnedCustomRole2?.systemRole).toBe(false);
|
||||
expect(returnedSystemRole?.systemRole).toBe(true);
|
||||
|
||||
expect(mockfindAllRoleCounts).toHaveBeenCalledTimes(1);
|
||||
mockfindAllRoleCounts.mockRestore();
|
||||
});
|
||||
|
||||
it('should preserve complete role structure when adding usage counts', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const fullRole = await createCustomRoleWithScopes(
|
||||
[testScopes.readScope, testScopes.writeScope, testScopes.deleteScope],
|
||||
{
|
||||
displayName: 'Complete Role',
|
||||
description: 'A role with full properties',
|
||||
},
|
||||
);
|
||||
|
||||
// Mock usage count
|
||||
const mockfindAllRoleCounts = jest.spyOn(roleRepository, 'findAllRoleCounts');
|
||||
mockfindAllRoleCounts.mockResolvedValue({
|
||||
[fullRole.slug]: 7,
|
||||
});
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const roles = await roleService.getAllRoles(true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
const returnedRole = roles.find((r) => r.slug === fullRole.slug);
|
||||
|
||||
expect(returnedRole).toBeDefined();
|
||||
expect(returnedRole).toMatchObject({
|
||||
slug: fullRole.slug,
|
||||
displayName: fullRole.displayName,
|
||||
description: fullRole.description,
|
||||
systemRole: false,
|
||||
roleType: fullRole.roleType,
|
||||
scopes: expect.arrayContaining([
|
||||
testScopes.readScope.slug,
|
||||
testScopes.writeScope.slug,
|
||||
testScopes.deleteScope.slug,
|
||||
]),
|
||||
licensed: expect.any(Boolean),
|
||||
usedByUsers: 7,
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date),
|
||||
});
|
||||
|
||||
// Verify all scopes are correctly converted to slugs
|
||||
expect(returnedRole?.scopes).toHaveLength(3);
|
||||
|
||||
mockfindAllRoleCounts.mockRestore();
|
||||
});
|
||||
|
||||
it('should verify repository findAllRoleCounts is called correctly', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const role1 = await createCustomRoleWithScopes([testScopes.readScope]);
|
||||
const role2 = await createSystemRole();
|
||||
|
||||
const mockfindAllRoleCounts = jest.spyOn(roleRepository, 'findAllRoleCounts');
|
||||
mockfindAllRoleCounts.mockResolvedValue({
|
||||
[role1.slug]: 4,
|
||||
[role2.slug]: 6,
|
||||
});
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
await roleService.getAllRoles(true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
// Verify findAllRoleCounts was called only once
|
||||
expect(mockfindAllRoleCounts).toHaveBeenCalledTimes(1);
|
||||
|
||||
mockfindAllRoleCounts.mockRestore();
|
||||
});
|
||||
|
||||
it('should not call findAllRoleCounts when withCount=false', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
await createCustomRoleWithScopes([testScopes.readScope]);
|
||||
|
||||
const mockfindAllRoleCounts = jest.spyOn(roleRepository, 'findAllRoleCounts');
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
await roleService.getAllRoles(false);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
// Verify findAllRoleCounts was never called
|
||||
expect(mockfindAllRoleCounts).not.toHaveBeenCalled();
|
||||
|
||||
mockfindAllRoleCounts.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRole', () => {
|
||||
it('should return role with licensing information when role exists', async () => {
|
||||
//
|
||||
@@ -180,6 +481,297 @@ describe('RoleService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRole with usage counting', () => {
|
||||
it('should return role without usage count when withCount=false', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const customRole = await createCustomRoleWithScopes(
|
||||
[testScopes.readScope, testScopes.writeScope],
|
||||
{
|
||||
displayName: 'Custom Test Role',
|
||||
description: 'A custom role for testing without usage count',
|
||||
},
|
||||
);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const result = await roleService.getRole(customRole.slug, false);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(result).toMatchObject({
|
||||
slug: customRole.slug,
|
||||
displayName: customRole.displayName,
|
||||
description: customRole.description,
|
||||
systemRole: false,
|
||||
roleType: customRole.roleType,
|
||||
scopes: expect.arrayContaining([testScopes.readScope.slug, testScopes.writeScope.slug]),
|
||||
licensed: expect.any(Boolean),
|
||||
});
|
||||
|
||||
// Verify usedByUsers is undefined when withCount=false
|
||||
expect(result.usedByUsers).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return role with accurate usage count when withCount=true', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const customRole = await createCustomRoleWithScopes([testScopes.adminScope], {
|
||||
displayName: 'Role With Usage Count',
|
||||
description: 'A custom role for usage counting testing',
|
||||
});
|
||||
|
||||
// Mock roleRepository.countUsersWithRole to return predictable count
|
||||
const mockCountUsersWithRole = jest.spyOn(roleRepository, 'countUsersWithRole');
|
||||
mockCountUsersWithRole.mockResolvedValue(5);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const result = await roleService.getRole(customRole.slug, true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(result).toMatchObject({
|
||||
slug: customRole.slug,
|
||||
displayName: customRole.displayName,
|
||||
description: customRole.description,
|
||||
systemRole: false,
|
||||
roleType: customRole.roleType,
|
||||
scopes: expect.arrayContaining([testScopes.adminScope.slug]),
|
||||
licensed: expect.any(Boolean),
|
||||
usedByUsers: 5,
|
||||
});
|
||||
|
||||
// Verify countUsersWithRole was called with the correct role
|
||||
expect(mockCountUsersWithRole).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ slug: customRole.slug }),
|
||||
);
|
||||
|
||||
mockCountUsersWithRole.mockRestore();
|
||||
});
|
||||
|
||||
it('should throw NotFoundError regardless of withCount parameter', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const nonExistentSlug = 'non-existent-role-for-usage-test';
|
||||
|
||||
//
|
||||
// ACT & ASSERT
|
||||
//
|
||||
await expect(roleService.getRole(nonExistentSlug, false)).rejects.toThrow(NotFoundError);
|
||||
await expect(roleService.getRole(nonExistentSlug, false)).rejects.toThrow('Role not found');
|
||||
|
||||
await expect(roleService.getRole(nonExistentSlug, true)).rejects.toThrow(NotFoundError);
|
||||
await expect(roleService.getRole(nonExistentSlug, true)).rejects.toThrow('Role not found');
|
||||
});
|
||||
|
||||
it('should work with system roles and usage counting', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const systemRole = await createSystemRole({
|
||||
displayName: 'System Role With Usage',
|
||||
description: 'A system role for usage testing',
|
||||
});
|
||||
|
||||
// Mock higher usage count for system role
|
||||
const mockCountUsersWithRole = jest.spyOn(roleRepository, 'countUsersWithRole');
|
||||
mockCountUsersWithRole.mockResolvedValue(12);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const result = await roleService.getRole(systemRole.slug, true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(result).toMatchObject({
|
||||
slug: systemRole.slug,
|
||||
displayName: systemRole.displayName,
|
||||
description: systemRole.description,
|
||||
systemRole: true,
|
||||
roleType: systemRole.roleType,
|
||||
scopes: expect.any(Array),
|
||||
licensed: expect.any(Boolean),
|
||||
usedByUsers: 12,
|
||||
});
|
||||
|
||||
// Verify system role properties are preserved
|
||||
expect(result.systemRole).toBe(true);
|
||||
|
||||
mockCountUsersWithRole.mockRestore();
|
||||
});
|
||||
|
||||
it('should return role with zero usage count', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const unusedRole = await createCustomRoleWithScopes([testScopes.readScope], {
|
||||
displayName: 'Unused Role',
|
||||
description: 'A role with no assigned users',
|
||||
});
|
||||
|
||||
// Mock countUsersWithRole to return 0
|
||||
const mockCountUsersWithRole = jest.spyOn(roleRepository, 'countUsersWithRole');
|
||||
mockCountUsersWithRole.mockResolvedValue(0);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const result = await roleService.getRole(unusedRole.slug, true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(result).toMatchObject({
|
||||
slug: unusedRole.slug,
|
||||
displayName: unusedRole.displayName,
|
||||
description: unusedRole.description,
|
||||
systemRole: false,
|
||||
roleType: unusedRole.roleType,
|
||||
scopes: expect.arrayContaining([testScopes.readScope.slug]),
|
||||
licensed: expect.any(Boolean),
|
||||
usedByUsers: 0,
|
||||
});
|
||||
|
||||
mockCountUsersWithRole.mockRestore();
|
||||
});
|
||||
|
||||
it('should preserve complete role structure with usage count', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const fullRole = await createCustomRoleWithScopes(
|
||||
[testScopes.readScope, testScopes.writeScope, testScopes.deleteScope],
|
||||
{
|
||||
displayName: 'Complete Role Structure',
|
||||
description: 'A role with full properties for structure verification',
|
||||
},
|
||||
);
|
||||
|
||||
// Mock usage count
|
||||
const mockCountUsersWithRole = jest.spyOn(roleRepository, 'countUsersWithRole');
|
||||
mockCountUsersWithRole.mockResolvedValue(8);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const result = await roleService.getRole(fullRole.slug, true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(result).toMatchObject({
|
||||
slug: fullRole.slug,
|
||||
displayName: fullRole.displayName,
|
||||
description: fullRole.description,
|
||||
systemRole: false,
|
||||
roleType: fullRole.roleType,
|
||||
scopes: expect.arrayContaining([
|
||||
testScopes.readScope.slug,
|
||||
testScopes.writeScope.slug,
|
||||
testScopes.deleteScope.slug,
|
||||
]),
|
||||
licensed: expect.any(Boolean),
|
||||
usedByUsers: 8,
|
||||
createdAt: expect.any(Date),
|
||||
updatedAt: expect.any(Date),
|
||||
});
|
||||
|
||||
// Verify all scopes are included
|
||||
expect(result.scopes).toHaveLength(3);
|
||||
|
||||
mockCountUsersWithRole.mockRestore();
|
||||
});
|
||||
|
||||
it('should verify countUsersWithRole is called only when withCount=true', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const testRole = await createCustomRoleWithScopes([testScopes.readScope]);
|
||||
|
||||
const mockCountUsersWithRole = jest.spyOn(roleRepository, 'countUsersWithRole');
|
||||
mockCountUsersWithRole.mockResolvedValue(3);
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
// Test with withCount=false
|
||||
await roleService.getRole(testRole.slug, false);
|
||||
|
||||
// Test with withCount=true
|
||||
await roleService.getRole(testRole.slug, true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
// Verify countUsersWithRole was called only once (for withCount=true)
|
||||
expect(mockCountUsersWithRole).toHaveBeenCalledTimes(1);
|
||||
expect(mockCountUsersWithRole).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ slug: testRole.slug }),
|
||||
);
|
||||
|
||||
mockCountUsersWithRole.mockRestore();
|
||||
});
|
||||
|
||||
it('should verify repository integration with different usage counts', async () => {
|
||||
//
|
||||
// ARRANGE
|
||||
//
|
||||
const testScopes = await createTestScopes();
|
||||
const role1 = await createCustomRoleWithScopes([testScopes.readScope], {
|
||||
displayName: 'Role One',
|
||||
});
|
||||
const role2 = await createCustomRoleWithScopes([testScopes.writeScope], {
|
||||
displayName: 'Role Two',
|
||||
});
|
||||
|
||||
// Mock different usage counts for different roles
|
||||
const mockCountUsersWithRole = jest.spyOn(roleRepository, 'countUsersWithRole');
|
||||
mockCountUsersWithRole.mockImplementation(async (role) => {
|
||||
if (role.slug === role1.slug) return 15;
|
||||
if (role.slug === role2.slug) return 3;
|
||||
return 0;
|
||||
});
|
||||
|
||||
//
|
||||
// ACT
|
||||
//
|
||||
const result1 = await roleService.getRole(role1.slug, true);
|
||||
const result2 = await roleService.getRole(role2.slug, true);
|
||||
|
||||
//
|
||||
// ASSERT
|
||||
//
|
||||
expect(result1.usedByUsers).toBe(15);
|
||||
expect(result2.usedByUsers).toBe(3);
|
||||
|
||||
// Verify each role was queried correctly
|
||||
expect(mockCountUsersWithRole).toHaveBeenCalledTimes(2);
|
||||
expect(mockCountUsersWithRole).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ slug: role1.slug }),
|
||||
);
|
||||
expect(mockCountUsersWithRole).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ slug: role2.slug }),
|
||||
);
|
||||
|
||||
mockCountUsersWithRole.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createCustomRole', () => {
|
||||
it('should create custom role with valid data', async () => {
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user