Files
swingmusic-extended/tests/conftest.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

234 lines
6.4 KiB
Python

"""
Pytest fixtures for SwingMusic backend tests.
"""
import os
import pytest
import tempfile
import shutil
from pathlib import Path
from unittest.mock import patch, MagicMock
@pytest.fixture(scope="session")
def test_data_dir():
"""Create a temporary directory for test data."""
dir_path = tempfile.mkdtemp(prefix="swingmusic_test_")
yield Path(dir_path)
shutil.rmtree(dir_path, ignore_errors=True)
@pytest.fixture(scope="session")
def test_config_dir(test_data_dir):
"""Create a test configuration directory."""
config_dir = test_data_dir / "config"
config_dir.mkdir(exist_ok=True)
return config_dir
@pytest.fixture(scope="session")
def test_music_dir(test_data_dir):
"""Create a test music library directory with sample structure."""
music_dir = test_data_dir / "music"
music_dir.mkdir(exist_ok=True)
# Create sample artist/album structure
artist_dir = music_dir / "Test Artist"
artist_dir.mkdir(exist_ok=True)
album_dir = artist_dir / "Test Album"
album_dir.mkdir(exist_ok=True)
# Create a dummy audio file (empty for testing)
(album_dir / "01 Test Track.mp3").touch()
return music_dir
@pytest.fixture
def mock_env(test_data_dir, test_config_dir, test_music_dir):
"""Set up test environment variables."""
env_vars = {
"SWINGMUSIC_CONFIG_DIR": str(test_config_dir),
"SWINGMUSIC_MUSIC_DIR": str(test_music_dir),
"SWINGMUSIC_SECRET_KEY": "test-secret-key-for-integration-tests",
"SWINGMUSIC_JWT_SECRET": "test-jwt-secret-for-integration-tests",
"SWINGMUSIC_PAIR_CODE_TTL_SECONDS": "60",
"SWINGMUSIC_PAIR_CODE_MAX_ACTIVE": "10",
}
with patch.dict(os.environ, env_vars, clear=False):
yield env_vars
@pytest.fixture
def app(mock_env):
"""Create a test Flask application."""
# Import here to avoid circular imports and ensure env is set first
from swingmusic.app_builder import build
test_app = build()
test_app.config.update({
"TESTING": True,
"JWT_ACCESS_TOKEN_EXPIRES": 3600,
"JWT_REFRESH_TOKEN_EXPIRES": 86400,
})
yield test_app
@pytest.fixture
def client(app):
"""Create a test client for the Flask application."""
return app.test_client()
@pytest.fixture
def runner(app):
"""Create a test CLI runner for the Flask application."""
return app.test_cli_runner()
@pytest.fixture
def db_session(app):
"""Create a test database session."""
from swingmusic.db.userdata import UserTable
from swingmusic.db.production import get_engine
engine = get_engine()
yield engine
# Cleanup after each test
engine.dispose()
@pytest.fixture
def test_user_data():
"""Sample user data for testing."""
return {
"username": "testuser",
"password": "testpassword123",
"email": "test@example.com",
}
@pytest.fixture
def admin_user_data():
"""Sample admin user data for testing."""
return {
"username": "adminuser",
"password": "adminpassword123",
"email": "admin@example.com",
"roles": ["admin", "user"],
}
@pytest.fixture
def auth_headers(client, test_user_data):
"""Get authentication headers for a test user."""
# First, bootstrap the owner if no users exist
response = client.get("/auth/bootstrap/status")
status = response.get_json()
if not status.get("setup_completed"):
# Bootstrap owner
client.post("/auth/bootstrap/owner", json={
"username": "owner",
"password": "ownerpassword123",
"root_dirs": [],
})
# Create a test user via invite
response = client.post("/auth/login", json={
"username": "owner",
"password": "ownerpassword123",
})
owner_token = response.get_json().get("accesstoken")
# Create invite
invite_response = client.post(
"/auth/invite/create",
json={"roles": ["user"]},
headers={"Authorization": f"Bearer {owner_token}"},
)
invite_token = invite_response.get_json().get("token")
# Accept invite with test user
client.post("/auth/invite/accept", json={
"token": invite_token,
"username": test_user_data["username"],
"password": test_user_data["password"],
})
# Login as test user
response = client.post("/auth/login", json={
"username": test_user_data["username"],
"password": test_user_data["password"],
})
token = response.get_json().get("accesstoken")
return {"Authorization": f"Bearer {token}"}
@pytest.fixture
def admin_auth_headers(client, admin_user_data):
"""Get authentication headers for an admin user."""
# Similar to auth_headers but with admin role
response = client.get("/auth/bootstrap/status")
status = response.get_json()
if not status.get("setup_completed"):
client.post("/auth/bootstrap/owner", json={
"username": "owner",
"password": "ownerpassword123",
"root_dirs": [],
})
response = client.post("/auth/login", json={
"username": "owner",
"password": "ownerpassword123",
})
owner_token = response.get_json().get("accesstoken")
invite_response = client.post(
"/auth/invite/create",
json={"roles": ["admin", "user"]},
headers={"Authorization": f"Bearer {owner_token}"},
)
invite_token = invite_response.get_json().get("token")
client.post("/auth/invite/accept", json={
"token": invite_token,
"username": admin_user_data["username"],
"password": admin_user_data["password"],
})
response = client.post("/auth/login", json={
"username": admin_user_data["username"],
"password": admin_user_data["password"],
})
token = response.get_json().get("accesstoken")
return {"Authorization": f"Bearer {token}"}
@pytest.fixture
def sample_trackhash():
"""Sample trackhash for testing."""
return "abc123def456"
@pytest.fixture
def sample_download_job():
"""Sample download job data for testing."""
return {
"source_url": "https://open.spotify.com/track/1234567890",
"source": "spotify",
"quality": "high",
"trackhash": "testhash123",
"title": "Test Track",
"artist": "Test Artist",
"album": "Test Album",
"item_type": "track",
}