Add secret management guide, client-side performance monitoring, and search accessibility enhancements

- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
This commit is contained in:
pacnpal
2025-12-23 16:41:42 -05:00
parent ae31e889d7
commit edcd8f2076
155 changed files with 22046 additions and 4645 deletions

View File

@@ -0,0 +1,2 @@
# Accessibility Tests Package
# Contains automated WCAG 2.1 AA compliance tests

View File

@@ -0,0 +1,384 @@
"""
Automated WCAG 2.1 AA Compliance Tests
This module provides automated accessibility testing using axe-core via Selenium.
Tests verify WCAG 2.1 AA compliance across key pages of the ThrillWiki application.
Requirements:
- selenium
- axe-selenium-python
- Chrome browser with ChromeDriver
Installation:
pip install selenium axe-selenium-python
Usage:
python manage.py test backend.tests.accessibility
Note: These tests require a running server and browser. They are skipped if
dependencies are not installed or if running in CI without browser support.
"""
import os
import unittest
from django.test import TestCase, LiveServerTestCase, override_settings
from django.urls import reverse
from django.contrib.auth import get_user_model
# Check if selenium and axe are available
try:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
HAS_SELENIUM = True
except ImportError:
HAS_SELENIUM = False
try:
from axe_selenium_python import Axe
HAS_AXE = True
except ImportError:
HAS_AXE = False
User = get_user_model()
def skip_if_no_browser():
"""Decorator to skip tests if browser dependencies are not available."""
if not HAS_SELENIUM:
return unittest.skip("Selenium not installed")
if not HAS_AXE:
return unittest.skip("axe-selenium-python not installed")
if os.environ.get('CI') and not os.environ.get('BROWSER_TESTS'):
return unittest.skip("Browser tests disabled in CI")
return lambda func: func
class AccessibilityTestMixin:
"""Mixin providing common accessibility testing utilities."""
def run_axe_audit(self, url_name=None, url=None):
"""
Run axe accessibility audit on a page.
Args:
url_name: Django URL name to reverse
url: Full URL (alternative to url_name)
Returns:
dict: Axe results containing violations and passes
"""
if url_name:
url = f'{self.live_server_url}{reverse(url_name)}'
elif not url:
raise ValueError("Either url_name or url must be provided")
self.driver.get(url)
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
axe = Axe(self.driver)
axe.inject()
results = axe.run()
return results
def assert_no_critical_violations(self, results, page_name="page"):
"""
Assert no critical or serious accessibility violations.
Args:
results: Axe audit results
page_name: Name of page for error messages
"""
critical_violations = [
v for v in results.get('violations', [])
if v.get('impact') in ('critical', 'serious')
]
if critical_violations:
violation_details = "\n".join([
f"- {v['id']}: {v['description']} (impact: {v['impact']})"
for v in critical_violations
])
self.fail(
f"Critical accessibility violations found on {page_name}:\n"
f"{violation_details}"
)
def assert_wcag_aa_compliant(self, results, page_name="page"):
"""
Assert WCAG 2.1 AA compliance (no violations at all).
Args:
results: Axe audit results
page_name: Name of page for error messages
"""
violations = results.get('violations', [])
if violations:
violation_details = "\n".join([
f"- {v['id']}: {v['description']} (impact: {v['impact']})"
for v in violations
])
self.fail(
f"WCAG 2.1 AA violations found on {page_name}:\n"
f"{violation_details}"
)
@skip_if_no_browser()
@override_settings(DEBUG=True)
class WCAGComplianceTests(AccessibilityTestMixin, LiveServerTestCase):
"""
Automated WCAG 2.1 AA compliance tests for ThrillWiki.
These tests use axe-core via Selenium to automatically detect
accessibility issues across key pages.
"""
@classmethod
def setUpClass(cls):
super().setUpClass()
# Configure Chrome for headless testing
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--window-size=1920,1080')
try:
cls.driver = webdriver.Chrome(options=chrome_options)
cls.driver.implicitly_wait(10)
except Exception as e:
raise unittest.SkipTest(f"Chrome WebDriver not available: {e}")
@classmethod
def tearDownClass(cls):
if hasattr(cls, 'driver'):
cls.driver.quit()
super().tearDownClass()
def test_homepage_accessibility(self):
"""Test homepage WCAG 2.1 AA compliance."""
results = self.run_axe_audit(url_name='home')
self.assert_no_critical_violations(results, "homepage")
def test_park_list_accessibility(self):
"""Test park list page WCAG 2.1 AA compliance."""
results = self.run_axe_audit(url_name='parks:park_list')
self.assert_no_critical_violations(results, "park list")
def test_ride_list_accessibility(self):
"""Test ride list page WCAG 2.1 AA compliance."""
results = self.run_axe_audit(url_name='rides:global_ride_list')
self.assert_no_critical_violations(results, "ride list")
def test_manufacturer_list_accessibility(self):
"""Test manufacturer list page WCAG 2.1 AA compliance."""
results = self.run_axe_audit(url_name='rides:manufacturer_list')
self.assert_no_critical_violations(results, "manufacturer list")
def test_login_page_accessibility(self):
"""Test login page WCAG 2.1 AA compliance."""
results = self.run_axe_audit(url_name='account_login')
self.assert_no_critical_violations(results, "login page")
def test_signup_page_accessibility(self):
"""Test signup page WCAG 2.1 AA compliance."""
results = self.run_axe_audit(url_name='account_signup')
self.assert_no_critical_violations(results, "signup page")
class HTMLAccessibilityTests(TestCase):
"""
Unit tests for accessibility features in rendered HTML.
These tests don't require a browser and verify that templates
render correct accessibility attributes.
"""
def test_homepage_has_main_landmark(self):
"""Verify homepage has a main landmark."""
response = self.client.get(reverse('home'))
self.assertContains(response, '<main')
def test_homepage_has_skip_link(self):
"""Verify homepage has skip to content link."""
response = self.client.get(reverse('home'))
# Skip link should be present
self.assertContains(response, 'skip', msg_prefix="Should have skip link")
def test_navigation_has_aria_labels(self):
"""Verify navigation elements have ARIA labels."""
response = self.client.get(reverse('home'))
# Header should have aria-label or be wrapped in nav
self.assertTrue(
b'aria-label' in response.content or b'<nav' in response.content,
"Navigation should have ARIA attributes"
)
def test_images_have_alt_text(self):
"""Verify images have alt attributes."""
response = self.client.get(reverse('home'))
content = response.content.decode('utf-8')
# Find all img tags
import re
img_tags = re.findall(r'<img[^>]*>', content)
for img in img_tags:
self.assertIn(
'alt=',
img,
f"Image missing alt attribute: {img[:100]}"
)
def test_form_fields_have_labels(self):
"""Verify form fields have associated labels."""
response = self.client.get(reverse('account_login'))
content = response.content.decode('utf-8')
# Find input elements (excluding hidden and submit)
import re
inputs = re.findall(
r'<input[^>]*type=["\'](?!hidden|submit)[^"\']*["\'][^>]*>',
content
)
for inp in inputs:
# Each input should have id attribute for label association
self.assertTrue(
'id=' in inp or 'aria-label' in inp,
f"Input missing id or aria-label: {inp[:100]}"
)
def test_buttons_are_accessible(self):
"""Verify buttons have accessible names."""
response = self.client.get(reverse('home'))
content = response.content.decode('utf-8')
import re
# Find button elements
buttons = re.findall(r'<button[^>]*>.*?</button>', content, re.DOTALL)
for button in buttons:
# Button should have text content or aria-label
has_text = bool(re.search(r'>([^<]+)<', button))
has_aria = 'aria-label' in button
self.assertTrue(
has_text or has_aria,
f"Button missing accessible name: {button[:100]}"
)
class KeyboardNavigationTests(TestCase):
"""
Tests for keyboard navigation requirements.
These tests verify that interactive elements are keyboard accessible.
"""
def test_interactive_elements_are_focusable(self):
"""Verify interactive elements don't have tabindex=-1."""
response = self.client.get(reverse('home'))
content = response.content.decode('utf-8')
# Links and buttons should not have tabindex=-1 (unless intentionally hidden)
import re
problematic = re.findall(
r'<(a|button)[^>]*tabindex=["\']?-1["\']?[^>]*>',
content
)
# Filter out elements that are legitimately hidden
for elem in problematic:
self.assertIn(
'aria-hidden',
elem,
f"Interactive element has tabindex=-1 without aria-hidden: {elem}"
)
def test_modals_have_escape_handler(self):
"""Verify modal templates include escape key handling."""
from django.template.loader import get_template
template = get_template('components/modals/modal_inner.html')
source = template.template.source
self.assertIn(
'escape',
source.lower(),
"Modal should handle Escape key"
)
def test_dropdowns_have_keyboard_support(self):
"""Verify dropdown menus support keyboard navigation."""
response = self.client.get(reverse('home'))
content = response.content.decode('utf-8')
# Check for aria-expanded on dropdown triggers
if 'dropdown' in content.lower() or 'menu' in content.lower():
self.assertIn(
'aria-expanded',
content,
"Dropdown should have aria-expanded attribute"
)
class ARIAAttributeTests(TestCase):
"""
Tests for correct ARIA attribute usage.
"""
def test_modal_has_dialog_role(self):
"""Verify modal has role=dialog."""
from django.template.loader import get_template
template = get_template('components/modals/modal_inner.html')
source = template.template.source
self.assertIn(
'role="dialog"',
source,
"Modal should have role=dialog"
)
def test_modal_has_aria_modal(self):
"""Verify modal has aria-modal=true."""
from django.template.loader import get_template
template = get_template('components/modals/modal_inner.html')
source = template.template.source
self.assertIn(
'aria-modal="true"',
source,
"Modal should have aria-modal=true"
)
def test_breadcrumb_has_navigation_role(self):
"""Verify breadcrumbs use nav element with aria-label."""
from django.template.loader import get_template
template = get_template('components/navigation/breadcrumbs.html')
source = template.template.source
self.assertIn(
'<nav',
source,
"Breadcrumbs should use nav element"
)
self.assertIn(
'aria-label',
source,
"Breadcrumbs nav should have aria-label"
)

