Files
bambuddy/backend/tests/integration/test_available_filaments.py
T
maziggy 940810cba3 Show filament subtypes in all mapping dropdowns (#624)
All filament mapping dropdowns (single-printer, multi-printer, and
  "Print to Any" model-based) showed only the base material type (e.g.
  "PLA") without distinguishing subtypes like "PLA Basic" vs "PLA Matte",
  making entries with different subtypes but same color look identical.

  Backend: Add tray_sub_brands to available-filaments response and include
  it in the dedup key so different subtypes of the same color appear as
  separate entries.

  Frontend: Show tray_sub_brands in FilamentMapping, FilamentOverride, and
  PrinterSelector dropdowns, falling back to base type when unset. Add
  traySubBrands field to LoadedFilament interface.
2026-03-06 08:35:06 +01:00

220 lines
7.8 KiB
Python

"""Integration tests for GET /api/v1/printers/available-filaments endpoint.
Tests that the endpoint returns deduplicated filaments with tray_sub_brands,
correctly distinguishing subtypes like "PLA Basic" vs "PLA Matte".
"""
from unittest.mock import MagicMock, patch
import pytest
from httpx import AsyncClient
def _make_mock_status(ams_data: list, vt_tray: list | None = None, ams_extruder_map: dict | None = None) -> MagicMock:
"""Create a mock printer status with raw_data containing AMS info."""
status = MagicMock()
raw = {"ams": ams_data}
if vt_tray is not None:
raw["vt_tray"] = vt_tray
if ams_extruder_map is not None:
raw["ams_extruder_map"] = ams_extruder_map
else:
raw["ams_extruder_map"] = {}
status.raw_data = raw
return status
class TestAvailableFilaments:
"""Tests for /api/v1/printers/available-filaments endpoint."""
@pytest.mark.asyncio
@pytest.mark.integration
async def test_returns_tray_sub_brands(self, async_client: AsyncClient, printer_factory):
"""Verify tray_sub_brands is included in the response."""
await printer_factory(name="Test Printer", model="X1C")
status = _make_mock_status(
ams_data=[
{
"id": 0,
"tray": [
{
"id": 0,
"tray_type": "PLA",
"tray_color": "000000FF",
"tray_info_idx": "GFL99",
"tray_sub_brands": "PLA Basic",
},
],
},
]
)
with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
mock_pm.get_status.return_value = status
response = await async_client.get("/api/v1/printers/available-filaments?model=X1C")
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["tray_sub_brands"] == "PLA Basic"
assert data[0]["type"] == "PLA"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_dedup_distinguishes_subtypes(self, async_client: AsyncClient, printer_factory):
"""PLA Basic Black and PLA Matte Black should be separate entries."""
await printer_factory(name="Printer 1", model="X1C")
status = _make_mock_status(
ams_data=[
{
"id": 0,
"tray": [
{
"id": 0,
"tray_type": "PLA",
"tray_color": "000000FF",
"tray_info_idx": "GFL99",
"tray_sub_brands": "PLA Basic",
},
{
"id": 1,
"tray_type": "PLA",
"tray_color": "000000FF",
"tray_info_idx": "GFL05",
"tray_sub_brands": "PLA Matte",
},
],
},
]
)
with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
mock_pm.get_status.return_value = status
response = await async_client.get("/api/v1/printers/available-filaments?model=X1C")
assert response.status_code == 200
data = response.json()
# Same type + color but different tray_sub_brands → 2 entries
assert len(data) == 2
sub_brands = {d["tray_sub_brands"] for d in data}
assert sub_brands == {"PLA Basic", "PLA Matte"}
@pytest.mark.asyncio
@pytest.mark.integration
async def test_dedup_same_subtype_same_color(self, async_client: AsyncClient, printer_factory):
"""Same subtype + same color across two printers should be deduped to one entry."""
await printer_factory(name="Printer 1", model="X1C")
await printer_factory(name="Printer 2", model="X1C")
status1 = _make_mock_status(
ams_data=[
{
"id": 0,
"tray": [
{
"id": 0,
"tray_type": "PLA",
"tray_color": "FF0000FF",
"tray_info_idx": "GFL99",
"tray_sub_brands": "PLA Basic",
}
],
},
]
)
status2 = _make_mock_status(
ams_data=[
{
"id": 0,
"tray": [
{
"id": 0,
"tray_type": "PLA",
"tray_color": "FF0000FF",
"tray_info_idx": "GFL99",
"tray_sub_brands": "PLA Basic",
}
],
},
]
)
with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
mock_pm.get_status.side_effect = [status1, status2]
response = await async_client.get("/api/v1/printers/available-filaments?model=X1C")
assert response.status_code == 200
data = response.json()
assert len(data) == 1
@pytest.mark.asyncio
@pytest.mark.integration
async def test_empty_sub_brands_handled(self, async_client: AsyncClient, printer_factory):
"""Filaments with empty/missing tray_sub_brands should still be returned."""
await printer_factory(name="Test Printer", model="X1C")
status = _make_mock_status(
ams_data=[
{
"id": 0,
"tray": [
{"id": 0, "tray_type": "PLA", "tray_color": "FF0000FF", "tray_info_idx": "GFL99"},
],
},
]
)
with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
mock_pm.get_status.return_value = status
response = await async_client.get("/api/v1/printers/available-filaments?model=X1C")
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["tray_sub_brands"] == ""
@pytest.mark.asyncio
@pytest.mark.integration
async def test_external_spool_includes_sub_brands(self, async_client: AsyncClient, printer_factory):
"""External spools (vt_tray) should also include tray_sub_brands."""
await printer_factory(name="Test Printer", model="X1C")
status = _make_mock_status(
ams_data=[],
vt_tray=[
{
"id": 254,
"tray_type": "PETG",
"tray_color": "00FF00FF",
"tray_info_idx": "GFG00",
"tray_sub_brands": "PETG HF",
},
],
)
with patch("backend.app.api.routes.printers.printer_manager") as mock_pm:
mock_pm.get_status.return_value = status
response = await async_client.get("/api/v1/printers/available-filaments?model=X1C")
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["tray_sub_brands"] == "PETG HF"
assert data[0]["type"] == "PETG"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_no_printers_returns_empty(self, async_client: AsyncClient):
"""Verify empty list when no printers match the model."""
response = await async_client.get("/api/v1/printers/available-filaments?model=X1C")
assert response.status_code == 200
assert response.json() == []