bff6d70b17
Aplicatie Flask + SQLite pentru productie alimentara: cantarirea componentelor unui produs finit si tiparirea etichetei prin QZ Tray. - produse finite (retete) formate din componente cantarite - designer vizual de etichete (mm/px, zona sigura, campuri variabile, cod QR cu continut din mai multe bucati) - legare produs finit -> sablon de eticheta - navigare pe doua taburi: Cantarire / Setari - administrare: componente, produse finite, sabloane, campuri, istoric - backup, resetare baza de date, checklist de pregatire, /healthz - CLI de administrare si serviciu systemd pentru productie - vendor/syncpulse-scale-bridge (GPL-3.0-or-later) pentru citirea cantarului
132 lines
3.6 KiB
Python
132 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from cantarire import create_app # noqa: E402
|
|
|
|
STABLE_READING = {
|
|
"ok": True,
|
|
"error": None,
|
|
"profile": "test-scale",
|
|
"port": "COM1",
|
|
"protocol": "mt_sics",
|
|
"open": True,
|
|
"value": 250.0,
|
|
"unit": "g",
|
|
"stable": True,
|
|
"stale": False,
|
|
"overload": False,
|
|
"age_ms": 120,
|
|
"measured_at": "2026-10-18T10:00:00.000+03:00",
|
|
"raw_hex": "53 20 53 20 20 20 20 32 35 30 2E 30",
|
|
"fields": {},
|
|
}
|
|
|
|
|
|
class FakeScale:
|
|
available = True
|
|
error = None
|
|
|
|
def __init__(self):
|
|
self.reading = dict(STABLE_READING)
|
|
self.calls = 0
|
|
self.probed = None
|
|
|
|
def profiles(self):
|
|
return [{"name": "test-scale", "label": "Cântar test", "port": "COM1", "protocol": "mt_sics"}]
|
|
|
|
def read(self, profile_name="", **_kwargs):
|
|
self.calls += 1
|
|
return dict(self.reading)
|
|
|
|
def probe(self, port, baud=9600, framing="8N1", seconds=5.0):
|
|
self.probed = {"port": port, "baud": baud, "framing": framing, "seconds": seconds}
|
|
return {
|
|
"ok": True,
|
|
"port": port,
|
|
"settings": f"{port} {baud} {framing}, listen only",
|
|
"baud": baud,
|
|
"framing": framing,
|
|
"seconds": seconds,
|
|
"error": None,
|
|
"bytes": 13,
|
|
"frames": [{"age_ms": 10, "hex": "02 2D 30 20 30 30 30 30 30 30 30 30 30 30 0D",
|
|
"text": "<STX>-0 000000000000<CR>", "size": 13}],
|
|
"guesses": [{"protocol": "toledo_cont", "name": "Toledo Continuous",
|
|
"kind": "continuous", "count": 2, "value": 12.345,
|
|
"unit": "kg", "stable": True, "specificity": 3}],
|
|
}
|
|
|
|
|
|
@pytest.fixture()
|
|
def app(tmp_path):
|
|
application = create_app({
|
|
"TESTING": True,
|
|
"INSTANCE_DIR": tmp_path,
|
|
"DATABASE": tmp_path / "test.db",
|
|
"BRIDGE_CONFIG": tmp_path / "bridge.json",
|
|
"SIMULATE": "",
|
|
"AUTO_PRINT": False,
|
|
"SECRET_KEY": "test",
|
|
})
|
|
application.extensions["scale"] = FakeScale()
|
|
return application
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(app):
|
|
return app.test_client()
|
|
|
|
|
|
@pytest.fixture()
|
|
def scale(app):
|
|
return app.extensions["scale"]
|
|
|
|
|
|
def _create_component(client, code: str, name: str, unit: str = "g") -> int:
|
|
response = client.post("/componente/nou", data={
|
|
"code": code, "name": name, "unit": unit, "active": "on",
|
|
})
|
|
assert response.status_code == 302, response.get_data(as_text=True)
|
|
with client.application.app_context():
|
|
from cantarire.repositories import get_component_by_code
|
|
|
|
return get_component_by_code(code)["id"]
|
|
|
|
|
|
@pytest.fixture()
|
|
def fasole(client):
|
|
return _create_component(client, "FAS", "Fasole")
|
|
|
|
|
|
@pytest.fixture()
|
|
def carne(client):
|
|
return _create_component(client, "CAR", "Carne")
|
|
|
|
|
|
@pytest.fixture()
|
|
def recipe(client, fasole, carne):
|
|
response = client.post("/retete/nou", data={
|
|
"code": "PF-001",
|
|
"name": "Fasole cu carne",
|
|
"unit": "g",
|
|
"target_weight": "450",
|
|
"component_1_id": str(fasole),
|
|
"component_1_target": "250",
|
|
"component_2_id": str(carne),
|
|
"component_2_target": "200",
|
|
"active": "on",
|
|
})
|
|
assert response.status_code == 302, response.get_data(as_text=True)
|
|
with client.application.app_context():
|
|
from cantarire.repositories import list_recipes
|
|
|
|
return list_recipes()[0]
|