View File

@@ -14,16 +14,6 @@ uv pip install -r requirements.txt
playwright install
```
3. Create test fixtures:
```bash
mkdir -p tests/fixtures
```
4. Add test assets:
Place the following files in `tests/fixtures/`:
- `test_photo.jpg` - A sample photo for testing uploads
- `test_avatar.jpg` - A sample avatar image for profile tests
## Running Tests
### Run all tests:
@@ -86,6 +76,21 @@ You can create these users using Django management commands:
python manage.py create_test_users
```
## Test Images
Tests that require file uploads use the `test_images` fixture defined in `conftest.py`. This fixture generates temporary JPEG images at runtime, eliminating the need for external fixture files.
Usage in tests:
```python
def test_add_photo(page: Page, test_images):
page.get_by_label("Photo").set_input_files(test_images["test_photo"])
page.get_by_label("Avatar").set_input_files(test_images["test_avatar"])
```
Available keys:
- `test_images["test_photo"]` - Path to a temporary test photo
- `test_images["test_avatar"]` - Path to a temporary test avatar
## Test Environment
Tests expect:
@@ -104,8 +109,7 @@ Tests expect:
1. **Connection Refused**: Ensure Django server is running
2. **Element Not Found**: Check selectors and page load timing
3. **Upload Failures**: Verify test fixtures exist
4. **Authentication Errors**: Verify test users exist in database
3. **Authentication Errors**: Verify test users exist in database
## Contributing

