From 720f8e6b7c0858c557d7cccd775707bf074df1a0 Mon Sep 17 00:00:00 2001 From: roai_linux Date: Sun, 29 Mar 2026 16:35:10 +0330 Subject: [PATCH] fix: change livkit udp ports and add ttl for livekit token --- Back/core/rate_limit.py | 2 +- Back/docker-compose.yml | 2 +- Back/integrations/livekit/token_service.py | 3 +- Back/livekit.yaml | 2 +- Back/tests/test_groups_service.py | 138 +++++++++++++++++++++ 5 files changed, 143 insertions(+), 4 deletions(-) diff --git a/Back/core/rate_limit.py b/Back/core/rate_limit.py index 97e6f62..44890be 100644 --- a/Back/core/rate_limit.py +++ b/Back/core/rate_limit.py @@ -38,5 +38,5 @@ class RateLimiter: # But Starlette's HTTPException is also handled by FastAPI for WebSockets by closing the connection. raise HTTPException( status_code=status.HTTP_429_TOO_MANY_REQUESTS, - detail="Too many requests. Please try again later." + detail="تعداد درخواست‌های شما از حد مجاز گذشته است. لطفاً بعداً دوباره تلاش کنید." ) \ No newline at end of file diff --git a/Back/docker-compose.yml b/Back/docker-compose.yml index 457081a..01285e1 100755 --- a/Back/docker-compose.yml +++ b/Back/docker-compose.yml @@ -42,7 +42,7 @@ services: ports: - "7780:7880" - "7781:7881" - - "51000-51100:51000-51100/udp" + - "51000-52000:51000-52000/udp" env_file: - .env volumes: diff --git a/Back/integrations/livekit/token_service.py b/Back/integrations/livekit/token_service.py index 90fadcf..4f9b104 100644 --- a/Back/integrations/livekit/token_service.py +++ b/Back/integrations/livekit/token_service.py @@ -1,7 +1,7 @@ from livekit import api from core.config import settings - +from datetime import timedelta def generate_join_token( user_id: str, @@ -15,6 +15,7 @@ def generate_join_token( ) token.with_identity(user_id) + token.with_ttl(timedelta(hours=2)) token.with_grants( api.VideoGrants( diff --git a/Back/livekit.yaml b/Back/livekit.yaml index 2a8b56b..76b8915 100644 --- a/Back/livekit.yaml +++ b/Back/livekit.yaml @@ -3,7 +3,7 @@ port: 7880 rtc: tcp_port: 7881 port_range_start: 51000 - port_range_end: 51100 + port_range_end: 52000 use_external_ip: false # node_ip: "94.183.170.121" diff --git a/Back/tests/test_groups_service.py b/Back/tests/test_groups_service.py index 7f7b499..9a41c17 100644 --- a/Back/tests/test_groups_service.py +++ b/Back/tests/test_groups_service.py @@ -102,7 +102,145 @@ def test_group_routes_are_namespaced(): } 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)