mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-30 05:47:01 -05:00
feat: Implement MFA authentication, add ride statistics model, and update various services, APIs, and tests across the application.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import pytest
|
||||
import contextlib
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from playwright.sync_api import Page
|
||||
|
||||
|
||||
@@ -212,9 +214,10 @@ def admin_page(page: Page, live_server, setup_test_data):
|
||||
def submission_pending(db):
|
||||
"""Create a pending EditSubmission for FSM testing."""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from apps.moderation.models import EditSubmission
|
||||
from apps.parks.models import Park
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -246,19 +249,18 @@ def submission_pending(db):
|
||||
yield submission
|
||||
|
||||
# Cleanup
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
submission.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def submission_approved(db):
|
||||
"""Create an approved EditSubmission for FSM testing."""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from apps.moderation.models import EditSubmission
|
||||
from apps.parks.models import Park
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -285,10 +287,8 @@ def submission_approved(db):
|
||||
|
||||
yield submission
|
||||
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
submission.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -322,9 +322,10 @@ def park_closed_temp(db):
|
||||
@pytest.fixture
|
||||
def park_closed_perm(db):
|
||||
"""Create a permanently closed Park for FSM testing."""
|
||||
from tests.factories import ParkFactory
|
||||
from datetime import date, timedelta
|
||||
|
||||
from tests.factories import ParkFactory
|
||||
|
||||
park = ParkFactory(
|
||||
name="FSM Test Park Closed Perm",
|
||||
slug="fsm-test-park-closed-perm",
|
||||
@@ -368,9 +369,10 @@ def ride_sbno(db, park_operating):
|
||||
@pytest.fixture
|
||||
def ride_closed_perm(db, park_operating):
|
||||
"""Create a permanently closed Ride for FSM testing."""
|
||||
from tests.factories import RideFactory
|
||||
from datetime import date, timedelta
|
||||
|
||||
from tests.factories import RideFactory
|
||||
|
||||
ride = RideFactory(
|
||||
name="FSM Test Ride Closed Perm",
|
||||
slug="fsm-test-ride-closed-perm",
|
||||
@@ -386,6 +388,7 @@ def ride_closed_perm(db, park_operating):
|
||||
def queue_item_pending(db):
|
||||
"""Create a pending ModerationQueue item for FSM testing."""
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from apps.moderation.models import ModerationQueue
|
||||
|
||||
User = get_user_model()
|
||||
@@ -406,16 +409,15 @@ def queue_item_pending(db):
|
||||
|
||||
yield queue_item
|
||||
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
queue_item.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bulk_operation_pending(db):
|
||||
"""Create a pending BulkOperation for FSM testing."""
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from apps.moderation.models import BulkOperation
|
||||
|
||||
User = get_user_model()
|
||||
@@ -437,10 +439,8 @@ def bulk_operation_pending(db):
|
||||
|
||||
yield operation
|
||||
|
||||
try:
|
||||
with contextlib.suppress(Exception):
|
||||
operation.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from playwright.sync_api import expect, Page
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
def test_login_page(page: Page):
|
||||
|
||||
@@ -17,6 +17,8 @@ These tests verify:
|
||||
- User-friendly error messages are displayed
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
@@ -73,9 +75,10 @@ class TestInvalidTransitionErrors:
|
||||
):
|
||||
"""Test that trying to approve an already-approved submission shows error."""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from apps.moderation.models import EditSubmission
|
||||
from apps.parks.models import Park
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -351,9 +354,10 @@ class TestConfirmationDialogs:
|
||||
):
|
||||
"""Test that confirmation dialog appears for reject transition."""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from apps.moderation.models import EditSubmission
|
||||
from apps.parks.models import Park
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -414,9 +418,10 @@ class TestConfirmationDialogs:
|
||||
):
|
||||
"""Test that canceling the confirmation dialog prevents the transition."""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from apps.moderation.models import EditSubmission
|
||||
from apps.parks.models import Park
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -472,9 +477,10 @@ class TestConfirmationDialogs:
|
||||
):
|
||||
"""Test that accepting the confirmation dialog executes the transition."""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from apps.moderation.models import EditSubmission
|
||||
from apps.parks.models import Park
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -629,7 +635,7 @@ class TestToastNotificationBehavior:
|
||||
expect(toast).to_be_visible(timeout=5000)
|
||||
|
||||
# Should have error/danger styling (red)
|
||||
expect(toast).to_have_class(/error|danger|bg-red|text-red/)
|
||||
expect(toast).to_have_class(re.compile(r"error|danger|bg-red|text-red"))
|
||||
|
||||
def test_success_toast_has_correct_styling(
|
||||
self, mod_page: Page, live_server, db
|
||||
@@ -665,4 +671,4 @@ class TestToastNotificationBehavior:
|
||||
expect(toast).to_be_visible(timeout=5000)
|
||||
|
||||
# Should have success styling (green)
|
||||
expect(toast).to_have_class(/success|bg-green|text-green/)
|
||||
expect(toast).to_have_class(re.compile(r"success|bg-green|text-green"))
|
||||
|
||||
@@ -89,9 +89,10 @@ class TestRegularUserPermissions:
|
||||
):
|
||||
"""Test that regular users cannot approve submissions."""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from apps.moderation.models import EditSubmission
|
||||
from apps.parks.models import Park
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -229,9 +230,10 @@ class TestModeratorPermissions:
|
||||
):
|
||||
"""Test that moderators CAN see and use approve button."""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from apps.moderation.models import EditSubmission
|
||||
from apps.parks.models import Park
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -501,5 +503,5 @@ class TestTransitionButtonVisibility:
|
||||
assert visible, "Expected demolish or relocate button for CLOSED_PERM state"
|
||||
|
||||
# Reopen should still be visible to restore to operating
|
||||
reopen_btn = status_actions.get_by_role("button", name="Reopen")
|
||||
status_actions.get_by_role("button", name="Reopen")
|
||||
# May or may not be visible depending on FSM configuration
|
||||
|
||||
@@ -15,14 +15,15 @@ These tests verify:
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pending_submission(db):
|
||||
"""Create a pending EditSubmission for testing."""
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from apps.moderation.models import EditSubmission
|
||||
from apps.parks.models import Park
|
||||
|
||||
@@ -63,9 +64,10 @@ def pending_submission(db):
|
||||
def pending_photo_submission(db):
|
||||
"""Create a pending PhotoSubmission for testing."""
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from apps.moderation.models import PhotoSubmission
|
||||
from apps.parks.models import Park
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -146,7 +148,6 @@ class TestEditSubmissionTransitions:
|
||||
expect(status_badge).to_contain_text("Approved")
|
||||
|
||||
# Verify database state
|
||||
from apps.moderation.models import EditSubmission
|
||||
pending_submission.refresh_from_db()
|
||||
assert pending_submission.status == "APPROVED"
|
||||
|
||||
@@ -180,7 +181,6 @@ class TestEditSubmissionTransitions:
|
||||
expect(status_badge).to_contain_text("Rejected")
|
||||
|
||||
# Verify database state
|
||||
from apps.moderation.models import EditSubmission
|
||||
pending_submission.refresh_from_db()
|
||||
assert pending_submission.status == "REJECTED"
|
||||
|
||||
@@ -214,7 +214,6 @@ class TestEditSubmissionTransitions:
|
||||
expect(status_badge).to_contain_text("Escalated")
|
||||
|
||||
# Verify database state
|
||||
from apps.moderation.models import EditSubmission
|
||||
pending_submission.refresh_from_db()
|
||||
assert pending_submission.status == "ESCALATED"
|
||||
|
||||
@@ -254,7 +253,6 @@ class TestPhotoSubmissionTransitions:
|
||||
expect(toast).to_contain_text("approved")
|
||||
|
||||
# Verify database state
|
||||
from apps.moderation.models import PhotoSubmission
|
||||
pending_photo_submission.refresh_from_db()
|
||||
assert pending_photo_submission.status == "APPROVED"
|
||||
|
||||
@@ -290,7 +288,6 @@ class TestPhotoSubmissionTransitions:
|
||||
expect(toast).to_contain_text("rejected")
|
||||
|
||||
# Verify database state
|
||||
from apps.moderation.models import PhotoSubmission
|
||||
pending_photo_submission.refresh_from_db()
|
||||
assert pending_photo_submission.status == "REJECTED"
|
||||
|
||||
@@ -302,6 +299,7 @@ class TestModerationQueueTransitions:
|
||||
def pending_queue_item(self, db):
|
||||
"""Create a pending ModerationQueue item for testing."""
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from apps.moderation.models import ModerationQueue
|
||||
|
||||
User = get_user_model()
|
||||
@@ -346,7 +344,6 @@ class TestModerationQueueTransitions:
|
||||
expect(status_badge).to_contain_text("In Progress", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.moderation.models import ModerationQueue
|
||||
pending_queue_item.refresh_from_db()
|
||||
assert pending_queue_item.status == "IN_PROGRESS"
|
||||
|
||||
@@ -376,7 +373,6 @@ class TestModerationQueueTransitions:
|
||||
toast = mod_page.locator('[data-toast]')
|
||||
expect(toast).to_be_visible(timeout=5000)
|
||||
|
||||
from apps.moderation.models import ModerationQueue
|
||||
pending_queue_item.refresh_from_db()
|
||||
assert pending_queue_item.status == "COMPLETED"
|
||||
|
||||
@@ -388,6 +384,7 @@ class TestBulkOperationTransitions:
|
||||
def pending_bulk_operation(self, db):
|
||||
"""Create a pending BulkOperation for testing."""
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from apps.moderation.models import BulkOperation
|
||||
|
||||
User = get_user_model()
|
||||
@@ -438,7 +435,6 @@ class TestBulkOperationTransitions:
|
||||
expect(toast).to_contain_text("cancel")
|
||||
|
||||
# Verify database state
|
||||
from apps.moderation.models import BulkOperation
|
||||
pending_bulk_operation.refresh_from_db()
|
||||
assert pending_bulk_operation.status == "CANCELLED"
|
||||
|
||||
@@ -470,7 +466,7 @@ class TestTransitionLoadingStates:
|
||||
|
||||
# Check for htmx-indicator visibility (may be brief)
|
||||
# The indicator should become visible during the request
|
||||
loading_indicator = submission_row.locator('.htmx-indicator')
|
||||
submission_row.locator('.htmx-indicator')
|
||||
|
||||
# Wait for transition to complete
|
||||
toast = mod_page.locator('[data-toast]')
|
||||
|
||||
@@ -83,7 +83,7 @@ class TestParkDetailPage:
|
||||
page.goto(f"{live_server.url}/parks/{park.slug}/")
|
||||
|
||||
# Look for rides section/tab
|
||||
rides_section = page.locator(
|
||||
page.locator(
|
||||
"[data-testid='rides-section'], #rides, [role='tabpanel']"
|
||||
)
|
||||
|
||||
@@ -162,7 +162,7 @@ class TestParkNavigation:
|
||||
# Click parks link in breadcrumb
|
||||
breadcrumb.get_by_role("link", name="Parks").click()
|
||||
|
||||
expect(page).to_have_url(f"**/parks/**")
|
||||
expect(page).to_have_url("**/parks/**")
|
||||
|
||||
def test__back_button__returns_to_previous_page(
|
||||
self, page: Page, live_server, parks_data
|
||||
@@ -179,4 +179,4 @@ class TestParkNavigation:
|
||||
# Go back
|
||||
page.go_back()
|
||||
|
||||
expect(page).to_have_url(f"**/parks/**")
|
||||
expect(page).to_have_url("**/parks/**")
|
||||
|
||||
@@ -13,15 +13,16 @@ These tests verify:
|
||||
- StateLog entry created in database
|
||||
"""
|
||||
|
||||
import re
|
||||
from datetime import date, timedelta
|
||||
|
||||
import pytest
|
||||
from playwright.sync_api import Page, expect
|
||||
from datetime import date, timedelta
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def operating_park(db):
|
||||
"""Create an operating Park for testing status transitions."""
|
||||
from apps.parks.models import Park
|
||||
from tests.factories import ParkFactory
|
||||
|
||||
# Use factory to create a complete park
|
||||
@@ -93,7 +94,6 @@ class TestParkStatusTransitions:
|
||||
expect(status_badge).to_contain_text("Temporarily Closed", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.parks.models import Park
|
||||
operating_park.refresh_from_db()
|
||||
assert operating_park.status == "CLOSED_TEMP"
|
||||
|
||||
@@ -127,7 +127,6 @@ class TestParkStatusTransitions:
|
||||
expect(status_badge).to_contain_text("Operating", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.parks.models import Park
|
||||
operating_park.refresh_from_db()
|
||||
assert operating_park.status == "OPERATING"
|
||||
|
||||
@@ -165,7 +164,6 @@ class TestParkStatusTransitions:
|
||||
expect(status_badge).to_contain_text("Permanently Closed", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.parks.models import Park
|
||||
operating_park.refresh_from_db()
|
||||
assert operating_park.status == "CLOSED_PERM"
|
||||
|
||||
@@ -206,7 +204,6 @@ class TestParkStatusTransitions:
|
||||
expect(status_badge).to_contain_text("Demolished", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.parks.models import Park
|
||||
operating_park.refresh_from_db()
|
||||
assert operating_park.status == "DEMOLISHED"
|
||||
|
||||
@@ -274,7 +271,6 @@ class TestRideStatusTransitions:
|
||||
expect(status_badge).to_contain_text("Temporarily Closed", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.rides.models import Ride
|
||||
operating_ride.refresh_from_db()
|
||||
assert operating_ride.status == "CLOSED_TEMP"
|
||||
|
||||
@@ -310,7 +306,6 @@ class TestRideStatusTransitions:
|
||||
expect(status_badge).to_contain_text("SBNO", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.rides.models import Ride
|
||||
operating_ride.refresh_from_db()
|
||||
assert operating_ride.status == "SBNO"
|
||||
|
||||
@@ -344,7 +339,6 @@ class TestRideStatusTransitions:
|
||||
expect(status_badge).to_contain_text("Operating", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.rides.models import Ride
|
||||
operating_ride.refresh_from_db()
|
||||
assert operating_ride.status == "OPERATING"
|
||||
|
||||
@@ -384,7 +378,6 @@ class TestRideStatusTransitions:
|
||||
expect(status_badge).to_contain_text("Permanently Closed", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.rides.models import Ride
|
||||
operating_ride.refresh_from_db()
|
||||
assert operating_ride.status == "CLOSED_PERM"
|
||||
|
||||
@@ -429,7 +422,6 @@ class TestRideStatusTransitions:
|
||||
expect(status_badge).to_contain_text("Demolished", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.rides.models import Ride
|
||||
operating_ride.refresh_from_db()
|
||||
assert operating_ride.status == "DEMOLISHED"
|
||||
|
||||
@@ -474,7 +466,6 @@ class TestRideStatusTransitions:
|
||||
expect(status_badge).to_contain_text("Relocated", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.rides.models import Ride
|
||||
operating_ride.refresh_from_db()
|
||||
assert operating_ride.status == "RELOCATED"
|
||||
|
||||
@@ -507,7 +498,6 @@ class TestRideClosingWorkflow:
|
||||
expect(status_badge).to_contain_text("Closing", timeout=5000)
|
||||
|
||||
# Verify database state
|
||||
from apps.rides.models import Ride
|
||||
operating_ride.refresh_from_db()
|
||||
assert operating_ride.status == "CLOSING"
|
||||
else:
|
||||
@@ -549,7 +539,7 @@ class TestStatusBadgeStyling:
|
||||
mod_page.wait_for_load_state("networkidle")
|
||||
|
||||
status_badge = mod_page.locator('[data-status-badge]')
|
||||
expect(status_badge).to_have_class(/bg-green|text-green|success/)
|
||||
expect(status_badge).to_have_class(re.compile(r"bg-green|text-green|success"))
|
||||
|
||||
def test_closed_temp_status_badge_style(
|
||||
self, mod_page: Page, operating_park, live_server
|
||||
@@ -562,7 +552,7 @@ class TestStatusBadgeStyling:
|
||||
mod_page.wait_for_load_state("networkidle")
|
||||
|
||||
status_badge = mod_page.locator('[data-status-badge]')
|
||||
expect(status_badge).to_have_class(/bg-yellow|text-yellow|warning/)
|
||||
expect(status_badge).to_have_class(re.compile(r"bg-yellow|text-yellow|warning"))
|
||||
|
||||
def test_closed_perm_status_badge_style(
|
||||
self, mod_page: Page, operating_park, live_server
|
||||
@@ -575,7 +565,7 @@ class TestStatusBadgeStyling:
|
||||
mod_page.wait_for_load_state("networkidle")
|
||||
|
||||
status_badge = mod_page.locator('[data-status-badge]')
|
||||
expect(status_badge).to_have_class(/bg-red|text-red|danger/)
|
||||
expect(status_badge).to_have_class(re.compile(r"bg-red|text-red|danger"))
|
||||
|
||||
def test_demolished_status_badge_style(
|
||||
self, mod_page: Page, operating_park, live_server
|
||||
@@ -588,4 +578,4 @@ class TestStatusBadgeStyling:
|
||||
mod_page.wait_for_load_state("networkidle")
|
||||
|
||||
status_badge = mod_page.locator('[data-status-badge]')
|
||||
expect(status_badge).to_have_class(/bg-gray|text-gray|muted/)
|
||||
expect(status_badge).to_have_class(re.compile(r"bg-gray|text-gray|muted"))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from playwright.sync_api import expect, Page
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
def test_parks_list_page(page: Page):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from playwright.sync_api import expect, Page
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
def test_profile_page(page: Page):
|
||||
|
||||
@@ -168,7 +168,6 @@ class TestReviewEditing:
|
||||
def test__own_review__shows_edit_button(self, auth_page: Page, live_server, test_review):
|
||||
"""Test user's own review shows edit button."""
|
||||
# Navigate to reviews after creating one
|
||||
park_url = auth_page.url
|
||||
|
||||
# Look for edit button on own review
|
||||
edit_button = auth_page.locator(
|
||||
@@ -324,7 +323,7 @@ class TestRideReviews:
|
||||
intensity_field = auth_page.locator(
|
||||
"select[name='intensity'], input[name='intensity']"
|
||||
)
|
||||
wait_time_field = auth_page.locator(
|
||||
auth_page.locator(
|
||||
"input[name='wait_time'], select[name='wait_time']"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from playwright.sync_api import expect, Page
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
def test_reviews_list_page(page: Page):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from playwright.sync_api import expect, Page
|
||||
from playwright.sync_api import Page, expect
|
||||
|
||||
|
||||
def test_rides_list_page(page: Page):
|
||||
|
||||
Reference in New Issue
Block a user