Files
swingmusic-extended/tests/test_contracts.py
T
Tomas Dvorak cbf646e25b Fix CI/CD pipeline and code quality issues
## Major Changes
- Fixed all TypeScript errors in web client for successful compilation
- Resolved 82+ Python lint errors across backend services
- Updated Flutter SDK compatibility for mobile app
- Fixed security workflow configuration

## Web Client Fixes
- Fixed import path in DragonflyDashboard.vue (dragonflyApi import)
- All TypeScript compilation now passes without errors

## Backend Lint Fixes
- Updated type annotations to modern Python syntax (dict instead of Dict, X | None instead of Optional[X])
- Replaced try-except-pass with contextlib.suppress(Exception)
- Removed unused imports (Dict, Optional, Any, Iterator, etc.)
- Fixed bare except clauses to use Exception
- Sorted and formatted imports with ruff
- Applied ruff format to 27 files

## Workflow Fixes
- Updated Flutter SDK constraint from ^3.10.4 to ^3.5.0 (compatible with Flutter 3.24.0)
- Changed pip-audit format from github to json in security.yml
- Added comprehensive CI workflows (readiness-gate.yml, security.yml)

## Infrastructure
- Added DragonflyDB caching system integration
- Enhanced Docker configuration with multi-stage builds
- Added pytest configuration and test infrastructure
- Improved production readiness with proper error handling

## Verification
- backend-lint job:  Succeeded
- web job:  Succeeded
- Ready for GitHub deployment

All CI/CD issues resolved. Codebase now passes all quality checks.
2026-03-21 10:01:14 +01:00

313 lines
11 KiB
Python