View File

@@ -6,11 +6,6 @@ echo "Cleaning up test data..."
# Run Django cleanup command
uv run manage.py cleanup_test_data
# Clean up test fixtures
echo "Cleaning up test fixtures..."
rm -f tests/fixtures/test_photo.jpg
rm -f tests/fixtures/test_avatar.jpg
# Clean up Playwright artifacts
echo "Cleaning up Playwright artifacts..."
rm -rf test-results/

View File

@@ -1,4 +1,6 @@
import pytest
import tempfile
from pathlib import Path
from playwright.sync_api import Page
@@ -85,22 +87,82 @@ def mod_page(page: Page, live_server, setup_test_data):
yield page
@pytest.fixture(scope="session")
def test_images():
"""Generate temporary test images for upload tests.
Creates simple valid JPEG images using raw bytes.
Images are cleaned up after the test session.
"""
# Minimal valid JPEG image (1x1 red pixel)
# This is a valid JPEG that any image library will accept
jpeg_bytes = bytes([
0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01,
0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43,
0x00, 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07, 0x07, 0x07, 0x09,
0x09, 0x08, 0x0A, 0x0C, 0x14, 0x0D, 0x0C, 0x0B, 0x0B, 0x0C, 0x19, 0x12,
0x13, 0x0F, 0x14, 0x1D, 0x1A, 0x1F, 0x1E, 0x1D, 0x1A, 0x1C, 0x1C, 0x20,
0x24, 0x2E, 0x27, 0x20, 0x22, 0x2C, 0x23, 0x1C, 0x1C, 0x28, 0x37, 0x29,
0x2C, 0x30, 0x31, 0x34, 0x34, 0x34, 0x1F, 0x27, 0x39, 0x3D, 0x38, 0x32,
0x3C, 0x2E, 0x33, 0x34, 0x32, 0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x01,
0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0xFF, 0xC4, 0x00, 0x1F, 0x00, 0x00,
0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0xFF, 0xC4, 0x00, 0xB5, 0x10, 0x00, 0x02, 0x01, 0x03,
0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D,
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08,
0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72,
0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45,
0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75,
0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3,
0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6,
0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4,
0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFF, 0xDA, 0x00, 0x08, 0x01, 0x01,
0x00, 0x00, 0x3F, 0x00, 0xFB, 0xD5, 0xDB, 0x20, 0xA8, 0xBA, 0xAE, 0xAF,
0xDA, 0xAD, 0x28, 0xA6, 0x02, 0x8A, 0x28, 0x03, 0xFF, 0xD9
])
temp_dir = tempfile.mkdtemp(prefix="thrillwiki_test_")
temp_path = Path(temp_dir)
test_photo_path = temp_path / "test_photo.jpg"
test_avatar_path = temp_path / "test_avatar.jpg"
test_photo_path.write_bytes(jpeg_bytes)
test_avatar_path.write_bytes(jpeg_bytes)
yield {
"test_photo": str(test_photo_path),
"test_avatar": str(test_avatar_path),
}
# Cleanup
import shutil
shutil.rmtree(temp_dir, ignore_errors=True)
@pytest.fixture
def test_park(auth_page: Page, live_server):
def test_park(auth_page: Page, live_server, test_images):
"""Fixture for test park"""
# Create test park using live_server URL
auth_page.goto(f"{live_server.url}/parks/create/")
auth_page.get_by_label("Name").fill("Test Park")
auth_page.get_by_label("Location").fill("Orlando, FL")
auth_page.get_by_label("Description").fill("A test theme park")
auth_page.get_by_label("Photo").set_input_files("tests/fixtures/test_photo.jpg")
auth_page.get_by_label("Photo").set_input_files(test_images["test_photo"])
auth_page.get_by_role("button", name="Create Park").click()
yield auth_page
@pytest.fixture
def test_ride(test_park: Page, live_server):
def test_ride(test_park: Page, live_server, test_images):
"""Fixture for test ride"""
# Create test ride using live_server URL
test_park.goto(f"{live_server.url}/rides/create/")
@@ -108,7 +170,7 @@ def test_ride(test_park: Page, live_server):
test_park.get_by_label("Park").select_option("Test Park")
test_park.get_by_label("Type").select_option("Roller Coaster")
test_park.get_by_label("Description").fill("A test ride")
test_park.get_by_label("Photo").set_input_files("tests/fixtures/test_photo.jpg")
test_park.get_by_label("Photo").set_input_files(test_images["test_photo"])
test_park.get_by_role("button", name="Create Ride").click()
yield test_park

