From 10a7a66a413597bac2708effb1827230b3c6b347 Mon Sep 17 00:00:00 2001 From: Jiang Date: Thu, 10 Sep 2026 14:49:53 +0800 Subject: [PATCH] fix(metadata): hide inactive projects from user list Project selection previously relied only on membership, so inactive projects remained visible. Filter at the repository boundary and add regression coverage. --- .../repositories/metadata_repository.py | 5 +- .../unit/test_metadata_repository_projects.py | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_metadata_repository_projects.py diff --git a/app/infra/db/metadb/repositories/metadata_repository.py b/app/infra/db/metadb/repositories/metadata_repository.py index 7345c82..f14e1d2 100644 --- a/app/infra/db/metadb/repositories/metadata_repository.py +++ b/app/infra/db/metadb/repositories/metadata_repository.py @@ -465,7 +465,10 @@ class MetadataRepository: models.UserProjectMembership, models.UserProjectMembership.project_id == models.Project.id, ) - .where(models.UserProjectMembership.user_id == user_id) + .where( + models.UserProjectMembership.user_id == user_id, + models.Project.status == "active", + ) .order_by(models.Project.name) ) result = await self.session.execute(stmt) diff --git a/tests/unit/test_metadata_repository_projects.py b/tests/unit/test_metadata_repository_projects.py new file mode 100644 index 0000000..0633843 --- /dev/null +++ b/tests/unit/test_metadata_repository_projects.py @@ -0,0 +1,51 @@ +from types import SimpleNamespace +from unittest.mock import AsyncMock +from uuid import uuid4 + +import pytest +from sqlalchemy.dialects import postgresql + +from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository + + +@pytest.fixture +def anyio_backend(): + return "asyncio" + + +class _ProjectListResult: + def __init__(self, rows): + self._rows = rows + + def all(self): + return self._rows + + +@pytest.mark.anyio +async def test_list_projects_for_user_only_queries_active_projects(): + user_id = uuid4() + project = SimpleNamespace( + id=uuid4(), + name="Active Project", + code="active-project", + description=None, + gs_workspace="active-project", + map_extent=None, + status="active", + ) + session = SimpleNamespace( + execute=AsyncMock(return_value=_ProjectListResult([(project, "member")])) + ) + + projects = await MetadataRepository(session).list_projects_for_user(user_id) + + statement = session.execute.await_args.args[0] + sql = str( + statement.compile( + dialect=postgresql.dialect(), + compile_kwargs={"literal_binds": True}, + ) + ) + assert "user_project_membership.user_id" in sql + assert "projects.status = 'active'" in sql + assert [item.code for item in projects] == ["active-project"]