"""Tests for the annotations app – Bookmarks & Notes API.""" import pytest from django.urls import reverse from rest_framework import status from rest_framework.test import APIClient from apps.annotations.models import Bookmark, Note from apps.books.models import Book, EBook from apps.users.models import User @pytest.fixture def api_client() -> APIClient: return APIClient() @pytest.fixture def user() -> User: return User.objects.create_user( username="testuser", email="test@example.com", password="testpass123", ) @pytest.fixture def other_user() -> User: return User.objects.create_user( username="other", email="other@example.com", password="testpass123", ) @pytest.fixture def auth_client(api_client: APIClient, user: User) -> APIClient: api_client.force_authenticate(user=user) return api_client @pytest.fixture def book() -> Book: return Book.objects.create( title="Test Book", author="Test Author", total_pages=300, ) @pytest.fixture def ebook(user: User) -> EBook: return EBook.objects.create( user=user, title="Test Ebook", author="Test Author", format="epub", ) @pytest.fixture def bookmark(auth_client, user: User, ebook: EBook) -> Bookmark: return Bookmark.objects.create( user=user, ebook=ebook, epub_cfi="epubcfi(/6/4!/4/2,/1:0,/1:10)", chapter_index=2, chapter_title="Chapter 3", page=3, location_text="important passage", content="", ) @pytest.fixture def note(auth_client, user: User, book: Book) -> Note: return Note.objects.create( user=user, book=book, page=15, location_text="highlighted section", content="This is my note about this section.", ) class TestBookmarkList: url = reverse("bookmark-list") def test_list_requires_auth(self, api_client: APIClient): response = api_client.get(self.url) assert response.status_code == status.HTTP_401_UNAUTHORIZED def test_list_returns_user_bookmarks_only( self, auth_client: APIClient, user: User, other_user: User, ebook: EBook ): Bookmark.objects.create( user=user, ebook=ebook, epub_cfi="epubcfi(/6/4!/4/2,/1:0,/1:5)", chapter_index=0, page=1, ) other_ebook = EBook.objects.create(user=other_user, title="Other", format="epub") Bookmark.objects.create( user=other_user, ebook=other_ebook, epub_cfi="epubcfi(/6/4!/4/2,/2:0,/2:5)", chapter_index=0, page=1, ) response = auth_client.get(self.url) assert response.status_code == status.HTTP_200_OK results = response.data["results"] assert len(results) == 1 def test_filter_by_ebook(self, auth_client: APIClient, bookmark: Bookmark, ebook: EBook): response = auth_client.get(self.url, {"ebook": str(ebook.id)}) assert response.status_code == status.HTTP_200_OK assert len(response.data["results"]) == 1 class TestBookmarkCreate: url = reverse("bookmark-list") def test_create_marker(self, auth_client: APIClient, ebook: EBook): data = { "ebook": ebook.id, "epub_cfi": "epubcfi(/6/4!/4/2,/1:0,/1:20)", "chapter_index": 1, "chapter_title": "Chapter 2", "location_text": "Selected text", "content": "My thought", } response = auth_client.post(self.url, data, format="json") assert response.status_code == status.HTTP_201_CREATED assert response.data["content"] == "My thought" assert response.data["ebook"] == ebook.id def test_create_bookmark_only_empty_content(self, auth_client: APIClient, ebook: EBook): data = { "ebook": ebook.id, "epub_cfi": "epubcfi(/6/4!/4/2,/3:0,/3:8)", "chapter_index": 0, "location_text": "Quote only", "content": "", } response = auth_client.post(self.url, data, format="json") assert response.status_code == status.HTTP_201_CREATED assert response.data["content"] == "" def test_duplicate_cfi_rejected(self, auth_client: APIClient, bookmark: Bookmark, ebook: EBook): data = { "ebook": ebook.id, "epub_cfi": bookmark.epub_cfi, "chapter_index": 0, "location_text": "dup", } response = auth_client.post(self.url, data, format="json") assert response.status_code == status.HTTP_400_BAD_REQUEST class TestBookmarkDetail: def test_delete_bookmark(self, auth_client: APIClient, bookmark: Bookmark): url = reverse("bookmark-detail", args=[str(bookmark.id)]) response = auth_client.delete(url) assert response.status_code == status.HTTP_204_NO_CONTENT