| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- """WebSocket dispatch-toast routing (#1625 follow-up).
- Two contracts pinned here:
- 1. ``broadcast_to_user(uid, msg)`` only delivers to connections whose
- ``websocket.state.bambuddy_principal_user_id`` matches the target,
- and fans out to all when the target is None (auth-disabled path).
- 2. The six ``send_queue_item_*`` helpers serialize the right payload
- shape — the frontend toast reads exact field names + types.
- """
- from __future__ import annotations
- import json
- from types import SimpleNamespace
- from unittest.mock import AsyncMock
- import pytest
- from backend.app.core.websocket import ConnectionManager
- def _mock_conn(user_id: int | None):
- """Build a stand-in WebSocket-shaped object with the principal stamp."""
- conn = SimpleNamespace()
- conn.state = SimpleNamespace()
- conn.state.bambuddy_principal_user_id = user_id
- conn.send_text = AsyncMock()
- return conn
- @pytest.mark.asyncio
- async def test_broadcast_to_user_filters_by_principal_user_id():
- """A targeted broadcast only reaches the principal's connections."""
- mgr = ConnectionManager()
- alice = _mock_conn(7)
- bob = _mock_conn(8)
- anon = _mock_conn(None) # auth-disabled session — skipped on targeted path
- mgr.active_connections = [alice, bob, anon]
- await mgr.broadcast_to_user(7, {"type": "queue_item_uploading", "queue_item_id": 1})
- alice.send_text.assert_awaited_once()
- bob.send_text.assert_not_awaited()
- anon.send_text.assert_not_awaited()
- @pytest.mark.asyncio
- async def test_broadcast_to_user_none_fans_out_to_all():
- """Auth-disabled installs route ``user_id=None`` to every connection
- via the regular broadcast — matches the legacy single-user toast
- behaviour where there was no per-user routing at all."""
- mgr = ConnectionManager()
- a = _mock_conn(None)
- b = _mock_conn(None)
- mgr.active_connections = [a, b]
- await mgr.broadcast_to_user(None, {"type": "queue_item_uploading", "queue_item_id": 1})
- a.send_text.assert_awaited_once()
- b.send_text.assert_awaited_once()
- @pytest.mark.asyncio
- async def test_send_queue_item_uploading_carries_total_bytes():
- mgr = ConnectionManager()
- target = _mock_conn(42)
- mgr.active_connections = [target]
- await mgr.send_queue_item_uploading(
- user_id=42,
- queue_item_id=11,
- printer_id=1,
- printer_name="H2D-1",
- file_name="cube.3mf",
- total_bytes=12345,
- )
- payload = json.loads(target.send_text.await_args.args[0])
- assert payload == {
- "type": "queue_item_uploading",
- "queue_item_id": 11,
- "printer_id": 1,
- "printer_name": "H2D-1",
- "file_name": "cube.3mf",
- "total_bytes": 12345,
- }
- @pytest.mark.asyncio
- async def test_send_queue_item_upload_progress_computes_pct_server_side():
- """The toast renders the pct field verbatim — the backend has to
- compute it. Avoid divide-by-zero on a zero-byte upload."""
- mgr = ConnectionManager()
- target = _mock_conn(5)
- mgr.active_connections = [target]
- await mgr.send_queue_item_upload_progress(
- user_id=5,
- queue_item_id=3,
- bytes_transferred=50,
- total_bytes=200,
- )
- payload = json.loads(target.send_text.await_args.args[0])
- assert payload["pct"] == 25
- target.send_text.reset_mock()
- await mgr.send_queue_item_upload_progress(
- user_id=5,
- queue_item_id=3,
- bytes_transferred=0,
- total_bytes=0,
- )
- payload = json.loads(target.send_text.await_args.args[0])
- assert payload["pct"] == 0
- @pytest.mark.asyncio
- async def test_send_queue_item_failed_carries_reason_key():
- """The frontend looks up ``dispatchToast.failed.{reason}`` — so the
- backend must hand the toast a reason string the i18n can match."""
- mgr = ConnectionManager()
- target = _mock_conn(99)
- mgr.active_connections = [target]
- await mgr.send_queue_item_failed(
- user_id=99,
- queue_item_id=8,
- printer_id=2,
- reason="upload_failed",
- )
- payload = json.loads(target.send_text.await_args.args[0])
- assert payload == {
- "type": "queue_item_failed",
- "queue_item_id": 8,
- "printer_id": 2,
- "reason": "upload_failed",
- }
|