| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596 |
- """Profile defaults per product (#1326).
- A default holds the slicer preset, the per-model preset overrides and the K
- profiles for one Brand/Material/Subtype, for all colours or for one, and every
- way a spool is added copies it onto the new spool. Both inventory modes; the
- default itself is shared.
- """
- from unittest.mock import AsyncMock, MagicMock, patch
- import pytest
- from httpx import AsyncClient
- from sqlalchemy import select
- DEFAULTS = "/api/v1/inventory/profile-defaults"
- INTERNAL = "/api/v1/inventory/spools"
- SPOOLMAN = "/api/v1/spoolman/inventory"
- def _k(printer_id, *, extruder=0, diameter="0.4", cali_idx=5, k_value=0.02, name="PLA HF"):
- return {
- "printer_id": printer_id,
- "extruder": extruder,
- "nozzle_diameter": diameter,
- "nozzle_type": None,
- "k_value": k_value,
- "name": name,
- "cali_idx": cali_idx,
- "setting_id": None,
- }
- def _preset(model="H2D", diameter="0.4", code="PFUS01", name="My PLA @BBL H2D"):
- return {"printer_model": model, "nozzle_diameter": diameter, "slicer_filament": code, "slicer_filament_name": name}
- def _default(color_name="", **overrides):
- body = {
- "brand": "Bambu Lab",
- "material": "PLA",
- "subtype": "Basic",
- "color_name": color_name,
- "slicer_filament": "PFUS00",
- "slicer_filament_name": "My PLA",
- "presets": [],
- "k_profiles": [],
- }
- body.update(overrides)
- return body
- def _spool_body(**overrides):
- body = {"material": "PLA", "subtype": "Basic", "brand": "Bambu Lab", "color_name": "Jade White"}
- body.update(overrides)
- return body
- @pytest.fixture
- async def printers(printer_factory):
- return [await printer_factory(model="H2D"), await printer_factory(model="X1C")]
- @pytest.mark.integration
- class TestDefaultsCrud:
- @pytest.mark.asyncio
- async def test_put_creates_then_replaces(self, async_client: AsyncClient, printers):
- a, b = printers
- response = await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)], presets=[_preset()]))
- assert response.status_code == 200, response.text
- first = response.json()
- response = await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(b.id)]))
- assert response.status_code == 200, response.text
- second = response.json()
- assert second["id"] == first["id"]
- assert [k["printer_id"] for k in second["k_profiles"]] == [b.id]
- assert second["presets"] == []
- listed = (await async_client.get(DEFAULTS)).json()
- assert len(listed) == 1
- @pytest.mark.asyncio
- async def test_product_matches_case_insensitively(self, async_client: AsyncClient):
- await async_client.put(DEFAULTS, json=_default())
- await async_client.put(DEFAULTS, json=_default(brand="bambu lab", material="pla", subtype="basic"))
- assert len((await async_client.get(DEFAULTS)).json()) == 1
- @pytest.mark.asyncio
- async def test_duplicate_hotend_is_refused_before_anything_changes(self, async_client: AsyncClient, printers):
- a, _ = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)]))
- response = await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id), _k(a.id, cali_idx=7)]))
- assert response.status_code == 422
- kept = (await async_client.get(DEFAULTS)).json()[0]
- assert [k["cali_idx"] for k in kept["k_profiles"]] == [5]
- @pytest.mark.asyncio
- async def test_unknown_printer_is_refused(self, async_client: AsyncClient):
- response = await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(999)]))
- assert response.status_code == 404
- @pytest.mark.asyncio
- async def test_delete(self, async_client: AsyncClient):
- row = (await async_client.put(DEFAULTS, json=_default())).json()
- assert (await async_client.delete(f"{DEFAULTS}/{row['id']}")).status_code == 200
- assert (await async_client.get(DEFAULTS)).json() == []
- assert (await async_client.delete(f"{DEFAULTS}/{row['id']}")).status_code == 404
- @pytest.mark.asyncio
- async def test_deleting_a_printer_takes_its_k_profiles_with_it(self, async_client: AsyncClient, printers):
- a, b = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id), _k(b.id)]))
- assert (await async_client.delete(f"/api/v1/printers/{a.id}")).status_code == 200
- kept = (await async_client.get(DEFAULTS)).json()[0]
- assert [k["printer_id"] for k in kept["k_profiles"]] == [b.id]
- @pytest.mark.integration
- class TestResolve:
- @pytest.mark.asyncio
- async def test_no_default(self, async_client: AsyncClient):
- response = await async_client.get(f"{DEFAULTS}/resolve", params={"material": "PLA"})
- assert response.status_code == 200
- assert response.json() is None
- @pytest.mark.asyncio
- async def test_all_colours_default_covers_every_colour(self, async_client: AsyncClient, printers):
- a, _ = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)]))
- resolved = (await async_client.get(f"{DEFAULTS}/resolve", params=_spool_body(color_name="Never Seen"))).json()
- assert resolved["slicer_filament"] == "PFUS00"
- assert [k["cali_idx"] for k in resolved["k_profiles"]] == [5]
- @pytest.mark.asyncio
- async def test_colour_wins_per_hotend_and_keeps_the_rest(self, async_client: AsyncClient, printers):
- a, b = printers
- await async_client.put(
- DEFAULTS,
- json=_default(
- k_profiles=[_k(a.id, extruder=0), _k(a.id, extruder=1), _k(b.id)],
- presets=[_preset("H2D"), _preset("X1C", code="PFUS02")],
- ),
- )
- await async_client.put(
- DEFAULTS,
- json=_default(
- "Jade White",
- slicer_filament=None,
- k_profiles=[_k(a.id, extruder=1, cali_idx=9, k_value=0.031)],
- presets=[_preset("X1C", code="PFUS09")],
- ),
- )
- resolved = (await async_client.get(f"{DEFAULTS}/resolve", params=_spool_body())).json()
- by_hotend = {(k["printer_id"], k["extruder"]): k["cali_idx"] for k in resolved["k_profiles"]}
- assert by_hotend == {(a.id, 0): 5, (a.id, 1): 9, (b.id, 0): 5}
- by_model = {p["printer_model"]: p["slicer_filament"] for p in resolved["presets"]}
- assert by_model == {"H2D": "PFUS01", "X1C": "PFUS09"}
- # The colour row sets no preset of its own, so the product's stays.
- assert resolved["slicer_filament"] == "PFUS00"
- @pytest.mark.asyncio
- async def test_other_products_are_not_matched(self, async_client: AsyncClient):
- await async_client.put(DEFAULTS, json=_default())
- for params in (_spool_body(subtype="Matte"), _spool_body(brand="Polymaker"), _spool_body(material="PETG")):
- assert (await async_client.get(f"{DEFAULTS}/resolve", params=params)).json() is None
- @pytest.mark.integration
- class TestInternalCreate:
- @pytest.mark.asyncio
- async def test_new_spool_gets_the_default(self, async_client: AsyncClient, printers):
- a, _ = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)], presets=[_preset()]))
- spool = (await async_client.post(INTERNAL, json=_spool_body())).json()
- assert spool["slicer_filament"] == "PFUS00"
- assert spool["slicer_filament_name"] == "My PLA"
- assert [(k["printer_id"], k["cali_idx"]) for k in spool["k_profiles"]] == [(a.id, 5)]
- presets = (await async_client.get(f"{INTERNAL}/{spool['id']}/filament-presets")).json()
- assert [p["slicer_filament"] for p in presets] == ["PFUS01"]
- @pytest.mark.asyncio
- async def test_a_preset_in_the_request_is_kept(self, async_client: AsyncClient):
- await async_client.put(DEFAULTS, json=_default())
- spool = (
- await async_client.post(
- INTERNAL, json=_spool_body(slicer_filament="GFA00", slicer_filament_name="Bambu PLA Basic")
- )
- ).json()
- assert spool["slicer_filament"] == "GFA00"
- @pytest.mark.asyncio
- async def test_bulk_create_gives_every_copy_the_default(self, async_client: AsyncClient, printers):
- a, _ = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)]))
- response = await async_client.post(f"{INTERNAL}/bulk", json={"spool": _spool_body(), "quantity": 3})
- assert response.status_code == 200, response.text
- spools = response.json()
- assert len(spools) == 3
- assert all([k["cali_idx"] for k in s["k_profiles"]] == [5] for s in spools)
- assert all(s["slicer_filament"] == "PFUS00" for s in spools)
- @pytest.mark.asyncio
- async def test_without_a_default_nothing_is_added(self, async_client: AsyncClient):
- spool = (await async_client.post(INTERNAL, json=_spool_body())).json()
- assert spool["slicer_filament"] is None
- assert spool["k_profiles"] == []
- @pytest.mark.asyncio
- async def test_rfid_auto_create_keeps_the_tag_preset_and_assigns_the_default_k(self, db_session, printers):
- from backend.app.models.filament_profile_default import (
- FilamentProfileDefault,
- FilamentProfileDefaultKProfile,
- )
- from backend.app.services.spool_tag_matcher import create_spool_from_tray
- a, _ = printers
- row = FilamentProfileDefault(
- match_key="bambu lab\x1fpla\x1fbasic\x1f",
- brand="Bambu Lab",
- material="PLA",
- subtype="Basic",
- slicer_filament="PFUS00",
- )
- db_session.add(row)
- await db_session.flush()
- db_session.add(FilamentProfileDefaultKProfile(profile_default_id=row.id, **_k(a.id)))
- await db_session.commit()
- spool = await create_spool_from_tray(
- db_session,
- {
- "tray_type": "PLA",
- "tray_sub_brands": "PLA Basic",
- "tray_color": "FFFFFFFF",
- "tray_info_idx": "GFA00",
- "tray_weight": "1000",
- "remain": 100,
- "tag_uid": "A1B2C3D4E5F60708",
- "tray_uuid": "0123456789ABCDEF0123456789ABCDEF",
- },
- )
- assert spool.slicer_filament == "GFA00"
- # In the in-memory list the RFID assign right after reads.
- assert [k.cali_idx for k in spool.k_profiles] == [5]
- @pytest.mark.integration
- class TestInternalApply:
- @pytest.mark.asyncio
- async def test_fills_only_what_is_missing(self, async_client: AsyncClient, printers):
- a, b = printers
- # Spools first, so the create path has no default to apply.
- bare = (await async_client.post(INTERNAL, json=_spool_body())).json()
- own = (await async_client.post(INTERNAL, json=_spool_body(slicer_filament="GFA00"))).json()
- await async_client.put(f"{INTERNAL}/{own['id']}/k-profiles", json=[_k(a.id, cali_idx=11, k_value=0.05)])
- other = (await async_client.post(INTERNAL, json=_spool_body(subtype="Matte"))).json()
- archived = (await async_client.post(INTERNAL, json=_spool_body())).json()
- await async_client.post(f"{INTERNAL}/{archived['id']}/archive")
- row = (await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id), _k(b.id)]))).json()
- response = await async_client.post(f"{DEFAULTS}/{row['id']}/apply")
- assert response.status_code == 200, response.text
- assert response.json() == {"updated": 2}
- def k_of(spool):
- return {k["printer_id"]: k["cali_idx"] for k in spool["k_profiles"]}
- bare = (await async_client.get(f"{INTERNAL}/{bare['id']}")).json()
- assert bare["slicer_filament"] == "PFUS00"
- assert k_of(bare) == {a.id: 5, b.id: 5}
- own = (await async_client.get(f"{INTERNAL}/{own['id']}")).json()
- assert own["slicer_filament"] == "GFA00"
- assert k_of(own) == {a.id: 11, b.id: 5}
- assert (await async_client.get(f"{INTERNAL}/{other['id']}")).json()["k_profiles"] == []
- assert (await async_client.get(f"{INTERNAL}/{archived['id']}")).json()["k_profiles"] == []
- # Nothing left to fill.
- assert (await async_client.post(f"{DEFAULTS}/{row['id']}/apply")).json() == {"updated": 0}
- @pytest.mark.asyncio
- async def test_all_colours_apply_gives_a_colour_its_own_values(self, async_client: AsyncClient, printers):
- a, _ = printers
- white = (await async_client.post(INTERNAL, json=_spool_body())).json()
- black = (await async_client.post(INTERNAL, json=_spool_body(color_name="Black"))).json()
- row = (await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)]))).json()
- await async_client.put(DEFAULTS, json=_default("Jade White", k_profiles=[_k(a.id, cali_idx=9)]))
- assert (await async_client.post(f"{DEFAULTS}/{row['id']}/apply")).json() == {"updated": 2}
- white = (await async_client.get(f"{INTERNAL}/{white['id']}")).json()
- black = (await async_client.get(f"{INTERNAL}/{black['id']}")).json()
- assert [k["cali_idx"] for k in white["k_profiles"]] == [9]
- assert [k["cali_idx"] for k in black["k_profiles"]] == [5]
- # ── Spoolman mode ────────────────────────────────────────────────────────────
- def _spoolman_spool(spool_id, *, extra=None, name="PLA Basic", archived=False):
- return {
- "id": spool_id,
- "filament": {
- "id": 7,
- "name": name,
- "material": "PLA",
- "color_hex": "FFFFFF",
- "weight": 1000,
- "vendor": {"id": 3, "name": "Bambu Lab"},
- },
- "remaining_weight": 1000.0,
- "used_weight": 0.0,
- "location": None,
- "comment": None,
- "first_used": None,
- "last_used": None,
- "registered": "2024-01-01T00:00:00+00:00",
- "archived": archived,
- "price": None,
- "extra": extra or {},
- }
- @pytest.fixture
- def spoolman_client():
- client = MagicMock()
- client.base_url = "http://localhost:7912"
- client.health_check = AsyncMock(return_value=True)
- ids = iter(range(100, 200))
- client.create_spool = AsyncMock(side_effect=lambda **kwargs: _spoolman_spool(next(ids)))
- client.find_or_create_filament = AsyncMock(return_value=7)
- client.find_or_create_vendor = AsyncMock(return_value=3)
- client.ensure_extra_field = AsyncMock(return_value=True)
- client.merge_spool_extra = AsyncMock(side_effect=lambda spool_id, extra: _spoolman_spool(spool_id, extra=extra))
- with patch("backend.app.api.routes.spoolman_inventory._get_client", AsyncMock(return_value=client)):
- yield client
- async def _spoolman_rows(db_session, spool_id):
- from backend.app.models.spool_filament_preset import SpoolmanFilamentPreset
- from backend.app.models.spoolman_k_profile import SpoolmanKProfile
- k = (
- (await db_session.execute(select(SpoolmanKProfile).where(SpoolmanKProfile.spoolman_spool_id == spool_id)))
- .scalars()
- .all()
- )
- presets = (
- (
- await db_session.execute(
- select(SpoolmanFilamentPreset).where(SpoolmanFilamentPreset.spoolman_spool_id == spool_id)
- )
- )
- .scalars()
- .all()
- )
- return [r.cali_idx for r in k], [p.slicer_filament for p in presets]
- @pytest.mark.integration
- class TestSpoolman:
- @pytest.mark.asyncio
- async def test_create_gets_the_default(self, async_client: AsyncClient, db_session, printers, spoolman_client):
- a, _ = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)], presets=[_preset()]))
- response = await async_client.post(f"{SPOOLMAN}/spools", json=_spool_body())
- assert response.status_code == 200, response.text
- assert response.json()["slicer_filament"] == "PFUS00"
- extra = spoolman_client.merge_spool_extra.call_args.args[1]
- assert extra["bambu_slicer_filament"] == '"PFUS00"'
- assert extra["bambu_color_name"] == '"Jade White"'
- assert await _spoolman_rows(db_session, 100) == ([5], ["PFUS01"])
- @pytest.mark.asyncio
- async def test_bulk_create_writes_preset_colour_and_default_on_every_copy(
- self, async_client: AsyncClient, db_session, printers, spoolman_client
- ):
- a, _ = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)]))
- response = await async_client.post(f"{SPOOLMAN}/spools/bulk", json={"spool": _spool_body(), "quantity": 2})
- assert response.status_code == 200, response.text
- # Bulk wrote neither the preset nor the colour name before.
- written = {c.args[0]: c.args[1] for c in spoolman_client.merge_spool_extra.call_args_list}
- assert set(written) == {100, 101}
- assert all(e["bambu_color_name"] == '"Jade White"' for e in written.values())
- assert all(e["bambu_slicer_filament"] == '"PFUS00"' for e in written.values())
- for spool_id in (100, 101):
- assert (await _spoolman_rows(db_session, spool_id))[0] == [5]
- @pytest.mark.asyncio
- async def test_apply_fills_spoolman_spools(self, async_client: AsyncClient, db_session, printers, spoolman_client):
- a, _ = printers
- spoolman_client.get_all_spools = AsyncMock(
- return_value=[
- _spoolman_spool(1, extra={"bambu_color_name": '"Jade White"'}),
- _spoolman_spool(2, extra={"bambu_slicer_filament": '"GFA00"'}),
- _spoolman_spool(3, name="PLA Matte"),
- _spoolman_spool(4, archived=True),
- ]
- )
- from backend.app.models.spoolman_k_profile import SpoolmanKProfile
- # Spool 2 has its own K profile for the hotend the default covers.
- db_session.add(SpoolmanKProfile(spoolman_spool_id=2, **_k(a.id, cali_idx=11)))
- await db_session.commit()
- row = (await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)]))).json()
- response = await async_client.post(f"{SPOOLMAN}/profile-defaults/{row['id']}/apply")
- assert response.status_code == 200, response.text
- # Spool 2 has both its preset and its K profile: nothing to fill.
- assert response.json() == {"updated": 1}
- assert (await _spoolman_rows(db_session, 1))[0] == [5]
- assert (await _spoolman_rows(db_session, 2))[0] == [11]
- assert (await _spoolman_rows(db_session, 3))[0] == []
- assert (await _spoolman_rows(db_session, 4))[0] == []
- # Only the spool without a preset of its own gets the default's.
- assert [c.args[0] for c in spoolman_client.merge_spool_extra.call_args_list] == [1]
- @pytest.mark.asyncio
- async def test_ams_auto_add_gets_k_profiles(self, async_client: AsyncClient, db_session, printers):
- from backend.app.services.filament_profile_default import fill_auto_added_spoolman_spool
- a, _ = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)], presets=[_preset()]))
- await fill_auto_added_spoolman_spool(_spoolman_spool(55))
- assert await _spoolman_rows(db_session, 55) == ([5], ["PFUS01"])
- @pytest.fixture
- def limited_to(request):
- """Run the request as a caller limited to some printers (#1727)."""
- from backend.app.core.auth import get_printer_scope_if_auth_enabled
- from backend.app.core.printer_scope import PrinterScope
- from backend.app.main import app
- def _limit(*printer_ids):
- app.dependency_overrides[get_printer_scope_if_auth_enabled] = lambda: PrinterScope(frozenset(printer_ids))
- yield _limit
- app.dependency_overrides.pop(get_printer_scope_if_auth_enabled, None)
- @pytest.mark.integration
- class TestPrinterScope:
- @pytest.mark.asyncio
- async def test_a_limited_caller_sees_only_its_printers(self, async_client: AsyncClient, printers, limited_to):
- a, b = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id), _k(b.id)]))
- limited_to(b.id)
- listed = (await async_client.get(DEFAULTS)).json()[0]
- assert [k["printer_id"] for k in listed["k_profiles"]] == [b.id]
- resolved = (await async_client.get(f"{DEFAULTS}/resolve", params=_spool_body())).json()
- assert [k["printer_id"] for k in resolved["k_profiles"]] == [b.id]
- @pytest.mark.asyncio
- async def test_a_limited_save_keeps_the_printers_it_cannot_see(
- self, async_client: AsyncClient, printers, limited_to
- ):
- a, b = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id), _k(b.id)]))
- limited_to(b.id)
- response = await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(b.id, cali_idx=8)]))
- assert response.status_code == 200, response.text
- limited_to(a.id, b.id)
- kept = (await async_client.get(DEFAULTS)).json()[0]
- assert {k["printer_id"]: k["cali_idx"] for k in kept["k_profiles"]} == {a.id: 5, b.id: 8}
- @pytest.mark.asyncio
- async def test_a_limited_caller_cannot_write_another_printer(self, async_client: AsyncClient, printers, limited_to):
- # The row is ignored, not refused: a form opened on a spool carries
- # every printer's profile, and saving it as the default must work.
- a, b = printers
- limited_to(b.id)
- response = await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id), _k(b.id)]))
- assert response.status_code == 200, response.text
- limited_to(a.id, b.id)
- kept = (await async_client.get(DEFAULTS)).json()[0]
- assert [k["printer_id"] for k in kept["k_profiles"]] == [b.id]
- @pytest.mark.asyncio
- async def test_a_limited_caller_cannot_delete_another_printers_profiles(
- self, async_client: AsyncClient, printers, limited_to
- ):
- a, b = printers
- row = (await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)]))).json()
- limited_to(b.id)
- assert (await async_client.delete(f"{DEFAULTS}/{row['id']}")).status_code == 403
- @pytest.mark.integration
- class TestInternalPresetFill:
- @pytest.mark.asyncio
- async def test_a_spool_keeps_its_own_preset_for_a_model(self, async_client: AsyncClient):
- spool = (await async_client.post(INTERNAL, json=_spool_body())).json()
- await async_client.put(f"{INTERNAL}/{spool['id']}/filament-presets", json=[_preset("H2D", code="OWN01")])
- row = (
- await async_client.put(DEFAULTS, json=_default(presets=[_preset("H2D"), _preset("X1C", code="PFUS02")]))
- ).json()
- assert (await async_client.post(f"{DEFAULTS}/{row['id']}/apply")).json() == {"updated": 1}
- presets = (await async_client.get(f"{INTERNAL}/{spool['id']}/filament-presets")).json()
- assert {p["printer_model"]: p["slicer_filament"] for p in presets} == {"H2D": "OWN01", "X1C": "PFUS02"}
- @pytest.mark.integration
- class TestNullKeyFields:
- @pytest.mark.asyncio
- async def test_null_brand_subtype_and_colour_read_as_empty(self, async_client: AsyncClient):
- response = await async_client.put(DEFAULTS, json=_default(brand=None, subtype=None, color_name=None))
- assert response.status_code == 200, response.text
- assert {k: response.json()[k] for k in ("brand", "subtype", "color_name")} == {
- "brand": "",
- "subtype": "",
- "color_name": "",
- }
- @pytest.mark.asyncio
- async def test_null_material_is_refused_not_a_500(self, async_client: AsyncClient):
- assert (await async_client.put(DEFAULTS, json=_default(material=None))).status_code == 422
- @pytest.mark.integration
- class TestReviewFixes:
- @pytest.mark.asyncio
- async def test_a_non_ascii_colour_matches_its_own_row(self, async_client: AsyncClient, printers):
- # SQLite's lower() folds ASCII only.
- a, _ = printers
- await async_client.put(DEFAULTS, json=_default("Красный", k_profiles=[_k(a.id, cali_idx=9)]))
- response = await async_client.put(DEFAULTS, json=_default("Красный", k_profiles=[_k(a.id, cali_idx=8)]))
- assert response.status_code == 200, response.text
- assert len((await async_client.get(DEFAULTS)).json()) == 1
- resolved = (await async_client.get(f"{DEFAULTS}/resolve", params=_spool_body(color_name="Красный"))).json()
- assert [k["cali_idx"] for k in resolved["k_profiles"]] == [8]
- @pytest.mark.asyncio
- async def test_a_limited_k_profile_save_keeps_the_other_printers(
- self, async_client: AsyncClient, printers, limited_to
- ):
- a, b = printers
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id), _k(b.id)]))
- spool = (await async_client.post(INTERNAL, json=_spool_body())).json()
- limited_to(b.id)
- # The limited form only ever saw printer b.
- response = await async_client.put(f"{INTERNAL}/{spool['id']}/k-profiles", json=[_k(b.id, cali_idx=7)])
- assert response.status_code == 200, response.text
- limited_to(a.id, b.id)
- spool = (await async_client.get(f"{INTERNAL}/{spool['id']}")).json()
- assert {k["printer_id"]: k["cali_idx"] for k in spool["k_profiles"]} == {a.id: 5, b.id: 7}
- @pytest.mark.asyncio
- async def test_a_limited_spoolman_k_profile_save_keeps_the_other_printers(
- self, async_client: AsyncClient, db_session, printers, limited_to, spoolman_client
- ):
- from backend.app.models.spoolman_k_profile import SpoolmanKProfile
- a, b = printers
- spoolman_client.get_spool = AsyncMock(return_value=_spoolman_spool(9))
- db_session.add(SpoolmanKProfile(spoolman_spool_id=9, **_k(a.id)))
- db_session.add(SpoolmanKProfile(spoolman_spool_id=9, **_k(b.id)))
- await db_session.commit()
- limited_to(b.id)
- response = await async_client.put(f"{SPOOLMAN}/spools/9/k-profiles", json=[_k(b.id, cali_idx=7)])
- assert response.status_code == 200, response.text
- rows = (
- (await db_session.execute(select(SpoolmanKProfile).where(SpoolmanKProfile.spoolman_spool_id == 9)))
- .scalars()
- .all()
- )
- assert {r.printer_id: r.cali_idx for r in rows} == {a.id: 5, b.id: 7}
- @pytest.mark.asyncio
- @pytest.mark.parametrize("include_slicer", [True, False])
- async def test_an_auto_added_spoolman_spool_gets_the_preset_only_without_one_of_its_own(
- self, async_client: AsyncClient, include_slicer
- ):
- from backend.app.services.filament_profile_default import fill_auto_added_spoolman_spool
- await async_client.put(DEFAULTS, json=_default())
- client = MagicMock()
- client.ensure_extra_field = AsyncMock(return_value=True)
- client.merge_spool_extra = AsyncMock(return_value={})
- await fill_auto_added_spoolman_spool(_spoolman_spool(56), client, include_slicer=include_slicer)
- if include_slicer:
- client.merge_spool_extra.assert_awaited_once()
- assert client.merge_spool_extra.call_args.args[1]["bambu_slicer_filament"] == '"PFUS00"'
- else:
- client.merge_spool_extra.assert_not_awaited()
- @pytest.mark.asyncio
- async def test_apply_folds_non_ascii_case_the_same_way_as_the_lookup(self, async_client: AsyncClient, printers):
- a, _ = printers
- spool = (await async_client.post(INTERNAL, json=_spool_body(color_name="красный"))).json()
- row = (await async_client.put(DEFAULTS, json=_default("Красный", k_profiles=[_k(a.id, cali_idx=9)]))).json()
- await async_client.put(DEFAULTS, json=_default(k_profiles=[_k(a.id)]))
- assert (await async_client.post(f"{DEFAULTS}/{row['id']}/apply")).json() == {"updated": 1}
- spool = (await async_client.get(f"{INTERNAL}/{spool['id']}")).json()
- assert [k["cali_idx"] for k in spool["k_profiles"]] == [9]
|