chore: `applyDepartmentRestrictions` to new package structure (#36733)
parent
776af0ae53
commit
1457924eb1
@ -1,32 +0,0 @@ |
||||
import type { ILivechatDepartment } from '@rocket.chat/core-typings'; |
||||
import type { FilterOperators } from 'mongodb'; |
||||
|
||||
import { callbacks } from '../../../../../lib/callbacks'; |
||||
import { cbLogger } from '../lib/logger'; |
||||
import { getUnitsFromUser } from '../methods/getUnitsFromUserRoles'; |
||||
|
||||
export const addQueryRestrictionsToDepartmentsModel = async (originalQuery: FilterOperators<ILivechatDepartment> = {}, userId: string) => { |
||||
const query: FilterOperators<ILivechatDepartment> = { $and: [originalQuery, { type: { $ne: 'u' } }] }; |
||||
|
||||
const units = await getUnitsFromUser(userId); |
||||
if (Array.isArray(units)) { |
||||
query.$and.push({ $or: [{ ancestors: { $in: units } }, { _id: { $in: units } }] }); |
||||
} |
||||
|
||||
cbLogger.debug({ msg: 'Applying department query restrictions', userId, units }); |
||||
return query; |
||||
}; |
||||
|
||||
callbacks.add( |
||||
'livechat.applyDepartmentRestrictions', |
||||
async (originalQuery: FilterOperators<ILivechatDepartment> = {}, { userId }: { userId?: string | null } = { userId: null }) => { |
||||
if (!userId) { |
||||
return originalQuery; |
||||
} |
||||
|
||||
cbLogger.debug('Applying department query restrictions'); |
||||
return addQueryRestrictionsToDepartmentsModel(originalQuery, userId); |
||||
}, |
||||
callbacks.priority.HIGH, |
||||
'livechat-apply-department-restrictions', |
||||
); |
||||
@ -1,5 +1,9 @@ |
||||
import { isDepartmentCreationAvailablePatch } from './isDepartmentCreationAvailable'; |
||||
import { applyDepartmentRestrictionsPatch } from './patches/applyDepartmentRestrictions'; |
||||
|
||||
export function patchOmniCore(): void { |
||||
isDepartmentCreationAvailablePatch(); |
||||
applyDepartmentRestrictionsPatch(); |
||||
} |
||||
|
||||
export * from './units/getUnitsFromUser'; |
||||
|
||||
@ -0,0 +1,24 @@ |
||||
import type { ILivechatDepartment } from '@rocket.chat/core-typings'; |
||||
import { License } from '@rocket.chat/license'; |
||||
import { applyDepartmentRestrictions } from '@rocket.chat/omni-core'; |
||||
import type { FilterOperators } from 'mongodb'; |
||||
|
||||
import { addQueryRestrictionsToDepartmentsModel } from '../units/addRoleBasedRestrictionsToDepartment'; |
||||
import { hooksLogger } from '../utils/logger'; |
||||
|
||||
export const applyDepartmentRestrictionsPatch = () => { |
||||
applyDepartmentRestrictions.patch( |
||||
async ( |
||||
prev: (query: FilterOperators<ILivechatDepartment>, userId: string) => FilterOperators<ILivechatDepartment>, |
||||
query: FilterOperators<ILivechatDepartment> = {}, |
||||
userId: string, |
||||
) => { |
||||
if (!License.hasModule('livechat-enterprise')) { |
||||
return prev(query, userId); |
||||
} |
||||
|
||||
hooksLogger.debug('Applying department query restrictions'); |
||||
return addQueryRestrictionsToDepartmentsModel(query, userId); |
||||
}, |
||||
); |
||||
}; |
||||
@ -0,0 +1,297 @@ |
||||
import type { ILivechatDepartment } from '@rocket.chat/core-typings'; |
||||
import type { FilterOperators } from 'mongodb'; |
||||
|
||||
import { addQueryRestrictionsToDepartmentsModel } from './addRoleBasedRestrictionsToDepartment'; |
||||
import { getUnitsFromUser } from './getUnitsFromUser'; |
||||
import { defaultLogger } from '../utils/logger'; |
||||
|
||||
// Mock dependencies
|
||||
jest.mock('./getUnitsFromUser'); |
||||
jest.mock('../utils/logger'); |
||||
|
||||
const mockedGetUnitsFromUser = jest.mocked(getUnitsFromUser); |
||||
const mockedLogger = jest.mocked(defaultLogger); |
||||
|
||||
describe('addQueryRestrictionsToDepartmentsModel', () => { |
||||
beforeEach(() => { |
||||
jest.clearAllMocks(); |
||||
}); |
||||
|
||||
describe('when getUnitsFromUser returns an array of units', () => { |
||||
it('should add unit restrictions to the query', async () => { |
||||
// Arrange
|
||||
const userId = 'user123'; |
||||
const units = ['unit1', 'unit2', 'unit3']; |
||||
const originalQuery: FilterOperators<ILivechatDepartment> = { name: 'test-department' }; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue(units); |
||||
|
||||
// Act
|
||||
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId); |
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ |
||||
$and: [ |
||||
{ name: 'test-department' }, |
||||
{ type: { $ne: 'u' } }, |
||||
{ |
||||
$or: [{ ancestors: { $in: ['unit1', 'unit2', 'unit3'] } }, { _id: { $in: ['unit1', 'unit2', 'unit3'] } }], |
||||
}, |
||||
], |
||||
}); |
||||
|
||||
expect(mockedGetUnitsFromUser).toHaveBeenCalledWith(userId); |
||||
expect(mockedLogger.debug).toHaveBeenCalledWith({ |
||||
msg: 'Applying department query restrictions', |
||||
userId, |
||||
units, |
||||
}); |
||||
}); |
||||
|
||||
it('should work with empty original query', async () => { |
||||
// Arrange
|
||||
const userId = 'user456'; |
||||
const units = ['unit4', 'unit5']; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue(units); |
||||
|
||||
// Act
|
||||
const result = await addQueryRestrictionsToDepartmentsModel({}, userId); |
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ |
||||
$and: [ |
||||
{}, |
||||
{ type: { $ne: 'u' } }, |
||||
{ |
||||
$or: [{ ancestors: { $in: ['unit4', 'unit5'] } }, { _id: { $in: ['unit4', 'unit5'] } }], |
||||
}, |
||||
], |
||||
}); |
||||
}); |
||||
|
||||
it('should work with undefined original query', async () => { |
||||
// Arrange
|
||||
const userId = 'user789'; |
||||
const units = ['unit6']; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue(units); |
||||
|
||||
// Act
|
||||
const result = await addQueryRestrictionsToDepartmentsModel(undefined, userId); |
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ |
||||
$and: [ |
||||
{}, |
||||
{ type: { $ne: 'u' } }, |
||||
{ |
||||
$or: [{ ancestors: { $in: ['unit6'] } }, { _id: { $in: ['unit6'] } }], |
||||
}, |
||||
], |
||||
}); |
||||
}); |
||||
|
||||
it('should handle single unit in array', async () => { |
||||
// Arrange
|
||||
const userId = 'user101'; |
||||
const units = ['single-unit']; |
||||
const originalQuery: FilterOperators<ILivechatDepartment> = { enabled: true }; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue(units); |
||||
|
||||
// Act
|
||||
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId); |
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ |
||||
$and: [ |
||||
{ enabled: true }, |
||||
{ type: { $ne: 'u' } }, |
||||
{ |
||||
$or: [{ ancestors: { $in: ['single-unit'] } }, { _id: { $in: ['single-unit'] } }], |
||||
}, |
||||
], |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when getUnitsFromUser returns non-array values', () => { |
||||
it('should not add unit restrictions when getUnitsFromUser returns null', async () => { |
||||
// Arrange
|
||||
const userId = 'user202'; |
||||
const originalQuery: FilterOperators<ILivechatDepartment> = { name: 'test' }; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue(undefined); |
||||
|
||||
// Act
|
||||
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId); |
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ |
||||
$and: [{ name: 'test' }, { type: { $ne: 'u' } }], |
||||
}); |
||||
|
||||
expect(mockedLogger.debug).toHaveBeenCalledWith({ |
||||
msg: 'Applying department query restrictions', |
||||
userId, |
||||
units: undefined, |
||||
}); |
||||
}); |
||||
|
||||
it('should not add unit restrictions when getUnitsFromUser returns undefined', async () => { |
||||
// Arrange
|
||||
const userId = 'user303'; |
||||
const originalQuery: FilterOperators<ILivechatDepartment> = { active: true }; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue(undefined); |
||||
|
||||
// Act
|
||||
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId); |
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ |
||||
$and: [{ active: true }, { type: { $ne: 'u' } }], |
||||
}); |
||||
|
||||
expect(mockedLogger.debug).toHaveBeenCalledWith({ |
||||
msg: 'Applying department query restrictions', |
||||
userId, |
||||
units: undefined, |
||||
}); |
||||
}); |
||||
|
||||
it('should not add unit restrictions when getUnitsFromUser returns a string', async () => { |
||||
// Arrange
|
||||
const userId = 'user404'; |
||||
const originalQuery: FilterOperators<ILivechatDepartment> = {}; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue('not-an-array' as any); |
||||
|
||||
// Act
|
||||
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId); |
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ |
||||
$and: [{}, { type: { $ne: 'u' } }], |
||||
}); |
||||
|
||||
expect(mockedLogger.debug).toHaveBeenCalledWith({ |
||||
msg: 'Applying department query restrictions', |
||||
userId, |
||||
units: 'not-an-array', |
||||
}); |
||||
}); |
||||
|
||||
it('should not add unit restrictions when getUnitsFromUser returns empty array', async () => { |
||||
// Arrange
|
||||
const userId = 'user505'; |
||||
const originalQuery: FilterOperators<ILivechatDepartment> = { department: 'support' }; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue([]); |
||||
|
||||
// Act
|
||||
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId); |
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ |
||||
$and: [ |
||||
{ department: 'support' }, |
||||
{ type: { $ne: 'u' } }, |
||||
{ |
||||
$or: [{ ancestors: { $in: [] } }, { _id: { $in: [] } }], |
||||
}, |
||||
], |
||||
}); |
||||
|
||||
expect(mockedLogger.debug).toHaveBeenCalledWith({ |
||||
msg: 'Applying department query restrictions', |
||||
userId, |
||||
units: [], |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('error handling', () => { |
||||
it('should propagate errors from getUnitsFromUser', async () => { |
||||
// Arrange
|
||||
const userId = 'user606'; |
||||
const error = new Error('Database connection failed'); |
||||
|
||||
mockedGetUnitsFromUser.mockRejectedValue(error); |
||||
|
||||
// Act & Assert
|
||||
await expect(addQueryRestrictionsToDepartmentsModel({}, userId)).rejects.toThrow('Database connection failed'); |
||||
|
||||
expect(mockedGetUnitsFromUser).toHaveBeenCalledWith(userId); |
||||
expect(mockedLogger.debug).not.toHaveBeenCalled(); |
||||
}); |
||||
}); |
||||
|
||||
describe('complex query scenarios', () => { |
||||
it('should handle complex original query with nested conditions', async () => { |
||||
// Arrange
|
||||
const userId = 'user707'; |
||||
const units = ['unit-a', 'unit-b']; |
||||
const originalQuery: FilterOperators<ILivechatDepartment> = { |
||||
$or: [{ name: { $regex: 'support' } }, { enabled: true }], |
||||
createdAt: { $gte: new Date('2023-01-01') }, |
||||
}; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue(units); |
||||
|
||||
// Act
|
||||
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId); |
||||
|
||||
// Assert
|
||||
expect(result).toEqual({ |
||||
$and: [ |
||||
{ |
||||
$or: [{ name: { $regex: 'support' } }, { enabled: true }], |
||||
createdAt: { $gte: new Date('2023-01-01') }, |
||||
}, |
||||
{ type: { $ne: 'u' } }, |
||||
{ |
||||
$or: [{ ancestors: { $in: ['unit-a', 'unit-b'] } }, { _id: { $in: ['unit-a', 'unit-b'] } }], |
||||
}, |
||||
], |
||||
}); |
||||
}); |
||||
|
||||
it('should always exclude departments with type "u"', async () => { |
||||
// Arrange
|
||||
const userId = 'user808'; |
||||
const units = ['unit-x']; |
||||
const originalQuery: FilterOperators<ILivechatDepartment> = { type: 'normal' }; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue(units); |
||||
|
||||
// Act
|
||||
const result = await addQueryRestrictionsToDepartmentsModel(originalQuery, userId); |
||||
|
||||
// Assert
|
||||
expect(result.$and).toContainEqual({ type: { $ne: 'u' } }); |
||||
}); |
||||
}); |
||||
|
||||
describe('logging', () => { |
||||
it('should always call debug logger with correct parameters', async () => { |
||||
// Arrange
|
||||
const userId = 'user909'; |
||||
const units = ['unit-test']; |
||||
|
||||
mockedGetUnitsFromUser.mockResolvedValue(units); |
||||
|
||||
// Act
|
||||
await addQueryRestrictionsToDepartmentsModel({}, userId); |
||||
|
||||
// Assert
|
||||
expect(mockedLogger.debug).toHaveBeenCalledTimes(1); |
||||
expect(mockedLogger.debug).toHaveBeenCalledWith({ |
||||
msg: 'Applying department query restrictions', |
||||
userId: 'user909', |
||||
units: ['unit-test'], |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,17 @@ |
||||
import type { ILivechatDepartment } from '@rocket.chat/core-typings'; |
||||
import type { FilterOperators } from 'mongodb'; |
||||
|
||||
import { getUnitsFromUser } from './getUnitsFromUser'; |
||||
import { defaultLogger } from '../utils/logger'; |
||||
|
||||
export const addQueryRestrictionsToDepartmentsModel = async (originalQuery: FilterOperators<ILivechatDepartment> = {}, userId: string) => { |
||||
const query: FilterOperators<ILivechatDepartment> = { $and: [originalQuery, { type: { $ne: 'u' } }] }; |
||||
|
||||
const units = await getUnitsFromUser(userId); |
||||
if (Array.isArray(units)) { |
||||
query.$and.push({ $or: [{ ancestors: { $in: units } }, { _id: { $in: units } }] }); |
||||
} |
||||
|
||||
defaultLogger.debug({ msg: 'Applying department query restrictions', userId, units }); |
||||
return query; |
||||
}; |
||||
@ -0,0 +1,230 @@ |
||||
import { Authorization } from '@rocket.chat/core-services'; |
||||
import { LivechatUnit, LivechatDepartmentAgents } from '@rocket.chat/models'; |
||||
|
||||
import { getUnitsFromUser } from './getUnitsFromUser'; |
||||
import { defaultLogger } from '../utils/logger'; |
||||
|
||||
// Mock the dependencies
|
||||
jest.mock('@rocket.chat/core-services', () => ({ |
||||
Authorization: { |
||||
hasAnyRole: jest.fn(), |
||||
}, |
||||
})); |
||||
jest.mock('@rocket.chat/models', () => ({ |
||||
LivechatUnit: { |
||||
findByMonitorId: jest.fn(), |
||||
countUnits: jest.fn(), |
||||
}, |
||||
LivechatDepartmentAgents: { |
||||
findByAgentId: jest.fn(), |
||||
}, |
||||
})); |
||||
|
||||
jest.mock('mem', () => (fn: any) => fn); |
||||
jest.mock('../utils/logger'); |
||||
|
||||
const mockAuthorization = Authorization as jest.Mocked<typeof Authorization>; |
||||
const mockLivechatUnit = LivechatUnit as jest.Mocked<typeof LivechatUnit>; |
||||
const mockLivechatDepartmentAgents = LivechatDepartmentAgents as jest.Mocked<typeof LivechatDepartmentAgents>; |
||||
const mockLogger = defaultLogger as jest.Mocked<typeof defaultLogger>; |
||||
describe('getUnitsFromUser', () => { |
||||
beforeEach(() => { |
||||
jest.resetAllMocks(); |
||||
|
||||
// Setup default mock implementations
|
||||
mockLivechatUnit.findByMonitorId.mockResolvedValue(['unit1', 'unit2']); |
||||
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({ |
||||
toArray: jest.fn().mockResolvedValue([{ departmentId: 'dept1' }, { departmentId: 'dept2' }]), |
||||
} as any); |
||||
mockLivechatUnit.countUnits.mockResolvedValue(5); |
||||
|
||||
mockAuthorization.hasAnyRole.mockResolvedValue(false); |
||||
mockLogger.debug.mockImplementation(() => { |
||||
//
|
||||
}); |
||||
}); |
||||
|
||||
describe('when userId is not provided', () => { |
||||
it('should return undefined for null userId', async () => { |
||||
const result = await getUnitsFromUser(null as any); |
||||
expect(result).toBeUndefined(); |
||||
}); |
||||
|
||||
it('should return undefined for undefined userId', async () => { |
||||
const result = await getUnitsFromUser(undefined); |
||||
expect(result).toBeUndefined(); |
||||
}); |
||||
|
||||
it('should return undefined for empty string userId', async () => { |
||||
const result = await getUnitsFromUser(''); |
||||
expect(result).toBeUndefined(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when there are no units in the system', () => { |
||||
it('should return undefined', async () => { |
||||
mockLivechatUnit.countUnits.mockResolvedValue(0); |
||||
|
||||
const result = await getUnitsFromUser('user123'); |
||||
|
||||
expect(result).toBeUndefined(); |
||||
expect(mockLivechatUnit.countUnits).toHaveBeenCalled(); |
||||
}); |
||||
}); |
||||
|
||||
describe('when user has admin role', () => { |
||||
it('should return undefined for admin users', async () => { |
||||
mockAuthorization.hasAnyRole |
||||
.mockResolvedValueOnce(true) // admin/livechat-manager check
|
||||
.mockResolvedValueOnce(false); // livechat-monitor/agent check
|
||||
|
||||
const result = await getUnitsFromUser('admin-user'); |
||||
|
||||
expect(mockLivechatUnit.countUnits).toHaveBeenCalled(); |
||||
expect(result).toBeUndefined(); |
||||
expect(mockAuthorization.hasAnyRole).toHaveBeenCalledWith('admin-user', ['admin', 'livechat-manager']); |
||||
}); |
||||
}); |
||||
|
||||
describe('when user does not have required roles', () => { |
||||
it('should return undefined for users without livechat-monitor or livechat-agent roles', async () => { |
||||
mockAuthorization.hasAnyRole |
||||
.mockResolvedValueOnce(false) // admin/livechat-manager check
|
||||
.mockResolvedValueOnce(false); // livechat-monitor/agent check
|
||||
|
||||
const result = await getUnitsFromUser('regular-user'); |
||||
|
||||
expect(result).toBeUndefined(); |
||||
expect(mockAuthorization.hasAnyRole).toHaveBeenCalledWith('regular-user', ['admin', 'livechat-manager']); |
||||
expect(mockAuthorization.hasAnyRole).toHaveBeenCalledWith('regular-user', ['livechat-monitor', 'livechat-agent']); |
||||
}); |
||||
}); |
||||
|
||||
describe('when user has livechat-manager role', () => { |
||||
it('should return undefined for livechat-manager users', async () => { |
||||
mockAuthorization.hasAnyRole |
||||
.mockResolvedValueOnce(true) // admin/livechat-manager check
|
||||
.mockResolvedValueOnce(false); // livechat-monitor/agent check
|
||||
|
||||
const result = await getUnitsFromUser('manager-user'); |
||||
|
||||
expect(result).toBeUndefined(); |
||||
expect(mockAuthorization.hasAnyRole).toHaveBeenCalledWith('manager-user', ['admin', 'livechat-manager']); |
||||
}); |
||||
}); |
||||
|
||||
describe('when user has livechat-monitor role', () => { |
||||
it('should return combined units and departments', async () => { |
||||
const userId = 'monitor-user'; |
||||
mockAuthorization.hasAnyRole |
||||
.mockResolvedValueOnce(false) // admin/livechat-manager check
|
||||
.mockResolvedValueOnce(true); // livechat-monitor/agent check
|
||||
|
||||
mockLivechatUnit.findByMonitorId.mockResolvedValue(['unit1', 'unit2']); |
||||
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({ |
||||
toArray: jest.fn().mockResolvedValue([{ departmentId: 'dept1' }, { departmentId: 'dept2' }]), |
||||
} as any); |
||||
|
||||
const result = await getUnitsFromUser(userId); |
||||
|
||||
expect(result).toEqual(['unit1', 'unit2', 'dept1', 'dept2']); |
||||
expect(mockLivechatUnit.findByMonitorId).toHaveBeenCalledWith(userId); |
||||
expect(mockLivechatDepartmentAgents.findByAgentId).toHaveBeenCalledWith(userId); |
||||
expect(mockLogger.debug).toHaveBeenCalledWith({ |
||||
msg: 'Calculating units for monitor', |
||||
user: userId, |
||||
unitsAndDepartments: ['unit1', 'unit2', 'dept1', 'dept2'], |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when user has livechat-agent role', () => { |
||||
it('should return combined units and departments', async () => { |
||||
const userId = 'agent-user'; |
||||
mockAuthorization.hasAnyRole |
||||
.mockResolvedValueOnce(false) // admin/livechat-manager check
|
||||
.mockResolvedValueOnce(true); // livechat-monitor/agent check
|
||||
|
||||
mockLivechatUnit.findByMonitorId.mockResolvedValue(['unit3']); |
||||
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({ |
||||
toArray: jest.fn().mockResolvedValue([{ departmentId: 'dept3' }, { departmentId: 'dept4' }]), |
||||
} as any); |
||||
|
||||
const result = await getUnitsFromUser(userId); |
||||
|
||||
expect(result).toEqual(['unit3', 'dept3', 'dept4']); |
||||
expect(mockLivechatUnit.findByMonitorId).toHaveBeenCalledWith(userId); |
||||
expect(mockLivechatDepartmentAgents.findByAgentId).toHaveBeenCalledWith(userId); |
||||
}); |
||||
}); |
||||
|
||||
describe('edge cases', () => { |
||||
it('should handle empty units and departments arrays', async () => { |
||||
const userId = 'empty-user'; |
||||
mockAuthorization.hasAnyRole |
||||
.mockResolvedValueOnce(false) // admin/livechat-manager check
|
||||
.mockResolvedValueOnce(true); // livechat-monitor/agent check
|
||||
|
||||
mockLivechatUnit.findByMonitorId.mockResolvedValue([]); |
||||
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({ |
||||
toArray: jest.fn().mockResolvedValue([]), |
||||
} as any); |
||||
|
||||
const result = await getUnitsFromUser(userId); |
||||
|
||||
expect(result).toEqual([]); |
||||
}); |
||||
|
||||
it('should handle when only units are returned', async () => { |
||||
const userId = 'units-only-user'; |
||||
mockAuthorization.hasAnyRole |
||||
.mockResolvedValueOnce(false) // admin/livechat-manager check
|
||||
.mockResolvedValueOnce(true); // livechat-monitor/agent check
|
||||
|
||||
mockLivechatUnit.findByMonitorId.mockResolvedValue(['unit1', 'unit2']); |
||||
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({ |
||||
toArray: jest.fn().mockResolvedValue([]), |
||||
} as any); |
||||
|
||||
const result = await getUnitsFromUser(userId); |
||||
|
||||
expect(result).toEqual(['unit1', 'unit2']); |
||||
}); |
||||
|
||||
it('should handle when only departments are returned', async () => { |
||||
const userId = 'departments-only-user'; |
||||
mockAuthorization.hasAnyRole |
||||
.mockResolvedValueOnce(false) // admin/livechat-manager check
|
||||
.mockResolvedValueOnce(true); // livechat-monitor/agent check
|
||||
|
||||
mockLivechatUnit.findByMonitorId.mockResolvedValue([]); |
||||
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({ |
||||
toArray: jest.fn().mockResolvedValue([{ departmentId: 'dept1' }, { departmentId: 'dept2' }]), |
||||
} as any); |
||||
|
||||
const result = await getUnitsFromUser(userId); |
||||
|
||||
expect(result).toEqual(['dept1', 'dept2']); |
||||
}); |
||||
}); |
||||
|
||||
describe('memoization', () => { |
||||
it('should work correctly with memoization disabled in tests', async () => { |
||||
const userId = 'test-user'; |
||||
mockAuthorization.hasAnyRole |
||||
.mockResolvedValueOnce(false) // admin/livechat-manager check
|
||||
.mockResolvedValueOnce(true); // livechat-monitor/agent check
|
||||
|
||||
mockLivechatUnit.findByMonitorId.mockResolvedValue(['unit1']); |
||||
mockLivechatDepartmentAgents.findByAgentId.mockReturnValue({ |
||||
toArray: jest.fn().mockResolvedValue([{ departmentId: 'dept1' }]), |
||||
} as any); |
||||
|
||||
const result = await getUnitsFromUser(userId); |
||||
|
||||
expect(result).toEqual(['unit1', 'dept1']); |
||||
expect(mockLivechatUnit.findByMonitorId).toHaveBeenCalledWith(userId); |
||||
expect(mockLivechatDepartmentAgents.findByAgentId).toHaveBeenCalledWith(userId); |
||||
}); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,48 @@ |
||||
import { Authorization } from '@rocket.chat/core-services'; |
||||
import { LivechatUnit, LivechatDepartmentAgents } from '@rocket.chat/models'; |
||||
import mem from 'mem'; |
||||
|
||||
import { defaultLogger } from '../utils/logger'; |
||||
|
||||
async function getUnitsFromUserRoles(user: string): Promise<string[]> { |
||||
return LivechatUnit.findByMonitorId(user); |
||||
} |
||||
|
||||
async function getDepartmentsFromUserRoles(user: string): Promise<string[]> { |
||||
return (await LivechatDepartmentAgents.findByAgentId(user).toArray()).map((department) => department.departmentId); |
||||
} |
||||
|
||||
const memoizedGetUnitFromUserRoles = mem(getUnitsFromUserRoles, { maxAge: process.env.TEST_MODE ? 1 : 10000 }); |
||||
const memoizedGetDepartmentsFromUserRoles = mem(getDepartmentsFromUserRoles, { maxAge: process.env.TEST_MODE ? 1 : 10000 }); |
||||
|
||||
async function hasUnits(): Promise<boolean> { |
||||
// @ts-expect-error - this prop is injected dynamically on ee license
|
||||
return (await LivechatUnit.countUnits({ type: 'u' })) > 0; |
||||
} |
||||
|
||||
// Units should't change really often, so we can cache the result
|
||||
const memoizedHasUnits = mem(hasUnits, { maxAge: process.env.TEST_MODE ? 1 : 10000 }); |
||||
|
||||
export const getUnitsFromUser = async (userId?: string): Promise<string[] | undefined> => { |
||||
if (!userId) { |
||||
return; |
||||
} |
||||
|
||||
if (!(await memoizedHasUnits())) { |
||||
return; |
||||
} |
||||
|
||||
// TODO: we can combine these 2 calls into one single query
|
||||
if (await Authorization.hasAnyRole(userId, ['admin', 'livechat-manager'])) { |
||||
return; |
||||
} |
||||
|
||||
if (!(await Authorization.hasAnyRole(userId, ['livechat-monitor', 'livechat-agent']))) { |
||||
return; |
||||
} |
||||
|
||||
const unitsAndDepartments = [...(await memoizedGetUnitFromUserRoles(userId)), ...(await memoizedGetDepartmentsFromUserRoles(userId))]; |
||||
defaultLogger.debug({ msg: 'Calculating units for monitor', user: userId, unitsAndDepartments }); |
||||
|
||||
return unitsAndDepartments; |
||||
}; |
||||
@ -0,0 +1,4 @@ |
||||
import { Logger } from '@rocket.chat/logger'; |
||||
|
||||
export const defaultLogger = new Logger('OmniCore-ee'); |
||||
export const hooksLogger = defaultLogger.section('hooks'); |
||||
@ -0,0 +1,7 @@ |
||||
import type { ILivechatDepartment } from '@rocket.chat/core-typings'; |
||||
import { makeFunction } from '@rocket.chat/patch-injection'; |
||||
import type { FilterOperators } from 'mongodb'; |
||||
|
||||
export const applyDepartmentRestrictions = makeFunction(async (query: FilterOperators<ILivechatDepartment> = {}, _userId: string) => { |
||||
return query; |
||||
}); |
||||
@ -1,2 +1,3 @@ |
||||
export * from './isDepartmentCreationAvailable'; |
||||
export * from './hooks/applyDepartmentRestrictions'; |
||||
export * from './visitor/create'; |
||||
|
||||
Loading…
Reference in new issue