diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ec69db7..e78088fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to - ✨(backend) call YHubService to seed initial document content - ✨(collaboration) add a get-ydoc endpoint on yhub - ✨(backend) duplicate a document through the collaboration server +- ✨(backend) serve `documents/{id}/formatted-content/` from yhub ### Changed diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index fa9424157..0bb1e29f2 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -2,7 +2,6 @@ # pylint: disable=too-many-lines -import base64 import ipaddress import json import logging @@ -2437,15 +2436,24 @@ class DocumentViewSet( "Invalid format. Must be one of: json, markdown, html" ) - # Get the base64 content from the document + # Get the content from the collaboration server, it is the source of + # truth for it + try: + update = YHubService(user=request.user).get_ydoc(document) + except YHubError as e: + logger.error("Error getting content for document %s: %s", pk, e) + return drf_response.Response( + {"error": "Failed to get document content"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + content = None - base64_content = document.content - if base64_content is not None: + if update is not None: # Convert using the y-provider service try: yprovider = Converter() result = yprovider.convert( - base64.b64decode(base64_content), + update, mime_types.YJS, { "markdown": mime_types.MARKDOWN, diff --git a/src/backend/core/tests/documents/test_api_documents_formatted_content.py b/src/backend/core/tests/documents/test_api_documents_formatted_content.py index b2318b627..7a800a10f 100644 --- a/src/backend/core/tests/documents/test_api_documents_formatted_content.py +++ b/src/backend/core/tests/documents/test_api_documents_formatted_content.py @@ -11,10 +11,30 @@ from rest_framework import status from rest_framework.test import APIClient from core import factories +from core.services.yhub_services import ( + ServiceUnavailableError as YHubServiceUnavailableError, +) pytestmark = pytest.mark.django_db +@pytest.fixture(autouse=True, name="mock_yhub") +def mock_yhub_fixture(): + """ + The content of a document is held by the collaboration server. + + It stands for a server holding the very content the factories gave the + documents, which is what an editor connected to it would have saved. + """ + + def get_ydoc(document): + return base64.b64decode(document.content) if document.content else None + + with patch("core.api.viewsets.YHubService") as mock_service: + mock_service.return_value.get_ydoc.side_effect = get_ydoc + yield mock_service + + @pytest.mark.parametrize( "reach, role", [ @@ -182,3 +202,46 @@ def test_api_documents_formatted_content_empty_document(mock_request): assert data["title"] == document.title assert data["content"] is None mock_request.assert_not_called() + + +@patch("core.services.converter_services.YdocConverter.convert") +def test_api_documents_formatted_content_from_collaboration_server( + mock_content, mock_yhub +): + """The content converted is the one held by the collaboration server.""" + document = factories.DocumentFactory(link_reach="public") + mock_content.return_value = {"some": "data"} + # what the collaboration server holds, edited since Django last saw it + mock_yhub.return_value.get_ydoc.side_effect = None + mock_yhub.return_value.get_ydoc.return_value = b"\x01\x02edited update" + + response = APIClient().get( + f"/api/v1.0/documents/{document.id!s}/formatted-content/" + ) + + assert response.status_code == status.HTTP_200_OK + mock_yhub.return_value.get_ydoc.assert_called_once_with(document) + mock_content.assert_called_once_with( + b"\x01\x02edited update", + "application/vnd.yjs.doc", + "application/json", + ) + + +@patch("core.services.converter_services.YdocConverter.convert") +def test_api_documents_formatted_content_collaboration_server_error( + mock_content, mock_yhub +): + """A content the collaboration server cannot serve should answer a 500.""" + document = factories.DocumentFactory(link_reach="public") + mock_yhub.return_value.get_ydoc.side_effect = YHubServiceUnavailableError( + "Failed to connect to the yhub service" + ) + + response = APIClient().get( + f"/api/v1.0/documents/{document.id!s}/formatted-content/" + ) + + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + assert response.json() == {"error": "Failed to get document content"} + mock_content.assert_not_called()