Trim redundant websocket phase tests

Keep room/subscription, parity, and state-reflection coverage.
Drop bare receipt checks and duplicated HTTP smoke tests.
This commit is contained in:
Antti Kettunen 2026-04-17 21:19:57 +03:00
parent a0f4eea9df
commit 279000435b
5 changed files with 6 additions and 499 deletions

View file

@ -35,34 +35,6 @@ class TestInfrastructure:
assert client.is_connected()
client.disconnect()
def test_http_status_still_works(self, flask_client):
"""GET /status returns 200 with expected keys."""
resp = flask_client.get('/status')
assert resp.status_code == 200
data = resp.get_json()
assert 'spotify' in data
assert 'media_server' in data
assert 'soulseek' in data
assert 'active_media_server' in data
def test_http_watchlist_count_still_works(self, flask_client):
"""GET /api/watchlist/count returns 200 with expected keys."""
resp = flask_client.get('/api/watchlist/count')
assert resp.status_code == 200
data = resp.get_json()
assert data['success'] is True
assert 'count' in data
assert 'next_run_in_seconds' in data
def test_http_download_batch_still_works(self, flask_client):
"""GET /api/download_status/batch returns 200 with expected structure."""
resp = flask_client.get('/api/download_status/batch')
assert resp.status_code == 200
data = resp.get_json()
assert 'batches' in data
assert 'metadata' in data
# =========================================================================
# Group B — Service Status Parity
# =========================================================================
@ -70,16 +42,6 @@ class TestInfrastructure:
class TestServiceStatus:
"""status:update socket events match GET /status HTTP responses."""
def test_status_update_received(self, test_app, shared_state):
"""Client receives a status:update event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_status_payload']
socketio.emit('status:update', build())
received = client.get_received()
status_events = [e for e in received if e['name'] == 'status:update']
assert len(status_events) >= 1
def test_status_update_shape(self, test_app, shared_state):
"""status:update event data has the expected keys."""
app, socketio = test_app
@ -139,16 +101,6 @@ class TestServiceStatus:
class TestWatchlistCount:
"""watchlist:count socket events match GET /api/watchlist/count."""
def test_watchlist_count_received(self, test_app, shared_state):
"""Client receives a watchlist:count event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_watchlist_count_payload']
socketio.emit('watchlist:count', build())
received = client.get_received()
wl_events = [e for e in received if e['name'] == 'watchlist:count']
assert len(wl_events) >= 1
def test_watchlist_count_shape(self, test_app, shared_state):
"""watchlist:count event data has expected keys."""
app, socketio = test_app
@ -222,12 +174,6 @@ class TestDownloadBatch:
with lock:
batches[batch_id] = defaults
def test_download_subscribe(self, test_app):
"""Client can subscribe to a batch room."""
app, socketio = test_app
client = socketio.test_client(app)
client.emit('downloads:subscribe', {'batch_ids': ['batch_abc']})
def test_download_receives_updates(self, test_app, shared_state):
"""After subscribing, client receives batch_update for that batch."""
app, socketio = test_app
@ -338,47 +284,6 @@ class TestDownloadBatch:
assert 'active_count' in data
assert 'max_concurrent' in data
def test_download_http_batch_still_works(self, test_app, shared_state):
"""HTTP batch endpoint works alongside WebSocket rooms."""
app, socketio = test_app
flask_client = app.test_client()
self._add_batch(shared_state, 'batch_http')
resp = flask_client.get('/api/download_status/batch?batch_ids=batch_http')
assert resp.status_code == 200
data = resp.get_json()
assert 'batch_http' in data['batches']
assert data['batches']['batch_http']['phase'] == 'downloading'
# =========================================================================
# Group E — Fallback Behavior
# =========================================================================
class TestFallback:
"""HTTP endpoints work when no WebSocket is connected."""
def test_http_works_without_websocket(self, flask_client):
"""All three HTTP endpoints work without any WebSocket connection."""
# Status
resp = flask_client.get('/status')
assert resp.status_code == 200
data = resp.get_json()
assert data['spotify']['connected'] is True
# Watchlist
resp = flask_client.get('/api/watchlist/count')
assert resp.status_code == 200
data = resp.get_json()
assert data['count'] == 7
# Download batch (empty — no active batches)
resp = flask_client.get('/api/download_status/batch')
assert resp.status_code == 200
data = resp.get_json()
assert data['batches'] == {}
def test_multiple_clients_get_updates(self, test_app, shared_state):
"""Multiple WebSocket clients each receive broadcast events."""
app, socketio = test_app

