tests: final ldap tests

This commit is contained in:
Marc Littlemore 2024-12-09 11:15:12 +00:00
parent 3ae6edc194
commit 4e1f8ae30f
No known key found for this signature in database

View file

@ -2,6 +2,7 @@ import { QueryFailedError } from '@n8n/typeorm';
import { mock } from 'jest-mock-extended'; import { mock } from 'jest-mock-extended';
import { Client } from 'ldapts'; import { Client } from 'ldapts';
import type { Cipher } from 'n8n-core'; import type { Cipher } from 'n8n-core';
import { randomString } from 'n8n-workflow';
import config from '@/config'; import config from '@/config';
import { AuthIdentityRepository } from '@/databases/repositories/auth-identity.repository'; import { AuthIdentityRepository } from '@/databases/repositories/auth-identity.repository';
@ -23,6 +24,7 @@ import {
resolveBinaryAttributes, resolveBinaryAttributes,
processUsers, processUsers,
mapLdapUserToDbUser, mapLdapUserToDbUser,
saveLdapSynchronization,
} from '../helpers.ee'; } from '../helpers.ee';
// Mock ldapts client // Mock ldapts client
@ -45,6 +47,11 @@ jest.mock('../helpers.ee', () => ({
processUsers: jest.fn(), processUsers: jest.fn(),
})); }));
jest.mock('n8n-workflow', () => ({
...jest.requireActual('n8n-workflow'),
randomString: jest.fn(),
}));
describe('LdapService', () => { describe('LdapService', () => {
const ldapConfig: LdapConfig = { const ldapConfig: LdapConfig = {
loginEnabled: true, loginEnabled: true,
@ -892,7 +899,12 @@ describe('LdapService', () => {
}); });
}); });
describe.only('runSync()', () => { describe('runSync()', () => {
beforeEach(() => {
const mockedRandomString = randomString as jest.Mock;
mockedRandomString.mockReturnValue('nonRandomPassword');
});
it('should search for users with expected parameters', async () => { it('should search for users with expected parameters', async () => {
const settingsRepository = mock<SettingsRepository>({ const settingsRepository = mock<SettingsRepository>({
findOneByOrFail: jest.fn().mockResolvedValue({ findOneByOrFail: jest.fn().mockResolvedValue({
@ -907,7 +919,7 @@ describe('LdapService', () => {
const mockedGetLdapIds = getLdapIds as jest.Mock; const mockedGetLdapIds = getLdapIds as jest.Mock;
mockedGetLdapIds.mockResolvedValue([]); mockedGetLdapIds.mockResolvedValue([]);
const expectedParameter = createFilter( const expectedFilter = createFilter(
`(${ldapConfig.loginIdAttribute}=*)`, `(${ldapConfig.loginIdAttribute}=*)`,
ldapConfig.userFilter, ldapConfig.userFilter,
); );
@ -916,7 +928,7 @@ describe('LdapService', () => {
await ldapService.runSync('dry'); await ldapService.runSync('dry');
expect(searchWithAdminBindingSpy).toHaveBeenCalledTimes(1); expect(searchWithAdminBindingSpy).toHaveBeenCalledTimes(1);
expect(searchWithAdminBindingSpy).toHaveBeenCalledWith(expectedParameter); expect(searchWithAdminBindingSpy).toHaveBeenCalledWith(expectedFilter);
}); });
it('should resolve binary attributes for users', async () => { it('should resolve binary attributes for users', async () => {
@ -965,46 +977,223 @@ describe('LdapService', () => {
await expect(ldapService.runSync('dry')).rejects.toThrowError('Error finding users'); await expect(ldapService.runSync('dry')).rejects.toThrowError('Error finding users');
}); });
it.skip('should process users if mode is "live"', async () => { it('should process expected users if mode is "live"', async () => {
const settingsRepository = mock<SettingsRepository>({ const settingsRepository = mock<SettingsRepository>({
findOneByOrFail: jest.fn().mockResolvedValue({ findOneByOrFail: jest.fn().mockResolvedValue({
value: JSON.stringify({ ldapConfig }), value: JSON.stringify(ldapConfig),
}), }),
}); });
const ldapService = new LdapService(mockLogger(), settingsRepository, mock(), mock()); const ldapService = new LdapService(mockLogger(), settingsRepository, mock(), mock());
const foundUsers = [
// New user // Users that don't exist in memory
const newUsers = [
{ {
dn: 'uid=jdoe,ou=users,dc=example,dc=com', dn: 'uid=johndoe,ou=users,dc=example,dc=com',
cn: ['John Doe'], cn: 'John Doe',
givenName: 'John', givenName: 'John',
sn: 'Doe', sn: 'Doe',
mail: ['jdoe@example.com'], mail: 'john.doe@example.com',
uid: ['jdoe'], uid: 'johndoe',
},
{
dn: 'uid=janedoe,ou=users,dc=example,dc=com',
cn: 'Jane Doe',
givenName: 'Jane',
sn: 'Doe',
mail: 'jane.doe@example.com',
uid: 'janedoe',
}, },
// Existing user
// User to delete
]; ];
// Users that exist in memory and in LDAP response
const updateUsers = [
{
dn: 'uid=emilyclark,ou=users,dc=example,dc=com',
cn: 'Emily Clark',
givenName: 'Emily',
sn: 'Clark',
mail: 'emily.clark@example.com',
uid: 'emilyclark',
},
];
// Users that only exist in memory
const deleteUsers = ['santaclaus', 'jackfrost'];
const foundUsers = [...newUsers, ...updateUsers];
Client.prototype.search = jest.fn().mockResolvedValue({ searchEntries: foundUsers }); Client.prototype.search = jest.fn().mockResolvedValue({ searchEntries: foundUsers });
const mockedGetLdapIds = getLdapIds as jest.Mock; const mockedGetLdapIds = getLdapIds as jest.Mock;
mockedGetLdapIds.mockResolvedValue([]);
const createDatabaseUser = mapLdapUserToDbUser(foundUsers[0], ldapConfig, true); // Delete users that exist in memory but not in the LDAP response
mockedGetLdapIds.mockResolvedValue(['emilyclark', ...deleteUsers]);
const newDbUsers = newUsers.map((user) => mapLdapUserToDbUser(user, ldapConfig, true));
const updateDbUsers = updateUsers.map((user) => mapLdapUserToDbUser(user, ldapConfig));
await ldapService.init(); await ldapService.init();
await ldapService.runSync('live'); await ldapService.runSync('live');
expect(processUsers).toHaveBeenCalledTimes(1); expect(processUsers).toHaveBeenCalledTimes(1);
expect(processUsers).toHaveBeenCalledWith([createDatabaseUser], [], []); expect(processUsers).toHaveBeenCalledWith(newDbUsers, updateDbUsers, deleteUsers);
}); });
it.todo('should write expected data to the database'); it('should sync expected LDAP data when no errors', async () => {
it.todo( const settingsRepository = mock<SettingsRepository>({
'should write expected data to the database with an error message if processing users fails', findOneByOrFail: jest.fn().mockResolvedValue({
value: JSON.stringify(ldapConfig),
}),
});
const ldapService = new LdapService(mockLogger(), settingsRepository, mock(), mock());
// Users that don't exist in memory
const newUsers = [
{
dn: 'uid=johndoe,ou=users,dc=example,dc=com',
cn: 'John Doe',
givenName: 'John',
sn: 'Doe',
mail: 'john.doe@example.com',
uid: 'johndoe',
},
{
dn: 'uid=janedoe,ou=users,dc=example,dc=com',
cn: 'Jane Doe',
givenName: 'Jane',
sn: 'Doe',
mail: 'jane.doe@example.com',
uid: 'janedoe',
},
];
// Users that exist in memory and in LDAP response
const updateUsers = [
{
dn: 'uid=emilyclark,ou=users,dc=example,dc=com',
cn: 'Emily Clark',
givenName: 'Emily',
sn: 'Clark',
mail: 'emily.clark@example.com',
uid: 'emilyclark',
},
];
// Users that only exist in memory
const deleteUsers = ['santaclaus', 'jackfrost'];
const foundUsers = [...newUsers, ...updateUsers];
Client.prototype.search = jest.fn().mockResolvedValue({ searchEntries: foundUsers });
const mockedGetLdapIds = getLdapIds as jest.Mock;
// Delete users that exist in memory but not in the LDAP response
mockedGetLdapIds.mockResolvedValue(['emilyclark', ...deleteUsers]);
const newDbUsers = newUsers.map((user) => mapLdapUserToDbUser(user, ldapConfig, true));
const updateDbUsers = updateUsers.map((user) => mapLdapUserToDbUser(user, ldapConfig));
jest.setSystemTime(new Date('2024-12-25'));
const expectedDate = new Date();
await ldapService.init();
await ldapService.runSync('live');
expect(saveLdapSynchronization).toHaveBeenCalledTimes(1);
expect(saveLdapSynchronization).toHaveBeenCalledWith({
startedAt: expectedDate,
endedAt: expectedDate,
created: newDbUsers.length,
updated: updateDbUsers.length,
disabled: deleteUsers.length,
scanned: foundUsers.length,
runMode: 'live',
status: 'success',
error: '',
});
});
it('should sync expected LDAP data when users fail to process', async () => {
const settingsRepository = mock<SettingsRepository>({
findOneByOrFail: jest.fn().mockResolvedValue({
value: JSON.stringify(ldapConfig),
}),
});
const ldapService = new LdapService(mockLogger(), settingsRepository, mock(), mock());
// Users that don't exist in memory
const newUsers = [
{
dn: 'uid=johndoe,ou=users,dc=example,dc=com',
cn: 'John Doe',
givenName: 'John',
sn: 'Doe',
mail: 'john.doe@example.com',
uid: 'johndoe',
},
{
dn: 'uid=janedoe,ou=users,dc=example,dc=com',
cn: 'Jane Doe',
givenName: 'Jane',
sn: 'Doe',
mail: 'jane.doe@example.com',
uid: 'janedoe',
},
];
// Users that exist in memory and in LDAP response
const updateUsers = [
{
dn: 'uid=emilyclark,ou=users,dc=example,dc=com',
cn: 'Emily Clark',
givenName: 'Emily',
sn: 'Clark',
mail: 'emily.clark@example.com',
uid: 'emilyclark',
},
];
// Users that only exist in memory
const deleteUsers = ['santaclaus', 'jackfrost'];
const foundUsers = [...newUsers, ...updateUsers];
Client.prototype.search = jest.fn().mockResolvedValue({ searchEntries: foundUsers });
const mockedProcessUsers = processUsers as jest.Mock;
mockedProcessUsers.mockRejectedValue(
new QueryFailedError('Query', [], new Error('Error processing users')),
); );
const mockedGetLdapIds = getLdapIds as jest.Mock;
// Delete users that exist in memory but not in the LDAP response
mockedGetLdapIds.mockResolvedValue(['emilyclark', ...deleteUsers]);
const newDbUsers = newUsers.map((user) => mapLdapUserToDbUser(user, ldapConfig, true));
const updateDbUsers = updateUsers.map((user) => mapLdapUserToDbUser(user, ldapConfig));
jest.setSystemTime(new Date('2024-12-25'));
const expectedDate = new Date();
await ldapService.init();
await ldapService.runSync('live');
expect(saveLdapSynchronization).toHaveBeenCalledTimes(1);
expect(saveLdapSynchronization).toHaveBeenCalledWith({
startedAt: expectedDate,
endedAt: expectedDate,
created: newDbUsers.length,
updated: updateDbUsers.length,
disabled: deleteUsers.length,
scanned: foundUsers.length,
runMode: 'live',
status: 'error',
error: 'Error processing users',
});
});
it('should emit expected event if synchronization is enabled', async () => { it('should emit expected event if synchronization is enabled', async () => {
const settingsRepository = mock<SettingsRepository>({ const settingsRepository = mock<SettingsRepository>({
findOneByOrFail: jest.fn().mockResolvedValue({ findOneByOrFail: jest.fn().mockResolvedValue({
@ -1074,7 +1263,7 @@ describe('LdapService', () => {
}); });
}); });
it('should emit expected event if processUsers fails', async () => { it('should emit expected event with error message if processUsers fails', async () => {
const settingsRepository = mock<SettingsRepository>({ const settingsRepository = mock<SettingsRepository>({
findOneByOrFail: jest.fn().mockResolvedValue({ findOneByOrFail: jest.fn().mockResolvedValue({
value: JSON.stringify(ldapConfig), value: JSON.stringify(ldapConfig),