From b6b2a49bf372a17f895ba3c5588a3e9bdc6bb551 Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Fri, 7 Aug 2026 16:50:31 -0300 Subject: [PATCH 01/13] test: add LDAP authentication end-to-end tests --- apps/meteor/tests/e2e/ldap.spec.ts | 160 +++++++++++++++++++++++++++++ docker-compose-ci.yml | 1 + 2 files changed, 161 insertions(+) create mode 100644 apps/meteor/tests/e2e/ldap.spec.ts diff --git a/apps/meteor/tests/e2e/ldap.spec.ts b/apps/meteor/tests/e2e/ldap.spec.ts new file mode 100644 index 0000000000000..56541234a5e8b --- /dev/null +++ b/apps/meteor/tests/e2e/ldap.spec.ts @@ -0,0 +1,160 @@ +import type { ISetting } from '@rocket.chat/core-typings'; +import { MongoClient } from 'mongodb'; + +import * as constants from './config/constants'; +import { Login } from './page-objects'; +import { getSettingValueById } from './utils/getSettingValueById'; +import { getUserInfo } from './utils/getUserInfo'; +import { setSettingValueById } from './utils/setSettingValueById'; +import type { BaseTest } from './utils/test'; +import { test, expect } from './utils/test'; + +const ldapUsernames = ['alan.bean', 'john.young', 'buzz.aldrin']; + +type Setting = { + _id: ISetting['_id']; + value: unknown; +}; + +const ldapSettings: Setting[] = [ + { _id: 'Accounts_ManuallyApproveNewUsers', value: false }, + { _id: 'LDAP_Server_Type', value: '' }, + { _id: 'LDAP_Host', value: process.env.CI === 'true' ? 'openldap' : 'localhost' }, + { _id: 'LDAP_Port', value: 1389 }, + { _id: 'LDAP_Authentication', value: true }, + { _id: 'LDAP_Authentication_UserDN', value: 'cn=admin,dc=space,dc=air' }, + { _id: 'LDAP_Authentication_Password', value: 'adminpassword' }, + { _id: 'LDAP_BaseDN', value: 'ou=users,dc=space,dc=air' }, + { _id: 'LDAP_User_Search_Field', value: 'uid' }, + { _id: 'LDAP_Username_Field', value: 'uid' }, + { _id: 'LDAP_Email_Field', value: 'mail' }, + { _id: 'LDAP_Name_Field', value: 'cn' }, + { _id: 'LDAP_Find_User_After_Login', value: false }, + { _id: 'LDAP_Sync_User_Active_State', value: 'none' }, +]; + +const setSetting = async (api: BaseTest['api'], { _id, value }: Setting) => { + const response = await setSettingValueById(api, _id, value); + expect(response.status(), `Failed to update setting ${_id}`).toBe(200); +}; + +const applyLdapSettings = async (api: BaseTest['api'], settings: Setting[], enabled: boolean) => { + await setSetting(api, { _id: 'LDAP_Enable', value: false }); + await Promise.all(settings.map((setting) => setSetting(api, setting))); + await setSetting(api, { _id: 'LDAP_Enable', value: enabled }); +}; + +const waitForLdapConnection = async (api: BaseTest['api']) => { + await expect + .poll( + async () => { + const connectionResponse = await api.post('/ldap.testConnection', {}); + if (!connectionResponse.ok()) { + return false; + } + + const result = await connectionResponse.json(); + return result.success; + }, + { + message: 'LDAP settings did not propagate to the running server', + timeout: 15_000, + }, + ) + .toBe(true); +}; + +const resetTestData = async () => { + const connection = await MongoClient.connect(constants.URL_MONGODB); + + try { + await connection + .db() + .collection('users') + .deleteMany({ + username: { + $in: ldapUsernames, + }, + }); + } finally { + await connection.close(); + } +}; + +test.describe('LDAP', () => { + test.skip(!constants.IS_EE, 'Enterprise only'); + let originalSettings: Setting[] | undefined; + + test.beforeAll(async ({ api }) => { + originalSettings = await Promise.all( + [...ldapSettings.map(({ _id }) => _id), 'LDAP_Enable'].map(async (_id) => ({ + _id, + value: await getSettingValueById(api, _id), + })), + ); + + await resetTestData(); + await applyLdapSettings(api, ldapSettings, true); + await waitForLdapConnection(api); + }); + + test.afterAll(async ({ api }) => { + if (!originalSettings) { + await resetTestData(); + return; + } + + const originalEnabled = Boolean(originalSettings.find(({ _id }) => _id === 'LDAP_Enable')?.value); + await Promise.all([ + applyLdapSettings( + api, + originalSettings.filter(({ _id }) => _id !== 'LDAP_Enable'), + originalEnabled, + ), + resetTestData(), + ]); + }); + + test('Connection Test', async ({ api }) => { + await test.step('Expect to successfully execute a connection test', async () => { + const response = await api.post('/ldap.testConnection', {}); + expect(response.status()).toBe(200); + const result = await response.json(); + expect(result.success).toBe(true); + }); + }); + + test('User Search Test', async ({ api }) => { + await test.step('Expect to successfully search for LDAP users', async () => { + const response = await api.post('/ldap.testSearch', { + username: 'alan.bean', + }); + expect(response.status()).toBe(200); + const result = await response.json(); + expect(result.success).toBe(true); + }); + }); + + test('Login using LDAP credentials', async ({ page, api }) => { + const poLogin = new Login(page); + await page.goto('/home'); + + await test.step('Expect to be able to login with LDAP credentials', async () => { + await poLogin.waitForDisplay(); + await poLogin.login('alan.bean', 'ldappassword'); + + await expect(page).toHaveURL('/home'); + await expect(page.getByRole('button', { name: 'User menu' })).toBeVisible(); + }); + + await test.step('Expect LDAP user data to have been mapped to the correct fields', async () => { + const user = await getUserInfo(api, 'alan.bean'); + + expect(user).toBeDefined(); + expect(user?.username).toBe('alan.bean'); + expect(user?.name).toBe('Alan Bean'); + expect(user?.emails).toBeDefined(); + expect(user?.emails?.[0].address).toBe('alan.bean@space.air'); + }); + }); +}); diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index be610fffe4a04..78cbb1e2bc6a3 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -255,6 +255,7 @@ services: environment: - LDAP_ADMIN_USERNAME=admin - LDAP_ADMIN_PASSWORD=adminpassword + - LDAP_PASSWORD_HASH={SHA256} - LDAP_ROOT=dc=space,dc=air - LDAP_ADMIN_DN=cn=admin,dc=space,dc=air - LDAP_CUSTOM_LDIF_DIR=/opt/bitnami/openldap/data From 3528ade5b8ea5d774e7e5db8529ad996cefb3191 Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Sat, 8 Aug 2026 14:52:05 -0300 Subject: [PATCH 02/13] test: improve LDAP test isolation and avatar coverage --- apps/meteor/tests/e2e/ldap.spec.ts | 68 ++++++++++-------- .../ldap-avatar-linux.jpeg | Bin 0 -> 522 bytes development/ldap/02-data.ldif | 17 +++++ 3 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 apps/meteor/tests/e2e/ldap.spec.ts-snapshots/ldap-avatar-linux.jpeg diff --git a/apps/meteor/tests/e2e/ldap.spec.ts b/apps/meteor/tests/e2e/ldap.spec.ts index 56541234a5e8b..328ece3913a7a 100644 --- a/apps/meteor/tests/e2e/ldap.spec.ts +++ b/apps/meteor/tests/e2e/ldap.spec.ts @@ -1,5 +1,4 @@ import type { ISetting } from '@rocket.chat/core-typings'; -import { MongoClient } from 'mongodb'; import * as constants from './config/constants'; import { Login } from './page-objects'; @@ -9,7 +8,7 @@ import { setSettingValueById } from './utils/setSettingValueById'; import type { BaseTest } from './utils/test'; import { test, expect } from './utils/test'; -const ldapUsernames = ['alan.bean', 'john.young', 'buzz.aldrin']; +const ldapUsername = 'ldap.e2e'; type Setting = { _id: ISetting['_id']; @@ -24,11 +23,13 @@ const ldapSettings: Setting[] = [ { _id: 'LDAP_Authentication', value: true }, { _id: 'LDAP_Authentication_UserDN', value: 'cn=admin,dc=space,dc=air' }, { _id: 'LDAP_Authentication_Password', value: 'adminpassword' }, - { _id: 'LDAP_BaseDN', value: 'ou=users,dc=space,dc=air' }, + { _id: 'LDAP_BaseDN', value: 'ou=others,dc=space,dc=air' }, { _id: 'LDAP_User_Search_Field', value: 'uid' }, { _id: 'LDAP_Username_Field', value: 'uid' }, { _id: 'LDAP_Email_Field', value: 'mail' }, { _id: 'LDAP_Name_Field', value: 'cn' }, + { _id: 'LDAP_Sync_User_Avatar', value: true }, + { _id: 'LDAP_Avatar_Field', value: 'jpegPhoto' }, { _id: 'LDAP_Find_User_After_Login', value: false }, { _id: 'LDAP_Sync_User_Active_State', value: 'none' }, ]; @@ -64,21 +65,16 @@ const waitForLdapConnection = async (api: BaseTest['api']) => { .toBe(true); }; -const resetTestData = async () => { - const connection = await MongoClient.connect(constants.URL_MONGODB); - - try { - await connection - .db() - .collection('users') - .deleteMany({ - username: { - $in: ldapUsernames, - }, - }); - } finally { - await connection.close(); +const deleteLdapUser = async (api: BaseTest['api']) => { + const response = await api.post('/users.delete', { username: ldapUsername }); + + if (response.ok()) { + return; } + + expect(response.status()).toBe(400); + const result = await response.json(); + expect(result.errorType).toBe('error-invalid-user'); }; test.describe('LDAP', () => { @@ -93,14 +89,14 @@ test.describe('LDAP', () => { })), ); - await resetTestData(); + await deleteLdapUser(api); await applyLdapSettings(api, ldapSettings, true); await waitForLdapConnection(api); }); test.afterAll(async ({ api }) => { if (!originalSettings) { - await resetTestData(); + await deleteLdapUser(api); return; } @@ -111,12 +107,12 @@ test.describe('LDAP', () => { originalSettings.filter(({ _id }) => _id !== 'LDAP_Enable'), originalEnabled, ), - resetTestData(), + deleteLdapUser(api), ]); }); test('Connection Test', async ({ api }) => { - await test.step('Expect to successfully execute a connection test', async () => { + await test.step('expect to successfully execute a connection test', async () => { const response = await api.post('/ldap.testConnection', {}); expect(response.status()).toBe(200); const result = await response.json(); @@ -125,9 +121,9 @@ test.describe('LDAP', () => { }); test('User Search Test', async ({ api }) => { - await test.step('Expect to successfully search for LDAP users', async () => { + await test.step('expect to successfully search for LDAP users', async () => { const response = await api.post('/ldap.testSearch', { - username: 'alan.bean', + username: ldapUsername, }); expect(response.status()).toBe(200); const result = await response.json(); @@ -139,22 +135,34 @@ test.describe('LDAP', () => { const poLogin = new Login(page); await page.goto('/home'); - await test.step('Expect to be able to login with LDAP credentials', async () => { + await test.step('expect to be able to login with LDAP credentials', async () => { await poLogin.waitForDisplay(); - await poLogin.login('alan.bean', 'ldappassword'); + await poLogin.login(ldapUsername, 'ldappassword'); await expect(page).toHaveURL('/home'); await expect(page.getByRole('button', { name: 'User menu' })).toBeVisible(); }); - await test.step('Expect LDAP user data to have been mapped to the correct fields', async () => { - const user = await getUserInfo(api, 'alan.bean'); + await test.step('expect LDAP user data to have been mapped to the correct fields', async () => { + const user = await getUserInfo(api, ldapUsername); expect(user).toBeDefined(); - expect(user?.username).toBe('alan.bean'); - expect(user?.name).toBe('Alan Bean'); + expect(user?.username).toBe(ldapUsername); + expect(user?.name).toBe('LDAP E2E'); expect(user?.emails).toBeDefined(); - expect(user?.emails?.[0].address).toBe('alan.bean@space.air'); + expect(user?.emails?.[0].address).toBe('ldap.e2e@space.air'); + }); + + await test.step('expect LDAP user avatar to have been synchronized', async () => { + await expect(async () => { + const response = await page.request.get(`/avatar/${ldapUsername}`); + + expect(response.status()).toBe(200); + expect(response.headers()['content-type']).toBe('image/jpeg'); + expect(await response.body()).toMatchSnapshot('ldap-avatar.jpeg', { + maxDiffPixelRatio: 0.01, + }); + }).toPass(); }); }); }); diff --git a/apps/meteor/tests/e2e/ldap.spec.ts-snapshots/ldap-avatar-linux.jpeg b/apps/meteor/tests/e2e/ldap.spec.ts-snapshots/ldap-avatar-linux.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..aa29d60dc2c67614fe674c8b4f5be71dd08c615a GIT binary patch literal 522 zcmd^&O%8%E6olWi(83E*8n9s1KqP1}l5hhT?)6Y!%!Lb%;Ll+yERmIKXE&2C^Vx@e z!wI*LR!XnSOfQR-Qu!e-w!3OyEwZ|)O;ei6^d~5U zAde!V$Y`a_&#^a<9< Date: Sat, 8 Aug 2026 15:08:44 -0300 Subject: [PATCH 03/13] test: simplify LDAP test assertions --- apps/meteor/tests/e2e/ldap.spec.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/meteor/tests/e2e/ldap.spec.ts b/apps/meteor/tests/e2e/ldap.spec.ts index 328ece3913a7a..d272288f6fea7 100644 --- a/apps/meteor/tests/e2e/ldap.spec.ts +++ b/apps/meteor/tests/e2e/ldap.spec.ts @@ -111,7 +111,7 @@ test.describe('LDAP', () => { ]); }); - test('Connection Test', async ({ api }) => { + test('connection', async ({ api }) => { await test.step('expect to successfully execute a connection test', async () => { const response = await api.post('/ldap.testConnection', {}); expect(response.status()).toBe(200); @@ -120,7 +120,7 @@ test.describe('LDAP', () => { }); }); - test('User Search Test', async ({ api }) => { + test('user search', async ({ api }) => { await test.step('expect to successfully search for LDAP users', async () => { const response = await api.post('/ldap.testSearch', { username: ldapUsername, @@ -131,7 +131,7 @@ test.describe('LDAP', () => { }); }); - test('Login using LDAP credentials', async ({ page, api }) => { + test('login using LDAP credentials', async ({ page, api }) => { const poLogin = new Login(page); await page.goto('/home'); @@ -154,15 +154,13 @@ test.describe('LDAP', () => { }); await test.step('expect LDAP user avatar to have been synchronized', async () => { - await expect(async () => { - const response = await page.request.get(`/avatar/${ldapUsername}`); - - expect(response.status()).toBe(200); - expect(response.headers()['content-type']).toBe('image/jpeg'); - expect(await response.body()).toMatchSnapshot('ldap-avatar.jpeg', { - maxDiffPixelRatio: 0.01, - }); - }).toPass(); + const response = await page.request.get(`/avatar/${ldapUsername}`); + + expect(response.status()).toBe(200); + expect(response.headers()['content-type']).toBe('image/jpeg'); + expect(await response.body()).toMatchSnapshot('ldap-avatar.jpeg', { + maxDiffPixelRatio: 0.01, + }); }); }); }); From 38601849edb734c6098f7a0d608071de9fd5246d Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Sat, 8 Aug 2026 15:17:41 -0300 Subject: [PATCH 04/13] test: run LDAP e2e coverage in Community --- apps/meteor/tests/e2e/ldap.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/meteor/tests/e2e/ldap.spec.ts b/apps/meteor/tests/e2e/ldap.spec.ts index d272288f6fea7..5ed3c371e4297 100644 --- a/apps/meteor/tests/e2e/ldap.spec.ts +++ b/apps/meteor/tests/e2e/ldap.spec.ts @@ -1,6 +1,5 @@ import type { ISetting } from '@rocket.chat/core-typings'; -import * as constants from './config/constants'; import { Login } from './page-objects'; import { getSettingValueById } from './utils/getSettingValueById'; import { getUserInfo } from './utils/getUserInfo'; @@ -31,7 +30,6 @@ const ldapSettings: Setting[] = [ { _id: 'LDAP_Sync_User_Avatar', value: true }, { _id: 'LDAP_Avatar_Field', value: 'jpegPhoto' }, { _id: 'LDAP_Find_User_After_Login', value: false }, - { _id: 'LDAP_Sync_User_Active_State', value: 'none' }, ]; const setSetting = async (api: BaseTest['api'], { _id, value }: Setting) => { @@ -78,7 +76,6 @@ const deleteLdapUser = async (api: BaseTest['api']) => { }; test.describe('LDAP', () => { - test.skip(!constants.IS_EE, 'Enterprise only'); let originalSettings: Setting[] | undefined; test.beforeAll(async ({ api }) => { From 696f338eb0f4626f9bf7873b68db71552c8d6f57 Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Sun, 9 Aug 2026 12:48:31 -0300 Subject: [PATCH 05/13] test: scope LDAP e2e to Enterprise CI --- apps/meteor/tests/e2e/ldap.spec.ts | 2 ++ docker-compose-ci.yml | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/meteor/tests/e2e/ldap.spec.ts b/apps/meteor/tests/e2e/ldap.spec.ts index 5ed3c371e4297..1f020e7dfb8d4 100644 --- a/apps/meteor/tests/e2e/ldap.spec.ts +++ b/apps/meteor/tests/e2e/ldap.spec.ts @@ -1,5 +1,6 @@ import type { ISetting } from '@rocket.chat/core-typings'; +import * as constants from './config/constants'; import { Login } from './page-objects'; import { getSettingValueById } from './utils/getSettingValueById'; import { getUserInfo } from './utils/getUserInfo'; @@ -76,6 +77,7 @@ const deleteLdapUser = async (api: BaseTest['api']) => { }; test.describe('LDAP', () => { + test.skip(!constants.IS_EE); let originalSettings: Setting[] | undefined; test.beforeAll(async ({ api }) => { diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index 78cbb1e2bc6a3..be610fffe4a04 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -255,7 +255,6 @@ services: environment: - LDAP_ADMIN_USERNAME=admin - LDAP_ADMIN_PASSWORD=adminpassword - - LDAP_PASSWORD_HASH={SHA256} - LDAP_ROOT=dc=space,dc=air - LDAP_ADMIN_DN=cn=admin,dc=space,dc=air - LDAP_CUSTOM_LDIF_DIR=/opt/bitnami/openldap/data From fa3ef6c3504b75cb7f930386770ffe0fb224bad6 Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Mon, 10 Aug 2026 10:48:15 -0300 Subject: [PATCH 06/13] test: validate LDAP search response --- apps/meteor/tests/e2e/ldap.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/meteor/tests/e2e/ldap.spec.ts b/apps/meteor/tests/e2e/ldap.spec.ts index 1f020e7dfb8d4..513b55f62aa19 100644 --- a/apps/meteor/tests/e2e/ldap.spec.ts +++ b/apps/meteor/tests/e2e/ldap.spec.ts @@ -127,6 +127,7 @@ test.describe('LDAP', () => { expect(response.status()).toBe(200); const result = await response.json(); expect(result.success).toBe(true); + expect(result.message).toBe('LDAP_User_Found'); }); }); From 3f7e5c90e7ca67402850b19ee85ccbdca93f305d Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Mon, 17 Aug 2026 14:40:07 -0300 Subject: [PATCH 07/13] test: batch LDAP settings updates --- apps/meteor/tests/e2e/ldap.spec.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/meteor/tests/e2e/ldap.spec.ts b/apps/meteor/tests/e2e/ldap.spec.ts index 513b55f62aa19..a1d1062ae8671 100644 --- a/apps/meteor/tests/e2e/ldap.spec.ts +++ b/apps/meteor/tests/e2e/ldap.spec.ts @@ -4,7 +4,7 @@ import * as constants from './config/constants'; import { Login } from './page-objects'; import { getSettingValueById } from './utils/getSettingValueById'; import { getUserInfo } from './utils/getUserInfo'; -import { setSettingValueById } from './utils/setSettingValueById'; +import { saveSettings } from './utils/saveSettings'; import type { BaseTest } from './utils/test'; import { test, expect } from './utils/test'; @@ -12,7 +12,7 @@ const ldapUsername = 'ldap.e2e'; type Setting = { _id: ISetting['_id']; - value: unknown; + value: ISetting['value']; }; const ldapSettings: Setting[] = [ @@ -33,15 +33,9 @@ const ldapSettings: Setting[] = [ { _id: 'LDAP_Find_User_After_Login', value: false }, ]; -const setSetting = async (api: BaseTest['api'], { _id, value }: Setting) => { - const response = await setSettingValueById(api, _id, value); - expect(response.status(), `Failed to update setting ${_id}`).toBe(200); -}; - const applyLdapSettings = async (api: BaseTest['api'], settings: Setting[], enabled: boolean) => { - await setSetting(api, { _id: 'LDAP_Enable', value: false }); - await Promise.all(settings.map((setting) => setSetting(api, setting))); - await setSetting(api, { _id: 'LDAP_Enable', value: enabled }); + const response = await saveSettings(api, [...settings, { _id: 'LDAP_Enable', value: enabled }]); + expect(response.status(), 'Failed to update LDAP settings').toBe(200); }; const waitForLdapConnection = async (api: BaseTest['api']) => { @@ -84,7 +78,7 @@ test.describe('LDAP', () => { originalSettings = await Promise.all( [...ldapSettings.map(({ _id }) => _id), 'LDAP_Enable'].map(async (_id) => ({ _id, - value: await getSettingValueById(api, _id), + value: (await getSettingValueById(api, _id)) as ISetting['value'], })), ); From 932e7b843c8054a3acc4713647e12610dc334c30 Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Tue, 18 Aug 2026 13:53:43 -0300 Subject: [PATCH 08/13] test: address LDAP e2e review feedback --- apps/meteor/tests/e2e/ldap.spec.ts | 63 +++++++++++------------------- 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/apps/meteor/tests/e2e/ldap.spec.ts b/apps/meteor/tests/e2e/ldap.spec.ts index a1d1062ae8671..ec81cdb2c8a38 100644 --- a/apps/meteor/tests/e2e/ldap.spec.ts +++ b/apps/meteor/tests/e2e/ldap.spec.ts @@ -1,9 +1,8 @@ import type { ISetting } from '@rocket.chat/core-typings'; -import * as constants from './config/constants'; -import { Login } from './page-objects'; +import { IS_EE } from './config/constants'; +import { AccountProfile, Login } from './page-objects'; import { getSettingValueById } from './utils/getSettingValueById'; -import { getUserInfo } from './utils/getUserInfo'; import { saveSettings } from './utils/saveSettings'; import type { BaseTest } from './utils/test'; import { test, expect } from './utils/test'; @@ -52,26 +51,15 @@ const waitForLdapConnection = async (api: BaseTest['api']) => { }, { message: 'LDAP settings did not propagate to the running server', - timeout: 15_000, }, ) .toBe(true); }; -const deleteLdapUser = async (api: BaseTest['api']) => { - const response = await api.post('/users.delete', { username: ldapUsername }); - - if (response.ok()) { - return; - } - - expect(response.status()).toBe(400); - const result = await response.json(); - expect(result.errorType).toBe('error-invalid-user'); -}; +const deleteLdapUser = (api: BaseTest['api']) => api.post('/users.delete', { username: ldapUsername }); test.describe('LDAP', () => { - test.skip(!constants.IS_EE); + test.skip(!IS_EE); let originalSettings: Setting[] | undefined; test.beforeAll(async ({ api }) => { @@ -104,47 +92,40 @@ test.describe('LDAP', () => { ]); }); - test('connection', async ({ api }) => { - await test.step('expect to successfully execute a connection test', async () => { - const response = await api.post('/ldap.testConnection', {}); - expect(response.status()).toBe(200); - const result = await response.json(); - expect(result.success).toBe(true); - }); + test('should connect to LDAP successfully', async ({ api }) => { + const response = await api.post('/ldap.testConnection', {}); + expect(response.status()).toBe(200); + const result = await response.json(); + expect(result.success).toBe(true); }); - test('user search', async ({ api }) => { - await test.step('expect to successfully search for LDAP users', async () => { - const response = await api.post('/ldap.testSearch', { - username: ldapUsername, - }); - expect(response.status()).toBe(200); - const result = await response.json(); - expect(result.success).toBe(true); - expect(result.message).toBe('LDAP_User_Found'); + test('should find the requested LDAP user', async ({ api }) => { + const response = await api.post('/ldap.testSearch', { + username: ldapUsername, }); + expect(response.status()).toBe(200); + const result = await response.json(); + expect(result.success).toBe(true); + expect(result.message).toBe('LDAP_User_Found'); }); - test('login using LDAP credentials', async ({ page, api }) => { + test('should log in with LDAP credentials and synchronize mapped profile data', async ({ page }) => { const poLogin = new Login(page); + const poAccountProfile = new AccountProfile(page); await page.goto('/home'); await test.step('expect to be able to login with LDAP credentials', async () => { await poLogin.waitForDisplay(); await poLogin.login(ldapUsername, 'ldappassword'); - await expect(page).toHaveURL('/home'); await expect(page.getByRole('button', { name: 'User menu' })).toBeVisible(); }); await test.step('expect LDAP user data to have been mapped to the correct fields', async () => { - const user = await getUserInfo(api, ldapUsername); - - expect(user).toBeDefined(); - expect(user?.username).toBe(ldapUsername); - expect(user?.name).toBe('LDAP E2E'); - expect(user?.emails).toBeDefined(); - expect(user?.emails?.[0].address).toBe('ldap.e2e@space.air'); + await page.goto('/account/profile'); + await expect(poAccountProfile.inputUsername).toHaveValue(ldapUsername); + await expect(poAccountProfile.inputName).toHaveValue('LDAP E2E'); + await expect(poAccountProfile.emailTextInput).toHaveValue('ldap.e2e@space.air'); }); await test.step('expect LDAP user avatar to have been synchronized', async () => { From 62678ff6050d2a1957ca6c320fadf1076cfcc885 Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Tue, 18 Aug 2026 14:07:15 -0300 Subject: [PATCH 09/13] fix: restore timeout --- apps/meteor/tests/e2e/ldap.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/meteor/tests/e2e/ldap.spec.ts b/apps/meteor/tests/e2e/ldap.spec.ts index ec81cdb2c8a38..e186440dd0707 100644 --- a/apps/meteor/tests/e2e/ldap.spec.ts +++ b/apps/meteor/tests/e2e/ldap.spec.ts @@ -51,6 +51,7 @@ const waitForLdapConnection = async (api: BaseTest['api']) => { }, { message: 'LDAP settings did not propagate to the running server', + timeout: 15_000, }, ) .toBe(true); From 9b7aab0a6bad4e55fd6bfd8fb8fb5b82a3eeedf4 Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Wed, 2 Sep 2026 13:46:07 -0300 Subject: [PATCH 10/13] test: move LDAP coverage to API suite --- apps/meteor/tests/e2e/ldap.spec.ts | 142 ------------------ .../ldap-avatar-linux.jpeg | Bin 522 -> 0 bytes apps/meteor/tests/end-to-end/api/LDAP.ts | 138 ++++++++++++++++- 3 files changed, 137 insertions(+), 143 deletions(-) delete mode 100644 apps/meteor/tests/e2e/ldap.spec.ts delete mode 100644 apps/meteor/tests/e2e/ldap.spec.ts-snapshots/ldap-avatar-linux.jpeg diff --git a/apps/meteor/tests/e2e/ldap.spec.ts b/apps/meteor/tests/e2e/ldap.spec.ts deleted file mode 100644 index e186440dd0707..0000000000000 --- a/apps/meteor/tests/e2e/ldap.spec.ts +++ /dev/null @@ -1,142 +0,0 @@ -import type { ISetting } from '@rocket.chat/core-typings'; - -import { IS_EE } from './config/constants'; -import { AccountProfile, Login } from './page-objects'; -import { getSettingValueById } from './utils/getSettingValueById'; -import { saveSettings } from './utils/saveSettings'; -import type { BaseTest } from './utils/test'; -import { test, expect } from './utils/test'; - -const ldapUsername = 'ldap.e2e'; - -type Setting = { - _id: ISetting['_id']; - value: ISetting['value']; -}; - -const ldapSettings: Setting[] = [ - { _id: 'Accounts_ManuallyApproveNewUsers', value: false }, - { _id: 'LDAP_Server_Type', value: '' }, - { _id: 'LDAP_Host', value: process.env.CI === 'true' ? 'openldap' : 'localhost' }, - { _id: 'LDAP_Port', value: 1389 }, - { _id: 'LDAP_Authentication', value: true }, - { _id: 'LDAP_Authentication_UserDN', value: 'cn=admin,dc=space,dc=air' }, - { _id: 'LDAP_Authentication_Password', value: 'adminpassword' }, - { _id: 'LDAP_BaseDN', value: 'ou=others,dc=space,dc=air' }, - { _id: 'LDAP_User_Search_Field', value: 'uid' }, - { _id: 'LDAP_Username_Field', value: 'uid' }, - { _id: 'LDAP_Email_Field', value: 'mail' }, - { _id: 'LDAP_Name_Field', value: 'cn' }, - { _id: 'LDAP_Sync_User_Avatar', value: true }, - { _id: 'LDAP_Avatar_Field', value: 'jpegPhoto' }, - { _id: 'LDAP_Find_User_After_Login', value: false }, -]; - -const applyLdapSettings = async (api: BaseTest['api'], settings: Setting[], enabled: boolean) => { - const response = await saveSettings(api, [...settings, { _id: 'LDAP_Enable', value: enabled }]); - expect(response.status(), 'Failed to update LDAP settings').toBe(200); -}; - -const waitForLdapConnection = async (api: BaseTest['api']) => { - await expect - .poll( - async () => { - const connectionResponse = await api.post('/ldap.testConnection', {}); - if (!connectionResponse.ok()) { - return false; - } - - const result = await connectionResponse.json(); - return result.success; - }, - { - message: 'LDAP settings did not propagate to the running server', - timeout: 15_000, - }, - ) - .toBe(true); -}; - -const deleteLdapUser = (api: BaseTest['api']) => api.post('/users.delete', { username: ldapUsername }); - -test.describe('LDAP', () => { - test.skip(!IS_EE); - let originalSettings: Setting[] | undefined; - - test.beforeAll(async ({ api }) => { - originalSettings = await Promise.all( - [...ldapSettings.map(({ _id }) => _id), 'LDAP_Enable'].map(async (_id) => ({ - _id, - value: (await getSettingValueById(api, _id)) as ISetting['value'], - })), - ); - - await deleteLdapUser(api); - await applyLdapSettings(api, ldapSettings, true); - await waitForLdapConnection(api); - }); - - test.afterAll(async ({ api }) => { - if (!originalSettings) { - await deleteLdapUser(api); - return; - } - - const originalEnabled = Boolean(originalSettings.find(({ _id }) => _id === 'LDAP_Enable')?.value); - await Promise.all([ - applyLdapSettings( - api, - originalSettings.filter(({ _id }) => _id !== 'LDAP_Enable'), - originalEnabled, - ), - deleteLdapUser(api), - ]); - }); - - test('should connect to LDAP successfully', async ({ api }) => { - const response = await api.post('/ldap.testConnection', {}); - expect(response.status()).toBe(200); - const result = await response.json(); - expect(result.success).toBe(true); - }); - - test('should find the requested LDAP user', async ({ api }) => { - const response = await api.post('/ldap.testSearch', { - username: ldapUsername, - }); - expect(response.status()).toBe(200); - const result = await response.json(); - expect(result.success).toBe(true); - expect(result.message).toBe('LDAP_User_Found'); - }); - - test('should log in with LDAP credentials and synchronize mapped profile data', async ({ page }) => { - const poLogin = new Login(page); - const poAccountProfile = new AccountProfile(page); - await page.goto('/home'); - - await test.step('expect to be able to login with LDAP credentials', async () => { - await poLogin.waitForDisplay(); - await poLogin.login(ldapUsername, 'ldappassword'); - - await expect(page.getByRole('button', { name: 'User menu' })).toBeVisible(); - }); - - await test.step('expect LDAP user data to have been mapped to the correct fields', async () => { - await page.goto('/account/profile'); - await expect(poAccountProfile.inputUsername).toHaveValue(ldapUsername); - await expect(poAccountProfile.inputName).toHaveValue('LDAP E2E'); - await expect(poAccountProfile.emailTextInput).toHaveValue('ldap.e2e@space.air'); - }); - - await test.step('expect LDAP user avatar to have been synchronized', async () => { - const response = await page.request.get(`/avatar/${ldapUsername}`); - - expect(response.status()).toBe(200); - expect(response.headers()['content-type']).toBe('image/jpeg'); - expect(await response.body()).toMatchSnapshot('ldap-avatar.jpeg', { - maxDiffPixelRatio: 0.01, - }); - }); - }); -}); diff --git a/apps/meteor/tests/e2e/ldap.spec.ts-snapshots/ldap-avatar-linux.jpeg b/apps/meteor/tests/e2e/ldap.spec.ts-snapshots/ldap-avatar-linux.jpeg deleted file mode 100644 index aa29d60dc2c67614fe674c8b4f5be71dd08c615a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 522 zcmd^&O%8%E6olWi(83E*8n9s1KqP1}l5hhT?)6Y!%!Lb%;Ll+yERmIKXE&2C^Vx@e z!wI*LR!XnSOfQR-Qu!e-w!3OyEwZ|)O;ei6^d~5U zAde!V$Y`a_&#^a<9< + request + .post(api('settings')) + .set(credentials) + .send({ settings: [...settings, { _id: 'LDAP_Enable', value: enabled }] }) + .expect('Content-Type', 'application/json') + .expect(200); + +const deleteLdapUser = () => request.post(api('users.delete')).set(credentials).send({ username: ldapUsername }); + +const waitForLdapConnection = async () => { + const timeoutAt = Date.now() + 15_000; + + do { + const response = await request.post(api('ldap.testConnection')).set(credentials); + if (response.ok && response.body.success) { + return; + } + + await new Promise((resolve) => setTimeout(resolve, 250)); + } while (Date.now() < timeoutAt); + + throw new Error('LDAP settings did not propagate to the running server'); +}; describe('LDAP', function () { this.retries(0); @@ -91,4 +143,88 @@ describe('LDAP', function () { }); }); }); + + (process.env.IS_EE ? describe : describe.skip)('configured LDAP integration', function () { + this.timeout(30_000); + + let originalSettings: Setting[] | undefined; + + before(async () => { + originalSettings = await Promise.all( + [...ldapSettings.map(({ _id }) => _id), 'LDAP_Enable'].map(async (_id) => ({ + _id, + value: await getSettingValueById(_id), + })), + ); + + await deleteLdapUser(); + await applyLdapSettings(ldapSettings, true); + await waitForLdapConnection(); + }); + + after(async () => { + await deleteLdapUser(); + + if (!originalSettings) { + return; + } + + const originalEnabled = Boolean(originalSettings.find(({ _id }) => _id === 'LDAP_Enable')?.value); + await applyLdapSettings( + originalSettings.filter(({ _id }) => _id !== 'LDAP_Enable'), + originalEnabled, + ); + }); + + it('should connect to LDAP successfully', async () => { + await request + .post(api('ldap.testConnection')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('message', 'LDAP_Connection_successful'); + }); + }); + + it('should find the requested LDAP user', async () => { + await request + .post(api('ldap.testSearch')) + .set(credentials) + .send({ username: ldapUsername }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('message', 'LDAP_User_Found'); + }); + }); + + it('should log in with LDAP credentials and synchronize mapped profile data and avatar', async () => { + const loginResponse = await request + .post(api('login')) + .send({ user: ldapUsername, password: 'ldappassword' }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(loginResponse.body).to.have.property('status', 'success'); + expect(loginResponse.body.data.me).to.include({ + username: ldapUsername, + name: 'LDAP E2E', + }); + expect(loginResponse.body.data.me.emails.map(({ address }: { address: string }) => address)).to.include('ldap.e2e@space.air'); + + const avatarResponse = await request.get(`/avatar/${ldapUsername}`).expect('Content-Type', 'image/jpeg').expect(200); + const metadata = await sharp(avatarResponse.body as Buffer).metadata(); + const stats = await sharp(avatarResponse.body as Buffer).stats(); + + expect(metadata).to.include({ format: 'jpeg', width: 200, height: 200 }); + expect(stats.channels.slice(0, 3).map(({ min, max }) => ({ min, max }))).to.deep.equal([ + { min: 0, max: 0 }, + { min: 102, max: 102 }, + { min: 203, max: 203 }, + ]); + }); + }); }); From f22bfb88055bf441da60f629b25cb04c681aa2d9 Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Wed, 2 Sep 2026 13:52:56 -0300 Subject: [PATCH 11/13] test: align LDAP API tests with suite conventions --- apps/meteor/tests/end-to-end/api/LDAP.ts | 59 ++++++++++++------------ 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/LDAP.ts b/apps/meteor/tests/end-to-end/api/LDAP.ts index b7c3a6fff11e3..65752b078fd34 100644 --- a/apps/meteor/tests/end-to-end/api/LDAP.ts +++ b/apps/meteor/tests/end-to-end/api/LDAP.ts @@ -4,15 +4,15 @@ import { before, after, describe, it } from 'mocha'; import sharp from 'sharp'; import type { Response } from 'supertest'; +import { retry } from './helpers/retry'; import { getCredentials, api, request, credentials } from '../../data/api-data'; import { getSettingValueById, updatePermission } from '../../data/permissions.helper'; +import { IS_EE } from '../../e2e/config/constants'; const ldapUsername = 'ldap.e2e'; +const ldapAvatarRgb = [0, 102, 203]; -type Setting = { - _id: ISetting['_id']; - value: ISetting['value']; -}; +type Setting = Pick; const ldapSettings: Setting[] = [ { _id: 'Accounts_ManuallyApproveNewUsers', value: false }, @@ -42,20 +42,21 @@ const applyLdapSettings = (settings: Setting[], enabled: boolean) => const deleteLdapUser = () => request.post(api('users.delete')).set(credentials).send({ username: ldapUsername }); -const waitForLdapConnection = async () => { - const timeoutAt = Date.now() + 15_000; - - do { - const response = await request.post(api('ldap.testConnection')).set(credentials); - if (response.ok && response.body.success) { - return; - } - - await new Promise((resolve) => setTimeout(resolve, 250)); - } while (Date.now() < timeoutAt); - - throw new Error('LDAP settings did not propagate to the running server'); -}; +const waitForLdapConnection = () => + retry( + 'LDAP settings propagation', + async () => { + await request + .post(api('ldap.testConnection')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + }); + }, + { retries: 20, delayMs: 250 }, + ); describe('LDAP', function () { this.retries(0); @@ -65,7 +66,7 @@ describe('LDAP', function () { it('should throw an error containing totp-required error when not running EE', async function () { // TODO this is not the right way to do it. We're doing this way for now just because we have separate CI jobs for EE and CE, // ideally we should have a single CI job that adds a license and runs both CE and EE tests. - if (process.env.IS_EE) { + if (IS_EE) { this.skip(); } await request @@ -80,7 +81,7 @@ describe('LDAP', function () { }); it('should throw an error of LDAP disabled when running EE', async function () { - if (!process.env.IS_EE) { + if (!IS_EE) { this.skip(); } await request @@ -144,9 +145,7 @@ describe('LDAP', function () { }); }); - (process.env.IS_EE ? describe : describe.skip)('configured LDAP integration', function () { - this.timeout(30_000); - + (IS_EE ? describe : describe.skip)('configured LDAP integration', () => { let originalSettings: Setting[] | undefined; before(async () => { @@ -215,16 +214,16 @@ describe('LDAP', function () { }); expect(loginResponse.body.data.me.emails.map(({ address }: { address: string }) => address)).to.include('ldap.e2e@space.air'); - const avatarResponse = await request.get(`/avatar/${ldapUsername}`).expect('Content-Type', 'image/jpeg').expect(200); + const avatarResponse = await request.get(`/avatar/${ldapUsername}`).buffer(true).expect('Content-Type', 'image/jpeg').expect(200); const metadata = await sharp(avatarResponse.body as Buffer).metadata(); const stats = await sharp(avatarResponse.body as Buffer).stats(); + const avatarChannels = stats.channels.slice(0, 3); - expect(metadata).to.include({ format: 'jpeg', width: 200, height: 200 }); - expect(stats.channels.slice(0, 3).map(({ min, max }) => ({ min, max }))).to.deep.equal([ - { min: 0, max: 0 }, - { min: 102, max: 102 }, - { min: 203, max: 203 }, - ]); + expect(metadata.format).to.equal('jpeg'); + expect(metadata.width).to.equal(200); + expect(metadata.height).to.equal(200); + expect(avatarChannels.map(({ min }) => min)).to.deep.equal(ldapAvatarRgb); + expect(avatarChannels.map(({ max }) => max)).to.deep.equal(ldapAvatarRgb); }); }); }); From 0f99e23e35c1a87b86a54b9f7e01669da1b01857 Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Wed, 2 Sep 2026 13:56:08 -0300 Subject: [PATCH 12/13] test: reduce LDAP connection retries --- apps/meteor/tests/end-to-end/api/LDAP.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/end-to-end/api/LDAP.ts b/apps/meteor/tests/end-to-end/api/LDAP.ts index 65752b078fd34..c30880fa12440 100644 --- a/apps/meteor/tests/end-to-end/api/LDAP.ts +++ b/apps/meteor/tests/end-to-end/api/LDAP.ts @@ -55,7 +55,7 @@ const waitForLdapConnection = () => expect(res.body).to.have.property('success', true); }); }, - { retries: 20, delayMs: 250 }, + { delayMs: 1_000 }, ); describe('LDAP', function () { From 0390f4183bf40aa2d165a1d57087ffe129f19c05 Mon Sep 17 00:00:00 2001 From: Jessica Schelly Souza Date: Wed, 2 Sep 2026 14:00:14 -0300 Subject: [PATCH 13/13] test: align LDAP cleanup with API suite --- apps/meteor/tests/end-to-end/api/LDAP.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/LDAP.ts b/apps/meteor/tests/end-to-end/api/LDAP.ts index c30880fa12440..52a16c40907ae 100644 --- a/apps/meteor/tests/end-to-end/api/LDAP.ts +++ b/apps/meteor/tests/end-to-end/api/LDAP.ts @@ -162,17 +162,20 @@ describe('LDAP', function () { }); after(async () => { - await deleteLdapUser(); - if (!originalSettings) { + await deleteLdapUser(); return; } const originalEnabled = Boolean(originalSettings.find(({ _id }) => _id === 'LDAP_Enable')?.value); - await applyLdapSettings( - originalSettings.filter(({ _id }) => _id !== 'LDAP_Enable'), - originalEnabled, - ); + + await Promise.all([ + deleteLdapUser(), + applyLdapSettings( + originalSettings.filter(({ _id }) => _id !== 'LDAP_Enable'), + originalEnabled, + ), + ]); }); it('should connect to LDAP successfully', async () => {