from __future__ import annotations import pytest from cantarire.repositories import list_packages def test_pages_load(client): for path in ("/", "/componente", "/componente/nou", "/retete", "/retete/nou", "/istoric", "/setari", "/sabloane", "/sabloane/1/designer", "/monitor"): response = client.get(path) assert response.status_code == 200, path def test_html_css_pages_are_gone(client): """Modul HTML/CSS a fost scos; rutele vechi nu mai exista.""" for path in ("/sabloane/nou", "/sabloane/1/editare"): assert client.get(path).status_code == 404, path html = client.get("/sabloane").get_data(as_text=True) assert "HTML/CSS" not in html def test_dashboard_lists_recipes(client, recipe): html = client.get("/").get_data(as_text=True) assert "PF-001" in html assert "Fasole cu carne" in html assert "Începe pachetul" in html or "Pachet nou" in html def test_component_crud(client): created = client.post("/componente/nou", data={ "code": "GAR", "name": "Garnitura", "unit": "g", "active": "on", }) assert created.status_code == 302 listing = client.get("/componente").get_data(as_text=True) assert "GAR" in listing and "Garnitura" in listing duplicate = client.post("/componente/nou", data={"code": "gar", "name": "Alt", "unit": "g"}) assert duplicate.status_code == 200 assert "Exista deja un component" in duplicate.get_data(as_text=True) def test_recipe_has_ordered_components(client, recipe): with client.application.app_context(): from cantarire.repositories import get_recipe stored = get_recipe(recipe["id"]) assert [c["name"] for c in stored["components"]] == ["Fasole", "Carne"] assert [c["target_weight"] for c in stored["components"]] == [250, 200] def test_recipe_requires_a_component(client): response = client.post("/retete/nou", data={ "code": "PF-EMPTY", "name": "Fara componente", "unit": "g", }) assert response.status_code == 200 assert "cel putin un component" in response.get_data(as_text=True) def test_two_step_weighing_completes_and_builds_label(client, recipe): started = client.post("/api/packages", json={"recipe_id": recipe["id"], "lot": "LOT-1"}) assert started.status_code == 201, started.get_json() data = started.get_json() package_id = data["package"]["id"] assert data["step"]["component_name"] == "Fasole" assert data["step"]["position"] == 1 assert data["planned_total"] == 2 first = client.post(f"/api/packages/{package_id}/items", json={"weight": "250"}) assert first.status_code == 201, first.get_json() body = first.get_json() assert body["step"]["component_name"] == "Carne" assert body["step"]["position"] == 2 assert body["total_weight"] == 250 second = client.post(f"/api/packages/{package_id}/items", json={"weight": "200"}) assert second.status_code == 201, second.get_json() done = second.get_json() assert done["complete"] is True assert done["step"] is None assert done["package"]["total_weight"] == 450 assert done["package"]["status"] == "done" label = done["label"] assert label is not None assert "Fasole cu carne" in label["html"] assert "250 g" in label["html"] and "Fasole" in label["html"] assert "200 g" in label["html"] and "Carne" in label["html"] assert label["fields"]["breakdown"] == "250 g Fasole, 200 g Carne" assert label["print_url"] == f"/labels/{package_id}/print" page = client.get(label["print_url"] + "?auto=0") assert page.status_code == 200 html = page.get_data(as_text=True) assert "Fasole cu carne" in html and "450" in html def test_scale_reading_is_used_when_no_weight_is_sent(client, recipe, scale): package_id = client.post("/api/packages", json={"recipe_id": recipe["id"]}).get_json()["package"]["id"] response = client.post(f"/api/packages/{package_id}/items", json={}) assert response.status_code == 201, response.get_json() body = response.get_json() assert body["package"]["items"][0]["weight"] == 250 assert body["package"]["items"][0]["source"] == "scale" assert scale.calls >= 1 def test_unstable_scale_requires_force(client, recipe, scale): scale.reading.update({"stable": False}) package_id = client.post("/api/packages", json={"recipe_id": recipe["id"]}).get_json()["package"]["id"] refused = client.post(f"/api/packages/{package_id}/items", json={}) assert refused.status_code == 422 assert "stabila" in refused.get_json()["error"].lower() forced = client.post(f"/api/packages/{package_id}/items", json={"force": True}) assert forced.status_code == 201 def test_stale_reading_is_rejected(client, recipe, scale): scale.reading.update({"stale": True}) package_id = client.post("/api/packages", json={"recipe_id": recipe["id"]}).get_json()["package"]["id"] response = client.post(f"/api/packages/{package_id}/items", json={"force": True}) assert response.status_code == 409 assert "veche" in response.get_json()["error"].lower() def test_undo_removes_last_component(client, recipe): package_id = client.post("/api/packages", json={"recipe_id": recipe["id"]}).get_json()["package"]["id"] client.post(f"/api/packages/{package_id}/items", json={"weight": "250"}) undone = client.post(f"/api/packages/{package_id}/undo", json={}) assert undone.status_code == 200 body = undone.get_json() assert body["items"] == [] assert body["step"]["position"] == 1 def test_void_package(client, recipe): package_id = client.post("/api/packages", json={"recipe_id": recipe["id"]}).get_json()["package"]["id"] voided = client.post(f"/api/packages/{package_id}/void", json={}) assert voided.status_code == 200 with client.application.app_context(): rows = list_packages(status="void") assert [row["id"] for row in rows] == [package_id] assert client.get("/api/package").get_json()["state"] is None def test_only_one_open_package(client, recipe): client.post("/api/packages", json={"recipe_id": recipe["id"]}) second = client.post("/api/packages", json={"recipe_id": recipe["id"]}) assert second.status_code == 409 assert "deja un pachet" in second.get_json()["error"].lower() def test_weight_is_converted_to_recipe_unit(client, fasole): client.post("/retete/nou", data={ "code": "PF-KG", "name": "Amestec kg", "unit": "kg", "component_1_id": str(fasole), "active": "on", }) with client.application.app_context(): from cantarire.repositories import list_recipes recipe = [r for r in list_recipes() if r["code"] == "PF-KG"][0] package_id = client.post("/api/packages", json={"recipe_id": recipe["id"]}).get_json()["package"]["id"] response = client.post(f"/api/packages/{package_id}/items", json={"weight": "0.25", "unit": "kg"}) assert response.status_code == 201 assert response.get_json()["complete"] is True assert response.get_json()["package"]["total_weight"] == 0.25 def test_invalid_weight(client, recipe): package_id = client.post("/api/packages", json={"recipe_id": recipe["id"]}).get_json()["package"]["id"] response = client.post(f"/api/packages/{package_id}/items", json={"weight": "abc"}) assert response.status_code == 400 def test_history_filter_and_csv(client, recipe): package_id = client.post("/api/packages", json={"recipe_id": recipe["id"], "lot": "LOT-A"}).get_json()["package"]["id"] client.post(f"/api/packages/{package_id}/items", json={"weight": "250"}) client.post(f"/api/packages/{package_id}/items", json={"weight": "200"}) filtered = client.get("/istoric?q=LOT-A").get_data(as_text=True) assert "LOT-A" in filtered assert "Fasole cu carne" in filtered empty = client.get("/istoric?q=NOPE").get_data(as_text=True) assert "Nicio înregistrare" in empty export = client.get("/istoric/export.csv?q=LOT-A") assert export.status_code == 200 assert export.mimetype.startswith("text/csv") body = export.get_data(as_text=True) assert "LOT-A" in body assert "Fasole" in body assert "Cod produs finit" in body def test_template_crud_and_preview(client): preview = client.get("/sabloane/1/previzualizare").get_data(as_text=True) assert "Fasole cu carne" in preview assert "250" in preview and "200" in preview created = client.post("/sabloane/designer/nou", data={ "name": "Etichetă mică", "width_mm": "60", "height_mm": "40", }) assert created.status_code == 302 assert "Etichetă mică" in client.get("/sabloane").get_data(as_text=True) def test_every_template_renders_a_label(client): """Orice sablon produce o eticheta, chiar si unul vechi fara design.""" with client.application.app_context(): from cantarire.db import get_db get_db().execute( "INSERT INTO label_templates(name, width_mm, height_mm, body_html, css," " design_json) VALUES ('Vechi', 100, 60, '
x
', '', '')" ) get_db().commit() html = client.get("/sabloane").get_data(as_text=True) assert "Vechi" in html assert "fără design" in html def test_printed_acknowledgement(client, recipe): package_id = client.post("/api/packages", json={"recipe_id": recipe["id"]}).get_json()["package"]["id"] client.post(f"/api/packages/{package_id}/items", json={"weight": "250"}) client.post(f"/api/packages/{package_id}/items", json={"weight": "200"}) ack = client.post(f"/api/packages/{package_id}/printed") assert ack.status_code == 200 package = ack.get_json()["package"] assert package["print_count"] == 1 assert package["printed_at"] def test_api_scale_status_and_weight(client): status = client.get("/api/scale").get_json() assert status["ok"] is True assert status["profiles"][0]["name"] == "test-scale" reading = client.get("/api/weight").get_json() assert reading["ok"] is True assert reading["value"] == 250.0 def test_qz_tray_assets_are_served(client): lib = client.get("/static/js/vendor/qz-tray.js") assert lib.status_code == 200 assert "PATCHED" in lib.get_data(as_text=True) module = client.get("/static/js/qz-print.js") assert module.status_code == 200 script = module.get_data(as_text=True) assert "window.CantarirePrint" in script assert "qz.printers.find()" in script assert "type: 'pdf'" in script def test_qz_ui_is_wired(client): dashboard = client.get("/").get_data(as_text=True) assert "vendor/qz-tray.js" in dashboard assert "js/qz-print.js" in dashboard assert "data-qz-printer" in dashboard assert "js/weigh.js" in dashboard def test_monitor_page_runs_probe(client, scale): page = client.get("/monitor") assert page.status_code == 200 assert "Monitor serial" in page.get_data(as_text=True) result = client.post("/monitor", data={ "port": "/dev/ttyUSB0", "baud": "9600", "framing": "8N1", "seconds": "2", }) assert result.status_code == 200 html = result.get_data(as_text=True) assert scale.probed == {"port": "/dev/ttyUSB0", "baud": 9600, "framing": "8N1", "seconds": 2.0} assert "toledo_cont" in html def test_monitor_requires_port(client): page = client.post("/monitor", data={"port": "", "baud": "9600", "framing": "8N1"}) assert page.status_code == 200 assert "Alege portul serial" in page.get_data(as_text=True) def test_settings_save(client): response = client.post("/setari", data={ "company_name": "Firma Test", "active_scale_profile": "test-scale", "default_operator": "Maria", "auto_print": "on", }) assert response.status_code == 302 page = client.get("/setari").get_data(as_text=True) assert "Firma Test" in page assert "Maria" in page