From f9c358e7b78e17fc862cf96bd6ce084f322c2e04 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Thu, 15 Jan 2026 18:31:31 +0300 Subject: [PATCH 1/6] refactor: tests --- tests/conftest.py | 29 +++++------ tests/search_request_datasets.py | 4 -- .../test_main/test_router/conftest.py | 49 +++++++++++++++++++ .../test_main/test_router/test_delete.py | 1 + .../test_main/test_router/test_modify.py | 1 + .../test_main/test_router/test_modify_dn.py | 1 + .../test_main/test_router/test_search.py | 2 - tests/test_ldap/test_roles/test_search.py | 2 - 8 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 tests/test_api/test_main/test_router/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py index 12ba86e40..546687f24 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -154,9 +154,9 @@ from ldap_protocol.server import PoolClientHandler from ldap_protocol.session_storage import RedisSessionStorage, SessionStorage from ldap_protocol.session_storage.repository import SessionRepository -from ldap_protocol.utils.queries import get_base_directories, get_user +from ldap_protocol.utils.queries import get_user from password_utils import PasswordUtils -from tests.constants import TEST_DATA, TEST_SYSTEM_ADMIN_DATA +from tests.constants import TEST_DATA class TestProvider(Provider): @@ -407,15 +407,16 @@ async def get_session( self._cached_session = async_session self._session_id = uuid.uuid4() - yield async_session + try: + yield async_session + finally: + self._cached_session = None + self._session_id = None - self._cached_session = None - self._session_id = None - - async_session.expire_all() - await trans.rollback() - await async_session.close() - await connection.close() + async_session.expire_all() + await trans.rollback() + await async_session.close() + await connection.close() @provide(scope=Scope.SESSION) async def get_ldap_session( @@ -989,14 +990,6 @@ async def setup_session( is_system=False, ) - domain = (await get_base_directories(session))[0] - await setup_gateway.create_dir( - data=TEST_SYSTEM_ADMIN_DATA, - is_system=True, - domain=domain, - parent=domain, - ) - # NOTE: after setup environment we need base DN to be created await password_use_cases.create_default_domain_policy() diff --git a/tests/search_request_datasets.py b/tests/search_request_datasets.py index fa36f135c..77557bae9 100644 --- a/tests/search_request_datasets.py +++ b/tests/search_request_datasets.py @@ -50,7 +50,6 @@ { "filter": f"(useraccountcontrol:1.2.840.113556.1.4.803:={UserAccountControlFlag.NORMAL_ACCOUNT})", # noqa: E501 "objects": [ - "cn=System Administrator,dc=md,dc=test", "cn=user0,cn=users,dc=md,dc=test", "cn=user_admin,cn=users,dc=md,dc=test", "cn=user_admin_for_roles,cn=users,dc=md,dc=test", @@ -84,7 +83,6 @@ { "filter": f"(!(userAccountControl:1.2.840.113556.1.4.803:={UserAccountControlFlag.ACCOUNTDISABLE}))", # noqa: E501 "objects": [ - "cn=System Administrator,dc=md,dc=test", "cn=user0,cn=users,dc=md,dc=test", "cn=user_admin,cn=users,dc=md,dc=test", "cn=user_admin_for_roles,cn=users,dc=md,dc=test", @@ -106,7 +104,6 @@ + UserAccountControlFlag.NORMAL_ACCOUNT })", "objects": [ - "cn=System Administrator,dc=md,dc=test", "cn=user0,cn=users,dc=md,dc=test", "cn=user_admin,cn=users,dc=md,dc=test", "cn=user_admin_for_roles,cn=users,dc=md,dc=test", @@ -127,7 +124,6 @@ { "filter": f"(!(userAccountControl:1.2.840.113556.1.4.804:={UserAccountControlFlag.ACCOUNTDISABLE}))", # noqa: E501 "objects": [ - "cn=System Administrator,dc=md,dc=test", "cn=user0,cn=users,dc=md,dc=test", "cn=user_admin,cn=users,dc=md,dc=test", "cn=user_admin_for_roles,cn=users,dc=md,dc=test", diff --git a/tests/test_api/test_main/test_router/conftest.py b/tests/test_api/test_main/test_router/conftest.py new file mode 100644 index 000000000..49d0338c1 --- /dev/null +++ b/tests/test_api/test_main/test_router/conftest.py @@ -0,0 +1,49 @@ +"""Test main config. + +Copyright (c) 2024 MultiFactor +License: https://github.com/MultiDirectoryLab/MultiDirectory/blob/main/LICENSE +""" + +import pytest_asyncio +from sqlalchemy.ext.asyncio import AsyncSession + +from ldap_protocol.auth.setup_gateway import SetupGateway +from ldap_protocol.ldap_schema.attribute_value_validator import ( + AttributeValueValidator, +) +from ldap_protocol.ldap_schema.entity_type_dao import EntityTypeDAO +from ldap_protocol.ldap_schema.object_class_dao import ObjectClassDAO +from ldap_protocol.utils.queries import get_base_directories +from password_utils import PasswordUtils +from tests.constants import TEST_SYSTEM_ADMIN_DATA + + +@pytest_asyncio.fixture(scope="function") +async def add_system_administrator( + session: AsyncSession, + password_utils: PasswordUtils, + setup_session: None, # noqa: ARG001 +) -> None: + """Get session and acquire after completion.""" + object_class_dao = ObjectClassDAO(session) + attribute_value_validator = AttributeValueValidator() + entity_type_dao = EntityTypeDAO( + session, + object_class_dao=object_class_dao, + attribute_value_validator=attribute_value_validator, + ) + + setup_gateway = SetupGateway( + session, + password_utils, + entity_type_dao, + attribute_value_validator=attribute_value_validator, + ) + + domain = (await get_base_directories(session))[0] + await setup_gateway.create_dir( + data=TEST_SYSTEM_ADMIN_DATA, + is_system=True, + domain=domain, + parent=domain, + ) diff --git a/tests/test_api/test_main/test_router/test_delete.py b/tests/test_api/test_main/test_router/test_delete.py index 6b359237c..0aa777cec 100644 --- a/tests/test_api/test_main/test_router/test_delete.py +++ b/tests/test_api/test_main/test_router/test_delete.py @@ -31,6 +31,7 @@ async def test_api_correct_delete(http_client: AsyncClient) -> None: @pytest.mark.asyncio @pytest.mark.usefixtures("setup_session") @pytest.mark.usefixtures("session") +@pytest.mark.usefixtures("add_system_administrator") async def test_api_cant_delete_system_directory( http_client: AsyncClient, ) -> None: diff --git a/tests/test_api/test_main/test_router/test_modify.py b/tests/test_api/test_main/test_router/test_modify.py index 9cb9d4eef..8bb9b823b 100644 --- a/tests/test_api/test_main/test_router/test_modify.py +++ b/tests/test_api/test_main/test_router/test_modify.py @@ -257,6 +257,7 @@ async def test_api_modify_non_exist_object(http_client: AsyncClient) -> None: @pytest.mark.asyncio @pytest.mark.usefixtures("session") +@pytest.mark.usefixtures("add_system_administrator") async def test_api_cant_modify_system_directory( http_client: AsyncClient, ) -> None: diff --git a/tests/test_api/test_main/test_router/test_modify_dn.py b/tests/test_api/test_main/test_router/test_modify_dn.py index d511fbd20..fe27de21c 100644 --- a/tests/test_api/test_main/test_router/test_modify_dn.py +++ b/tests/test_api/test_main/test_router/test_modify_dn.py @@ -456,6 +456,7 @@ async def test_api_update_dn_non_exist_superior( @pytest.mark.asyncio @pytest.mark.usefixtures("setup_session") @pytest.mark.usefixtures("session") +@pytest.mark.usefixtures("add_system_administrator") async def test_api_cant_update_system_directory( http_client: AsyncClient, ) -> None: diff --git a/tests/test_api/test_main/test_router/test_search.py b/tests/test_api/test_main/test_router/test_search.py index 911e4b344..34a9377aa 100644 --- a/tests/test_api/test_main/test_router/test_search.py +++ b/tests/test_api/test_main/test_router/test_search.py @@ -96,7 +96,6 @@ async def test_api_search(http_client: AsyncClient) -> None: assert response["resultCode"] == LDAPCodes.SUCCESS sub_dirs = { - "cn=System Administrator,dc=md,dc=test", "cn=groups,dc=md,dc=test", "cn=users,dc=md,dc=test", "ou=testModifyDn1,dc=md,dc=test", @@ -281,7 +280,6 @@ async def test_api_search_recursive_memberof(http_client: AsyncClient) -> None: """Test api search.""" group = "cn=domain admins,cn=groups,dc=md,dc=test" members = [ - "cn=System Administrator,dc=md,dc=test", "cn=developers,cn=groups,dc=md,dc=test", "cn=user0,cn=users,dc=md,dc=test", "cn=user_admin,cn=users,dc=md,dc=test", diff --git a/tests/test_ldap/test_roles/test_search.py b/tests/test_ldap/test_roles/test_search.py index 7965a3fe0..a20e8f0dd 100644 --- a/tests/test_ldap/test_roles/test_search.py +++ b/tests/test_ldap/test_roles/test_search.py @@ -102,7 +102,6 @@ async def test_role_search_3( creds=creds, search_base=BASE_DN, expected_dn=[ - "dn: cn=System Administrator,dc=md,dc=test", "dn: cn=groups,dc=md,dc=test", "dn: cn=users,dc=md,dc=test", "dn: cn=user_non_admin,cn=users,dc=md,dc=test", @@ -190,7 +189,6 @@ async def test_role_search_5( creds=creds, search_base=BASE_DN, expected_dn=[ - "dn: cn=System Administrator,dc=md,dc=test", "dn: cn=user1,cn=moscow,cn=russia,cn=users,dc=md,dc=test", "dn: cn=user_non_admin,cn=users,dc=md,dc=test", "dn: cn=user_admin_for_roles,cn=users,dc=md,dc=test", From f689d92e3f14fe111d4083ee367b1722938a9a1f Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Thu, 15 Jan 2026 19:16:37 +0300 Subject: [PATCH 2/6] fix: tests: disable warnings --- docker-compose.test.yml | 2 +- pyproject.toml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 221a4e4bf..840eeb02b 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -20,7 +20,7 @@ services: POSTGRES_HOST: postgres # PYTHONTRACEMALLOC: 1 PYTHONDONTWRITEBYTECODE: 1 - command: sh -c "python -B -m pytest -n auto -x -W ignore::DeprecationWarning -vv" + command: sh -c "python -B -m pytest -n auto -x -W ignore::DeprecationWarning -W ignore::coverage.exceptions.CoverageWarning -vv" tty: true postgres: diff --git a/pyproject.toml b/pyproject.toml index 45573c966..f7adf0e26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,6 +92,9 @@ show_missing = true [tool.coverage.run] concurrency = ["thread", "gevent"] +omit = [ + "*/__dishka_factory_*", +] # RUFF # Ruff is a linter, not a type checker. From 77c0e3eec4fddcaa731174b33b99d603dbeb0012 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Thu, 15 Jan 2026 19:23:04 +0300 Subject: [PATCH 3/6] fix --- tests/conftest.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 546687f24..6a206ce3e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -407,16 +407,15 @@ async def get_session( self._cached_session = async_session self._session_id = uuid.uuid4() - try: - yield async_session - finally: - self._cached_session = None - self._session_id = None - - async_session.expire_all() - await trans.rollback() - await async_session.close() - await connection.close() + yield async_session + + self._cached_session = None + self._session_id = None + + async_session.expire_all() + await trans.rollback() + await async_session.close() + await connection.close() @provide(scope=Scope.SESSION) async def get_ldap_session( From 4c506eb9eb8b7f6fe47c35a7b57002a857404759 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Thu, 15 Jan 2026 19:39:40 +0300 Subject: [PATCH 4/6] fix: docstring --- tests/test_api/test_main/test_router/conftest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_api/test_main/test_router/conftest.py b/tests/test_api/test_main/test_router/conftest.py index 49d0338c1..5ec37b884 100644 --- a/tests/test_api/test_main/test_router/conftest.py +++ b/tests/test_api/test_main/test_router/conftest.py @@ -1,6 +1,6 @@ -"""Test main config. +"""Test router config. -Copyright (c) 2024 MultiFactor +Copyright (c) 2026 MultiFactor License: https://github.com/MultiDirectoryLab/MultiDirectory/blob/main/LICENSE """ @@ -24,7 +24,7 @@ async def add_system_administrator( password_utils: PasswordUtils, setup_session: None, # noqa: ARG001 ) -> None: - """Get session and acquire after completion.""" + """Create system administrator user for tests that require it.""" object_class_dao = ObjectClassDAO(session) attribute_value_validator = AttributeValueValidator() entity_type_dao = EntityTypeDAO( From 25cf9e3479ee12c2f12b20fec9f3d3d6383883fd Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Fri, 16 Jan 2026 11:26:50 +0300 Subject: [PATCH 5/6] fix: allow modify system directories task_796 --- app/ldap_protocol/ldap_requests/modify.py | 8 +---- .../test_main/test_router/test_modify.py | 29 ------------------- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/app/ldap_protocol/ldap_requests/modify.py b/app/ldap_protocol/ldap_requests/modify.py index 8b192671f..1d79fcfd6 100644 --- a/app/ldap_protocol/ldap_requests/modify.py +++ b/app/ldap_protocol/ldap_requests/modify.py @@ -149,7 +149,7 @@ async def _update_password_expiration( now = datetime.now(timezone.utc) + timedelta(days=max_age_days) change.modification.vals[0] = now.strftime("%Y%m%d%H%M%SZ") - async def handle( # noqa: C901 + async def handle( self, ctx: LDAPModifyRequestContext, ) -> AsyncGenerator[ModifyResponse, None]: @@ -184,12 +184,6 @@ async def handle( # noqa: C901 yield ModifyResponse(result_code=LDAPCodes.NO_SUCH_OBJECT) return - if directory.is_system: - yield ModifyResponse( - result_code=LDAPCodes.UNWILLING_TO_PERFORM, - ) - return - can_modify = ctx.access_manager.check_modify_access( changes=self.changes, aces=directory.access_control_entries, diff --git a/tests/test_api/test_main/test_router/test_modify.py b/tests/test_api/test_main/test_router/test_modify.py index 8bb9b823b..3e46e879d 100644 --- a/tests/test_api/test_main/test_router/test_modify.py +++ b/tests/test_api/test_main/test_router/test_modify.py @@ -255,35 +255,6 @@ async def test_api_modify_non_exist_object(http_client: AsyncClient) -> None: assert data.get("resultCode") == LDAPCodes.NO_SUCH_OBJECT -@pytest.mark.asyncio -@pytest.mark.usefixtures("session") -@pytest.mark.usefixtures("add_system_administrator") -async def test_api_cant_modify_system_directory( - http_client: AsyncClient, -) -> None: - """Test API for modify system directory.""" - response = await http_client.patch( - "/entry/update", - json={ - "object": "cn=System Administrator,dc=md,dc=test", - "changes": [ - { - "operation": Operation.REPLACE, - "modification": { - "type": "name", - "vals": ["new_test"], - }, - }, - ], - }, - ) - - data = response.json() - - assert isinstance(data, dict) - assert data.get("resultCode") == LDAPCodes.UNWILLING_TO_PERFORM - - @pytest.mark.asyncio @pytest.mark.usefixtures("setup_session") @pytest.mark.usefixtures("session") From 65267fb5b9aa1282558035091cf6d79ba3f13b72 Mon Sep 17 00:00:00 2001 From: Milov Dmitriy Date: Fri, 16 Jan 2026 11:34:54 +0300 Subject: [PATCH 6/6] fix: ignore CoverageWarning into tests --- docker-compose.remote.test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.remote.test.yml b/docker-compose.remote.test.yml index 96b9c8ce1..bd5659b02 100644 --- a/docker-compose.remote.test.yml +++ b/docker-compose.remote.test.yml @@ -9,7 +9,7 @@ services: POSTGRES_PASSWORD: password123 SECRET_KEY: 6a0452ae20cab4e21b6e9d18fa4b7bf397dd66ec3968b2d7407694278fd84cce POSTGRES_HOST: postgres - command: sh -c "python -m pytest -n auto -W ignore::DeprecationWarning -vv" + command: sh -c "python -m pytest -n auto -W ignore::DeprecationWarning -W ignore::coverage.exceptions.CoverageWarning -vv" postgres: image: postgres:16