| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- """SpoolBuddy with Spoolman switched on but not usable.
- With Spoolman enabled and a URL set, the spools are Spoolman's, even while
- that URL is refused. A tag lookup, a tag write and a scale reading then report
- Spoolman as unavailable instead of reading or writing the built-in inventory,
- whose spool ids are not Spoolman's. Switched on without a URL is the built-in
- inventory, as every SpoolBuddy screen shows it.
- """
- from datetime import datetime, timezone
- from unittest.mock import AsyncMock, patch
- import pytest
- from httpx import AsyncClient
- from sqlalchemy import select
- from backend.app.models.settings import Settings
- from backend.app.models.spool import Spool
- from backend.app.models.spoolbuddy_device import SpoolBuddyDevice
- API = "/api/v1/spoolbuddy"
- @pytest.fixture
- async def unusable_spoolman(db_session):
- db_session.add(Settings(key="spoolman_enabled", value="true"))
- # A cloud metadata address, which the URL guard refuses.
- db_session.add(Settings(key="spoolman_url", value="http://169.254.169.254"))
- db_session.add(
- SpoolBuddyDevice(
- device_id="sb-1",
- hostname="spoolbuddy-1",
- ip_address="10.0.0.1",
- firmware_version="1.0.0",
- has_nfc=True,
- has_scale=True,
- tare_offset=0,
- calibration_factor=1.0,
- last_seen=datetime.now(timezone.utc),
- )
- )
- spool = Spool(
- material="PLA",
- label_weight=1000,
- core_weight=250,
- weight_used=100,
- tag_uid="AABB1122",
- )
- db_session.add(spool)
- await db_session.commit()
- return spool.id
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_a_scanned_tag_is_not_looked_up_in_the_built_in_inventory(async_client: AsyncClient, unusable_spoolman):
- with patch("backend.app.api.routes.spoolbuddy.ws_manager") as mock_ws:
- mock_ws.broadcast = AsyncMock()
- resp = await async_client.post(f"{API}/nfc/tag-scanned", json={"device_id": "sb-1", "tag_uid": "AABB1122"})
- assert resp.status_code == 200
- assert resp.json()["matched"] is False
- sent = [call.args[0]["type"] for call in mock_ws.broadcast.call_args_list]
- assert "spoolman_unavailable" in sent
- assert "spoolbuddy_tag_matched" not in sent
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_a_tag_write_is_refused(async_client: AsyncClient, unusable_spoolman, db_session):
- with patch("backend.app.api.routes.spoolbuddy.ws_manager") as mock_ws:
- mock_ws.broadcast = AsyncMock()
- resp = await async_client.post(
- f"{API}/nfc/write-tag", json={"device_id": "sb-1", "spool_id": unusable_spoolman}
- )
- assert resp.status_code == 503
- device = await db_session.scalar(select(SpoolBuddyDevice).where(SpoolBuddyDevice.device_id == "sb-1"))
- await db_session.refresh(device)
- assert device.pending_command is None
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_a_scale_reading_does_not_touch_the_built_in_spool(
- async_client: AsyncClient, unusable_spoolman, db_session
- ):
- resp = await async_client.post(
- f"{API}/scale/update-spool-weight", json={"spool_id": unusable_spoolman, "weight_grams": 600}
- )
- assert resp.status_code == 503
- spool = await db_session.get(Spool, unusable_spoolman)
- await db_session.refresh(spool)
- assert spool.weight_used == 100
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_a_tag_written_after_spoolman_was_switched_off_reports_why(async_client: AsyncClient, db_session):
- """The write began in Spoolman mode; by the time the tag is written
- Spoolman is off, so the link cannot be stored."""
- import json
- db_session.add(
- SpoolBuddyDevice(
- device_id="sb-2",
- hostname="spoolbuddy-2",
- ip_address="10.0.0.2",
- firmware_version="1.0.0",
- has_nfc=True,
- has_scale=True,
- tare_offset=0,
- calibration_factor=1.0,
- last_seen=datetime.now(timezone.utc),
- pending_command="write_tag",
- pending_write_payload=json.dumps({"spool_id": 9, "ndef_data_hex": "deadbeef", "data_origin": "spoolman"}),
- )
- )
- await db_session.commit()
- with patch("backend.app.api.routes.spoolbuddy.ws_manager") as mock_ws:
- mock_ws.broadcast = AsyncMock()
- resp = await async_client.post(
- f"{API}/nfc/write-result",
- json={"device_id": "sb-2", "spool_id": 9, "tag_uid": "AABBCCDD", "success": True},
- )
- assert resp.status_code == 502
- msg = mock_ws.broadcast.call_args[0][0]
- assert (msg["type"], msg["reason"]) == ("spoolbuddy_tag_link_failed", "spoolman_not_configured")
- @pytest.mark.asyncio
- @pytest.mark.integration
- async def test_switched_on_without_a_url_is_the_built_in_inventory(async_client: AsyncClient, db_session):
- db_session.add(Settings(key="spoolman_enabled", value="true"))
- db_session.add(Settings(key="spoolman_url", value=""))
- spool = Spool(material="PLA", label_weight=1000, core_weight=250, weight_used=100)
- db_session.add(spool)
- await db_session.commit()
- resp = await async_client.post(f"{API}/scale/update-spool-weight", json={"spool_id": spool.id, "weight_grams": 600})
- assert resp.status_code == 200
- await db_session.refresh(spool)
- assert spool.weight_used == 650
|