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

329 lines
11 KiB
Python

"""
Mobile offline API integration tests.
"""
import pytest
class TestMobileDeviceRegistration:
"""Tests for mobile device registration."""
def test_register_device_requires_auth(self, client):
"""Register device should require authentication."""
response = client.post("/api/mobile-offline/devices/register", json={
"name": "Test Device",
"type": "phone",
})
assert response.status_code in [401, 423]
def test_get_devices_requires_auth(self, client):
"""Get devices should require authentication."""
response = client.get("/api/mobile-offline/devices")
assert response.status_code in [401, 423]
class TestMobileDeviceWithAuth:
"""Tests for mobile device operations with authentication."""
def test_register_device_creates_device(self, client, auth_headers):
"""Register device should create a new device entry."""
response = client.post(
"/api/mobile-offline/devices/register",
headers=auth_headers,
json={
"name": "Test Phone",
"type": "phone",
"device_id": "test-device-id-12345",
},
)
# Should succeed or return existing device
assert response.status_code in [200, 201, 400]
if response.status_code in [200, 201]:
data = response.get_json()
assert "device_id" in data or "id" in data
def test_get_devices_returns_list(self, client, auth_headers):
"""Get devices should return list of registered devices."""
response = client.get("/api/mobile-offline/devices", headers=auth_headers)
assert response.status_code == 200
data = response.get_json()
assert "devices" in data or isinstance(data, list)
def test_register_device_with_storage_info(self, client, auth_headers):
"""Register device should accept storage information."""
response = client.post(
"/api/mobile-offline/devices/register",
headers=auth_headers,
json={
"name": "Test Tablet",
"type": "tablet",
"device_id": "test-tablet-id-12345",
"storage_capacity": 64000,
"available_storage": 32000,
},
)
assert response.status_code in [200, 201, 400]
class TestMobileOfflineLibrary:
"""Tests for mobile offline library management."""
def test_get_offline_library_requires_auth(self, client):
"""Get offline library should require authentication."""
response = client.get("/api/mobile-offline/devices/test-device/offline-library")
assert response.status_code in [401, 423]
def test_add_tracks_requires_auth(self, client):
"""Add tracks to offline should require authentication."""
response = client.post("/api/mobile-offline/devices/test-device/add-tracks", json={
"tracks": [],
})
assert response.status_code in [401, 423]
def test_remove_tracks_requires_auth(self, client):
"""Remove tracks from offline should require authentication."""
response = client.post("/api/mobile-offline/devices/test-device/remove-tracks", json={
"trackhashes": [],
})
assert response.status_code in [401, 423]
class TestMobileOfflineLibraryWithAuth:
"""Tests for offline library operations with authentication."""
def test_get_offline_library_for_device(self, client, auth_headers):
"""Get offline library should return library for registered device."""
# Register device first
client.post(
"/api/mobile-offline/devices/register",
headers=auth_headers,
json={
"name": "Library Test Device",
"type": "phone",
"device_id": "library-test-device-12345",
},
)
response = client.get(
"/api/mobile-offline/devices/library-test-device-12345/offline-library",
headers=auth_headers,
)
# Should return library or 404 if device not found
assert response.status_code in [200, 404]
if response.status_code == 200:
data = response.get_json()
assert isinstance(data, dict)
def test_add_tracks_to_offline(self, client, auth_headers):
"""Add tracks to offline library should work."""
# Register device first
client.post(
"/api/mobile-offline/devices/register",
headers=auth_headers,
json={
"name": "Add Tracks Device",
"type": "phone",
"device_id": "add-tracks-device-12345",
},
)
response = client.post(
"/api/mobile-offline/devices/add-tracks-device-12345/add-tracks",
headers=auth_headers,
json={
"tracks": [
{"trackhash": "testhash1", "title": "Test Track 1"},
{"trackhash": "testhash2", "title": "Test Track 2"},
],
"quality": "high",
},
)
assert response.status_code in [200, 201, 404]
def test_remove_tracks_from_offline(self, client, auth_headers):
"""Remove tracks from offline library should work."""
# Register device first
client.post(
"/api/mobile-offline/devices/register",
headers=auth_headers,
json={
"name": "Remove Tracks Device",
"type": "phone",
"device_id": "remove-tracks-device-12345",
},
)
response = client.post(
"/api/mobile-offline/devices/remove-tracks-device-12345/remove-tracks",
headers=auth_headers,
json={
"trackhashes": ["testhash1", "testhash2"],
},
)
assert response.status_code in [200, 404]
class TestMobileSyncOperations:
"""Tests for mobile sync operations."""
def test_sync_collection_requires_auth(self, client):
"""Sync collection should require authentication."""
response = client.post("/api/mobile-offline/devices/test-device/sync-collection", json={
"collection_type": "playlist",
"collection_id": "test123",
})
assert response.status_code in [401, 423]
def test_get_sync_progress_requires_auth(self, client):
"""Get sync progress should require authentication."""
response = client.get("/api/mobile-offline/devices/test-device/sync-progress")
assert response.status_code in [401, 423]
class TestMobileSyncWithAuth:
"""Tests for sync operations with authentication."""
def test_sync_collection_to_device(self, client, auth_headers):
"""Sync collection to device should initiate sync."""
# Register device first
client.post(
"/api/mobile-offline/devices/register",
headers=auth_headers,
json={
"name": "Sync Device",
"type": "phone",
"device_id": "sync-device-12345",
},
)
response = client.post(
"/api/mobile-offline/devices/sync-device-12345/sync-collection",
headers=auth_headers,
json={
"collection_type": "playlist",
"collection_id": "test-playlist-id",
"quality": "high",
},
)
assert response.status_code in [200, 201, 404, 400]
def test_get_sync_progress_for_device(self, client, auth_headers):
"""Get sync progress should return progress info."""
# Register device first
client.post(
"/api/mobile-offline/devices/register",
headers=auth_headers,
json={
"name": "Progress Device",
"type": "phone",
"device_id": "progress-device-12345",
},
)
response = client.get(
"/api/mobile-offline/devices/progress-device-12345/sync-progress",
headers=auth_headers,
)
assert response.status_code in [200, 404]
class TestMobileEvents:
"""Tests for mobile event batching."""
def test_push_events_requires_auth(self, client):
"""Push events should require authentication."""
response = client.post("/api/mobile-offline/devices/test-device/events/batch", json={
"events": [],
})
assert response.status_code in [401, 423]
def test_push_events_to_device(self, client, auth_headers):
"""Push events should batch upload events."""
# Register device first
client.post(
"/api/mobile-offline/devices/register",
headers=auth_headers,
json={
"name": "Events Device",
"type": "phone",
"device_id": "events-device-12345",
},
)
response = client.post(
"/api/mobile-offline/devices/events-device-12345/events/batch",
headers=auth_headers,
json={
"events": [
{
"type": "play",
"trackhash": "testhash123",
"timestamp": 1234567890,
"duration": 180,
},
{
"type": "favorite",
"trackhash": "testhash456",
"timestamp": 1234567891,
},
],
},
)
assert response.status_code in [200, 404]
class TestMobileOfflineContract:
"""Contract tests for mobile offline API."""
def test_device_registration_schema(self, client, auth_headers):
"""Device registration response should match expected schema."""
response = client.post(
"/api/mobile-offline/devices/register",
headers=auth_headers,
json={
"name": "Contract Test Device",
"type": "phone",
"device_id": "contract-device-12345",
"storage_capacity": 128000,
"available_storage": 64000,
},
)
if response.status_code in [200, 201]:
data = response.get_json()
# Should have device identifier
assert "device_id" in data or "id" in data
def test_offline_library_schema(self, client, auth_headers):
"""Offline library response should match expected schema."""
# Register device
client.post(
"/api/mobile-offline/devices/register",
headers=auth_headers,
json={
"name": "Schema Test Device",
"type": "phone",
"device_id": "schema-device-12345",
},
)
response = client.get(
"/api/mobile-offline/devices/schema-device-12345/offline-library",
headers=auth_headers,
)
if response.status_code == 200:
data = response.get_json()
# Library should be a dict with tracks or collections
assert isinstance(data, dict)