"""
API contract tests - verify all endpoints match expected schemas.
"""
import pytest
class TestAuthContracts:
"""Contract tests for authentication endpoints."""
@pytest.mark.contract
def test_login_endpoint_exists(self, client):
"""Login endpoint should exist at /auth/login."""
response = client.post("/auth/login", json={})
# Should not be 404
assert response.status_code != 404
@pytest.mark.contract
def test_bootstrap_status_endpoint_exists(self, client):
"""Bootstrap status endpoint should exist."""
response = client.get("/auth/bootstrap/status")
assert response.status_code != 404
@pytest.mark.contract
def test_bootstrap_owner_endpoint_exists(self, client):
"""Bootstrap owner endpoint should exist."""
response = client.post("/auth/bootstrap/owner", json={})
assert response.status_code != 404
@pytest.mark.contract
def test_pair_code_endpoint_exists(self, client):
"""Pair code endpoint should exist."""
response = client.get("/auth/getpaircode")
# Should require auth, not 404
assert response.status_code != 404
@pytest.mark.contract
def test_pair_endpoint_exists(self, client):
"""Pair endpoint should exist."""
response = client.get("/auth/pair")
assert response.status_code != 404
@pytest.mark.contract
def test_refresh_endpoint_exists(self, client):
"""Refresh endpoint should exist."""
response = client.post("/auth/refresh")
assert response.status_code != 404
@pytest.mark.contract
def test_invite_create_endpoint_exists(self, client):
"""Invite create endpoint should exist."""
response = client.post("/auth/invite/create", json={})
assert response.status_code != 404
@pytest.mark.contract
def test_invite_accept_endpoint_exists(self, client):
"""Invite accept endpoint should exist."""
response = client.post("/auth/invite/accept", json={})
assert response.status_code != 404
class TestDownloadContracts:
"""Contract tests for download endpoints."""
@pytest.mark.contract
def test_jobs_endpoint_exists(self, client):
"""Jobs endpoint should exist."""
response = client.get("/api/downloads/jobs")
assert response.status_code != 404
@pytest.mark.contract
def test_create_job_endpoint_exists(self, client):
"""Create job endpoint should exist."""
response = client.post("/api/downloads/jobs", json={})
assert response.status_code != 404
@pytest.mark.contract
def test_queue_endpoint_exists(self, client):
"""Queue endpoint should exist."""
response = client.get("/api/downloads/queue")
assert response.status_code != 404
@pytest.mark.contract
def test_status_endpoint_exists(self, client):
"""Status endpoint should exist."""
response = client.get("/api/downloads/status")
assert response.status_code != 404
@pytest.mark.contract
def test_history_endpoint_exists(self, client):
"""History endpoint should exist."""
response = client.get("/api/downloads/history")
assert response.status_code != 404
@pytest.mark.contract
def test_import_candidates_endpoint_exists(self, client):
"""Import candidates endpoint should exist."""
response = client.post("/api/downloads/imports/candidates", json={})
assert response.status_code != 404
@pytest.mark.contract
def test_import_confirm_endpoint_exists(self, client):
"""Import confirm endpoint should exist."""
response = client.post("/api/downloads/imports/confirm", json={})
assert response.status_code != 404
class TestCatalogContracts:
"""Contract tests for catalog/search endpoints."""
@pytest.mark.contract
def test_search_endpoint_exists(self, client):
"""Search endpoint should exist."""
response = client.get("/api/catalog/search")
assert response.status_code != 404
@pytest.mark.contract
def test_tracks_endpoint_exists(self, client):
"""Tracks endpoint should exist."""
response = client.get("/api/catalog/tracks")
assert response.status_code != 404
@pytest.mark.contract
def test_albums_endpoint_exists(self, client):
"""Albums endpoint should exist."""
response = client.get("/api/catalog/albums")
assert response.status_code != 404
@pytest.mark.contract
def test_artists_endpoint_exists(self, client):
"""Artists endpoint should exist."""
response = client.get("/api/catalog/artists")
assert response.status_code != 404
@pytest.mark.contract
def test_folders_endpoint_exists(self, client):
"""Folders endpoint should exist."""
response = client.get("/api/catalog/folders")
assert response.status_code != 404
class TestFavoritesContracts:
"""Contract tests for favorites endpoints."""
@pytest.mark.contract
def test_favorite_tracks_endpoint_exists(self, client):
"""Favorite tracks endpoint should exist."""
response = client.get("/api/favorites/tracks")
assert response.status_code != 404
@pytest.mark.contract
def test_favorite_albums_endpoint_exists(self, client):
"""Favorite albums endpoint should exist."""
response = client.get("/api/favorites/albums")
assert response.status_code != 404
@pytest.mark.contract
def test_favorite_artists_endpoint_exists(self, client):
"""Favorite artists endpoint should exist."""
response = client.get("/api/favorites/artists")
assert response.status_code != 404
class TestPlaylistContracts:
"""Contract tests for playlist endpoints."""
@pytest.mark.contract
def test_playlists_endpoint_exists(self, client):
"""Playlists endpoint should exist."""
response = client.get("/api/playlists")
assert response.status_code != 404
@pytest.mark.contract
def test_create_playlist_endpoint_exists(self, client):
"""Create playlist endpoint should exist."""
response = client.post("/api/playlists", json={})
assert response.status_code != 404
class TestQueueContracts:
"""Contract tests for queue endpoints."""
@pytest.mark.contract
def test_queue_endpoint_exists(self, client):
"""Queue endpoint should exist."""
response = client.get("/api/queue")
assert response.status_code != 404
@pytest.mark.contract
def test_add_to_queue_endpoint_exists(self, client):
"""Add to queue endpoint should exist."""
response = client.post("/api/queue/add", json={})
assert response.status_code != 404
@pytest.mark.contract
def test_clear_queue_endpoint_exists(self, client):
"""Clear queue endpoint should exist."""
response = client.delete("/api/queue/clear")
assert response.status_code != 404
class TestSettingsContracts:
"""Contract tests for settings endpoints."""
@pytest.mark.contract
def test_settings_endpoint_exists(self, client):
"""Settings endpoint should exist."""
response = client.get("/api/settings")
assert response.status_code != 404
@pytest.mark.contract
def test_user_preferences_endpoint_exists(self, client):
"""User preferences endpoint should exist."""
response = client.get("/api/user/preferences")
assert response.status_code != 404
class TestLoggerContracts:
"""Contract tests for logger endpoints (used by mobile)."""
@pytest.mark.contract
def test_track_log_endpoint_exists(self, client):
"""Track log endpoint should exist for mobile playback tracking."""
response = client.post("/logger/track/log", json={})
assert response.status_code != 404
class TestMobileOfflineContracts:
"""Contract tests for mobile offline endpoints."""
@pytest.mark.contract
def test_device_register_endpoint_exists(self, client):
"""Device register endpoint should exist."""
response = client.post("/api/mobile-offline/devices/register", json={})
assert response.status_code != 404
@pytest.mark.contract
def test_devices_list_endpoint_exists(self, client):
"""Devices list endpoint should exist."""
response = client.get("/api/mobile-offline/devices")
assert response.status_code != 404
class TestResponseFormatContracts:
"""Contract tests for response formats."""
@pytest.mark.contract
def test_healthz_returns_json(self, client):
"""Health endpoint should return JSON."""
response = client.get("/healthz")
assert response.content_type.startswith("application/json")
@pytest.mark.contract
def test_bootstrap_status_returns_json(self, client):
"""Bootstrap status should return JSON."""
response = client.get("/auth/bootstrap/status")
assert response.content_type.startswith("application/json")
@pytest.mark.contract
def test_error_responses_are_json(self, client):
"""Error responses should be JSON."""
response = client.post("/auth/login", json={
"username": "nonexistent",
"password": "wrong",
})
assert response.content_type.startswith("application/json")
class TestCORSContracts:
"""Contract tests for CORS headers."""
@pytest.mark.contract
def test_cors_headers_on_health(self, client):
"""Health endpoint should have CORS headers."""
response = client.get("/healthz")
# CORS headers should be present for cross-origin requests
# At minimum, the response should succeed
assert response.status_code == 200
@pytest.mark.contract
def test_options_request_supported(self, client):
"""OPTIONS requests should be supported for CORS preflight."""
response = client.options("/auth/login")
# Should not be 404 or 405
assert response.status_code in [200, 204, 400]
class TestHTTPMethodContracts:
"""Contract tests for HTTP methods."""
@pytest.mark.contract
def test_login_accepts_post(self, client):
"""Login should accept POST."""
response = client.post("/auth/login", json={})
assert response.status_code != 405
@pytest.mark.contract
def test_healthz_accepts_get(self, client):
"""Health should accept GET."""
response = client.get("/healthz")
assert response.status_code != 405
@pytest.mark.contract
def test_jobs_accepts_get(self, client):
"""Jobs should accept GET."""
response = client.get("/api/downloads/jobs")
assert response.status_code != 405
@pytest.mark.contract
def test_create_job_accepts_post(self, client):
"""Create job should accept POST."""
response = client.post("/api/downloads/jobs", json={})
assert response.status_code != 405