240 lines
8.6 KiB
Python
240 lines
8.6 KiB
Python
"""Тесты API учётных документов и org_settings."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
from werkzeug.security import generate_password_hash
|
|
|
|
from app import create_app, db
|
|
from app.models import (
|
|
Component,
|
|
ComponentStock,
|
|
LoadingReport,
|
|
LoadingReportComponent,
|
|
OrgSettings,
|
|
WebUser,
|
|
)
|
|
from app.services.setup_state import mark_setup_complete
|
|
from config import TestingConfig
|
|
|
|
|
|
class FeedAccountingTestConfig(TestingConfig):
|
|
_TMP_DIR = tempfile.mkdtemp(prefix="wesp-fa-tests-")
|
|
SQLALCHEMY_DATABASE_URI = f"sqlite:///{os.path.join(_TMP_DIR, 'recipes_test.db')}"
|
|
SQLALCHEMY_BINDS = {"reports": f"sqlite:///{os.path.join(_TMP_DIR, 'reports_test.db')}"}
|
|
AUTH_LOGIN = "fa-admin"
|
|
AUTH_PASSWORD = "fa-secret"
|
|
|
|
|
|
class FeedAccountingExportTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.app = create_app(FeedAccountingTestConfig)
|
|
self.client = self.app.test_client()
|
|
self.ctx = self.app.app_context()
|
|
self.ctx.push()
|
|
db.create_all()
|
|
db.session.add(
|
|
WebUser(
|
|
login="fa-admin",
|
|
password_hash=generate_password_hash("fa-secret"),
|
|
is_superuser=True,
|
|
)
|
|
)
|
|
db.session.commit()
|
|
mark_setup_complete(self.app)
|
|
login = self.client.post(
|
|
"/api/auth/login",
|
|
json={"login": "fa-admin", "password": "fa-secret"},
|
|
)
|
|
self.assertEqual(login.status_code, 200, login.get_data(as_text=True))
|
|
self._seed_data()
|
|
|
|
def tearDown(self) -> None:
|
|
db.session.remove()
|
|
db.drop_all()
|
|
self.ctx.pop()
|
|
|
|
def _seed_data(self) -> None:
|
|
comp = Component(name="Сено", type="roughage")
|
|
db.session.add(comp)
|
|
db.session.flush()
|
|
stock = ComponentStock(
|
|
component_id=comp.id,
|
|
component_name=comp.name,
|
|
total_kg=1000.0,
|
|
stocktake_at=datetime.now(),
|
|
updated_at=datetime.now(),
|
|
)
|
|
db.session.add(stock)
|
|
report = LoadingReport(
|
|
recipe_id="r1",
|
|
recipe_name="Test",
|
|
start_time=datetime.now(),
|
|
dispenser_type="dispenser",
|
|
content_hash="abc",
|
|
)
|
|
db.session.add(report)
|
|
db.session.flush()
|
|
lrc = LoadingReportComponent(
|
|
report_id=report.id,
|
|
component_id=comp.id,
|
|
component_name=comp.name,
|
|
target_weight=10.0,
|
|
actual_weight=5.0,
|
|
loading_order=1,
|
|
content_hash="def",
|
|
)
|
|
db.session.add(lrc)
|
|
db.session.commit()
|
|
|
|
def test_org_settings_roundtrip(self) -> None:
|
|
put = self.client.put(
|
|
"/api/feed-accounting/org-settings",
|
|
json={"organization_name": "ООО Тест", "okpo": "12345678"},
|
|
)
|
|
self.assertEqual(put.status_code, 200)
|
|
get = self.client.get("/api/feed-accounting/org-settings")
|
|
self.assertEqual(get.status_code, 200)
|
|
body = get.get_json()
|
|
self.assertEqual(body["organization_name"], "ООО Тест")
|
|
self.assertEqual(body.get("okpo"), "12345678")
|
|
self.assertIn("available_farms", body)
|
|
self.assertIn("signatures", body)
|
|
row = db.session.get(OrgSettings, 1)
|
|
self.assertIsNotNone(row)
|
|
self.assertEqual(row.organization_name, "ООО Тест")
|
|
self.assertEqual(row.okpo, "12345678")
|
|
|
|
def test_sp20_preview_has_lines(self) -> None:
|
|
month = datetime.now().strftime("%Y-%m")
|
|
r = self.client.get(f"/api/feed-accounting/sp20-preview?month={month}")
|
|
self.assertEqual(r.status_code, 200)
|
|
body = r.get_json()
|
|
self.assertGreaterEqual(body.get("lines_count", 0), 1)
|
|
self.assertGreater(body.get("total_kg", 0), 0)
|
|
|
|
def test_signature_png_not_found(self) -> None:
|
|
from app.services.org_settings import get_signatures_dir
|
|
|
|
sig_dir = get_signatures_dir()
|
|
backup = None
|
|
target = sig_dir / "zootechnician.png"
|
|
if target.is_file():
|
|
backup = target.read_bytes()
|
|
target.unlink()
|
|
try:
|
|
r = self.client.get("/api/feed-accounting/signatures/zootechnician.png")
|
|
self.assertEqual(r.status_code, 404)
|
|
finally:
|
|
if backup is not None:
|
|
sig_dir.mkdir(parents=True, exist_ok=True)
|
|
target.write_bytes(backup)
|
|
|
|
def test_stock_balances_xlsx_requires_auth(self) -> None:
|
|
self.client.post("/api/auth/logout")
|
|
r = self.client.get("/api/feed-accounting/stock-balances.xlsx")
|
|
self.assertEqual(r.status_code, 401)
|
|
|
|
def test_stock_balances_xlsx_ok(self) -> None:
|
|
today = datetime.now().date()
|
|
start = today.replace(day=1).isoformat()
|
|
end = today.isoformat()
|
|
r = self.client.get(
|
|
f"/api/feed-accounting/stock-balances.xlsx?date_from={start}&date_to={end}"
|
|
)
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertIn("spreadsheetml", r.content_type)
|
|
self.assertGreater(len(r.data), 100)
|
|
|
|
def test_consumption_xlsx_requires_auth(self) -> None:
|
|
self.client.post("/api/auth/logout")
|
|
r = self.client.get("/api/feed-accounting/consumption.xlsx")
|
|
self.assertEqual(r.status_code, 401)
|
|
|
|
def test_consumption_xlsx_ok(self) -> None:
|
|
today = datetime.now().date()
|
|
start = today.replace(day=1).isoformat()
|
|
end = today.isoformat()
|
|
r = self.client.get(
|
|
f"/api/feed-accounting/consumption.xlsx?date_from={start}&date_to={end}"
|
|
)
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertIn("spreadsheetml", r.content_type)
|
|
cd = r.headers.get("Content-Disposition") or ""
|
|
self.assertIn("filename*=UTF-8", cd)
|
|
self.assertIn(f"Potreblenie_korma_{start}_{end}.xlsx", cd)
|
|
try:
|
|
from openpyxl import load_workbook
|
|
|
|
wb = load_workbook(io.BytesIO(r.data))
|
|
self.assertEqual(wb.sheetnames, ["Сводка", "Детализация", "Расход по дням"])
|
|
self.assertGreaterEqual(wb["Детализация"].max_row, 2)
|
|
except ImportError:
|
|
pass
|
|
|
|
def test_consumption_pdf_ok(self) -> None:
|
|
today = datetime.now().date()
|
|
start = today.replace(day=1).isoformat()
|
|
end = today.isoformat()
|
|
r = self.client.get(
|
|
f"/api/feed-accounting/consumption.pdf?date_from={start}&date_to={end}"
|
|
)
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertIn("pdf", r.content_type)
|
|
cd = r.headers.get("Content-Disposition") or ""
|
|
self.assertIn("filename*=UTF-8", cd)
|
|
self.assertIn(f"Potreblenie_korma_{start}_{end}.pdf", cd)
|
|
self.assertGreater(len(r.data), 100)
|
|
self.assertTrue(r.data.startswith(b"%PDF"))
|
|
|
|
def test_sp20_xlsx_ok(self) -> None:
|
|
month = datetime.now().strftime("%Y-%m")
|
|
r = self.client.get(f"/api/feed-accounting/sp20.xlsx?month={month}")
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertIn("spreadsheetml", r.content_type)
|
|
try:
|
|
from openpyxl import load_workbook
|
|
|
|
wb = load_workbook(io.BytesIO(r.data))
|
|
self.assertEqual(wb.sheetnames, ["стр1", "стр2"])
|
|
ws1 = wb["стр1"]
|
|
self.assertGreater(len(ws1.merged_cells.ranges), 50)
|
|
bordered = sum(
|
|
1
|
|
for row in ws1.iter_rows(max_row=31, max_col=29)
|
|
for c in row
|
|
if c.border and (c.border.left.style or c.border.top.style or c.border.bottom.style)
|
|
)
|
|
self.assertGreater(bordered, 100)
|
|
self.assertEqual(ws1.cell(row=6, column=27).value, "0325020")
|
|
except ImportError:
|
|
pass
|
|
|
|
def test_journal_xlsx_ok(self) -> None:
|
|
month = datetime.now().strftime("%Y-%m")
|
|
r = self.client.get(f"/api/feed-accounting/journal.xlsx?month={month}")
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertIn("spreadsheetml", r.content_type)
|
|
|
|
def test_documents_zip_ok(self) -> None:
|
|
month = datetime.now().strftime("%Y-%m")
|
|
today = datetime.now().date()
|
|
start = today.replace(day=1).isoformat()
|
|
end = today.isoformat()
|
|
r = self.client.get(
|
|
f"/api/feed-accounting/documents.zip?month={month}&date_from={start}&date_to={end}"
|
|
)
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertIn("zip", r.content_type)
|
|
self.assertGreater(len(r.data), 200)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|