mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
Bed levelling, flow calibration, and nozzle-offset calibration were on/off only, so the sole way to run bed levelling was to force a full level before every print. Bambu Studio has always offered a third "Auto" state that lets the printer skip the calibration when it was done recently -- the state most users actually want. Make these three options tri-state (off/on/auto), defaulting to auto, and leave vibration/layer-inspect/timelapse as on/off (Bambu Studio exposes no auto for those). Wire encoding follows Bambu Studio's source exactly: each option sends a JSON bool (true only for "on") plus a companion int -- off=0, on=1, auto=2. The bool fields stay booleans (the #1478 H2S regression); only the companion int widened from {0,1} to {0,1,2}. #1721's observation that stage 8/39 stays queued when sending 2 is the auto contract (queued, skipped at runtime if recent), not a broken "off". - schemas: TriState = Literal[off/on/auto] with a BeforeValidator coercing legacy bool / 0-1 / true-false so old clients and un-migrated rows validate - model + migration: boolean columns -> String; SQLite via column affinity + data backfill, PostgreSQL via ALTER COLUMN TYPE guarded on information_schema (verified on both dialects); settings rows normalised true/false -> on/off - MQTT: start_print takes the tri-state strings and emits the paired bool+int - Virtual Printer: reconstructs the slicer's auto/on/off from the int companion (auto_bed_leveling / extrude_cali_flag) in both capture paths - frontend: CalibrationMode type; off/auto/on segmented controls in the print dialog, queue bulk-edit, and Settings -> Workflow; calibrationMode_* strings in all 11 locales
131 lines
4.4 KiB
Python
131 lines
4.4 KiB
Python
"""Regression tests for the webhook `/printer/{id}/start` route.
|
|
|
|
The previous implementation called `printer_manager.start_print()` directly
|
|
with `queue_item.archive_id` (an int) as the filename arg and no print
|
|
options, and used `await` on a non-async function. That route 500'd on
|
|
every invocation. The fix mirrors `POST /print-queue/{item_id}/start`:
|
|
clear the next pending item's `manual_start` so the scheduler picks it up
|
|
with the queue's stored options (timelapse, bed_levelling, etc.) intact.
|
|
"""
|
|
|
|
import pytest
|
|
from httpx import AsyncClient
|
|
|
|
|
|
@pytest.fixture
|
|
async def api_key_data(async_client: AsyncClient, db_session):
|
|
"""Create an API key with control_printer permission."""
|
|
from backend.app.core.auth import generate_api_key
|
|
from backend.app.models.api_key import APIKey
|
|
|
|
full_key, key_hash, key_prefix = generate_api_key()
|
|
api_key = APIKey(
|
|
name="webhook-test-key",
|
|
key_hash=key_hash,
|
|
key_prefix=key_prefix,
|
|
can_queue=True,
|
|
can_control_printer=True,
|
|
can_read_status=True,
|
|
enabled=True,
|
|
)
|
|
db_session.add(api_key)
|
|
await db_session.commit()
|
|
return full_key
|
|
|
|
|
|
@pytest.fixture
|
|
async def printer_with_queue(db_session):
|
|
"""Create a printer and a pending queue item with manual_start=True."""
|
|
from backend.app.models.print_queue import PrintQueueItem
|
|
from backend.app.models.printer import Printer
|
|
|
|
printer = Printer(
|
|
name="WebhookTest",
|
|
ip_address="192.168.1.42",
|
|
access_code="12345678",
|
|
serial_number="00M00A000000000",
|
|
model="P1S",
|
|
)
|
|
db_session.add(printer)
|
|
await db_session.commit()
|
|
|
|
item = PrintQueueItem(
|
|
printer_id=printer.id,
|
|
position=1,
|
|
status="pending",
|
|
manual_start=True,
|
|
timelapse=True,
|
|
bed_levelling="on",
|
|
flow_cali="off",
|
|
vibration_cali=True,
|
|
layer_inspect=False,
|
|
use_ams=True,
|
|
)
|
|
db_session.add(item)
|
|
await db_session.commit()
|
|
return printer, item
|
|
|
|
|
|
class TestWebhookStartPrint:
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.integration
|
|
async def test_clears_manual_start_on_next_pending_item(
|
|
self, async_client: AsyncClient, db_session, api_key_data, printer_with_queue
|
|
):
|
|
"""The webhook flips manual_start to False so the scheduler picks it up.
|
|
|
|
Pre-fix the route called `printer_manager.start_print()` directly
|
|
with no options and `archive_id` (int) as the filename — 500'd on
|
|
every invocation. Now it mirrors the regular `/print-queue/{id}/start`
|
|
affordance: scheduler dispatch handles FTP upload and all print
|
|
options via the queue's stored fields.
|
|
"""
|
|
printer, item = printer_with_queue
|
|
|
|
resp = await async_client.post(
|
|
f"/api/v1/webhook/printer/{printer.id}/start",
|
|
headers={"X-API-Key": api_key_data},
|
|
)
|
|
|
|
assert resp.status_code == 200, resp.text
|
|
assert resp.json()["queue_item_id"] == item.id
|
|
|
|
await db_session.refresh(item)
|
|
assert item.manual_start is False, "manual_start must be cleared so scheduler dispatches"
|
|
# Stored options must be untouched so the scheduler picks the user's choice.
|
|
assert item.timelapse is True
|
|
assert item.bed_levelling == "on"
|
|
assert item.vibration_cali is True
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.integration
|
|
async def test_returns_404_when_no_pending_items(self, async_client: AsyncClient, db_session, api_key_data):
|
|
from backend.app.models.printer import Printer
|
|
|
|
printer = Printer(
|
|
name="EmptyQueue",
|
|
ip_address="192.168.1.43",
|
|
access_code="12345678",
|
|
serial_number="00M00A000000001",
|
|
model="P1S",
|
|
)
|
|
db_session.add(printer)
|
|
await db_session.commit()
|
|
|
|
resp = await async_client.post(
|
|
f"/api/v1/webhook/printer/{printer.id}/start",
|
|
headers={"X-API-Key": api_key_data},
|
|
)
|
|
|
|
assert resp.status_code == 404
|
|
assert "No pending prints" in resp.json()["detail"]
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.integration
|
|
async def test_returns_404_when_printer_does_not_exist(self, async_client: AsyncClient, api_key_data):
|
|
resp = await async_client.post(
|
|
"/api/v1/webhook/printer/99999/start",
|
|
headers={"X-API-Key": api_key_data},
|
|
)
|
|
assert resp.status_code == 404
|