✨ Major Features Added: - Complete magical theming and rebranding from LifeRPG to The Wizard's Grimoire - Production-grade React frontend with Tailwind CSS v4 and magical aesthetics - Comprehensive analytics dashboard with Recharts integration (ScryingPortal) - Push notifications system with PWA service worker support - Drag & drop functionality using @dnd-kit for habit reordering - Social features with friends system and leaderboards - Performance optimization tools and monitoring - Mobile app enhancement with PWA installation support 🏗️ Technical Infrastructure: - Advanced service worker with offline support and background sync - Zustand state management for scalable application state - Production-ready UI component system with enhanced Button, Card, Input - Progressive Web App (PWA) with manifest and app installation - FastAPI backend with comprehensive API endpoints - Docker containerization and CI/CD pipeline setup 📱 Progressive Web App Features: - Offline functionality with intelligent caching - Push notification support for habit reminders - App installation on mobile and desktop platforms - Background sync for offline data management - Performance monitoring and optimization tools 🎨 User Experience: - Magical wizard/grimoire theming throughout application - Responsive design optimized for all device sizes - Drag & drop habit management with smooth animations - Interactive analytics with multiple chart types - Social connectivity with friends and competitive features - Comprehensive notification and performance settings 🔧 Developer Experience: - Modern development stack with Vite and React - Comprehensive testing setup and CI/CD pipelines - Code quality tools with pre-commit hooks - Docker development environment - Detailed documentation and implementation guides This represents a complete transformation from prototype to production-ready application with enterprise-grade features and magical user experience.
51 lines
2.1 KiB
Python
51 lines
2.1 KiB
Python
def test_security_headers_present(client):
|
|
r = client.get('/health')
|
|
assert r.status_code == 200
|
|
# core headers always present
|
|
assert r.headers.get('X-Content-Type-Options') == 'nosniff'
|
|
assert r.headers.get('X-Frame-Options') == 'DENY'
|
|
assert r.headers.get('Referrer-Policy') == 'no-referrer'
|
|
assert r.headers.get('Permissions-Policy') == 'geolocation=()'
|
|
assert 'Content-Security-Policy' in r.headers
|
|
|
|
|
|
def test_cors_preflight(client):
|
|
# Simulate a preflight request from allowed origin
|
|
headers = {
|
|
'Origin': 'http://localhost:5173',
|
|
'Access-Control-Request-Method': 'POST',
|
|
'Access-Control-Request-Headers': 'content-type,authorization'
|
|
}
|
|
r = client.options('/api/v1/hello', headers=headers)
|
|
assert r.status_code in (200, 204)
|
|
assert r.headers.get('access-control-allow-origin') == 'http://localhost:5173'
|
|
assert r.headers.get('access-control-allow-credentials') == 'true'
|
|
|
|
|
|
def test_headers_on_other_routes(client):
|
|
# simple GET route
|
|
r = client.get('/api/v1/hello')
|
|
assert r.status_code == 200
|
|
assert r.headers.get('X-Frame-Options') == 'DENY'
|
|
# POST route
|
|
r2 = client.post('/api/v1/users', json={'email': 'h@test'})
|
|
assert r2.status_code in (200, 400) # email required in some contexts; just check headers presence
|
|
assert 'Content-Security-Policy' in r2.headers
|
|
|
|
|
|
def test_request_size_limit(client):
|
|
# Build a body > 1MiB (default limit)
|
|
big = 'x' * (1024 * 1024 + 1)
|
|
r = client.post('/api/v1/users', data=big, headers={'content-type': 'text/plain'})
|
|
assert r.status_code == 413
|
|
assert r.json().get('detail') == 'request entity too large'
|
|
|
|
|
|
def test_rate_limit_basic(client):
|
|
# Hit a lightweight endpoint enough times to trip limiter quickly by reducing RPM via env is harder in this fixture.
|
|
# Instead, we assume default 120; we'll just assert headers exist for now and simulate close to limit by making a few calls.
|
|
for _ in range(3):
|
|
r = client.get('/api/v1/hello')
|
|
assert r.status_code == 200
|
|
assert 'X-RateLimit-Limit' in r.headers
|
|
assert 'X-RateLimit-Remaining' in r.headers |