From 500d4ea5ac784fb34c23a0f8abd810d5a8ffe40d Mon Sep 17 00:00:00 2001 From: Manuel Raynaud Date: Thu, 10 Jul 2025 14:24:38 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(back)=20manage=20can-edit=20endpoi?= =?UTF-8?q?nt=20without=20created=20room=20in=20the=20ws=20(#1152)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In a scenario where the first user is editing a docs without websocket and nobody has reached the websocket server first, the y-provider service will return a 404 and we don't handle this case in the can-edit endpoint leading to a server error. --- CHANGELOG.md | 1 + .../core/services/collaboration_services.py | 18 +++-- .../documents/test_api_documents_can_edit.py | 70 +++++++++++++++++++ .../documents/test_api_documents_update.py | 41 +++++++++++ 4 files changed, 123 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ddcbc5a..74d1c92f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to ### Fixed - 🌐(frontend) keep simple tag during export #1154 +- 🐛(back) manage can-edit endpoint without created room in the ws ## [3.4.0] - 2025-07-09 diff --git a/src/backend/core/services/collaboration_services.py b/src/backend/core/services/collaboration_services.py index ae4df1d5..9587a1b1 100644 --- a/src/backend/core/services/collaboration_services.py +++ b/src/backend/core/services/collaboration_services.py @@ -62,10 +62,14 @@ class CollaborationService: except requests.RequestException as e: raise requests.HTTPError("Failed to get document connection info.") from e - if response.status_code != 200: - raise requests.HTTPError( - f"Failed to get document connection info. Status code: {response.status_code}, " - f"Response: {response.text}" - ) - result = response.json() - return result.get("count", 0), result.get("exists", False) + if response.status_code == 200: + result = response.json() + return result.get("count", 0), result.get("exists", False) + + if response.status_code == 404: + return 0, False + + raise requests.HTTPError( + f"Failed to get document connection info. Status code: {response.status_code}, " + f"Response: {response.text}" + ) diff --git a/src/backend/core/tests/documents/test_api_documents_can_edit.py b/src/backend/core/tests/documents/test_api_documents_can_edit.py index 7db0a13c..05bc3970 100644 --- a/src/backend/core/tests/documents/test_api_documents_can_edit.py +++ b/src/backend/core/tests/documents/test_api_documents_can_edit.py @@ -246,3 +246,73 @@ def test_api_documents_can_edit_websocket_server_unreachable_fallback_to_no_webs assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key" assert ws_resp.call_count == 1 + + +@responses.activate +def test_api_documents_can_edit_websocket_server_room_not_found( + settings, +): + """ + When the websocket server returns a 404, the document can be updated like if the user was + not connected to the websocket. + """ + user = factories.UserFactory(with_owned_document=True) + client = APIClient() + client.force_login(user) + session_key = client.session.session_key + + document = factories.DocumentFactory(users=[(user, "editor")]) + + settings.COLLABORATION_API_URL = "http://example.com/" + settings.COLLABORATION_SERVER_SECRET = "secret-token" + settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True + endpoint_url = ( + f"{settings.COLLABORATION_API_URL}get-connections/" + f"?room={document.id}&sessionKey={session_key}" + ) + ws_resp = responses.get(endpoint_url, status=404) + + assert cache.get(f"docs:no-websocket:{document.id}") is None + + response = client.get( + f"/api/v1.0/documents/{document.id!s}/can-edit/", + ) + assert response.status_code == 200 + assert response.json() == {"can_edit": True} + + assert ws_resp.call_count == 1 + + +@responses.activate +def test_api_documents_can_edit_websocket_server_room_not_found_other_already_editing( + settings, +): + """ + When the websocket server returns a 404 and another user is editing the document, + the response should be can-edit=False. + """ + user = factories.UserFactory(with_owned_document=True) + client = APIClient() + client.force_login(user) + session_key = client.session.session_key + + document = factories.DocumentFactory(users=[(user, "editor")]) + + settings.COLLABORATION_API_URL = "http://example.com/" + settings.COLLABORATION_SERVER_SECRET = "secret-token" + settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True + endpoint_url = ( + f"{settings.COLLABORATION_API_URL}get-connections/" + f"?room={document.id}&sessionKey={session_key}" + ) + ws_resp = responses.get(endpoint_url, status=404) + + cache.set(f"docs:no-websocket:{document.id}", "other_session_key") + + response = client.get( + f"/api/v1.0/documents/{document.id!s}/can-edit/", + ) + assert response.status_code == 200 + assert response.json() == {"can_edit": False} + + assert ws_resp.call_count == 1 diff --git a/src/backend/core/tests/documents/test_api_documents_update.py b/src/backend/core/tests/documents/test_api_documents_update.py index a92354db..a08ec2c6 100644 --- a/src/backend/core/tests/documents/test_api_documents_update.py +++ b/src/backend/core/tests/documents/test_api_documents_update.py @@ -539,6 +539,47 @@ def test_api_documents_update_websocket_server_unreachable_fallback_to_no_websoc assert ws_resp.call_count == 1 +@responses.activate +def test_api_documents_update_websocket_server_room_not_found_fallback_to_no_websocket_other_users( + settings, +): + """ + When the WebSocket server does not have the room created, the logic should fallback to + no-WebSocket. If another user is already editing, the update must be denied. + """ + user = factories.UserFactory(with_owned_document=True) + client = APIClient() + client.force_login(user) + session_key = client.session.session_key + + document = factories.DocumentFactory(users=[(user, "editor")]) + + new_document_values = serializers.DocumentSerializer( + instance=factories.DocumentFactory() + ).data + new_document_values["websocket"] = False + settings.COLLABORATION_API_URL = "http://example.com/" + settings.COLLABORATION_SERVER_SECRET = "secret-token" + settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True + endpoint_url = ( + f"{settings.COLLABORATION_API_URL}get-connections/" + f"?room={document.id}&sessionKey={session_key}" + ) + ws_resp = responses.get(endpoint_url, status=404) + + cache.set(f"docs:no-websocket:{document.id}", "other_session_key") + + response = client.put( + f"/api/v1.0/documents/{document.id!s}/", + new_document_values, + format="json", + ) + assert response.status_code == 403 + + assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key" + assert ws_resp.call_count == 1 + + @responses.activate def test_api_documents_update_force_websocket_param_to_true(settings): """