126 lines
4.9 KiB
Python
126 lines
4.9 KiB
Python
"""UI-контракты кормоцеха: HTML/JS и страницы (без браузерного раннера)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from app import create_app, db
|
|
from app.services.setup_state import mark_setup_complete
|
|
from config import TestingConfig
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
STATIC = PROJECT_ROOT / "static"
|
|
|
|
|
|
class FeedMillUiConfig(TestingConfig):
|
|
_TMP_DIR = tempfile.mkdtemp(prefix="wesp-feed-mill-ui-")
|
|
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 = "mill-ui-admin"
|
|
AUTH_PASSWORD = "mill-ui-secret"
|
|
|
|
|
|
def _drain_response(resp) -> None:
|
|
try:
|
|
resp.get_data()
|
|
finally:
|
|
close = getattr(resp, "close", None)
|
|
if callable(close):
|
|
close()
|
|
|
|
|
|
class FeedMillUiTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.app = create_app(FeedMillUiConfig)
|
|
self.client = self.app.test_client()
|
|
self.ctx = self.app.app_context()
|
|
self.ctx.push()
|
|
db.create_all()
|
|
mark_setup_complete(self.app)
|
|
|
|
def tearDown(self) -> None:
|
|
db.session.remove()
|
|
db.drop_all()
|
|
self.ctx.pop()
|
|
|
|
def _login(self) -> None:
|
|
resp = self.client.post(
|
|
"/api/auth/login", json={"login": "mill-ui-admin", "password": "mill-ui-secret"}
|
|
)
|
|
self.assertEqual(resp.status_code, 200)
|
|
|
|
def test_feed_dispensers_page_requires_auth(self) -> None:
|
|
resp = self.client.get("/feed_dispensers", follow_redirects=False)
|
|
self.assertIn(resp.status_code, (302, 401, 403))
|
|
|
|
def test_feed_dispensers_and_recipes_pages_ok_when_authed(self) -> None:
|
|
self._login()
|
|
for path in ("/feed_dispensers", "/recipes"):
|
|
resp = self.client.get(path)
|
|
_drain_response(resp)
|
|
self.assertEqual(resp.status_code, 200, path)
|
|
self.assertIn(b"<!DOCTYPE html>", resp.data or b"")
|
|
|
|
def test_feed_dispensers_html_mill_modal_and_create_payload(self) -> None:
|
|
html = (STATIC / "feed_dispensers.html").read_text(encoding="utf-8")
|
|
self.assertIn("Добавить кормоцех", html)
|
|
self.assertIn('id="addMillModalLabel"', html)
|
|
self.assertIn("type: 'mill'", html)
|
|
self.assertIn("dispenser-card--mill", html)
|
|
self.assertIn("!isMill", html)
|
|
self.assertIn("managePeriods", html)
|
|
|
|
def test_recipes_data_controller_mill_branch(self) -> None:
|
|
js = (STATIC / "js" / "pages" / "recipes-data-controller.js").read_text(encoding="utf-8")
|
|
self.assertIn('getCurrentDispenserType() === "mill"', js)
|
|
self.assertIn("loadMillRecipes", js)
|
|
self.assertIn('millRecipesList', js)
|
|
self.assertIn('dispenser.type === "mill"', js)
|
|
self.assertIn('? "recipes" : "periods"', js)
|
|
|
|
def test_recipes_operations_mill_uses_global_delete(self) -> None:
|
|
js = (STATIC / "js" / "pages" / "recipes-operations-controller.js").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
self.assertIn('getCurrentDispenserType() === "mill"', js)
|
|
self.assertIn("unlinkFromPeriodOnly", js)
|
|
self.assertIn("/api/recipes/", js)
|
|
# mill: DELETE глобальный, не unlink из периода
|
|
self.assertRegex(
|
|
js,
|
|
r"unlinkFromPeriodOnly\s*=\s*Boolean\(periodId\s*&&\s*dispenserId\s*&&\s*!isMill\)",
|
|
)
|
|
|
|
def test_recipes_editor_mill_target_component(self) -> None:
|
|
js = (STATIC / "js" / "pages" / "recipes-editor-controller.js").read_text(encoding="utf-8")
|
|
self.assertIn('getCurrentDispenserType() === "mill"', js)
|
|
self.assertIn("target_component", js)
|
|
|
|
def test_recipe_period_transfer_excludes_mill(self) -> None:
|
|
js = (STATIC / "js" / "modules" / "recipes" / "recipe-period-transfer.js").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
self.assertIn('dataset.dispenserType === "mill"', js)
|
|
mill_skips = len(re.findall(r'dispenserType\s*===\s*["\']mill["\']', js))
|
|
self.assertGreaterEqual(mill_skips, 2)
|
|
|
|
def test_recipes_html_legacy_mill_flow_present(self) -> None:
|
|
html = (STATIC / "recipes.html").read_text(encoding="utf-8")
|
|
self.assertIn("currentDispenserType = 'dispenser'", html)
|
|
self.assertIn("currentDispenserType === 'mill'", html)
|
|
self.assertIn("target_component_id", html)
|
|
self.assertIn("Загрузка рецептов кормоцеха", html)
|
|
|
|
def test_recipe_skeleton_mill_label(self) -> None:
|
|
js = (STATIC / "js" / "modules" / "recipes" / "recipe-skeleton.js").read_text(encoding="utf-8")
|
|
self.assertIn("mill:", js)
|
|
self.assertIn("Загрузка рецептов", js)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|