mirror of
https://github.com/wizarrrr/wizarr.git
synced 2026-07-31 07:17:10 -04:00
571 lines
19 KiB
Python
571 lines
19 KiB
Python
"""
|
|
Tests for wizard step drag-and-drop functionality.
|
|
|
|
This module tests the drag-and-drop reordering of wizard steps, including:
|
|
- Reordering within the same category
|
|
- Moving steps between categories (pre-invite ↔ post-invite)
|
|
- Preventing moves between different server types
|
|
- Error handling and UI feedback
|
|
"""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
from sqlalchemy.exc import IntegrityError
|
|
|
|
from app.extensions import db
|
|
from app.models import AdminAccount, Library, MediaServer, User, WizardStep
|
|
|
|
|
|
@pytest.fixture
|
|
def session(app):
|
|
"""Return a clean database session inside an app context."""
|
|
with app.app_context():
|
|
# Clean up any existing data before each test - delete in correct order for FKs
|
|
db.session.rollback()
|
|
db.session.execute(db.text("DELETE FROM invitation_server"))
|
|
db.session.execute(db.text("DELETE FROM invitation_user"))
|
|
db.session.query(User).delete()
|
|
db.session.query(WizardStep).delete()
|
|
db.session.query(Library).delete()
|
|
db.session.query(MediaServer).delete()
|
|
db.session.query(AdminAccount).delete()
|
|
db.session.commit()
|
|
|
|
yield db.session
|
|
|
|
# Clean up after the test - rollback first in case of errors
|
|
try:
|
|
db.session.rollback()
|
|
db.session.execute(db.text("DELETE FROM invitation_server"))
|
|
db.session.execute(db.text("DELETE FROM invitation_user"))
|
|
db.session.query(User).delete()
|
|
db.session.query(WizardStep).delete()
|
|
db.session.query(Library).delete()
|
|
db.session.query(MediaServer).delete()
|
|
db.session.query(AdminAccount).delete()
|
|
db.session.commit()
|
|
except Exception:
|
|
db.session.rollback()
|
|
|
|
|
|
@pytest.fixture
|
|
def admin_user(session):
|
|
"""Create an admin user for authentication."""
|
|
admin = AdminAccount(username="testadmin")
|
|
admin.set_password("testpass123")
|
|
session.add(admin)
|
|
session.commit()
|
|
return admin
|
|
|
|
|
|
def authenticate_client(client, admin_user):
|
|
"""Helper function to authenticate a client with an admin user."""
|
|
with client.session_transaction() as sess:
|
|
sess["_user_id"] = str(admin_user.id)
|
|
sess["_fresh"] = True
|
|
|
|
|
|
@pytest.fixture
|
|
def wizard_steps(session):
|
|
"""Create test wizard steps for drag-and-drop testing."""
|
|
steps = [
|
|
# Plex pre-invite steps
|
|
WizardStep(
|
|
server_type="plex",
|
|
category="pre_invite",
|
|
position=0,
|
|
title="Plex Pre Step 1",
|
|
markdown="# Pre Step 1",
|
|
),
|
|
WizardStep(
|
|
server_type="plex",
|
|
category="pre_invite",
|
|
position=1,
|
|
title="Plex Pre Step 2",
|
|
markdown="# Pre Step 2",
|
|
),
|
|
WizardStep(
|
|
server_type="plex",
|
|
category="pre_invite",
|
|
position=2,
|
|
title="Plex Pre Step 3",
|
|
markdown="# Pre Step 3",
|
|
),
|
|
# Plex post-invite steps
|
|
WizardStep(
|
|
server_type="plex",
|
|
category="post_invite",
|
|
position=0,
|
|
title="Plex Post Step 1",
|
|
markdown="# Post Step 1",
|
|
),
|
|
WizardStep(
|
|
server_type="plex",
|
|
category="post_invite",
|
|
position=1,
|
|
title="Plex Post Step 2",
|
|
markdown="# Post Step 2",
|
|
),
|
|
# Jellyfin pre-invite steps
|
|
WizardStep(
|
|
server_type="jellyfin",
|
|
category="pre_invite",
|
|
position=0,
|
|
title="Jellyfin Pre Step 1",
|
|
markdown="# Jellyfin Pre",
|
|
),
|
|
]
|
|
|
|
session.add_all(steps)
|
|
session.commit()
|
|
|
|
# Return step IDs for testing
|
|
return {
|
|
"plex_pre": [s.id for s in steps[:3]],
|
|
"plex_post": [s.id for s in steps[3:5]],
|
|
"jellyfin_pre": [steps[5].id],
|
|
}
|
|
|
|
|
|
class TestDragDropReordering:
|
|
"""Test drag-and-drop reordering functionality."""
|
|
|
|
def test_reorder_within_same_category(self, client, admin_user, wizard_steps):
|
|
"""Test reordering steps within the same category."""
|
|
# Authenticate using session
|
|
with client.session_transaction() as sess:
|
|
sess["_user_id"] = str(admin_user.id)
|
|
sess["_fresh"] = True
|
|
|
|
# Get original order
|
|
plex_pre_ids = wizard_steps["plex_pre"]
|
|
|
|
# Reverse the order
|
|
new_order = list(reversed(plex_pre_ids))
|
|
|
|
# Send reorder request
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{"server_type": "plex", "category": "pre_invite", "order": new_order}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.get_json()
|
|
assert data["status"] == "ok"
|
|
|
|
# Verify new positions in database
|
|
steps = (
|
|
WizardStep.query.filter_by(server_type="plex", category="pre_invite")
|
|
.order_by(WizardStep.position)
|
|
.all()
|
|
)
|
|
|
|
assert len(steps) == 3
|
|
assert [s.id for s in steps] == new_order
|
|
assert steps[0].position == 0
|
|
assert steps[1].position == 1
|
|
assert steps[2].position == 2
|
|
|
|
def test_move_step_between_categories(self, client, admin_user, wizard_steps):
|
|
"""Test moving a step from pre_invite to post_invite category."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
# Get a pre-invite step
|
|
step_id = wizard_steps["plex_pre"][0]
|
|
|
|
# Move it to post-invite category
|
|
plex_post_ids = wizard_steps["plex_post"]
|
|
new_order = plex_post_ids + [step_id]
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{"server_type": "plex", "category": "post_invite", "order": new_order}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.get_json()
|
|
assert data["status"] == "ok"
|
|
|
|
# Verify step moved to post_invite category
|
|
step = db.session.get(WizardStep, step_id)
|
|
assert step is not None
|
|
assert step.category == "post_invite"
|
|
assert step.position == 2 # Last position in new category
|
|
|
|
# Verify it's no longer in pre_invite
|
|
pre_steps = WizardStep.query.filter_by(
|
|
server_type="plex", category="pre_invite"
|
|
).all()
|
|
assert len(pre_steps) == 2
|
|
assert step_id not in [s.id for s in pre_steps]
|
|
|
|
def test_reorder_updates_all_positions(self, client, admin_user, wizard_steps):
|
|
"""Test that reordering updates all step positions correctly."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
# Get original order
|
|
plex_pre_ids = wizard_steps["plex_pre"]
|
|
|
|
# Create a custom order: [2, 0, 1]
|
|
new_order = [plex_pre_ids[2], plex_pre_ids[0], plex_pre_ids[1]]
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{"server_type": "plex", "category": "pre_invite", "order": new_order}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
# Verify positions are sequential starting from 0
|
|
steps = (
|
|
WizardStep.query.filter_by(server_type="plex", category="pre_invite")
|
|
.order_by(WizardStep.position)
|
|
.all()
|
|
)
|
|
|
|
assert [s.id for s in steps] == new_order
|
|
assert [s.position for s in steps] == [0, 1, 2]
|
|
|
|
def test_reorder_with_invalid_category(self, client, admin_user, wizard_steps):
|
|
"""Test that reordering with invalid category returns error."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{
|
|
"server_type": "plex",
|
|
"category": "invalid_category",
|
|
"order": wizard_steps["plex_pre"],
|
|
}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
def test_reorder_with_missing_parameters(self, client, admin_user, wizard_steps):
|
|
"""Test that reordering with missing parameters returns error."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
# Missing category
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps({"server_type": "plex", "order": wizard_steps["plex_pre"]}),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
def test_reorder_requires_authentication(self, client, wizard_steps):
|
|
"""Test that reordering requires authentication."""
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{
|
|
"server_type": "plex",
|
|
"category": "pre_invite",
|
|
"order": wizard_steps["plex_pre"],
|
|
}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
# Should redirect to login or return 401/403
|
|
assert response.status_code in [302, 401, 403]
|
|
|
|
def test_reorder_preserves_other_categories(self, client, admin_user, wizard_steps):
|
|
"""Test that reordering one category doesn't affect other categories."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
# Get original post-invite steps
|
|
original_post_steps = (
|
|
WizardStep.query.filter_by(server_type="plex", category="post_invite")
|
|
.order_by(WizardStep.position)
|
|
.all()
|
|
)
|
|
original_post_ids = [s.id for s in original_post_steps]
|
|
|
|
# Reorder pre-invite steps
|
|
plex_pre_ids = wizard_steps["plex_pre"]
|
|
new_order = list(reversed(plex_pre_ids))
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{"server_type": "plex", "category": "pre_invite", "order": new_order}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
# Verify post-invite steps unchanged
|
|
post_steps = (
|
|
WizardStep.query.filter_by(server_type="plex", category="post_invite")
|
|
.order_by(WizardStep.position)
|
|
.all()
|
|
)
|
|
|
|
assert [s.id for s in post_steps] == original_post_ids
|
|
assert [s.position for s in post_steps] == [0, 1]
|
|
|
|
def test_reorder_preserves_other_servers(self, client, admin_user, wizard_steps):
|
|
"""Test that reordering one server doesn't affect other servers."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
# Get original jellyfin step
|
|
original_jellyfin = WizardStep.query.filter_by(
|
|
server_type="jellyfin", category="pre_invite"
|
|
).first()
|
|
assert original_jellyfin is not None
|
|
original_jellyfin_id = original_jellyfin.id
|
|
original_jellyfin_position = original_jellyfin.position
|
|
|
|
# Reorder plex steps
|
|
plex_pre_ids = wizard_steps["plex_pre"]
|
|
new_order = list(reversed(plex_pre_ids))
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{"server_type": "plex", "category": "pre_invite", "order": new_order}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
# Verify jellyfin step unchanged
|
|
jellyfin_step = db.session.get(WizardStep, original_jellyfin_id)
|
|
assert jellyfin_step is not None
|
|
assert jellyfin_step.server_type == "jellyfin"
|
|
assert jellyfin_step.category == "pre_invite"
|
|
assert jellyfin_step.position == original_jellyfin_position
|
|
|
|
|
|
class TestDragDropEdgeCases:
|
|
"""Test edge cases and error scenarios for drag-and-drop."""
|
|
|
|
def test_reorder_empty_list(self, client, admin_user):
|
|
"""Test reordering with empty order list."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{"server_type": "plex", "category": "pre_invite", "order": []}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
# Should succeed (no-op)
|
|
assert response.status_code == 200
|
|
|
|
def test_reorder_single_step(self, client, admin_user, wizard_steps):
|
|
"""Test reordering with single step (no-op)."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
# jellyfin_pre is a single-item list, get the first element
|
|
jellyfin_id = (
|
|
wizard_steps["jellyfin_pre"][0]
|
|
if isinstance(wizard_steps["jellyfin_pre"], list)
|
|
else wizard_steps["jellyfin_pre"]
|
|
)
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{
|
|
"server_type": "jellyfin",
|
|
"category": "pre_invite",
|
|
"order": [jellyfin_id],
|
|
}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
# Verify step unchanged
|
|
step = db.session.get(WizardStep, jellyfin_id)
|
|
assert step is not None
|
|
assert step.position == 0
|
|
|
|
def test_reorder_with_nonexistent_step_id(self, client, admin_user, wizard_steps):
|
|
"""Test reordering with non-existent step ID."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
# Use a very high ID that doesn't exist
|
|
nonexistent_id = 99999
|
|
plex_pre_ids = wizard_steps["plex_pre"]
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{
|
|
"server_type": "plex",
|
|
"category": "pre_invite",
|
|
"order": plex_pre_ids + [nonexistent_id],
|
|
}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
# Should succeed but ignore non-existent ID
|
|
assert response.status_code == 200
|
|
|
|
# Verify only valid steps were reordered
|
|
steps = (
|
|
WizardStep.query.filter_by(server_type="plex", category="pre_invite")
|
|
.order_by(WizardStep.position)
|
|
.all()
|
|
)
|
|
|
|
assert len(steps) == 3
|
|
assert all(s.id in plex_pre_ids for s in steps)
|
|
|
|
def test_reorder_with_duplicate_ids(self, client, admin_user, wizard_steps):
|
|
"""Test reordering with duplicate step IDs causes constraint violation."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
plex_pre_ids = wizard_steps["plex_pre"]
|
|
# Include first ID twice - this will cause a unique constraint violation
|
|
# because the same step ID will try to have the same position twice
|
|
order_with_duplicate = [plex_pre_ids[0], plex_pre_ids[0], plex_pre_ids[1]]
|
|
|
|
# This should fail with a 500 error due to database constraint
|
|
# In a production system, this would be caught and handled gracefully
|
|
with pytest.raises(IntegrityError):
|
|
client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{
|
|
"server_type": "plex",
|
|
"category": "pre_invite",
|
|
"order": order_with_duplicate,
|
|
}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
def test_reorder_with_invalid_json(self, client, admin_user):
|
|
"""Test reordering with invalid JSON."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data="invalid json",
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
def test_reorder_with_wrong_content_type(self, client, admin_user):
|
|
"""Test reordering with wrong content type."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data="server_type=plex&category=pre_invite",
|
|
content_type="application/x-www-form-urlencoded",
|
|
)
|
|
|
|
# Should fail or handle gracefully
|
|
assert response.status_code in [400, 415]
|
|
|
|
|
|
class TestDragDropUIIntegration:
|
|
"""Test UI integration aspects of drag-and-drop."""
|
|
|
|
def test_wizard_steps_page_loads(self, client, admin_user, wizard_steps):
|
|
"""Test that wizard steps page loads successfully with steps."""
|
|
# Need wizard_steps fixture to populate the page
|
|
_ = wizard_steps
|
|
|
|
authenticate_client(client, admin_user)
|
|
|
|
response = client.get("/settings/wizard", follow_redirects=True)
|
|
assert response.status_code == 200
|
|
|
|
html = response.data.decode("utf-8")
|
|
|
|
# Check that the page contains wizard-related content
|
|
# The exact structure may vary, but we should see wizard-related elements
|
|
assert "wizard" in html.lower() or "step" in html.lower()
|
|
|
|
def test_wizard_admin_page_accessible(self, client, admin_user):
|
|
"""Test that wizard admin page is accessible to authenticated users."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
response = client.get("/settings/wizard", follow_redirects=True)
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestDragDropPayloadStructure:
|
|
"""Test the structure of AJAX request payloads."""
|
|
|
|
def test_payload_includes_server_type(self, client, admin_user, wizard_steps):
|
|
"""Test that reorder payload includes server_type."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{
|
|
"server_type": "plex",
|
|
"category": "pre_invite",
|
|
"order": wizard_steps["plex_pre"],
|
|
}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
def test_payload_includes_category(self, client, admin_user, wizard_steps):
|
|
"""Test that reorder payload includes category."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{
|
|
"server_type": "plex",
|
|
"category": "pre_invite",
|
|
"order": wizard_steps["plex_pre"],
|
|
}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
def test_payload_includes_order_array(self, client, admin_user, wizard_steps):
|
|
"""Test that reorder payload includes order array."""
|
|
authenticate_client(client, admin_user)
|
|
|
|
response = client.post(
|
|
"/settings/wizard/reorder",
|
|
data=json.dumps(
|
|
{
|
|
"server_type": "plex",
|
|
"category": "pre_invite",
|
|
"order": wizard_steps["plex_pre"],
|
|
}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 200
|