diff --git a/backend/tests/employees/test_department_role_ldap_sync.py b/backend/tests/employees/test_department_role_ldap_sync.py index 194257cc..cc297aad 100644 --- a/backend/tests/employees/test_department_role_ldap_sync.py +++ b/backend/tests/employees/test_department_role_ldap_sync.py @@ -2,9 +2,12 @@ from types import SimpleNamespace from unittest.mock import MagicMock, patch import pytest +from django.contrib.auth import get_user_model from employees.ldap.services.department_service import DepartmentService -from employees.models import Department, DepartmentRole +from employees.models import Department, DepartmentRole, EmployeeDepartment, LdapSyncState + +User = get_user_model() @pytest.mark.django_db @@ -42,3 +45,58 @@ def test_sync_role_state_nests_role_group_into_department_group(): assert fake_group.member == [role_group_dn] fake_group.save.assert_called_once() + + +@pytest.mark.django_db +def test_reconcile_department_group_keeps_members_and_multiple_role_groups(): + dept = Department.objects.create(name="LDAP Dept Aggregate") + role1 = DepartmentRole.objects.create( + department=dept, + name="Reviewer", + ldap_group_dn="CN=ROLE_Reviewer,OU=LDAP Dept Aggregate,OU=Departments,DC=example,DC=local", + ) + role2 = DepartmentRole.objects.create( + department=dept, + name="Approver", + ldap_group_dn="CN=ROLE_Approver,OU=LDAP Dept Aggregate,OU=Departments,DC=example,DC=local", + ) + employee = User.objects.create( + email="dept-role-aggregate@example.com", + phone_number="+79990009999", + first_name="Role", + last_name="Aggregate", + is_active=True, + email_verified=True, + ) + employee.set_password("pass") + employee.save() + EmployeeDepartment.objects.create( + employee=employee, + department=dept, + is_active=True, + ) + LdapSyncState.objects.create( + model="employee", + object_pk=str(employee.pk), + ldap_dn="CN=Test User,OU=LDAP Dept Aggregate,OU=Departments,DC=example,DC=local", + ) + fake_group = SimpleNamespace(member=[], save=MagicMock()) + + with patch.object( + DepartmentService, + "_ensure_department_group", + return_value="CN=DEP_LDAP Dept Aggregate,OU=LDAP Dept Aggregate,OU=Departments,DC=example,DC=local", + ), patch( + "employees.ldap.services.department_service.LdapOrganizationalUnitGroup.objects.get", + return_value=fake_group, + ): + DepartmentService()._reconcile_department_group( + dept, + "OU=LDAP Dept Aggregate,OU=Departments,DC=example,DC=local", + ) + + assert fake_group.member[0] == ( + "CN=Test User,OU=LDAP Dept Aggregate,OU=Departments,DC=example,DC=local" + ) + assert set(fake_group.member[1:]) == {role1.ldap_group_dn, role2.ldap_group_dn} + fake_group.save.assert_called_once() diff --git a/backend/tests/manual/ldap_integration/test_entities_roundtrip.py b/backend/tests/manual/ldap_integration/test_entities_roundtrip.py index f74c5dd5..d399e206 100644 --- a/backend/tests/manual/ldap_integration/test_entities_roundtrip.py +++ b/backend/tests/manual/ldap_integration/test_entities_roundtrip.py @@ -347,3 +347,51 @@ def test_department_role_service_roundtrip_syncs_live_ldap( service.delete_role(updated_role) assert not DepartmentRole.objects.filter(pk=updated_role.pk).exists() assert not LdapOrganizationalUnitGroup.objects.filter(dn=updated_role.ldap_group_dn).exists() + + +def test_department_role_api_roundtrip_syncs_live_ldap_and_dep_group( + ensure_live_ldap, + superuser_client, + create_ldap_user, + ldap_cleanup, + unique_name, +): + employee = create_ldap_user(first_name="ApiRole", last_name="Member") + department = _bootstrap_department_for_role_tests(unique_name("qa-role-api")) + ldap_cleanup(_department_dn(department)) + + create_role_response = superuser_client.post( + "/api/v1/department-roles/", + {"department": department.pk, "name": "Operator API"}, + format="json", + ) + assert create_role_response.status_code == 201, create_role_response.content + + role = DepartmentRole.objects.get(pk=create_role_response.json()["id"]) + ldap_cleanup(role.ldap_group_dn) + + dept_group = LdapOrganizationalUnitGroup.objects.get(dn=department.ldap_group_dn) + dept_group_members = {_norm_dn(dn) for dn in list(dept_group.member or [])} + assert _norm_dn(role.ldap_group_dn) in dept_group_members + + assign_response = superuser_client.post( + f"/api/v1/department-roles/{role.pk}/assign/", + {"employee_id": employee.pk}, + format="json", + ) + assert assign_response.status_code == 201, assign_response.content + + role_group = LdapOrganizationalUnitGroup.objects.get(dn=role.ldap_group_dn) + role_member_dns = {_norm_dn(dn) for dn in list(role_group.member or [])} + assert _norm_dn(_employee_dn(employee)) in role_member_dns + + revoke_response = superuser_client.post( + f"/api/v1/department-roles/{role.pk}/revoke/", + {"employee_id": employee.pk}, + format="json", + ) + assert revoke_response.status_code == 204, revoke_response.content + + role_group = LdapOrganizationalUnitGroup.objects.get(dn=role.ldap_group_dn) + revoked_member_dns = {_norm_dn(dn) for dn in list(role_group.member or [])} + assert _norm_dn(_employee_dn(employee)) not in revoked_member_dns