View file

@ -21,16 +21,6 @@ import time
class TestSystemStats:
"""dashboard:stats socket events match GET /api/system/stats."""
def test_stats_event_received(self, test_app, shared_state):
"""Client receives a dashboard:stats event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_system_stats']
socketio.emit('dashboard:stats', build())
received = client.get_received()
stats_events = [e for e in received if e['name'] == 'dashboard:stats']
assert len(stats_events) >= 1
def test_stats_shape(self, test_app, shared_state):
"""dashboard:stats event data has expected keys."""
app, socketio = test_app
@ -70,16 +60,6 @@ class TestSystemStats:
assert ws_data['uptime'] == http_data['uptime']
assert ws_data['memory_usage'] == http_data['memory_usage']
def test_http_stats_still_works(self, flask_client):
"""GET /api/system/stats returns 200 with expected keys."""
resp = flask_client.get('/api/system/stats')
assert resp.status_code == 200
data = resp.get_json()
assert 'active_downloads' in data
assert 'finished_downloads' in data
assert 'download_speed' in data
# =========================================================================
# Group B — Activity Feed
# =========================================================================
@ -87,16 +67,6 @@ class TestSystemStats:
class TestActivityFeed:
"""dashboard:activity socket events match GET /api/activity/feed."""
def test_activity_event_received(self, test_app, shared_state):
"""Client receives a dashboard:activity event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_activity_feed_payload']
socketio.emit('dashboard:activity', build())
received = client.get_received()
events = [e for e in received if e['name'] == 'dashboard:activity']
assert len(events) >= 1
def test_activity_shape(self, test_app, shared_state):
"""dashboard:activity event data has activities array."""
app, socketio = test_app
@ -136,14 +106,6 @@ class TestActivityFeed:
if ws_data['activities']:
assert ws_data['activities'][0]['title'] == http_data['activities'][0]['title']
def test_http_activity_still_works(self, flask_client):
"""GET /api/activity/feed returns 200 with expected structure."""
resp = flask_client.get('/api/activity/feed')
assert resp.status_code == 200
data = resp.get_json()
assert 'activities' in data
# =========================================================================
# Group C — Toasts (instant push)
# =========================================================================
@ -201,18 +163,6 @@ class TestToasts:
assert 'show_toast' in data
assert data['show_toast'] is True
def test_http_toasts_still_works(self, flask_client, shared_state):
"""GET /api/activity/toasts returns 200 with expected structure."""
# Add a toast-worthy activity first
add_item = shared_state['add_activity_item']
add_item('', 'Test', 'Sub', show_toast=True)
resp = flask_client.get('/api/activity/toasts')
assert resp.status_code == 200
data = resp.get_json()
assert 'toasts' in data
# =========================================================================
# Group D — DB Stats
# =========================================================================
@ -220,16 +170,6 @@ class TestToasts:
class TestDbStats:
"""dashboard:db_stats socket events match GET /api/database/stats."""
def test_db_stats_event_received(self, test_app, shared_state):
"""Client receives a dashboard:db_stats event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_db_stats']
socketio.emit('dashboard:db_stats', build())
received = client.get_received()
events = [e for e in received if e['name'] == 'dashboard:db_stats']
assert len(events) >= 1
def test_db_stats_shape(self, test_app, shared_state):
"""dashboard:db_stats event data has expected keys."""
app, socketio = test_app
@ -266,16 +206,6 @@ class TestDbStats:
assert ws_data['database_size_mb'] == http_data['database_size_mb']
assert ws_data['server_source'] == http_data['server_source']
def test_http_db_stats_still_works(self, flask_client):
"""GET /api/database/stats returns 200 with expected keys."""
resp = flask_client.get('/api/database/stats')
assert resp.status_code == 200
data = resp.get_json()
assert 'artists' in data
assert 'albums' in data
assert 'tracks' in data
# =========================================================================
# Group E — Wishlist Count
# =========================================================================
@ -283,16 +213,6 @@ class TestDbStats:
class TestWishlistCount:
"""dashboard:wishlist_count socket events match GET /api/wishlist/count."""
def test_wishlist_count_received(self, test_app, shared_state):
"""Client receives a dashboard:wishlist_count event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_wishlist_count_payload_ws']
socketio.emit('dashboard:wishlist_count', build())
received = client.get_received()
events = [e for e in received if e['name'] == 'dashboard:wishlist_count']
assert len(events) >= 1
def test_wishlist_count_shape(self, test_app, shared_state):
"""dashboard:wishlist_count event data has count key."""
app, socketio = test_app
@ -322,54 +242,6 @@ class TestWishlistCount:
assert ws_data['count'] == http_data['count']
def test_http_wishlist_count_still_works(self, flask_client):
"""GET /api/wishlist/count returns 200 with count."""
resp = flask_client.get('/api/wishlist/count')
assert resp.status_code == 200
data = resp.get_json()
assert 'count' in data
assert isinstance(data['count'], int)
# =========================================================================
# Group F — Backward Compatibility
# =========================================================================
class TestBackwardCompat:
"""HTTP endpoints work when no WebSocket is connected."""
def test_all_http_endpoints_work_without_socket(self, flask_client):
"""All 5 Phase 2 HTTP endpoints work without any WebSocket connection."""
# System stats
resp = flask_client.get('/api/system/stats')
assert resp.status_code == 200
data = resp.get_json()
assert data['active_downloads'] == 2
# Activity feed
resp = flask_client.get('/api/activity/feed')
assert resp.status_code == 200
data = resp.get_json()
assert 'activities' in data
# Toasts
resp = flask_client.get('/api/activity/toasts')
assert resp.status_code == 200
data = resp.get_json()
assert 'toasts' in data
# DB stats
resp = flask_client.get('/api/database/stats')
assert resp.status_code == 200
data = resp.get_json()
assert data['artists'] == 350
# Wishlist count
resp = flask_client.get('/api/wishlist/count')
assert resp.status_code == 200
data = resp.get_json()
assert data['count'] == 5
def test_multiple_clients_get_dashboard_updates(self, test_app, shared_state):
"""Multiple WebSocket clients each receive dashboard broadcast events."""
app, socketio = test_app

