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 schemas.shared.user import UserContext from v1.friendships.dependencies import get_friendship_service from v1.friendships.schemas import ( FriendRequestCreate, FriendRequestResponse, FriendResponse, ) 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=UserContext(id="user-1", username="sender", avatar_url=None), recipient=UserContext(id="user-2", username="recipient", avatar_url=None), content={"text": request.content} if request.content else None, status="pending", created_at=datetime.now(timezone.utc), ) async def accept_request(self, friendship_id: UUID) -> FriendRequestResponse: return FriendRequestResponse( id=friendship_id, sender=UserContext(id="user-1", username="sender", avatar_url=None), recipient=UserContext(id="user-2", username="recipient", avatar_url=None), content={"text": "Hello!"}, status="accepted", created_at=datetime.now(timezone.utc), ) async def decline_request(self, friendship_id: UUID) -> FriendRequestResponse: return FriendRequestResponse( id=friendship_id, sender=UserContext(id="user-1", username="sender", avatar_url=None), recipient=UserContext(id="user-2", username="recipient", avatar_url=None), content={"text": "Hello!"}, status="rejected", created_at=datetime.now(timezone.utc), ) async def cancel_request(self, friendship_id: UUID) -> FriendRequestResponse: return FriendRequestResponse( id=friendship_id, sender=UserContext(id="user-1", username="sender", avatar_url=None), recipient=UserContext(id="user-2", username="recipient", avatar_url=None), content={"text": "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=UserContext(id="user-1", username="sender", avatar_url=None), recipient=UserContext( id="user-2", username="recipient", avatar_url=None ), content={"text": "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=UserContext(id="user-1", username="sender", avatar_url=None), recipient=UserContext(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=UserContext(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=UserContext(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"), phone="+8613812345678", ) 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 = {}