diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..e9ec56d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,30 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + pytest: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + + - name: Install DejaVu fonts + # Matches the Dockerfile; the swatch renderer prefers DejaVu for umlauts. + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends fonts-dejavu-core + + - name: Install dependencies + run: pip install -r requirements-dev.txt + + - name: Run tests + run: pytest -q diff --git a/handlers.py b/handlers.py index 6a30ab0..435541f 100644 --- a/handlers.py +++ b/handlers.py @@ -966,7 +966,7 @@ async def _await_slice(job_id, timeout=300, interval=4): "failed": "❌", "skipped": "⏭️", "cancelled": "🚫", } # Finished statuses hidden from !liste — they just pile up and clutter the view. -_DONE_QUEUE_STATUS = {"completed", "cancelled", "skipped"} +_DONE_QUEUE_STATUS = {"completed", "cancelled", "skipped", "failed"} async def _skip(group_id): @@ -1063,7 +1063,12 @@ async def _list(group_id): # didn't queue (e.g. a Bambu Studio print — we don't know). e = ejects.get(it.get("id")) tag = eject_tag if e else (noeject_tag if e is False else "") - lines.append(f'{i}. {_STATUS_EMOJI.get(st, "")} {nm} ({st}){tag}'.replace(" ", " ")) + # Sliced print time in seconds (verified against the real Bambuddy + # /queue/ payload — the field is always `print_time_seconds`), shown as + # e.g. ' · 21 min'; omitted when the queue item doesn't carry it. + secs = it.get("print_time_seconds") + dur = f" · {colors._fmt_minutes(secs)}" if secs else "" + lines.append(f'{i}. {_STATUS_EMOJI.get(st, "")} {nm} ({st}){dur}{tag}'.replace(" ", " ")) await signal_client.send_to_group(group_id, i18n.t(lang, "list_header") + "\n".join(lines)) diff --git a/tests/test_list.py b/tests/test_list.py new file mode 100644 index 0000000..64b6748 --- /dev/null +++ b/tests/test_list.py @@ -0,0 +1,50 @@ +import asyncio +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) +import config # noqa: E402 +import handlers # noqa: E402 +import store # noqa: E402 + + +def _setup(tmp_path, monkeypatch, queue_items): + config.DB_PATH = str(tmp_path / "t.db") + store.init_db() + sent = [] + + async def fake_send(group_id, msg, **k): + sent.append(msg) + + async def fake_list_queue(): + return queue_items + + monkeypatch.setattr(handlers.signal_client, "send_to_group", fake_send) + monkeypatch.setattr(handlers.bambuddy, "list_queue", fake_list_queue) + return sent + + +def test_list_shows_print_duration(tmp_path, monkeypatch): + sent = _setup(tmp_path, monkeypatch, [ + {"id": 1, "status": "pending", "library_file_name": "Quick", + "print_time_seconds": 1267}, # 21 min + {"id": 2, "status": "pending", "library_file_name": "Long", + "print_time_seconds": 7800}, # 2h10 + {"id": 3, "status": "pending", "library_file_name": "Unknown"}, # no time + ]) + asyncio.run(handlers._list("g1")) + out = sent[-1] + assert "Quick (pending) · 21 min" in out + assert "Long (pending) · 2h10" in out + # No duration field → nothing appended after the status. + assert "Unknown (pending)\n" in out or out.rstrip().endswith("Unknown (pending)") + + +def test_list_done_jobs_hidden(tmp_path, monkeypatch): + sent = _setup(tmp_path, monkeypatch, [ + {"id": 1, "status": "failed", "library_file_name": "Boom"}, + {"id": 2, "status": "completed", "library_file_name": "Done"}, + ]) + asyncio.run(handlers._list("g1")) + assert "Boom" not in sent[-1] + assert "Done" not in sent[-1]