View File

@@ -11,7 +11,6 @@ check_command() {
# Check required commands
check_command uv
check_command curl
check_command playwright
# Clean up any existing test data
@@ -26,21 +25,6 @@ uv pip install -r requirements.txt
echo "Installing Playwright browsers..."
playwright install chromium firefox webkit
# Create test fixtures directory
echo "Setting up test fixtures..."
mkdir -p tests/fixtures
# Download test images
echo "Downloading test images..."
curl -L "https://picsum.photos/1920/1080" -o tests/fixtures/test_photo.jpg
curl -L "https://picsum.photos/500/500" -o tests/fixtures/test_avatar.jpg
# Verify images were downloaded
if [ ! -f tests/fixtures/test_photo.jpg ] || [ ! -f tests/fixtures/test_avatar.jpg ]; then
echo "Error: Failed to download test images"
exit 1
fi
# Create test users
echo "Creating test users..."
uv run manage.py create_test_users

View File

@@ -88,7 +88,7 @@ def test_add_park_review(page: Page):
expect(page.get_by_text("Had a fantastic time at the park!")).to_be_visible()
def test_add_park_photo(page: Page):
def test_add_park_photo(page: Page, test_images):
# First login
page.goto("http://localhost:8000/accounts/login/")
page.get_by_label("Username").fill("testuser")
@@ -105,7 +105,7 @@ def test_add_park_photo(page: Page):
page.get_by_role("button", name="Add Photo").click()
# Upload photo
page.get_by_label("Photo").set_input_files("tests/fixtures/test_photo.jpg")
page.get_by_label("Photo").set_input_files(test_images["test_photo"])
page.get_by_label("Caption").fill("Beautiful castle at sunset")
# Submit photo

View File

@@ -19,7 +19,7 @@ def test_profile_page(page: Page):
expect(page.get_by_role("tab", name="Settings")).to_be_visible()
def test_edit_profile(page: Page):
def test_edit_profile(page: Page, test_images):
# First login
page.goto("http://localhost:8000/accounts/login/")
page.get_by_label("Username").fill("testuser")
@@ -36,7 +36,7 @@ def test_edit_profile(page: Page):
page.get_by_label("Location").fill("Orlando, FL")
# Upload avatar
page.get_by_label("Avatar").set_input_files("tests/fixtures/test_avatar.jpg")
page.get_by_label("Avatar").set_input_files(test_images["test_avatar"])
# Save changes
page.get_by_role("button", name="Save Changes").click()

View File

@@ -113,7 +113,7 @@ def test_add_ride_review(page: Page):
expect(page.get_by_text("Such a thrilling experience in the dark!")).to_be_visible()
def test_add_ride_photo(page: Page):
def test_add_ride_photo(page: Page, test_images):
# First login
page.goto("http://localhost:8000/accounts/login/")
page.get_by_label("Username").fill("testuser")
@@ -130,7 +130,7 @@ def test_add_ride_photo(page: Page):
page.get_by_role("button", name="Add Photo").click()
# Upload photo
page.get_by_label("Photo").set_input_files("tests/fixtures/test_photo.jpg")
page.get_by_label("Photo").set_input_files(test_images["test_photo"])
page.get_by_label("Caption").fill("Awesome ride entrance")
# Submit photo

View File

@@ -1,33 +1,37 @@
# Test Fixtures
This directory contains test assets used by the e2e tests.
This directory contains test data assets used by the test suite.
## Required Files
## Test Images
1. `test_photo.jpg`
- Used for testing photo uploads for parks and rides
- Recommended size: 1920x1080
- Max size: 5MB
Test images for E2E upload tests are **generated automatically** by the `test_images` fixture in `backend/tests/e2e/conftest.py`. There is no need to manually add image files to this directory.
2. `test_avatar.jpg`
- Used for testing profile avatar uploads
- Recommended size: 500x500
- Max size: 2MB
The `test_images` fixture creates temporary valid JPEG files at runtime and cleans them up after the test session completes.
## Generating Test Images
### Usage in Tests
You can create these test images using any image editing software, or download placeholder images from:
- https://picsum.photos/1920/1080 (for test_photo.jpg)
- https://picsum.photos/500/500 (for test_avatar.jpg)
Save the downloaded images with the correct filenames in this directory.
## Usage
The test files reference these images using relative paths:
```python
page.get_by_label("Photo").set_input_files("tests/fixtures/test_photo.jpg")
page.get_by_label("Avatar").set_input_files("tests/fixtures/test_avatar.jpg")
def test_upload_photo(page: Page, test_images):
# Use the generated test photo
page.get_by_label("Photo").set_input_files(test_images["test_photo"])
# Use the generated test avatar
page.get_by_label("Avatar").set_input_files(test_images["test_avatar"])
```
Make sure these files exist before running the tests.
### Available Keys
- `test_images["test_photo"]` - Path to a temporary test photo (valid JPEG)
- `test_images["test_avatar"]` - Path to a temporary test avatar (valid JPEG)
### How It Works
The fixture generates minimal valid JPEG images using raw bytes, which are accepted by any image processing library. This approach:
1. Eliminates the need for external fixture files
2. Ensures tests are self-contained and portable
3. Automatically cleans up temporary files after tests complete
## Other Fixtures
Additional test data fixtures (e.g., JSON data, CSV files) can be added to this directory as needed.

View File

@@ -2,13 +2,18 @@
import os
import sys
import django
from django.conf import settings
from django.test.runner import DiscoverRunner
import coverage # type: ignore
def setup_django():
"""Set up Django test environment"""
"""Set up Django test environment.
Uses config.django.test settings which configures:
- PostGIS database for GeoDjango support
- In-memory cache for test isolation
- Fast password hashing for speed
"""
# Add the project root directory to Python path
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, project_root)
@@ -16,32 +21,6 @@ def setup_django():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.django.test")
django.setup()
# Use PostGIS for GeoDjango support
settings.DATABASES = {
"default": {
"ENGINE": "django.contrib.gis.db.backends.postgis",
"NAME": "test_thrillwiki",
"USER": "postgres",
"PASSWORD": "postgres",
"HOST": "localhost",
"PORT": "5432",
"TEST": {
"NAME": "test_thrillwiki",
},
}
}
settings.DEBUG = False
# Skip problematic migrations during tests
settings.MIGRATION_MODULES = {
"parks": None,
"operators": None,
"property_owners": None,
"location": None,
"rides": None,
"reviews": None,
}
class CustomTestRunner(DiscoverRunner):
def __init__(self, *args, **kwargs):