mirror of
https://github.com/Donkie/Spoolman.git
synced 2026-10-19 00:29:46 -05:00
backend: notify spools when their filament changes
Spool responses carry fields derived from the filament (remaining_length, plus the initial_weight/price fall-backs). Editing a filament emitted only a filament WS event, so subscribers to that filament's spools kept stale derived values until each spool was next touched — visible in client_v2 as a length inconsistent with the displayed density, but affecting every consumer. filament.update now re-broadcasts the filament's spools (archived included) as spool "updated" events after commit. Adds a WS integration test asserting a density patch reaches /api/v1/spool with the new density and changed length. Claude-Session: https://claude.ai/code/session_014Cwk5Yyze9yPvkYkUtMAP4
This commit is contained in:
1 parent
990f16a8e9
commit
6ade40517f
3 files changed
+74
No files matched your search
@@ -191,9 +191,26 @@ async def update(
|
||||
setattr(filament, k, v)
|
||||
await db.commit()
|
||||
await filament_changed(filament, EventType.UPDATED)
|
||||
# A spool's response carries fields derived from its filament (remaining_length,
|
||||
# and the initial_weight/price fall-backs to the filament's values). Those change
|
||||
# when the filament changes, yet no spool row was touched — so subscribers to the
|
||||
# affected spools would keep stale derived values until each spool is next edited.
|
||||
# Re-broadcast the filament's spools so websocket clients refresh them.
|
||||
await _notify_filament_spools(db, filament_id)
|
||||
return filament
|
||||
|
||||
|
||||
async def _notify_filament_spools(db: AsyncSession, filament_id: int) -> None:
|
||||
"""Emit a spool 'updated' event for every spool belonging to a filament."""
|
||||
# Local import: spool.py imports this module at load time, so importing it at the
|
||||
# top here would be a circular import.
|
||||
from spoolman.database import spool # noqa: PLC0415
|
||||
|
||||
spools, _ = await spool.find(db=db, filament_id=filament_id, allow_archived=True)
|
||||
for s in spools:
|
||||
await spool.spool_changed(s, EventType.UPDATED)
|
||||
|
||||
|
||||
async def delete(db: AsyncSession, filament_id: int) -> None:
|
||||
"""Delete a filament object."""
|
||||
filament = await get_by_id(db, filament_id)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pytest==9.1.1
|
||||
pytest-asyncio==1.4.0
|
||||
httpx==0.27.0
|
||||
websockets==13.1
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Integration tests: a filament update re-broadcasts its spools over websockets.
|
||||
|
||||
Spool responses carry fields derived from the filament (e.g. remaining_length, and the
|
||||
initial_weight/price fall-backs). Editing the filament changes those derived values
|
||||
without touching any spool row, so the server must emit a spool 'updated' event for
|
||||
every spool of the filament — otherwise websocket subscribers keep stale values until
|
||||
the spool itself is next edited.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from websockets.asyncio.client import connect
|
||||
|
||||
from ..conftest import URL
|
||||
|
||||
WS_URL = URL.replace("http://", "ws://", 1)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_filament_update_notifies_spools(random_filament: dict[str, Any]) -> None:
|
||||
"""Patching a filament's density emits a spool 'updated' event with fresh derived fields."""
|
||||
# Setup: one spool for this filament.
|
||||
result = httpx.post(
|
||||
f"{URL}/api/v1/spool",
|
||||
json={"filament_id": random_filament["id"], "remaining_weight": 500},
|
||||
)
|
||||
result.raise_for_status()
|
||||
spool = result.json()
|
||||
original_length = spool["remaining_length"]
|
||||
|
||||
try:
|
||||
async with connect(f"{WS_URL}/api/v1/spool") as ws:
|
||||
# The subscription is registered synchronously right after the handshake,
|
||||
# but give the server a beat before triggering the event.
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
new_density = random_filament["density"] * 2
|
||||
httpx.patch(
|
||||
f"{URL}/api/v1/filament/{random_filament['id']}",
|
||||
json={"density": new_density},
|
||||
).raise_for_status()
|
||||
|
||||
event = json.loads(await asyncio.wait_for(ws.recv(), timeout=10))
|
||||
|
||||
assert event["type"] == "updated"
|
||||
assert event["resource"] == "spool"
|
||||
assert event["payload"]["id"] == spool["id"]
|
||||
# The embedded filament and the derived length must reflect the new density.
|
||||
assert event["payload"]["filament"]["density"] == new_density
|
||||
assert event["payload"]["remaining_length"] != original_length
|
||||
finally:
|
||||
httpx.delete(f"{URL}/api/v1/spool/{spool['id']}").raise_for_status()
|
||||
Reference in new issue
Block a user