Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions spoolman/api/v1/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,9 @@ class SpoolEvent(Event):

payload: Spool = Field(description="Updated spool.")
resource: Literal["spool"] = Field(description="Resource type.")
payload_extras: dict[str, float] | None = \
Comment thread
chof747 marked this conversation as resolved.
Outdated
Field(default=None,
description="Payload extra fields outside of core Spool model")


class FilamentEvent(Event):
Expand Down
10 changes: 6 additions & 4 deletions spoolman/database/spool.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ async def use_weight(db: AsyncSession, spool_id: int, weight: float) -> models.S
spool.last_used = datetime.utcnow().replace(microsecond=0)

await db.commit()
await spool_changed(spool, EventType.UPDATED)
await spool_changed(spool, EventType.UPDATED, {"weight_delta": weight})
return spool


Expand Down Expand Up @@ -631,7 +631,7 @@ async def use_length(db: AsyncSession, spool_id: int, length: float) -> models.S
spool.last_used = datetime.utcnow().replace(microsecond=0)

await db.commit()
await spool_changed(spool, EventType.UPDATED)
await spool_changed(spool, EventType.UPDATED, {"weight_delta": weight})
return spool


Expand Down Expand Up @@ -723,16 +723,18 @@ async def find_lot_numbers(
return [row[0] for row in rows.all() if row[0] is not None]


async def spool_changed(spool: models.Spool, typ: EventType) -> None:
async def spool_changed(spool: models.Spool, typ: EventType, delta: Optional[dict] = None) -> None:
"""Notify websocket clients that a spool has changed."""
try:
spool = Spool.from_db(spool)
await websocket_manager.send(
("spool", str(spool.id)),
SpoolEvent(
type=typ,
resource="spool",
date=datetime.utcnow(),
payload=Spool.from_db(spool),
payload=spool,
payload_extras=delta
),
)
except Exception:
Expand Down
2 changes: 1 addition & 1 deletion tests_integration/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pytest==9.1.1
pytest-asyncio==1.4.0
httpx==0.27.0
websockets==13.1
websockets==15.0.1
51 changes: 51 additions & 0 deletions tests_integration/tests/spool/test_spool_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Integration tests for spool websocket events."""

import asyncio
import json
from typing import Any

import httpx
import pytest
import websockets

from ..conftest import URL


@pytest.mark.asyncio
async def test_use_weight_websocket_has_weight_delta(random_filament: dict[str, Any]):
"""Test websocket payload extras for spool weight usage."""
#Setup
result = httpx.post(
f"{URL}/api/v1/spool",
json={"filament_id": random_filament["id"], "remaining_weight": 1000},
)
result.raise_for_status()
spool = result.json()
spool_id = spool["id"]
ws_url = URL.replace("http://", "ws://").replace("https://", "wss://") + f"/api/v1/spool/{spool_id}"
use_weight = 6.9

try:
async with websockets.connect(ws_url) as ws:
# keep the socket loop healthy before triggering update
await ws.send("ping")
check = json.loads(await asyncio.wait_for(ws.recv(), timeout=2))
assert check["status"] == "healthy"

#execute
r = httpx.put(f"{URL}/api/v1/spool/{spool_id}/use", json={"use_weight": use_weight})
r.raise_for_status()
raw = await asyncio.wait_for(ws.recv(), timeout=5)
evt = json.loads(raw)
#verify
assert evt["resource"] == "spool"
assert evt["type"] == "updated"
assert evt["payload"]["id"] == spool_id
assert evt["payload_extras"]["weight_delta"] == pytest.approx(use_weight)
assert "event_delta" not in evt["payload"].get("extra", {})
#cleanup-ws
await ws.close(code=1000)
await asyncio.sleep(0.6)
finally:
#cleanup
httpx.delete(f"{URL}/api/v1/spool/{spool_id}").raise_for_status()
Loading