mirror of
https://github.com/wizarrrr/wizarr.git
synced 2025-12-23 15:51:07 -05:00
Changes: - Pre/post invitation wizard phases - Phase-aware routing (/pre-wizard/*, /post-wizard/*) - Phase indicator badges (visual categorization) - Dynamic completion button behavior - Two-column admin layout with category drag-and-drop - Invite code management service - Enhanced wizard export/import - Comprehensive test coverage (17 new test files)
333 lines
12 KiB
Python
333 lines
12 KiB
Python
"""Tests for wizard rendering logic (_serve_wizard function).
|
|
|
|
This module tests the refactored _serve_wizard() function to ensure:
|
|
- It works correctly with both pre and post phases
|
|
- HTMX partial rendering vs full page rendering works
|
|
- Interaction requirement detection works
|
|
- Phase parameter is passed to templates
|
|
|
|
Requirements tested:
|
|
- 1.3: Existing _render() function is reused for both pre and post-invite steps
|
|
- 6.5: Pre-wizard UI matches existing wizard interface
|
|
- 8.4: Post-wizard UI matches existing wizard interface
|
|
- 15.1: Unit tests for wizard rendering
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from app.extensions import db
|
|
from app.models import MediaServer, WizardStep
|
|
|
|
|
|
@pytest.fixture
|
|
def session_fixture(app):
|
|
"""Return a clean database session inside an app context."""
|
|
with app.app_context():
|
|
# Clean up any existing data before the test
|
|
db.session.query(WizardStep).delete()
|
|
db.session.query(MediaServer).delete()
|
|
db.session.commit()
|
|
|
|
yield db.session
|
|
|
|
# Clean up after the test
|
|
db.session.rollback()
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_wizard_steps(session_fixture):
|
|
"""Create sample wizard steps for testing."""
|
|
steps = [
|
|
WizardStep(
|
|
server_type="plex",
|
|
category="pre_invite",
|
|
position=0,
|
|
title="Pre-Welcome",
|
|
markdown="# Pre-Welcome\nBefore you join...",
|
|
require_interaction=False,
|
|
),
|
|
WizardStep(
|
|
server_type="plex",
|
|
category="pre_invite",
|
|
position=1,
|
|
title="Pre-Requirements",
|
|
markdown="# Requirements\nYou need...",
|
|
require_interaction=True,
|
|
),
|
|
WizardStep(
|
|
server_type="plex",
|
|
category="post_invite",
|
|
position=0,
|
|
title="Post-Welcome",
|
|
markdown="# Welcome\nWelcome to Plex!",
|
|
require_interaction=False,
|
|
),
|
|
WizardStep(
|
|
server_type="plex",
|
|
category="post_invite",
|
|
position=1,
|
|
title="Post-Setup",
|
|
markdown="# Setup\nLet's get started...",
|
|
require_interaction=True,
|
|
),
|
|
]
|
|
|
|
for step in steps:
|
|
session_fixture.add(step)
|
|
session_fixture.commit()
|
|
|
|
return steps
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_media_server(session_fixture):
|
|
"""Create a sample media server for testing."""
|
|
server = MediaServer(
|
|
name="Test Plex Server",
|
|
server_type="plex",
|
|
url="http://localhost:32400",
|
|
external_url="https://plex.example.com",
|
|
)
|
|
session_fixture.add(server)
|
|
session_fixture.commit()
|
|
return server
|
|
|
|
|
|
class TestServeWizardFunction:
|
|
"""Test the _serve_wizard() function with different phases."""
|
|
|
|
def test_serve_wizard_with_pre_phase(
|
|
self, app, client, sample_wizard_steps, sample_media_server
|
|
):
|
|
"""Test _serve_wizard() with pre phase."""
|
|
from app.blueprints.wizard.routes import _serve_wizard, _settings, _steps
|
|
|
|
with (
|
|
app.app_context(),
|
|
client,
|
|
client.application.test_request_context(),
|
|
):
|
|
cfg = _settings()
|
|
steps = _steps("plex", cfg, category="pre_invite")
|
|
|
|
# Call _serve_wizard with pre phase
|
|
response = _serve_wizard("plex", 0, steps, phase="pre")
|
|
|
|
# Should return a response
|
|
assert response is not None
|
|
# Response should be a string (rendered HTML)
|
|
assert isinstance(response, str)
|
|
# Response should contain the step content
|
|
assert "Pre-Welcome" in response or "Before you join" in response
|
|
|
|
def test_serve_wizard_with_post_phase(
|
|
self, app, client, sample_wizard_steps, sample_media_server
|
|
):
|
|
"""Test _serve_wizard() with post phase."""
|
|
from app.blueprints.wizard.routes import _serve_wizard, _settings, _steps
|
|
|
|
with (
|
|
app.app_context(),
|
|
client,
|
|
client.application.test_request_context(),
|
|
):
|
|
cfg = _settings()
|
|
steps = _steps("plex", cfg, category="post_invite")
|
|
|
|
# Call _serve_wizard with post phase
|
|
response = _serve_wizard("plex", 0, steps, phase="post")
|
|
|
|
# Should return a response
|
|
assert response is not None
|
|
# Response should be a string (rendered HTML)
|
|
assert isinstance(response, str)
|
|
# Response should contain the step content
|
|
assert "Welcome to Plex!" in response or "Welcome" in response
|
|
|
|
def test_serve_wizard_full_page_rendering(
|
|
self, app, client, sample_wizard_steps, sample_media_server
|
|
):
|
|
"""Test that _serve_wizard returns full page for non-HTMX requests."""
|
|
from app.blueprints.wizard.routes import _serve_wizard, _settings, _steps
|
|
|
|
with app.app_context(), client:
|
|
cfg = _settings()
|
|
steps = _steps("plex", cfg, category="pre_invite")
|
|
|
|
# Simulate non-HTMX request (initial page load)
|
|
with client.application.test_request_context():
|
|
response = _serve_wizard("plex", 0, steps, phase="pre")
|
|
|
|
# Should use frame.html template (full page)
|
|
# Check for typical frame.html elements
|
|
assert response is not None
|
|
|
|
def test_serve_wizard_htmx_partial_rendering(
|
|
self, app, client, sample_wizard_steps, sample_media_server
|
|
):
|
|
"""Test that _serve_wizard returns partial for HTMX requests."""
|
|
from app.blueprints.wizard.routes import _serve_wizard, _settings, _steps
|
|
|
|
with (
|
|
app.app_context(),
|
|
client,
|
|
):
|
|
cfg = _settings()
|
|
steps = _steps("plex", cfg, category="pre_invite")
|
|
|
|
# Simulate HTMX request
|
|
with client.application.test_request_context(
|
|
headers={"HX-Request": "true"}
|
|
):
|
|
response = _serve_wizard("plex", 0, steps, phase="pre")
|
|
|
|
# Should return a response with custom headers
|
|
assert response is not None
|
|
# Check for HTMX-specific headers
|
|
assert "X-Wizard-Idx" in response.headers
|
|
assert response.headers["X-Wizard-Idx"] == "0"
|
|
|
|
def test_serve_wizard_interaction_requirement_detection(
|
|
self, app, client, sample_wizard_steps, sample_media_server
|
|
):
|
|
"""Test that _serve_wizard correctly detects interaction requirements."""
|
|
from app.blueprints.wizard.routes import _serve_wizard, _settings, _steps
|
|
|
|
with (
|
|
app.app_context(),
|
|
client,
|
|
):
|
|
cfg = _settings()
|
|
steps = _steps("plex", cfg, category="pre_invite")
|
|
|
|
# Test step without interaction requirement (idx=0)
|
|
with client.application.test_request_context(
|
|
headers={"HX-Request": "true"}
|
|
):
|
|
response = _serve_wizard("plex", 0, steps, phase="pre")
|
|
assert response.headers["X-Require-Interaction"] == "false"
|
|
|
|
# Test step with interaction requirement (idx=1)
|
|
with client.application.test_request_context(
|
|
headers={"HX-Request": "true"}
|
|
):
|
|
response = _serve_wizard("plex", 1, steps, phase="pre")
|
|
assert response.headers["X-Require-Interaction"] == "true"
|
|
|
|
def test_serve_wizard_phase_parameter_passed_to_template(
|
|
self, app, client, sample_wizard_steps, sample_media_server
|
|
):
|
|
"""Test that phase parameter is passed to templates."""
|
|
from app.blueprints.wizard.routes import _serve_wizard, _settings, _steps
|
|
|
|
with (
|
|
app.app_context(),
|
|
client,
|
|
):
|
|
cfg = _settings()
|
|
pre_steps = _steps("plex", cfg, category="pre_invite")
|
|
post_steps = _steps("plex", cfg, category="post_invite")
|
|
|
|
# Test pre phase
|
|
with client.application.test_request_context():
|
|
response = _serve_wizard("plex", 0, pre_steps, phase="pre")
|
|
# Phase should be available in template context
|
|
# We can't directly check template context, but we can verify response is valid
|
|
assert response is not None
|
|
|
|
# Test post phase
|
|
with client.application.test_request_context():
|
|
response = _serve_wizard("plex", 0, post_steps, phase="post")
|
|
assert response is not None
|
|
|
|
def test_serve_wizard_handles_empty_steps(self, app, client, sample_media_server):
|
|
"""Test that _serve_wizard handles empty steps list correctly."""
|
|
from werkzeug.exceptions import NotFound
|
|
|
|
from app.blueprints.wizard.routes import _serve_wizard
|
|
|
|
with (
|
|
app.app_context(),
|
|
client,
|
|
pytest.raises(NotFound),
|
|
client.application.test_request_context(),
|
|
):
|
|
# Empty steps list should raise 404
|
|
_serve_wizard("plex", 0, [], phase="pre")
|
|
|
|
def test_serve_wizard_clamps_index_to_valid_range(
|
|
self, app, client, sample_wizard_steps, sample_media_server
|
|
):
|
|
"""Test that _serve_wizard clamps index to valid range."""
|
|
from app.blueprints.wizard.routes import _serve_wizard, _settings, _steps
|
|
|
|
with (
|
|
app.app_context(),
|
|
client,
|
|
):
|
|
cfg = _settings()
|
|
steps = _steps("plex", cfg, category="pre_invite")
|
|
|
|
# Test negative index (should clamp to 0)
|
|
with client.application.test_request_context(
|
|
headers={"HX-Request": "true"}
|
|
):
|
|
response = _serve_wizard("plex", -5, steps, phase="pre")
|
|
assert response.headers["X-Wizard-Idx"] == "0"
|
|
|
|
# Test index beyond max (should clamp to max_idx)
|
|
with client.application.test_request_context(
|
|
headers={"HX-Request": "true"}
|
|
):
|
|
response = _serve_wizard("plex", 999, steps, phase="pre")
|
|
max_idx = len(steps) - 1
|
|
assert response.headers["X-Wizard-Idx"] == str(max_idx)
|
|
|
|
|
|
class TestLegacyServeFunction:
|
|
"""Test that the legacy _serve() function still works."""
|
|
|
|
def test_legacy_serve_delegates_to_serve_wizard(
|
|
self, app, client, sample_wizard_steps, sample_media_server
|
|
):
|
|
"""Test that _serve() delegates to _serve_wizard() with post phase."""
|
|
from app.blueprints.wizard.routes import _serve
|
|
|
|
with (
|
|
app.app_context(),
|
|
client,
|
|
client.application.test_request_context(),
|
|
):
|
|
response = _serve("plex", 0)
|
|
|
|
# Should return a response
|
|
assert response is not None
|
|
# Response should be a string (rendered HTML)
|
|
assert isinstance(response, str)
|
|
# Should contain post-invite content
|
|
assert "Welcome to Plex!" in response or "Welcome" in response
|
|
|
|
def test_legacy_serve_uses_post_invite_category(
|
|
self, app, client, sample_wizard_steps, sample_media_server
|
|
):
|
|
"""Test that _serve() uses post_invite category by default."""
|
|
from app.blueprints.wizard.routes import _serve
|
|
|
|
with (
|
|
app.app_context(),
|
|
client,
|
|
client.application.test_request_context(headers={"HX-Request": "true"}),
|
|
):
|
|
response = _serve("plex", 0)
|
|
|
|
# Should return post-invite steps (Response object for HTMX)
|
|
assert response is not None
|
|
# For HTMX requests, response is a Response object
|
|
from flask import Response
|
|
|
|
if isinstance(response, Response):
|
|
assert b"Before you join" not in response.data
|
|
else:
|
|
# Should not contain pre-invite content
|
|
assert "Before you join" not in response
|