174 lines
6.5 KiB
Python
174 lines
6.5 KiB
Python
"""Тесты Plymouth: рендер кадров и API админки."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import tempfile
|
|
import threading
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
from app import create_app, db
|
|
from app.routes.auth import _ensure_default_superuser
|
|
from app.services import plymouth_frame_renderer as renderer
|
|
from app.services import plymouth_theme_service as plymouth_svc
|
|
from app.services.plymouth_theme_service import build_and_install_theme, get_plymouth_status
|
|
from app.services.setup_state import mark_setup_complete
|
|
from config import TestingConfig
|
|
|
|
|
|
class PlymouthTestConfig(TestingConfig):
|
|
_TMP = tempfile.mkdtemp(prefix="wesp-plymouth-test-")
|
|
BASE_DIR = _TMP
|
|
DATA_DIR = f"{_TMP}/data"
|
|
SQLALCHEMY_DATABASE_URI = f"sqlite:///{_TMP}/recipes_test.db"
|
|
SQLALCHEMY_BINDS = {"reports": f"sqlite:///{_TMP}/reports_test.db"}
|
|
AUTH_LOGIN = "admin"
|
|
AUTH_PASSWORD = "admin-secret"
|
|
SECRET_KEY = "unit-test-secret-key-not-default-32chars"
|
|
|
|
|
|
class PlymouthThemeTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.app = create_app(PlymouthTestConfig)
|
|
self.client = self.app.test_client()
|
|
self.ctx = self.app.app_context()
|
|
self.ctx.push()
|
|
db.create_all()
|
|
_ensure_default_superuser()
|
|
mark_setup_complete(self.app)
|
|
self._login()
|
|
|
|
def _login(self) -> None:
|
|
r = self.client.post(
|
|
"/api/auth/login",
|
|
json={"login": "admin", "password": "admin-secret"},
|
|
)
|
|
self.assertEqual(r.status_code, 200)
|
|
|
|
def tearDown(self) -> None:
|
|
db.session.remove()
|
|
db.drop_all()
|
|
self.ctx.pop()
|
|
|
|
def test_frame_count_matches_duration(self) -> None:
|
|
self.assertGreater(renderer.frame_count(6000, 25), 100)
|
|
self.assertEqual(renderer.animation_end_ms(6000), renderer.wave2_end_ms(6000) + 60)
|
|
|
|
def test_status_endpoint_requires_superuser(self) -> None:
|
|
anon = create_app(PlymouthTestConfig)
|
|
with anon.app_context():
|
|
db.create_all()
|
|
r = anon.test_client().get("/api/admin/plymouth/status")
|
|
self.assertIn(r.status_code, (401, 403, 302))
|
|
|
|
def test_status_endpoint_ok(self) -> None:
|
|
r = self.client.get("/api/admin/plymouth/status")
|
|
self.assertEqual(r.status_code, 200)
|
|
data = r.get_json()
|
|
self.assertEqual(data.get("status"), "success")
|
|
self.assertEqual(data.get("resolution"), "800x600")
|
|
self.assertIn("job", data)
|
|
|
|
@patch("app.services.plymouth_theme_service.build_and_install_theme")
|
|
def test_install_starts_background_job(self, mock_build) -> None:
|
|
r = self.client.post("/api/admin/plymouth/install")
|
|
self.assertEqual(r.status_code, 200)
|
|
self.assertTrue(r.get_json().get("ok"))
|
|
mock_build.assert_called_once()
|
|
|
|
@patch("app.services.plymouth_theme_service.build_and_install_theme")
|
|
def test_install_busy_when_running(self, mock_build) -> None:
|
|
from app.services import plymouth_theme_service as svc
|
|
|
|
with svc._lock:
|
|
svc._job_state["running"] = True
|
|
try:
|
|
r = self.client.post("/api/admin/plymouth/install")
|
|
self.assertEqual(r.status_code, 409)
|
|
mock_build.assert_not_called()
|
|
finally:
|
|
with svc._lock:
|
|
svc._job_state["running"] = False
|
|
|
|
def test_get_status_in_app(self) -> None:
|
|
with self.app.app_context():
|
|
st = get_plymouth_status(self.app)
|
|
self.assertIn("plymouth_installed", st)
|
|
self.assertIn("build_dir", st)
|
|
|
|
@patch("app.services.plymouth_theme_service._run_install_subprocess")
|
|
@patch("app.services.plymouth_theme_service.render_all_frames")
|
|
@patch("app.services.plymouth_theme_service.pillow_available", return_value=True)
|
|
def test_build_thread_has_app_context(
|
|
self, _pillow: object, mock_render: object, mock_install: object
|
|
) -> None:
|
|
from flask import current_app
|
|
root = Path(__file__).resolve().parents[1]
|
|
logo = root / "static" / "logo2.png"
|
|
if not logo.is_file():
|
|
self.skipTest("logo2.png missing")
|
|
|
|
mock_render.return_value = {"frames": 2, "output_dir": "animation"}
|
|
mock_install.return_value = {"ok": True, "message": "Тема установлена"}
|
|
|
|
errors: list[BaseException] = []
|
|
|
|
def worker() -> None:
|
|
try:
|
|
with self.app.test_request_context():
|
|
build_and_install_theme(current_app)
|
|
except BaseException as exc:
|
|
errors.append(exc)
|
|
|
|
thread = threading.Thread(target=worker, name="test-plymouth")
|
|
thread.start()
|
|
thread.join(timeout=10)
|
|
self.assertFalse(thread.is_alive(), "plymouth build thread hung")
|
|
self.assertEqual(errors, [], errors[0] if errors else None)
|
|
with plymouth_svc._lock:
|
|
phase = plymouth_svc._job_state.get("phase")
|
|
self.assertIn(phase, ("done", "error"))
|
|
if phase == "error":
|
|
self.assertNotIn(
|
|
"application context",
|
|
str(plymouth_svc._job_state.get("error", "")).lower(),
|
|
)
|
|
|
|
|
|
@unittest.skipUnless(renderer.pillow_available(), "Pillow required")
|
|
class PlymouthFrameRenderTests(unittest.TestCase):
|
|
def test_render_single_frame(self) -> None:
|
|
root = Path(__file__).resolve().parents[1]
|
|
logo = root / "static" / "logo2.png"
|
|
if not logo.is_file():
|
|
self.skipTest("logo2.png missing")
|
|
logo_img = renderer._load_logo_buffer(logo)
|
|
cells = renderer._build_logo_cells(logo_img, renderer.INITIAL_CELL)
|
|
layout = renderer._compute_logo_layout(800, 600, embedded=True)
|
|
frame = renderer.render_frame(logo_img, cells, layout, 0)
|
|
self.assertEqual(frame.size, (800, 600))
|
|
|
|
def test_render_all_frames_small(self) -> None:
|
|
root = Path(__file__).resolve().parents[1]
|
|
logo = root / "static" / "logo2.png"
|
|
if not logo.is_file():
|
|
self.skipTest("logo2.png missing")
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
out = Path(tmp)
|
|
summary = renderer.render_all_frames(
|
|
logo,
|
|
out,
|
|
width=320,
|
|
height=240,
|
|
duration_ms=400,
|
|
fps=10,
|
|
)
|
|
self.assertGreater(summary["frames"], 2)
|
|
anim = out / "animation"
|
|
self.assertTrue((anim / "0.png").is_file())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|