import uuid from types import SimpleNamespace from unittest.mock import AsyncMock import pytest from domains.groups.models import GroupMemberRole, GroupType from domains.groups import service @pytest.mark.asyncio async def test_create_user_group_creates_private_group_and_owner_membership(monkeypatch): created_group = None added_membership = None async def fake_create_group(db, group): nonlocal created_group created_group = group group.id = uuid.uuid4() return group async def fake_add_group_member(db, membership): nonlocal added_membership added_membership = membership return membership monkeypatch.setattr(service, "create_group", fake_create_group) monkeypatch.setattr(service, "add_group_member", fake_add_group_member) owner_id = uuid.uuid4() group = await service.create_user_group(object(), "team-alpha", owner_id) assert group.type == GroupType.PRIVATE assert group.owner_id == owner_id assert created_group is group assert added_membership is not None assert added_membership.user_id == owner_id assert added_membership.role == GroupMemberRole.MANAGER @pytest.mark.asyncio async def test_create_admin_group_creates_public_group_without_membership(monkeypatch): create_group_mock = AsyncMock(side_effect=lambda db, group: setattr(group, "id", uuid.uuid4()) or group) add_group_member_mock = AsyncMock() monkeypatch.setattr(service, "create_group", create_group_mock) monkeypatch.setattr(service, "add_group_member", add_group_member_mock) owner_id = uuid.uuid4() group = await service.create_admin_group(object(), "ops-room", owner_id) assert group.type == GroupType.PUBLIC assert group.owner_id == owner_id add_group_member_mock.assert_not_awaited() @pytest.mark.asyncio async def test_list_group_members_api_returns_only_real_members(monkeypatch): group_id = uuid.uuid4() member_id = uuid.uuid4() members_data = [ ( SimpleNamespace( user_id=member_id, role=GroupMemberRole.MEMBER, ), "neda-user", ) ] monkeypatch.setattr( service, "get_group_members_with_details", AsyncMock(return_value=members_data), ) monkeypatch.setattr( service, "list_online_users", AsyncMock(return_value=[str(member_id)]), ) response = await service.list_group_members_api(object(), group_id) assert response == [ { "user_id": member_id, "username": "neda-user", "role": GroupMemberRole.MEMBER, "is_online": True, } ] def test_group_routes_are_namespaced(): from main import app route_map = { (route.path, method) for route in app.routes if getattr(route, "methods", None) for method in route.methods } assert ("/groups/", "POST") in route_map assert ("/groups/{group_id}/members/{user_id}", "DELETE") in route_map assert ("/admin/groups/", "POST") in route_map assert ("/admin/groups/", "GET") in route_map assert ("/admin/groups/{group_id}/members", "GET") in route_map assert ("/admin/groups/{group_id}/members", "POST") in route_map @pytest.mark.asyncio async def test_remove_member_from_group_allows_manager_to_remove_member(monkeypatch): db = object() group_id = uuid.uuid4() manager_id = uuid.uuid4() member_id = uuid.uuid4() group = SimpleNamespace(owner_id=uuid.uuid4()) removed = AsyncMock() async def fake_get_group_member(db, requested_group_id, requested_user_id): if requested_user_id == manager_id: return SimpleNamespace(role=GroupMemberRole.MANAGER) if requested_user_id == member_id: return SimpleNamespace(role=GroupMemberRole.MEMBER) return None monkeypatch.setattr(service, "get_group_by_id", AsyncMock(return_value=group)) monkeypatch.setattr(service, "get_group_member", fake_get_group_member) monkeypatch.setattr(service, "delete_group_member", removed) requester = SimpleNamespace(id=manager_id, is_admin=False) await service.remove_member_from_group(db, group_id, member_id, requester) removed.assert_awaited_once_with(db, group_id, member_id) @pytest.mark.asyncio async def test_remove_member_from_group_rejects_non_manager(monkeypatch): group_id = uuid.uuid4() requester_id = uuid.uuid4() member_id = uuid.uuid4() group = SimpleNamespace(owner_id=uuid.uuid4()) monkeypatch.setattr(service, "get_group_by_id", AsyncMock(return_value=group)) monkeypatch.setattr( service, "get_group_member", AsyncMock(return_value=SimpleNamespace(role=GroupMemberRole.MEMBER)), ) monkeypatch.setattr(service, "delete_group_member", AsyncMock()) requester = SimpleNamespace(id=requester_id, is_admin=False) with pytest.raises(PermissionError, match="دسترسی لازم را ندارید"): await service.remove_member_from_group(object(), group_id, member_id, requester) @pytest.mark.asyncio async def test_remove_member_from_group_rejects_owner_removal(monkeypatch): group_id = uuid.uuid4() manager_id = uuid.uuid4() owner_id = uuid.uuid4() group = SimpleNamespace(owner_id=owner_id) async def fake_get_group_member(db, requested_group_id, requested_user_id): if requested_user_id == manager_id: return SimpleNamespace(role=GroupMemberRole.MANAGER) return SimpleNamespace(role=GroupMemberRole.MEMBER) monkeypatch.setattr(service, "get_group_by_id", AsyncMock(return_value=group)) monkeypatch.setattr(service, "get_group_member", fake_get_group_member) monkeypatch.setattr(service, "delete_group_member", AsyncMock()) requester = SimpleNamespace(id=manager_id, is_admin=False) with pytest.raises(ValueError, match="حذف سازنده گروه مجاز نیست"): await service.remove_member_from_group(object(), group_id, owner_id, requester) @pytest.mark.asyncio async def test_remove_member_from_group_rejects_missing_target_membership(monkeypatch): group_id = uuid.uuid4() manager_id = uuid.uuid4() member_id = uuid.uuid4() group = SimpleNamespace(owner_id=uuid.uuid4()) async def fake_get_group_member(db, requested_group_id, requested_user_id): if requested_user_id == manager_id: return SimpleNamespace(role=GroupMemberRole.MANAGER) return None monkeypatch.setattr(service, "get_group_by_id", AsyncMock(return_value=group)) monkeypatch.setattr(service, "get_group_member", fake_get_group_member) monkeypatch.setattr(service, "delete_group_member", AsyncMock()) requester = SimpleNamespace(id=manager_id, is_admin=False) with pytest.raises(ValueError, match="کاربر عضو این گروه نیست"): await service.remove_member_from_group(object(), group_id, member_id, requester) @pytest.mark.asyncio async def test_remove_member_from_group_rejects_manager_target_for_non_admin(monkeypatch): group_id = uuid.uuid4() manager_id = uuid.uuid4() other_manager_id = uuid.uuid4() group = SimpleNamespace(owner_id=uuid.uuid4()) async def fake_get_group_member(db, requested_group_id, requested_user_id): if requested_user_id == manager_id: return SimpleNamespace(role=GroupMemberRole.MANAGER) if requested_user_id == other_manager_id: return SimpleNamespace(role=GroupMemberRole.MANAGER) return None monkeypatch.setattr(service, "get_group_by_id", AsyncMock(return_value=group)) monkeypatch.setattr(service, "get_group_member", fake_get_group_member) monkeypatch.setattr(service, "delete_group_member", AsyncMock()) requester = SimpleNamespace(id=manager_id, is_admin=False) with pytest.raises(ValueError, match="حذف مدیر گروه مجاز نیست"): await service.remove_member_from_group(object(), group_id, other_manager_id, requester) @pytest.mark.asyncio async def test_remove_member_from_group_allows_admin_to_remove_manager(monkeypatch): db = object() group_id = uuid.uuid4() target_manager_id = uuid.uuid4() group = SimpleNamespace(owner_id=uuid.uuid4()) removed = AsyncMock() monkeypatch.setattr(service, "get_group_by_id", AsyncMock(return_value=group)) monkeypatch.setattr( service, "get_group_member", AsyncMock(return_value=SimpleNamespace(role=GroupMemberRole.MANAGER)), ) monkeypatch.setattr(service, "delete_group_member", removed) requester = SimpleNamespace(id=uuid.uuid4(), is_admin=True) await service.remove_member_from_group(db, group_id, target_manager_id, requester) removed.assert_awaited_once_with(db, group_id, target_manager_id)