diff --git a/backend/tests/integration/test_friendship_routes.py b/backend/tests/integration/test_friendship_routes.py new file mode 100644 index 0000000..64cfe01 --- /dev/null +++ b/backend/tests/integration/test_friendship_routes.py @@ -0,0 +1,364 @@ +from __future__ import annotations + +from datetime import datetime, timezone +from typing import Callable +from uuid import UUID + +from fastapi import HTTPException +from fastapi.testclient import TestClient + +from app import app +from core.auth.models import CurrentUser +from v1.friendships.dependencies import get_friendship_service +from v1.friendships.schemas import ( + FriendRequestCreate, + FriendRequestResponse, + FriendResponse, + UserBasicInfo, +) +from v1.friendships.service import FriendshipService +from v1.users.dependencies import get_current_user + + +def _raise_unauthorized() -> CurrentUser: + raise HTTPException(status_code=401, detail="Unauthorized") + + +class FakeFriendshipService(FriendshipService): + def __init__(self) -> None: + pass + + async def send_request(self, request: FriendRequestCreate) -> FriendRequestResponse: + return FriendRequestResponse( + id=UUID("11111111-1111-1111-1111-111111111111"), + sender=UserBasicInfo(id="user-1", username="sender", avatar_url=None), + recipient=UserBasicInfo(id="user-2", username="recipient", avatar_url=None), + content=request.content, + status="pending", + created_at=datetime.now(timezone.utc), + ) + + async def accept_request(self, friendship_id: UUID) -> FriendRequestResponse: + return FriendRequestResponse( + id=friendship_id, + sender=UserBasicInfo(id="user-1", username="sender", avatar_url=None), + recipient=UserBasicInfo(id="user-2", username="recipient", avatar_url=None), + content="Hello!", + status="accepted", + created_at=datetime.now(timezone.utc), + ) + + async def decline_request(self, friendship_id: UUID) -> FriendRequestResponse: + return FriendRequestResponse( + id=friendship_id, + sender=UserBasicInfo(id="user-1", username="sender", avatar_url=None), + recipient=UserBasicInfo(id="user-2", username="recipient", avatar_url=None), + content="Hello!", + status="rejected", + created_at=datetime.now(timezone.utc), + ) + + async def cancel_request(self, friendship_id: UUID) -> FriendRequestResponse: + return FriendRequestResponse( + id=friendship_id, + sender=UserBasicInfo(id="user-1", username="sender", avatar_url=None), + recipient=UserBasicInfo(id="user-2", username="recipient", avatar_url=None), + content="Hello!", + status="canceled", + created_at=datetime.now(timezone.utc), + ) + + async def get_inbox(self) -> list[FriendRequestResponse]: + return [ + FriendRequestResponse( + id=UUID("11111111-1111-1111-1111-111111111111"), + sender=UserBasicInfo(id="user-1", username="sender", avatar_url=None), + recipient=UserBasicInfo( + id="user-2", username="recipient", avatar_url=None + ), + content="Hello!", + status="pending", + created_at=datetime.now(timezone.utc), + ) + ] + + async def get_outgoing_requests(self) -> list[FriendRequestResponse]: + return [ + FriendRequestResponse( + id=UUID("22222222-2222-2222-2222-222222222222"), + sender=UserBasicInfo(id="user-1", username="sender", avatar_url=None), + recipient=UserBasicInfo( + id="user-3", username="target", avatar_url=None + ), + content=None, + status="pending", + created_at=datetime.now(timezone.utc), + ) + ] + + async def get_friends_list(self) -> list[FriendResponse]: + return [ + FriendResponse( + id=UUID("33333333-3333-3333-3333-333333333333"), + friend=UserBasicInfo(id="user-2", username="friend", avatar_url=None), + status="active", + created_at=datetime.now(timezone.utc), + accepted_at=datetime.now(timezone.utc), + ) + ] + + async def remove_friend(self, friend_id: UUID) -> FriendResponse: + return FriendResponse( + id=UUID("33333333-3333-3333-3333-333333333333"), + friend=UserBasicInfo(id=str(friend_id), username="friend", avatar_url=None), + status="active", + created_at=datetime.now(timezone.utc), + accepted_at=datetime.now(timezone.utc), + ) + + +def _override_friendship_service( + service: FriendshipService, +) -> Callable[[], FriendshipService]: + def _get_service() -> FriendshipService: + return service + + return _get_service + + +def _get_fake_current_user() -> CurrentUser: + return CurrentUser( + id=UUID("00000000-0000-0000-0000-000000000001"), + email="test@example.com", + ) + + +class TestSendFriendRequest: + def test_send_friend_request_returns_201(self) -> None: + app.dependency_overrides[get_friendship_service] = _override_friendship_service( + FakeFriendshipService() + ) + app.dependency_overrides[get_current_user] = _get_fake_current_user + + client = TestClient(app) + try: + response = client.post( + "/api/v1/friends/requests", + json={ + "target_user_id": "22222222-2222-2222-2222-222222222222", + "content": "Hello!", + }, + ) + assert response.status_code == 201 + body = response.json() + assert body["status"] == "pending" + assert body["sender"]["username"] == "sender" + assert body["recipient"]["username"] == "recipient" + finally: + app.dependency_overrides = {} + + def test_send_friend_request_missing_target_returns_422(self) -> None: + app.dependency_overrides[get_friendship_service] = _override_friendship_service( + FakeFriendshipService() + ) + app.dependency_overrides[get_current_user] = _get_fake_current_user + + client = TestClient(app) + try: + response = client.post("/api/v1/friends/requests", json={}) + assert response.status_code == 422 + assert response.headers["content-type"].startswith( + "application/problem+json" + ) + finally: + app.dependency_overrides = {} + + def test_send_friend_request_invalid_uuid_returns_422(self) -> None: + app.dependency_overrides[get_friendship_service] = _override_friendship_service( + FakeFriendshipService() + ) + app.dependency_overrides[get_current_user] = _get_fake_current_user + + client = TestClient(app) + try: + response = client.post( + "/api/v1/friends/requests", + json={"target_user_id": "invalid-uuid"}, + ) + assert response.status_code == 422 + assert response.headers["content-type"].startswith( + "application/problem+json" + ) + finally: + app.dependency_overrides = {} + + +class TestGetInbox: + def test_get_inbox_returns_200(self) -> None: + app.dependency_overrides[get_friendship_service] = _override_friendship_service( + FakeFriendshipService() + ) + app.dependency_overrides[get_current_user] = _get_fake_current_user + + client = TestClient(app) + try: + response = client.get("/api/v1/friends/requests/inbox") + assert response.status_code == 200 + body = response.json() + assert isinstance(body, list) + assert len(body) == 1 + assert body[0]["status"] == "pending" + finally: + app.dependency_overrides = {} + + +class TestGetOutgoingRequests: + def test_get_outgoing_requests_returns_200(self) -> None: + app.dependency_overrides[get_friendship_service] = _override_friendship_service( + FakeFriendshipService() + ) + app.dependency_overrides[get_current_user] = _get_fake_current_user + + client = TestClient(app) + try: + response = client.get("/api/v1/friends/requests/outgoing") + assert response.status_code == 200 + body = response.json() + assert isinstance(body, list) + assert len(body) == 1 + assert body[0]["status"] == "pending" + finally: + app.dependency_overrides = {} + + +class TestAcceptFriendRequest: + def test_accept_friend_request_returns_200(self) -> None: + app.dependency_overrides[get_friendship_service] = _override_friendship_service( + FakeFriendshipService() + ) + app.dependency_overrides[get_current_user] = _get_fake_current_user + + client = TestClient(app) + try: + response = client.post( + "/api/v1/friends/requests/11111111-1111-1111-1111-111111111111/accept", + json={}, + ) + assert response.status_code == 200 + body = response.json() + assert body["status"] == "accepted" + finally: + app.dependency_overrides = {} + + +class TestDeclineFriendRequest: + def test_decline_friend_request_returns_200(self) -> None: + app.dependency_overrides[get_friendship_service] = _override_friendship_service( + FakeFriendshipService() + ) + app.dependency_overrides[get_current_user] = _get_fake_current_user + + client = TestClient(app) + try: + response = client.post( + "/api/v1/friends/requests/11111111-1111-1111-1111-111111111111/decline", + json={}, + ) + assert response.status_code == 200 + body = response.json() + assert body["status"] == "rejected" + finally: + app.dependency_overrides = {} + + +class TestCancelFriendRequest: + def test_cancel_friend_request_returns_204(self) -> None: + app.dependency_overrides[get_friendship_service] = _override_friendship_service( + FakeFriendshipService() + ) + app.dependency_overrides[get_current_user] = _get_fake_current_user + + client = TestClient(app) + try: + response = client.delete( + "/api/v1/friends/requests/11111111-1111-1111-1111-111111111111" + ) + assert response.status_code == 204 + assert response.content == b"" + finally: + app.dependency_overrides = {} + + +class TestGetFriendsList: + def test_get_friends_list_returns_200(self) -> None: + app.dependency_overrides[get_friendship_service] = _override_friendship_service( + FakeFriendshipService() + ) + app.dependency_overrides[get_current_user] = _get_fake_current_user + + client = TestClient(app) + try: + response = client.get("/api/v1/friends") + assert response.status_code == 200 + body = response.json() + assert isinstance(body, list) + assert len(body) == 1 + assert body[0]["status"] == "active" + assert body[0]["friend"]["username"] == "friend" + finally: + app.dependency_overrides = {} + + +class TestRemoveFriend: + def test_remove_friend_returns_204(self) -> None: + app.dependency_overrides[get_friendship_service] = _override_friendship_service( + FakeFriendshipService() + ) + app.dependency_overrides[get_current_user] = _get_fake_current_user + + client = TestClient(app) + try: + response = client.delete( + "/api/v1/friends/33333333-3333-3333-3333-333333333333" + ) + assert response.status_code == 204 + assert response.content == b"" + finally: + app.dependency_overrides = {} + + +class TestFriendshipRequiresAuth: + def test_send_request_without_auth_returns_401(self) -> None: + app.dependency_overrides[get_current_user] = _raise_unauthorized + + client = TestClient(app) + try: + response = client.post( + "/api/v1/friends/requests", + json={ + "target_user_id": "22222222-2222-2222-2222-222222222222", + }, + ) + assert response.status_code == 401 + finally: + app.dependency_overrides = {} + + def test_get_inbox_without_auth_returns_401(self) -> None: + app.dependency_overrides[get_current_user] = _raise_unauthorized + + client = TestClient(app) + try: + response = client.get("/api/v1/friends/requests/inbox") + assert response.status_code == 401 + finally: + app.dependency_overrides = {} + + def test_get_friends_without_auth_returns_401(self) -> None: + app.dependency_overrides[get_current_user] = _raise_unauthorized + + client = TestClient(app) + try: + response = client.get("/api/v1/friends") + assert response.status_code == 401 + finally: + app.dependency_overrides = {}