diff --git a/tests/test_driver_upload.py b/tests/test_driver_upload.py index bce9a88..ceb00a0 100644 --- a/tests/test_driver_upload.py +++ b/tests/test_driver_upload.py @@ -160,3 +160,99 @@ def test_unused_files_in_response(client: TestClient) -> None: assert resp.status_code == 200 # Response should indicate unused files (count or the word "unused") assert "unused" in resp.text.lower() + + +# --------------------------------------------------------------------------- +# Regression + OOB contract tests (Wave 0 additions -- Task 1 of 09-01) +# --------------------------------------------------------------------------- + + +def _make_driver_zip_with_cat( + inf_content: str = SAMPLE_INF, + inf_name: str = "sample.inf", +) -> bytes: + """Build a ZIP with an .inf and a .cat file (has_cat_file=True).""" + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as zf: + zf.writestr(inf_name, inf_content.encode("utf-8")) + zf.writestr(inf_name.replace(".inf", ".cat"), b"fake-cat-content") + return buf.getvalue() + + +def _make_bom_driver_zip() -> bytes: + """Build a ZIP with a UTF-16 LE BOM-encoded .inf (encoding edge case).""" + bom_inf_text = ( + "[Version]\r\nSignature=\"$Windows NT$\"\r\nClass=Printer\r\n\r\n" + "[Manufacturer]\r\n%MFG%=Models,NTamd64\r\n\r\n" + "[Models.NTamd64]\r\n%DRIVER_NAME%=Install,{ABCD1234-0000-0000-0000-000000000001}\r\n\r\n" + "[Strings]\r\nMFG=\"BOM Manufacturer\"\r\nDRIVER_NAME=\"BOM LaserJet 9000\"\r\n" + ) + bom_inf_bytes = b"\xff\xfe" + bom_inf_text.encode("utf-16-le") + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as zf: + zf.writestr("driver.inf", bom_inf_bytes) + zf.writestr("driver.cat", b"fake-catalog") + return buf.getvalue() + + +@pytest.mark.parametrize( + "zip_bytes_fn, label", + [ + (lambda: _make_driver_zip(extra_files={"sample.cat": b"cat"}), "plain_utf8_inf"), + (_make_bom_driver_zip, "bom_utf16le_inf"), + ], +) +def test_upload_500_regression(client: TestClient, zip_bytes_fn, label: str) -> None: + """POST /drivers/upload with a valid driver ZIP MUST return 200, never 500.""" + zip_bytes = zip_bytes_fn() + resp = client.post( + "/drivers/upload", + files={"file": ("driver.zip", zip_bytes, "application/zip")}, + ) + assert resp.status_code != 500, f"[{label}] Upload returned HTTP 500:\n{resp.text}" + assert resp.status_code == 200, f"[{label}] Expected 200, got {resp.status_code}:\n{resp.text}" + + +def test_upload_returns_oob_when_called_from_form(client: TestClient) -> None: + """POST /drivers/upload with caller=printer_form must return OOB swap markup.""" + zip_bytes = _make_driver_zip_with_cat() + resp = client.post( + "/drivers/upload", + files={"file": ("driver.zip", zip_bytes, "application/zip")}, + data={"caller": "printer_form"}, + ) + assert resp.status_code == 200, f"Expected 200, got {resp.status_code}:\n{resp.text}" + assert 'hx-swap-oob="true"' in resp.text, "Response missing hx-swap-oob attribute" + assert 'id="printer-form-driver-select"' in resp.text, "Response missing OOB select id" + + +def test_upload_oob_autoselects_new_driver(client: TestClient) -> None: + """POST /drivers/upload with caller=printer_form must auto-select the new driver.""" + import re + + from imptune.db.models import Driver + + zip_bytes = _make_driver_zip_with_cat() + resp = client.post( + "/drivers/upload", + files={"file": ("driver.zip", zip_bytes, "application/zip")}, + data={"caller": "printer_form"}, + ) + assert resp.status_code == 200, f"Expected 200, got {resp.status_code}:\n{resp.text}" + sha = hashlib.sha256(zip_bytes).hexdigest() + driver = Driver.get(Driver.sha256 == sha) + new_id = driver.id + assert f'value="{new_id}"' in resp.text, f"Driver id={new_id} not found in OOB response" + pattern = rf' None: + """POST /drivers/upload WITHOUT caller field must NOT contain hx-swap-oob.""" + zip_bytes = _make_driver_zip_with_cat() + resp = client.post( + "/drivers/upload", + files={"file": ("driver.zip", zip_bytes, "application/zip")}, + ) + assert resp.status_code == 200, f"Expected 200, got {resp.status_code}:\n{resp.text}" + assert "hx-swap-oob" not in resp.text, "Standalone upload should NOT return OOB fragments"