View file

@ -39,19 +39,6 @@ ENDPOINTS = {
class TestEnrichmentEventDelivery:
"""enrichment:<worker> socket events are received by the client."""
@pytest.mark.parametrize('worker', WORKERS)
def test_enrichment_event_received(self, test_app, shared_state, worker):
"""Client receives an enrichment:<worker> event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_enrichment_status']
socketio.emit(f'enrichment:{worker}', build(worker))
received = client.get_received()
events = [e for e in received if e['name'] == f'enrichment:{worker}']
assert len(events) >= 1
# =========================================================================
# Group B — Data Shape (parameterized)
# =========================================================================
@ -160,38 +147,11 @@ class TestEnrichmentHttpParity:
# =========================================================================
# Group D — HTTP Still Works (parameterized)
# =========================================================================
class TestEnrichmentHttpStillWorks:
"""HTTP endpoints return 200 with expected structure."""
@pytest.mark.parametrize('worker', WORKERS)
def test_http_enrichment_still_works(self, flask_client, worker):
"""GET /api/<worker>/status returns 200."""
endpoint = ENDPOINTS[worker]
resp = flask_client.get(endpoint)
assert resp.status_code == 200
data = resp.get_json()
assert 'running' in data
assert 'paused' in data
# =========================================================================
# Group E — Backward Compatibility
# Group D — Backward Compatibility
# =========================================================================
class TestEnrichmentBackwardCompat:
"""HTTP endpoints work when no WebSocket is connected."""
def test_all_http_endpoints_work_without_socket(self, flask_client):
"""All 7 enrichment HTTP endpoints work without any WebSocket connection."""
for worker in WORKERS:
endpoint = ENDPOINTS[worker]
resp = flask_client.get(endpoint)
assert resp.status_code == 200
data = resp.get_json()
assert data['running'] is True
"""WebSocket clients still receive broadcast enrichment updates."""
def test_multiple_clients_get_enrichment_updates(self, test_app, shared_state):
"""Multiple WebSocket clients each receive enrichment events."""

View file

@ -38,19 +38,6 @@ ENDPOINTS = {
class TestToolEventDelivery:
"""tool:<name> socket events are received by the client."""
@pytest.mark.parametrize('tool', TOOLS)
def test_tool_event_received(self, test_app, shared_state, tool):
"""Client receives a tool:<name> event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_tool_status']
socketio.emit(f'tool:{tool}', build(tool))
received = client.get_received()
events = [e for e in received if e['name'] == f'tool:{tool}']
assert len(events) >= 1
# =========================================================================
# Group B — Data Shape (individual per tool)
# =========================================================================
@ -255,41 +242,11 @@ class TestToolHttpParity:
# =========================================================================
# Group D — HTTP Still Works (parameterized)
# =========================================================================
class TestToolHttpStillWorks:
"""HTTP endpoints return 200 with expected structure."""
@pytest.mark.parametrize('tool', TOOLS)
def test_http_tool_still_works(self, flask_client, tool):
"""GET /api/<tool>/status returns 200."""
endpoint = ENDPOINTS[tool]
resp = flask_client.get(endpoint)
assert resp.status_code == 200
data = resp.get_json()
if tool == 'logs':
assert 'logs' in data
elif tool == 'metadata':
assert 'success' in data
assert 'status' in data
else:
assert 'status' in data
# =========================================================================
# Group E — Backward Compatibility
# Group D — Backward Compatibility
# =========================================================================
class TestToolBackwardCompat:
"""HTTP endpoints work when no WebSocket is connected."""
def test_all_http_endpoints_work_without_socket(self, flask_client):
"""All 7 tool HTTP endpoints work without any WebSocket connection."""
for tool in TOOLS:
endpoint = ENDPOINTS[tool]
resp = flask_client.get(endpoint)
assert resp.status_code == 200
"""WebSocket clients still receive broadcast tool updates."""
def test_multiple_clients_get_tool_updates(self, test_app, shared_state):
"""Multiple WebSocket clients each receive tool events."""

View file

@ -29,28 +29,6 @@ SYNC_PLAYLIST_IDS = ['test-playlist-1']
class TestSyncEventDelivery:
"""sync:progress socket events are received by subscribed clients."""
def test_sync_event_received(self, test_app, shared_state):
"""Client subscribes and receives sync:progress event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_sync_status']
# Subscribe to sync room
client.emit('sync:subscribe', {'playlist_ids': ['test-playlist-1']})
# Server emits to the room
socketio.emit('sync:progress', {
'playlist_id': 'test-playlist-1',
**build('test-playlist-1')
}, room='sync:test-playlist-1')
received = client.get_received()
events = [e for e in received if e['name'] == 'sync:progress']
assert len(events) >= 1
client.disconnect()
def test_sync_only_subscribed(self, test_app, shared_state):
"""Unsubscribed client does NOT receive sync:progress events."""
app, socketio = test_app
@ -155,30 +133,6 @@ class TestSyncDataShape:
class TestDiscoveryEventDelivery:
"""discovery:progress socket events are received by subscribed clients."""
@pytest.mark.parametrize('platform,pid', [
('tidal', 'test-tidal-1'),
('youtube', 'test-yt-hash'),
])
def test_discovery_event_received(self, test_app, shared_state, platform, pid):
"""Client subscribes and receives discovery:progress event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_discovery_status']
client.emit('discovery:subscribe', {'ids': [pid]})
payload = build(platform, pid)
payload['platform'] = platform
payload['id'] = pid
socketio.emit('discovery:progress', payload, room=f'discovery:{pid}')
received = client.get_received()
events = [e for e in received if e['name'] == 'discovery:progress']
assert len(events) >= 1
client.disconnect()
def test_discovery_only_subscribed(self, test_app, shared_state):
"""Unsubscribed client does NOT receive discovery:progress events."""
app, socketio = test_app
@ -281,24 +235,6 @@ class TestDiscoveryDataShape:
class TestScanEventDelivery:
"""Broadcast scan events are received by all connected clients."""
def test_watchlist_scan_received(self, test_app, shared_state):
"""All clients receive scan:watchlist event."""
app, socketio = test_app
client1 = socketio.test_client(app)
client2 = socketio.test_client(app)
build = shared_state['build_watchlist_scan_status']
socketio.emit('scan:watchlist', build())
for client in [client1, client2]:
received = client.get_received()
events = [e for e in received if e['name'] == 'scan:watchlist']
assert len(events) >= 1
client1.disconnect()
client2.disconnect()
def test_watchlist_scan_shape(self, test_app, shared_state):
"""scan:watchlist data has success, status, current_artist_name."""
app, socketio = test_app
@ -321,23 +257,6 @@ class TestScanEventDelivery:
client.disconnect()
def test_media_scan_received(self, test_app, shared_state):
"""All clients receive scan:media event."""
app, socketio = test_app
client1 = socketio.test_client(app)
client2 = socketio.test_client(app)
build = shared_state['build_media_scan_status']
socketio.emit('scan:media', build())
for client in [client1, client2]:
received = client.get_received()
events = [e for e in received if e['name'] == 'scan:media']
assert len(events) >= 1
client1.disconnect()
client2.disconnect()
def test_media_scan_shape(self, test_app, shared_state):
"""scan:media data has success and status with is_scanning."""
app, socketio = test_app
@ -491,33 +410,11 @@ class TestScanHttpParity:
class TestHttpStillWorks:
"""HTTP endpoints return 200 with expected structure."""
def test_sync_http_works(self, flask_client):
"""GET /api/sync/status/:id returns 200."""
resp = flask_client.get('/api/sync/status/test-playlist-1')
assert resp.status_code == 200
data = resp.get_json()
assert 'status' in data
assert 'progress' in data
def test_sync_http_404_unknown(self, flask_client):
"""GET /api/sync/status/:id returns 404 for unknown playlist."""
resp = flask_client.get('/api/sync/status/nonexistent')
assert resp.status_code == 404
@pytest.mark.parametrize('platform,endpoint', [
('tidal', '/api/tidal/discovery/status/test-tidal-1'),
('youtube', '/api/youtube/discovery/status/test-yt-hash'),
])
def test_discovery_http_works(self, flask_client, platform, endpoint):
"""GET /api/<platform>/discovery/status/:id returns 200."""
resp = flask_client.get(endpoint)
assert resp.status_code == 200
data = resp.get_json()
assert 'phase' in data
assert 'status' in data
assert 'results' in data
def test_discovery_http_not_found(self, flask_client):
"""GET /api/tidal/discovery/status/:id returns error for unknown ID."""
resp = flask_client.get('/api/tidal/discovery/status/nonexistent')
@ -525,42 +422,12 @@ class TestHttpStillWorks:
data = resp.get_json()
assert 'error' in data
def test_watchlist_scan_http_works(self, flask_client):
"""GET /api/watchlist/scan/status returns 200."""
resp = flask_client.get('/api/watchlist/scan/status')
assert resp.status_code == 200
data = resp.get_json()
assert data['success'] is True
assert 'status' in data
def test_media_scan_http_works(self, flask_client):
"""GET /api/scan/status returns 200."""
resp = flask_client.get('/api/scan/status')
assert resp.status_code == 200
data = resp.get_json()
assert data['success'] is True
assert 'status' in data
# =========================================================================
# Group H — Backward Compatibility
# Group H — Broadcast Updates
# =========================================================================
class TestBackwardCompat:
"""HTTP endpoints work when no WebSocket is connected."""
def test_all_http_endpoints_work_without_socket(self, flask_client):
"""All Phase 5 HTTP endpoints work without any WebSocket connection."""
endpoints = [
'/api/sync/status/test-playlist-1',
'/api/tidal/discovery/status/test-tidal-1',
'/api/youtube/discovery/status/test-yt-hash',
'/api/watchlist/scan/status',
'/api/scan/status',
]
for endpoint in endpoints:
resp = flask_client.get(endpoint)
assert resp.status_code == 200
"""Broadcast scan events still reach multiple clients."""
def test_multiple_clients_get_scan_updates(self, test_app, shared_state):
"""Multiple WebSocket clients each receive scan events."""
@ -668,29 +535,6 @@ PLATFORM_SYNC_IDS = [
class TestPlatformSyncEventDelivery:
"""Platform sync pollers receive sync:progress via WS rooms."""
@pytest.mark.parametrize('platform,sync_id', PLATFORM_SYNC_IDS)
def test_platform_sync_event_received(self, test_app, shared_state, platform, sync_id):
"""Client subscribes to platform sync_playlist_id and receives sync:progress."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_sync_status']
client.emit('sync:subscribe', {'playlist_ids': [sync_id]})
socketio.emit('sync:progress', {
'playlist_id': sync_id, **build(sync_id)
}, room=f'sync:{sync_id}')
received = client.get_received()
events = [e for e in received if e['name'] == 'sync:progress']
assert len(events) >= 1
data = events[0]['args'][0]
assert data['playlist_id'] == sync_id
assert data['status'] == 'syncing'
client.disconnect()
@pytest.mark.parametrize('platform,sync_id', PLATFORM_SYNC_IDS)
def test_platform_sync_not_received_when_unsubscribed(
self, test_app, shared_state, platform, sync_id):
@ -796,35 +640,12 @@ class TestPlatformSyncEventDelivery:
client.disconnect()
@pytest.mark.parametrize('platform,sync_id', PLATFORM_SYNC_IDS)
def test_platform_sync_http_still_works(self, flask_client, platform, sync_id):
"""GET /api/sync/status/<platform_sync_id> returns 200."""
resp = flask_client.get(f'/api/sync/status/{sync_id}')
assert resp.status_code == 200
data = resp.get_json()
assert 'status' in data
assert 'progress' in data
# =========================================================================
# Group H — Wishlist Stats (broadcast)
# =========================================================================
class TestWishlistStatsEventDelivery:
"""wishlist:stats broadcast events are received by the client."""
def test_wishlist_stats_event_received(self, test_app, shared_state):
"""Client receives a wishlist:stats event."""
app, socketio = test_app
client = socketio.test_client(app)
build = shared_state['build_wishlist_stats']
socketio.emit('wishlist:stats', build())
received = client.get_received()
events = [e for e in received if e['name'] == 'wishlist:stats']
assert len(events) >= 1
client.disconnect()
def test_wishlist_stats_data_shape(self, test_app, shared_state):
"""wishlist:stats has is_auto_processing and next_run_in_seconds."""
app, socketio = test_app
@ -843,14 +664,6 @@ class TestWishlistStatsEventDelivery:
assert isinstance(data['next_run_in_seconds'], (int, float))
client.disconnect()
def test_wishlist_stats_http_still_works(self, flask_client):
"""GET /api/wishlist/stats returns 200 with expected keys."""
resp = flask_client.get('/api/wishlist/stats')
assert resp.status_code == 200
data = resp.get_json()
assert 'is_auto_processing' in data
assert 'next_run_in_seconds' in data
def test_wishlist_stats_auto_processing_detection(self, test_app, shared_state):
"""When is_auto_processing is True, client detects it."""
app, socketio = test_app