mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 23:31:08 -05:00
Add autocomplete functionality for parks: implement BaseAutocomplete class and integrate with forms
This commit is contained in:
81
parks/tests/README.md
Normal file
81
parks/tests/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Park Search Tests
|
||||
|
||||
## Overview
|
||||
|
||||
Test suite for the park search functionality including:
|
||||
- Autocomplete widget integration
|
||||
- Search form validation
|
||||
- Filter integration
|
||||
- HTMX interaction
|
||||
- View mode persistence
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all park tests
|
||||
uv run pytest parks/tests/
|
||||
|
||||
# Run specific search tests
|
||||
uv run pytest parks/tests/test_search.py
|
||||
|
||||
# Run with coverage
|
||||
uv run pytest --cov=parks parks/tests/
|
||||
```
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### Unit Tests
|
||||
- `test_autocomplete_results`: Validates search result filtering
|
||||
- `test_search_form_valid`: Ensures form validation works
|
||||
- `test_autocomplete_class`: Checks autocomplete configuration
|
||||
- `test_search_with_filters`: Verifies filter integration
|
||||
|
||||
### Integration Tests
|
||||
- `test_empty_search`: Tests default behavior
|
||||
- `test_partial_match_search`: Validates partial text matching
|
||||
- `test_htmx_request_handling`: Ensures HTMX compatibility
|
||||
- `test_view_mode_persistence`: Checks view state management
|
||||
- `test_unauthenticated_access`: Verifies authentication requirements
|
||||
|
||||
### Security Tests
|
||||
Parks search implements a tiered access approach:
|
||||
- Basic search is public
|
||||
- Autocomplete requires authentication
|
||||
- Configuration set in settings.py: `AUTOCOMPLETE_BLOCK_UNAUTHENTICATED = True`
|
||||
|
||||
## Configuration
|
||||
|
||||
Tests use pytest-django and require:
|
||||
- PostgreSQL database
|
||||
- HTMX middleware
|
||||
- Autocomplete app configuration
|
||||
|
||||
## Fixtures
|
||||
|
||||
The test suite uses standard Django test fixtures. No additional fixtures required.
|
||||
|
||||
## Common Issues
|
||||
|
||||
1. Database Errors
|
||||
- Ensure PostGIS extensions are installed
|
||||
- Verify database permissions
|
||||
|
||||
2. HTMX Tests
|
||||
- Use `HTTP_HX_REQUEST` header for HTMX requests
|
||||
- Check response content for HTMX attributes
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
When adding tests, ensure:
|
||||
1. Database isolation using `@pytest.mark.django_db`
|
||||
2. Proper test naming following `test_*` convention
|
||||
3. Clear test descriptions in docstrings
|
||||
4. Coverage for both success and failure cases
|
||||
5. HTMX interaction testing where applicable
|
||||
|
||||
## Future Improvements
|
||||
|
||||
- Add performance benchmarks
|
||||
- Include accessibility tests
|
||||
- Add Playwright e2e tests
|
||||
- Implement geographic search tests
|
||||
119
parks/tests/test_search.py
Normal file
119
parks/tests/test_search.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
from django.test import Client
|
||||
|
||||
from parks.models import Park
|
||||
from parks.forms import ParkAutocomplete, ParkSearchForm
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestParkSearch:
|
||||
def test_autocomplete_results(self, client: Client):
|
||||
"""Test that autocomplete returns correct results"""
|
||||
# Create test parks
|
||||
park1 = Park.objects.create(name="Test Park")
|
||||
park2 = Park.objects.create(name="Another Park")
|
||||
park3 = Park.objects.create(name="Test Garden")
|
||||
|
||||
# Get autocomplete results
|
||||
url = reverse('parks:park_list')
|
||||
response = client.get(url, {'park': 'Test'})
|
||||
|
||||
# Check response
|
||||
assert response.status_code == 200
|
||||
content = response.content.decode()
|
||||
assert park1.name in content
|
||||
assert park3.name in content
|
||||
assert park2.name not in content
|
||||
|
||||
def test_search_form_valid(self):
|
||||
"""Test ParkSearchForm validation"""
|
||||
form = ParkSearchForm(data={'park': ''})
|
||||
assert form.is_valid()
|
||||
|
||||
def test_autocomplete_class(self):
|
||||
"""Test ParkAutocomplete configuration"""
|
||||
ac = ParkAutocomplete()
|
||||
assert ac.model == Park
|
||||
assert 'name' in ac.search_attrs
|
||||
|
||||
def test_search_with_filters(self, client: Client):
|
||||
"""Test search works with filters"""
|
||||
park = Park.objects.create(name="Test Park", status="OPERATING")
|
||||
|
||||
# Search with status filter
|
||||
url = reverse('parks:park_list')
|
||||
response = client.get(url, {
|
||||
'park': str(park.pk),
|
||||
'status': 'OPERATING'
|
||||
})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert park.name in response.content.decode()
|
||||
|
||||
def test_empty_search(self, client: Client):
|
||||
"""Test empty search returns all parks"""
|
||||
Park.objects.create(name="Test Park")
|
||||
Park.objects.create(name="Another Park")
|
||||
|
||||
url = reverse('parks:park_list')
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
content = response.content.decode()
|
||||
assert "Test Park" in content
|
||||
assert "Another Park" in content
|
||||
|
||||
def test_partial_match_search(self, client: Client):
|
||||
"""Test partial matching in search"""
|
||||
Park.objects.create(name="Adventure World")
|
||||
Park.objects.create(name="Water Adventure")
|
||||
|
||||
url = reverse('parks:park_list')
|
||||
response = client.get(url, {'park': 'Adv'})
|
||||
|
||||
assert response.status_code == 200
|
||||
content = response.content.decode()
|
||||
assert "Adventure World" in content
|
||||
assert "Water Adventure" in content
|
||||
|
||||
def test_htmx_request_handling(self, client: Client):
|
||||
"""Test HTMX-specific request handling"""
|
||||
Park.objects.create(name="Test Park")
|
||||
|
||||
url = reverse('parks:park_list')
|
||||
response = client.get(
|
||||
url,
|
||||
{'park': 'Test'},
|
||||
HTTP_HX_REQUEST='true'
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Test Park" in response.content.decode()
|
||||
|
||||
def test_view_mode_persistence(self, client: Client):
|
||||
"""Test view mode is maintained during search"""
|
||||
Park.objects.create(name="Test Park")
|
||||
|
||||
url = reverse('parks:park_list')
|
||||
response = client.get(url, {
|
||||
'park': 'Test',
|
||||
'view_mode': 'list'
|
||||
})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'data-view-mode="list"' in response.content.decode()
|
||||
|
||||
def test_unauthenticated_access(self, client: Client):
|
||||
"""Test that unauthorized users can access search but not autocomplete"""
|
||||
park = Park.objects.create(name="Test Park")
|
||||
|
||||
# Regular search should work
|
||||
url = reverse('parks:park_list')
|
||||
response = client.get(url, {'park_name': 'Test'})
|
||||
assert response.status_code == 200
|
||||
assert "Test Park" in response.content.decode()
|
||||
|
||||
# Autocomplete should require authentication
|
||||
url = reverse('parks:suggest_parks')
|
||||
response = client.get(url, {'search': 'Test'})
|
||||
assert response.status_code == 302 # Redirects to login
|
||||
Reference in New Issue
Block a user