test_printer_sensor_history.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. """Tests for printer heater (nozzle / bed / chamber) sensor history."""
  2. from datetime import datetime, timedelta, timezone
  3. import pytest
  4. from httpx import AsyncClient
  5. from sqlalchemy import select
  6. from sqlalchemy.ext.asyncio import AsyncSession
  7. from backend.app.models.printer import Printer
  8. from backend.app.models.printer_sensor_history import PrinterSensorHistory
  9. @pytest.mark.asyncio
  10. async def test_get_returns_per_sensor_series(async_client: AsyncClient, db_session: AsyncSession):
  11. """Each sensor_kind returns its own series with stats."""
  12. printer = Printer(name="X1C", serial_number="X1C-TEST-001", ip_address="10.0.0.10", access_code="12345678")
  13. db_session.add(printer)
  14. await db_session.commit()
  15. await db_session.refresh(printer)
  16. base = datetime.now(timezone.utc) - timedelta(hours=1)
  17. samples = [
  18. ("nozzle", 210.0, 220.0, 0),
  19. ("nozzle", 215.0, 220.0, 5),
  20. ("nozzle", 220.0, 220.0, 10),
  21. ("bed", 55.0, 60.0, 0),
  22. ("bed", 60.0, 60.0, 5),
  23. ("chamber", 38.0, 40.0, 0),
  24. ]
  25. for kind, value, target, minutes in samples:
  26. db_session.add(
  27. PrinterSensorHistory(
  28. printer_id=printer.id,
  29. sensor_kind=kind,
  30. value=value,
  31. target=target,
  32. recorded_at=base + timedelta(minutes=minutes),
  33. )
  34. )
  35. await db_session.commit()
  36. response = await async_client.get(f"/api/v1/printer-sensor-history/{printer.id}?hours=24")
  37. assert response.status_code == 200
  38. body = response.json()
  39. assert body["printer_id"] == printer.id
  40. series_by_kind = {s["sensor_kind"]: s for s in body["series"]}
  41. assert series_by_kind["nozzle"]["min_value"] == 210.0
  42. assert series_by_kind["nozzle"]["max_value"] == 220.0
  43. assert series_by_kind["nozzle"]["avg_value"] == pytest.approx(215.0, rel=0.01)
  44. assert len(series_by_kind["nozzle"]["data"]) == 3
  45. assert series_by_kind["bed"]["min_value"] == 55.0
  46. assert series_by_kind["bed"]["max_value"] == 60.0
  47. assert len(series_by_kind["bed"]["data"]) == 2
  48. assert series_by_kind["chamber"]["max_value"] == 38.0
  49. # nozzle_2 wasn't recorded — series present but empty
  50. assert series_by_kind["nozzle_2"]["data"] == []
  51. assert series_by_kind["nozzle_2"]["min_value"] is None
  52. @pytest.mark.asyncio
  53. async def test_get_filters_by_kinds_query(async_client: AsyncClient, db_session: AsyncSession):
  54. """`kinds=bed,chamber` only returns those series."""
  55. printer = Printer(name="X1C", serial_number="X1C-TEST-002", ip_address="10.0.0.11", access_code="12345678")
  56. db_session.add(printer)
  57. await db_session.commit()
  58. await db_session.refresh(printer)
  59. db_session.add(PrinterSensorHistory(printer_id=printer.id, sensor_kind="bed", value=60.0, target=60.0))
  60. await db_session.commit()
  61. response = await async_client.get(f"/api/v1/printer-sensor-history/{printer.id}?hours=24&kinds=bed,chamber")
  62. assert response.status_code == 200
  63. kinds_returned = {s["sensor_kind"] for s in response.json()["series"]}
  64. assert kinds_returned == {"bed", "chamber"}
  65. @pytest.mark.asyncio
  66. async def test_get_clamps_to_hours_window(async_client: AsyncClient, db_session: AsyncSession):
  67. """Rows older than the requested window are excluded."""
  68. printer = Printer(name="X1C", serial_number="X1C-TEST-003", ip_address="10.0.0.12", access_code="12345678")
  69. db_session.add(printer)
  70. await db_session.commit()
  71. await db_session.refresh(printer)
  72. now = datetime.now(timezone.utc)
  73. # One inside the window, one outside.
  74. db_session.add(
  75. PrinterSensorHistory(
  76. printer_id=printer.id,
  77. sensor_kind="bed",
  78. value=60.0,
  79. target=60.0,
  80. recorded_at=now - timedelta(minutes=30),
  81. )
  82. )
  83. db_session.add(
  84. PrinterSensorHistory(
  85. printer_id=printer.id,
  86. sensor_kind="bed",
  87. value=55.0,
  88. target=60.0,
  89. recorded_at=now - timedelta(hours=10),
  90. )
  91. )
  92. await db_session.commit()
  93. response = await async_client.get(f"/api/v1/printer-sensor-history/{printer.id}?hours=1")
  94. body = response.json()
  95. bed_series = next(s for s in body["series"] if s["sensor_kind"] == "bed")
  96. assert len(bed_series["data"]) == 1
  97. assert bed_series["data"][0]["value"] == 60.0
  98. @pytest.mark.asyncio
  99. async def test_delete_removes_old_rows(async_client: AsyncClient, db_session: AsyncSession):
  100. """DELETE removes rows older than `days` for the given printer only."""
  101. keep_printer = Printer(name="Keep", serial_number="KEEP-001", ip_address="10.0.0.20", access_code="12345678")
  102. other_printer = Printer(name="Other", serial_number="OTHER-001", ip_address="10.0.0.21", access_code="12345678")
  103. db_session.add_all([keep_printer, other_printer])
  104. await db_session.commit()
  105. await db_session.refresh(keep_printer)
  106. await db_session.refresh(other_printer)
  107. now = datetime.now(timezone.utc)
  108. old = now - timedelta(days=40)
  109. db_session.add(PrinterSensorHistory(printer_id=keep_printer.id, sensor_kind="bed", value=60.0, recorded_at=old))
  110. db_session.add(PrinterSensorHistory(printer_id=keep_printer.id, sensor_kind="bed", value=60.0, recorded_at=now))
  111. db_session.add(PrinterSensorHistory(printer_id=other_printer.id, sensor_kind="bed", value=60.0, recorded_at=old))
  112. await db_session.commit()
  113. response = await async_client.delete(f"/api/v1/printer-sensor-history/{keep_printer.id}?days=30")
  114. assert response.status_code == 200
  115. assert response.json()["deleted"] == 1
  116. # other printer's old row untouched.
  117. rows = (await db_session.execute(select(PrinterSensorHistory))).scalars().all()
  118. kinds_left = sorted((r.printer_id, r.value) for r in rows)
  119. assert kinds_left == sorted([(keep_printer.id, 60.0), (other_printer.id, 60.0)])