Skip to content

Commit 500d4ea

Browse files
authored
🐛(back) manage can-edit endpoint without created room in the ws (#1152)
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.
1 parent 8a057b9 commit 500d4ea

File tree

4 files changed

+123
-7
lines changed

4 files changed

+123
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to
1111
### Fixed
1212

1313
- 🌐(frontend) keep simple tag during export #1154
14+
- 🐛(back) manage can-edit endpoint without created room in the ws
1415

1516
## [3.4.0] - 2025-07-09
1617

src/backend/core/services/collaboration_services.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,14 @@ def get_document_connection_info(self, room, session_key):
6262
except requests.RequestException as e:
6363
raise requests.HTTPError("Failed to get document connection info.") from e
6464

65-
if response.status_code != 200:
66-
raise requests.HTTPError(
67-
f"Failed to get document connection info. Status code: {response.status_code}, "
68-
f"Response: {response.text}"
69-
)
70-
result = response.json()
71-
return result.get("count", 0), result.get("exists", False)
65+
if response.status_code == 200:
66+
result = response.json()
67+
return result.get("count", 0), result.get("exists", False)
68+
69+
if response.status_code == 404:
70+
return 0, False
71+
72+
raise requests.HTTPError(
73+
f"Failed to get document connection info. Status code: {response.status_code}, "
74+
f"Response: {response.text}"
75+
)

src/backend/core/tests/documents/test_api_documents_can_edit.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,73 @@ def test_api_documents_can_edit_websocket_server_unreachable_fallback_to_no_webs
246246

247247
assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key"
248248
assert ws_resp.call_count == 1
249+
250+
251+
@responses.activate
252+
def test_api_documents_can_edit_websocket_server_room_not_found(
253+
settings,
254+
):
255+
"""
256+
When the websocket server returns a 404, the document can be updated like if the user was
257+
not connected to the websocket.
258+
"""
259+
user = factories.UserFactory(with_owned_document=True)
260+
client = APIClient()
261+
client.force_login(user)
262+
session_key = client.session.session_key
263+
264+
document = factories.DocumentFactory(users=[(user, "editor")])
265+
266+
settings.COLLABORATION_API_URL = "http://example.com/"
267+
settings.COLLABORATION_SERVER_SECRET = "secret-token"
268+
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
269+
endpoint_url = (
270+
f"{settings.COLLABORATION_API_URL}get-connections/"
271+
f"?room={document.id}&sessionKey={session_key}"
272+
)
273+
ws_resp = responses.get(endpoint_url, status=404)
274+
275+
assert cache.get(f"docs:no-websocket:{document.id}") is None
276+
277+
response = client.get(
278+
f"/api/v1.0/documents/{document.id!s}/can-edit/",
279+
)
280+
assert response.status_code == 200
281+
assert response.json() == {"can_edit": True}
282+
283+
assert ws_resp.call_count == 1
284+
285+
286+
@responses.activate
287+
def test_api_documents_can_edit_websocket_server_room_not_found_other_already_editing(
288+
settings,
289+
):
290+
"""
291+
When the websocket server returns a 404 and another user is editing the document,
292+
the response should be can-edit=False.
293+
"""
294+
user = factories.UserFactory(with_owned_document=True)
295+
client = APIClient()
296+
client.force_login(user)
297+
session_key = client.session.session_key
298+
299+
document = factories.DocumentFactory(users=[(user, "editor")])
300+
301+
settings.COLLABORATION_API_URL = "http://example.com/"
302+
settings.COLLABORATION_SERVER_SECRET = "secret-token"
303+
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
304+
endpoint_url = (
305+
f"{settings.COLLABORATION_API_URL}get-connections/"
306+
f"?room={document.id}&sessionKey={session_key}"
307+
)
308+
ws_resp = responses.get(endpoint_url, status=404)
309+
310+
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
311+
312+
response = client.get(
313+
f"/api/v1.0/documents/{document.id!s}/can-edit/",
314+
)
315+
assert response.status_code == 200
316+
assert response.json() == {"can_edit": False}
317+
318+
assert ws_resp.call_count == 1

src/backend/core/tests/documents/test_api_documents_update.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,47 @@ def test_api_documents_update_websocket_server_unreachable_fallback_to_no_websoc
539539
assert ws_resp.call_count == 1
540540

541541

542+
@responses.activate
543+
def test_api_documents_update_websocket_server_room_not_found_fallback_to_no_websocket_other_users(
544+
settings,
545+
):
546+
"""
547+
When the WebSocket server does not have the room created, the logic should fallback to
548+
no-WebSocket. If another user is already editing, the update must be denied.
549+
"""
550+
user = factories.UserFactory(with_owned_document=True)
551+
client = APIClient()
552+
client.force_login(user)
553+
session_key = client.session.session_key
554+
555+
document = factories.DocumentFactory(users=[(user, "editor")])
556+
557+
new_document_values = serializers.DocumentSerializer(
558+
instance=factories.DocumentFactory()
559+
).data
560+
new_document_values["websocket"] = False
561+
settings.COLLABORATION_API_URL = "http://example.com/"
562+
settings.COLLABORATION_SERVER_SECRET = "secret-token"
563+
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
564+
endpoint_url = (
565+
f"{settings.COLLABORATION_API_URL}get-connections/"
566+
f"?room={document.id}&sessionKey={session_key}"
567+
)
568+
ws_resp = responses.get(endpoint_url, status=404)
569+
570+
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
571+
572+
response = client.put(
573+
f"/api/v1.0/documents/{document.id!s}/",
574+
new_document_values,
575+
format="json",
576+
)
577+
assert response.status_code == 403
578+
579+
assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key"
580+
assert ws_resp.call_count == 1
581+
582+
542583
@responses.activate
543584
def test_api_documents_update_force_websocket_param_to_true(settings):
544585
"""

0 commit comments

Comments
 (0)