- dashboard.html: headings, quick actions, empty states wired to x-text - printers.html: h1, add button, h2 wired to x-text - printers_new.html: all form labels (span pattern), options, submit buttons wired - clients.html: headings, label, submit button wired - client_detail.html: back link, section heading wired - drivers.html: all headings, label, buttons, spinner wired - packages.html: heading, description, table headers, empty state wired - printer_detail.html: all dt/dd, section headings, copy buttons, script/export links wired with conditional Jinja2 for color/collate/client - partials/printer_list.html: table headers, Yes/No cells (Alpine ternary), delete button wired - partials/printer_edit_modal.html: edit trigger, modal title, all labels (span pattern), options, save/cancel wired - partials/client_list.html: empty state, table headers wired - partials/driver_list.html: table headers, unknown values, empty state wired - Fix test_printers_library_no_form: check form element attribute instead of UI label string (now in i18n dict)
390 lines
12 KiB
Python
390 lines
12 KiB
Python
"""Integration tests for printer and client CRUD endpoints."""
|
|
import json
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Task 1: RED state — routes do not exist yet, tests should fail
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_create_printer_persisted(client: TestClient) -> None:
|
|
"""POST /printers with required fields returns 303; GET /printers contains printer name."""
|
|
from imptune.db.models import Printer
|
|
|
|
resp = client.post(
|
|
"/printers",
|
|
data={
|
|
"name": "Test Printer",
|
|
"ip_address": "192.168.1.100",
|
|
"port_name": "IP_192_168_1_100",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert resp.status_code == 303
|
|
|
|
# GET /printers page contains the printer name
|
|
page = client.get("/printers")
|
|
assert page.status_code == 200
|
|
assert "Test Printer" in page.text
|
|
|
|
# Verify DB persistence
|
|
count = Printer.select().where(Printer.name == "Test Printer").count()
|
|
assert count == 1
|
|
|
|
|
|
def test_create_printer_duplex(client: TestClient) -> None:
|
|
"""POST /printers with duplex_mode=LongEdge persists correctly."""
|
|
from imptune.db.models import Printer
|
|
|
|
resp = client.post(
|
|
"/printers",
|
|
data={
|
|
"name": "Duplex Printer",
|
|
"ip_address": "192.168.1.101",
|
|
"port_name": "IP_192_168_1_101",
|
|
"duplex_mode": "LongEdge",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert resp.status_code == 303
|
|
|
|
printers = list(Printer.select().where(Printer.name == "Duplex Printer"))
|
|
assert len(printers) == 1
|
|
assert printers[0].duplex_mode == "LongEdge"
|
|
|
|
|
|
def test_create_printer_color_mode(client: TestClient) -> None:
|
|
"""POST /printers with color_mode not sent (unchecked) sets color_mode=False."""
|
|
from imptune.db.models import Printer
|
|
|
|
resp = client.post(
|
|
"/printers",
|
|
data={
|
|
"name": "Mono Printer",
|
|
"ip_address": "192.168.1.102",
|
|
"port_name": "IP_192_168_1_102",
|
|
# color_mode intentionally omitted (unchecked checkbox)
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert resp.status_code == 303
|
|
|
|
printers = list(Printer.select().where(Printer.name == "Mono Printer"))
|
|
assert len(printers) == 1
|
|
assert printers[0].color_mode is False
|
|
|
|
|
|
def test_create_printer_paper_size(client: TestClient) -> None:
|
|
"""POST /printers with paper_size=Letter persists correctly."""
|
|
from imptune.db.models import Printer
|
|
|
|
resp = client.post(
|
|
"/printers",
|
|
data={
|
|
"name": "Letter Printer",
|
|
"ip_address": "192.168.1.103",
|
|
"port_name": "IP_192_168_1_103",
|
|
"paper_size": "Letter",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert resp.status_code == 303
|
|
|
|
printers = list(Printer.select().where(Printer.name == "Letter Printer"))
|
|
assert len(printers) == 1
|
|
assert printers[0].paper_size == "Letter"
|
|
|
|
|
|
def test_create_printer_collate(client: TestClient) -> None:
|
|
"""POST /printers with collate not sent (unchecked) sets collate=False."""
|
|
from imptune.db.models import Printer
|
|
|
|
resp = client.post(
|
|
"/printers",
|
|
data={
|
|
"name": "No Collate Printer",
|
|
"ip_address": "192.168.1.104",
|
|
"port_name": "IP_192_168_1_104",
|
|
# collate intentionally omitted (unchecked checkbox)
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert resp.status_code == 303
|
|
|
|
printers = list(Printer.select().where(Printer.name == "No Collate Printer"))
|
|
assert len(printers) == 1
|
|
assert printers[0].collate is False
|
|
|
|
|
|
def test_create_client(client: TestClient) -> None:
|
|
"""POST /clients with name=Contoso returns 200; GET /clients contains Contoso."""
|
|
resp = client.post("/clients", data={"name": "Contoso"})
|
|
assert resp.status_code == 200
|
|
|
|
page = client.get("/clients")
|
|
assert page.status_code == 200
|
|
assert "Contoso" in page.text
|
|
|
|
|
|
def test_printer_grouped_by_client(client: TestClient) -> None:
|
|
"""Printer assigned to a client appears under that client group heading."""
|
|
from imptune.db.models import Client, Printer
|
|
|
|
# Create client
|
|
resp = client.post("/clients", data={"name": "Contoso"})
|
|
assert resp.status_code == 200
|
|
|
|
contoso_list = list(Client.select().where(Client.name == "Contoso"))
|
|
assert len(contoso_list) == 1
|
|
contoso = contoso_list[0]
|
|
|
|
# Create printer assigned to that client
|
|
resp = client.post(
|
|
"/printers",
|
|
data={
|
|
"name": "Contoso Printer",
|
|
"ip_address": "10.0.0.1",
|
|
"port_name": "IP_10_0_0_1",
|
|
"client_id": str(contoso.id),
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert resp.status_code == 303
|
|
|
|
# GET /printers should show "Contoso" as a group header
|
|
page = client.get("/printers")
|
|
assert page.status_code == 200
|
|
html = page.text
|
|
assert "Contoso" in html
|
|
# Client name should appear in a heading element
|
|
assert "<h3" in html
|
|
|
|
|
|
def test_create_printer_missing_name(client: TestClient) -> None:
|
|
"""POST /printers with empty name returns 400."""
|
|
resp = client.post(
|
|
"/printers",
|
|
data={
|
|
"name": "",
|
|
"ip_address": "192.168.1.100",
|
|
"port_name": "IP_192_168_1_100",
|
|
},
|
|
)
|
|
assert resp.status_code == 400
|
|
|
|
|
|
def test_create_printer_invalid_ip(client: TestClient) -> None:
|
|
"""POST /printers with empty ip_address returns 400."""
|
|
resp = client.post(
|
|
"/printers",
|
|
data={
|
|
"name": "Valid Name",
|
|
"ip_address": "",
|
|
"port_name": "SOME_PORT",
|
|
},
|
|
)
|
|
assert resp.status_code == 400
|
|
|
|
|
|
def test_printer_detail_shows_driver(client: TestClient) -> None:
|
|
"""GET /printers/{id} returns 200 with all printer fields and driver name."""
|
|
from imptune.db.models import Driver, Printer
|
|
|
|
driver_obj = Driver.create(
|
|
sha256="abc123",
|
|
original_filename="hp_universal.zip",
|
|
size_bytes=1000,
|
|
driver_desc=json.dumps(["HP Universal"]),
|
|
)
|
|
printer = Printer.create(
|
|
name="HP Office Printer",
|
|
ip_address="10.0.1.1",
|
|
port_name="IP_10_0_1_1",
|
|
driver=driver_obj,
|
|
)
|
|
|
|
resp = client.get(f"/printers/{printer.id}")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert "HP Office Printer" in html
|
|
assert "10.0.1.1" in html
|
|
assert "HP Universal" in html
|
|
|
|
|
|
def test_printer_detail_not_found(client: TestClient) -> None:
|
|
"""GET /printers/9999 returns 404."""
|
|
resp = client.get("/printers/9999")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
def test_printer_detail_no_driver(client: TestClient) -> None:
|
|
"""GET /printers/{id} for printer with no driver returns 200 with 'No driver assigned'."""
|
|
from imptune.db.models import Printer
|
|
|
|
printer = Printer.create(
|
|
name="Driverless Printer",
|
|
ip_address="10.0.1.2",
|
|
port_name="IP_10_0_1_2",
|
|
driver=None,
|
|
)
|
|
|
|
resp = client.get(f"/printers/{printer.id}")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert "No driver assigned" in html
|
|
|
|
|
|
def test_delete_printer(client: TestClient) -> None:
|
|
"""DELETE /printers/{id} removes the printer; GET /printers no longer shows it."""
|
|
from imptune.db.models import Printer
|
|
|
|
# Create a printer
|
|
resp = client.post(
|
|
"/printers",
|
|
data={
|
|
"name": "To Delete",
|
|
"ip_address": "192.168.1.200",
|
|
"port_name": "IP_192_168_1_200",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert resp.status_code == 303
|
|
|
|
# Find its ID
|
|
printers = list(Printer.select().where(Printer.name == "To Delete"))
|
|
assert len(printers) == 1
|
|
printer_id = printers[0].id
|
|
|
|
# Delete it
|
|
resp = client.delete(f"/printers/{printer_id}")
|
|
assert resp.status_code == 200
|
|
|
|
# Confirm it's gone from the DB
|
|
count = Printer.select().where(Printer.id == printer_id).count()
|
|
assert count == 0
|
|
|
|
# GET /printers should no longer show it
|
|
page = client.get("/printers")
|
|
assert "To Delete" not in page.text
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Wave 0 scaffolds — UIE-01 (PATCH), UIE-02 (separated form/list), UIE-03 (client detail)
|
|
# These tests are RED until plan 01 task 2 and plans 02-03 implement the routes.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_printers_new_returns_200(client: TestClient) -> None:
|
|
"""GET /printers/new returns 200 with the add printer form markup."""
|
|
resp = client.get("/printers/new")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
# Must contain form fields
|
|
assert 'name="name"' in html or "Printer Name" in html
|
|
|
|
|
|
def test_create_printer_redirects(client: TestClient) -> None:
|
|
"""POST /printers (no HX-Request header) returns 303 redirect to /printers."""
|
|
resp = client.post(
|
|
"/printers",
|
|
data={
|
|
"name": "Redirect Printer",
|
|
"ip_address": "192.168.2.1",
|
|
"port_name": "IP_192_168_2_1",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert resp.status_code == 303
|
|
assert resp.headers["location"] == "/printers"
|
|
|
|
|
|
def test_printers_library_no_form(client: TestClient) -> None:
|
|
"""GET /printers does NOT contain the inline add-printer form markup."""
|
|
resp = client.get("/printers")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
# The inline form must be gone — check for the actual form element, not UI label strings
|
|
assert 'hx-post="/printers"' not in html
|
|
assert 'action="/printers" method="post"' not in html
|
|
|
|
|
|
def test_patch_printer(client: TestClient) -> None:
|
|
"""PATCH /printers/{id} with updated name returns 200, updated name in response, DB updated."""
|
|
from imptune.db.models import Printer
|
|
|
|
printer = Printer.create(
|
|
name="Original Name",
|
|
ip_address="10.0.2.1",
|
|
port_name="IP_10_0_2_1",
|
|
)
|
|
|
|
resp = client.patch(
|
|
f"/printers/{printer.id}",
|
|
data={"name": "Updated Name"},
|
|
)
|
|
assert resp.status_code == 200
|
|
assert "Updated Name" in resp.text
|
|
|
|
# Verify DB update
|
|
updated = Printer.get_by_id(printer.id)
|
|
assert updated.name == "Updated Name"
|
|
|
|
|
|
def test_patch_printer_not_found(client: TestClient) -> None:
|
|
"""PATCH /printers/9999 returns 404."""
|
|
resp = client.patch("/printers/9999", data={"name": "Ghost"})
|
|
assert resp.status_code == 404
|
|
|
|
|
|
def test_client_detail_returns_200(client: TestClient) -> None:
|
|
"""GET /clients/{id} returns 200 with client name and assigned printer name."""
|
|
from imptune.db.models import Client, Printer
|
|
|
|
# Create client
|
|
cl = Client.create(name="Detail Client")
|
|
# Create printer assigned to that client
|
|
Printer.create(
|
|
name="Client Printer",
|
|
ip_address="10.0.3.1",
|
|
port_name="IP_10_0_3_1",
|
|
client=cl,
|
|
)
|
|
|
|
resp = client.get(f"/clients/{cl.id}")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
assert "Detail Client" in html
|
|
assert "Client Printer" in html
|
|
|
|
|
|
def test_client_detail_not_found(client: TestClient) -> None:
|
|
"""GET /clients/9999 returns 404."""
|
|
resp = client.get("/clients/9999")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
def test_client_links_in_printer_list(client: TestClient) -> None:
|
|
"""GET /printers with a printer assigned to a client contains href to client detail."""
|
|
from imptune.db.models import Client, Printer
|
|
|
|
# Create client via POST /clients
|
|
resp = client.post("/clients", data={"name": "Link Client"})
|
|
assert resp.status_code == 200
|
|
|
|
cl = Client.get(Client.name == "Link Client")
|
|
|
|
# Create printer assigned to that client
|
|
Printer.create(
|
|
name="Linked Printer",
|
|
ip_address="10.0.4.1",
|
|
port_name="IP_10_0_4_1",
|
|
client=cl,
|
|
)
|
|
|
|
resp = client.get("/printers")
|
|
assert resp.status_code == 200
|
|
assert f'href="/clients/{cl.id}"' in resp.text
|