Initial commit: aplicatie de cantarire si etichetare
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
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
"""Pregatire pentru productie: backup, reset, verificare, healthcheck."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from cantarire import maintenance
|
||||
|
||||
|
||||
def test_status_reports_counts_and_integrity(client):
|
||||
with client.application.app_context():
|
||||
status = maintenance.database_status()
|
||||
assert status["exists"] is True
|
||||
assert status["integrity"] == "ok"
|
||||
assert status["schema_version"] == maintenance.SCHEMA_VERSION
|
||||
assert set(status["counts"]) == {
|
||||
"components", "recipes", "packages", "package_items", "templates", "fields"
|
||||
}
|
||||
assert status["backups"] == 0
|
||||
|
||||
|
||||
def test_backup_creates_file_and_is_listed(client):
|
||||
with client.application.app_context():
|
||||
path = maintenance.backup_database("test")
|
||||
assert path.exists() and path.read_bytes()[:15] == b"SQLite format 3"
|
||||
listed = maintenance.list_backups()
|
||||
assert [item["name"] for item in listed] == [path.name]
|
||||
assert listed[0]["size"] > 0
|
||||
|
||||
|
||||
def test_backup_prune_keeps_newest(client):
|
||||
with client.application.app_context():
|
||||
first = maintenance.backup_database("a")
|
||||
second = maintenance.backup_database("b")
|
||||
removed = maintenance.prune_backups(keep=1)
|
||||
listed = maintenance.list_backups()
|
||||
assert removed == 1
|
||||
assert len(listed) == 1
|
||||
assert listed[0]["path"] in (str(first), str(second))
|
||||
|
||||
|
||||
def test_readiness_lists_missing_setup(client):
|
||||
with client.application.app_context():
|
||||
summary = maintenance.readiness_summary()
|
||||
keys = {item["key"]: item for item in summary["checks"]}
|
||||
assert set(keys) == {
|
||||
"database", "company", "components", "recipes", "template",
|
||||
"company_contact", "scale", "secret",
|
||||
}
|
||||
# baza e goala intr-un test nou
|
||||
assert keys["components"]["ok"] is False
|
||||
assert keys["recipes"]["ok"] is False
|
||||
assert keys["template"]["ok"] is True # sablonul implicit exista
|
||||
assert summary["ready"] is False
|
||||
assert "Componente" in {item["label"] for item in summary["missing"]}
|
||||
|
||||
|
||||
def test_readiness_becomes_true_after_setup(client, recipe):
|
||||
client.post("/setari", data={
|
||||
"company_name": "Fabrica SRL",
|
||||
"company_phone": "0700 111 222",
|
||||
"active_scale_profile": "",
|
||||
"default_template_id": "",
|
||||
"default_operator": "Ana",
|
||||
"auto_print": "on",
|
||||
})
|
||||
with client.application.app_context():
|
||||
summary = maintenance.readiness_summary()
|
||||
assert summary["ready"] is True, [i for i in summary["missing"]]
|
||||
assert summary["missing"] == []
|
||||
|
||||
|
||||
def test_reset_clears_data_and_restores_defaults(client, recipe):
|
||||
with client.application.app_context():
|
||||
from cantarire.repositories import list_components, list_recipes, list_templates
|
||||
|
||||
assert list_components() and list_recipes()
|
||||
result = maintenance.reset_database()
|
||||
assert result["backup"] and maintenance.list_backups()
|
||||
assert list_components() == []
|
||||
assert list_recipes() == []
|
||||
templates = list_templates()
|
||||
assert len(templates) == 1 # sablonul standard recreat
|
||||
status = maintenance.database_status()
|
||||
from cantarire.db import all_settings
|
||||
|
||||
settings = all_settings()
|
||||
assert status["counts"]["components"] == 0
|
||||
assert settings["company_name"] == "Firma SRL" # valoarea implicita
|
||||
assert settings["schema_version"] == str(maintenance.SCHEMA_VERSION)
|
||||
assert result["removed"]["components"] == 2
|
||||
|
||||
|
||||
def test_reset_without_backup(client, recipe):
|
||||
with client.application.app_context():
|
||||
before = len(maintenance.list_backups())
|
||||
result = maintenance.reset_database(keep_backup=False)
|
||||
after = len(maintenance.list_backups())
|
||||
assert result["backup"] is None
|
||||
assert after == before
|
||||
|
||||
|
||||
def test_seed_demo_creates_data(client):
|
||||
with client.application.app_context():
|
||||
result = maintenance.seed_demo()
|
||||
from cantarire.repositories import get_component_by_code, list_recipes
|
||||
|
||||
assert result["created"]["components"] == 3
|
||||
assert result["created"]["recipes"] == 2
|
||||
assert get_component_by_code("FAS") is not None
|
||||
codes = {r["code"] for r in list_recipes()}
|
||||
assert {"PF-001", "PF-002"} <= codes
|
||||
|
||||
# idempotent: a doua rulare nu dubleaza nimic
|
||||
again = maintenance.seed_demo()
|
||||
assert again["created"] == {"components": 0, "recipes": 0}
|
||||
|
||||
|
||||
def test_healthz_endpoint(client):
|
||||
response = client.get("/healthz")
|
||||
assert response.status_code == 200
|
||||
data = response.get_json()
|
||||
assert data["ok"] is True
|
||||
assert data["database"]["integrity"] == "ok"
|
||||
assert data["setup"]["ready"] is False
|
||||
assert "Componente" in data["setup"]["missing"]
|
||||
|
||||
|
||||
def test_settings_page_shows_system_card(client):
|
||||
html = client.get("/setari").get_data(as_text=True)
|
||||
assert "<h2>Sistem</h2>" in html
|
||||
assert "Descarcă backup" in html
|
||||
assert "/healthz" in html
|
||||
|
||||
|
||||
def test_backup_download_route(client):
|
||||
response = client.get("/setari/backup")
|
||||
assert response.status_code == 200
|
||||
assert response.headers["Content-Disposition"].startswith("attachment")
|
||||
assert response.data[:15] == b"SQLite format 3"
|
||||
with client.application.app_context():
|
||||
assert len(maintenance.list_backups()) == 1
|
||||
|
||||
|
||||
def test_dashboard_shows_setup_checklist_when_empty(client):
|
||||
html = client.get("/").get_data(as_text=True)
|
||||
assert "Pregătire pentru prima cântărire" in html
|
||||
assert "Adaugă component" in html
|
||||
assert "Adaugă produs finit" in html
|
||||
|
||||
|
||||
def test_dashboard_hides_checklist_when_ready(client, recipe):
|
||||
client.post("/setari", data={
|
||||
"company_name": "Fabrica SRL", "company_phone": "0700 111 222",
|
||||
"active_scale_profile": "", "default_template_id": "", "auto_print": "on",
|
||||
})
|
||||
html = client.get("/").get_data(as_text=True)
|
||||
assert "Pregătire pentru prima cântărire" not in html
|
||||
|
||||
|
||||
def test_admin_cli_status_and_check(client):
|
||||
"""CLI-ul ruleaza pe aplicatia de test, fara sa atinga baza reala."""
|
||||
from cantarire.tools import admin
|
||||
|
||||
app = client.application
|
||||
assert admin.main(["status"], app=app) == 0
|
||||
# pe baza de test goala, check semnaleaza lipsuri
|
||||
assert admin.main(["check", "--json"], app=app) == 1
|
||||
|
||||
|
||||
def test_ensure_instance_dirs(client):
|
||||
with client.application.app_context():
|
||||
maintenance.ensure_instance_dirs()
|
||||
from pathlib import Path
|
||||
|
||||
root = Path(client.application.config["INSTANCE_DIR"])
|
||||
assert (root / "backups").is_dir()
|
||||
assert (root / "captures").is_dir()
|
||||
|
||||
|
||||
def test_reset_report_is_readable(client):
|
||||
with client.application.app_context():
|
||||
result = maintenance.reset_database(keep_backup=False)
|
||||
text = maintenance.reset_report(result)
|
||||
assert "Date sterse" in text
|
||||
assert "Backup: (nu s-a facut)" in text
|
||||
assert "schema v" in text
|
||||
Reference in New Issue
Block a user