"""Тесты 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()