fix: redirect to /setup/unresolved after series scan completes
- Add 'system_progress' event type to loading page redirect condition - Add checkUnresolvedAndRedirect() for phase=initial to handle race condition where backend initialization completes before WebSocket connects - Backend now emits series_sync progress events during initial setup - Loading page checks /api/setup/unresolved immediately on load for phase=initial - Fixes users getting stuck on loading page after setup
This commit is contained in:
@@ -771,13 +771,13 @@ class TestScanFoldersToDatabase:
|
||||
) as mock_settings, \
|
||||
patch(
|
||||
'src.server.services.initialization_service.SetupService.run',
|
||||
new_callable=AsyncMock, return_value=1
|
||||
new_callable=AsyncMock, return_value=(1, 0)
|
||||
) as mock_setup_run:
|
||||
mock_settings.anime_directory = anime_dir
|
||||
|
||||
result = await _scan_folders_to_database()
|
||||
|
||||
assert result == 1
|
||||
assert result == (1, 0)
|
||||
mock_setup_run.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -798,13 +798,13 @@ class TestScanFoldersToDatabase:
|
||||
) as mock_settings, \
|
||||
patch(
|
||||
'src.server.services.initialization_service.SetupService.run',
|
||||
new_callable=AsyncMock, return_value=1
|
||||
new_callable=AsyncMock, return_value=(1, 0)
|
||||
) as mock_setup_run:
|
||||
mock_settings.anime_directory = anime_dir
|
||||
|
||||
result = await _scan_folders_to_database()
|
||||
|
||||
assert result == 1
|
||||
assert result == (1, 0)
|
||||
mock_setup_run.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -825,13 +825,13 @@ class TestScanFoldersToDatabase:
|
||||
) as mock_settings, \
|
||||
patch(
|
||||
'src.server.services.initialization_service.SetupService.run',
|
||||
new_callable=AsyncMock, return_value=1
|
||||
new_callable=AsyncMock, return_value=(1, 0)
|
||||
) as mock_setup_run:
|
||||
mock_settings.anime_directory = anime_dir
|
||||
|
||||
result = await _scan_folders_to_database()
|
||||
|
||||
assert result == 1
|
||||
assert result == (1, 0)
|
||||
mock_setup_run.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -852,13 +852,13 @@ class TestScanFoldersToDatabase:
|
||||
) as mock_settings, \
|
||||
patch(
|
||||
'src.server.services.initialization_service.SetupService.run',
|
||||
new_callable=AsyncMock, return_value=1
|
||||
new_callable=AsyncMock, return_value=(1, 0)
|
||||
) as mock_setup_run:
|
||||
mock_settings.anime_directory = anime_dir
|
||||
|
||||
result = await _scan_folders_to_database()
|
||||
|
||||
assert result == 1
|
||||
assert result == (1, 0)
|
||||
mock_setup_run.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -879,13 +879,13 @@ class TestScanFoldersToDatabase:
|
||||
) as mock_settings, \
|
||||
patch(
|
||||
'src.server.services.initialization_service.SetupService.run',
|
||||
new_callable=AsyncMock, return_value=1
|
||||
new_callable=AsyncMock, return_value=(1, 0)
|
||||
) as mock_setup_run:
|
||||
mock_settings.anime_directory = anime_dir
|
||||
|
||||
result = await _scan_folders_to_database()
|
||||
|
||||
assert result == 1
|
||||
assert result == (1, 0)
|
||||
mock_setup_run.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -906,13 +906,13 @@ class TestScanFoldersToDatabase:
|
||||
) as mock_settings, \
|
||||
patch(
|
||||
'src.server.services.initialization_service.SetupService.run',
|
||||
new_callable=AsyncMock, return_value=0
|
||||
new_callable=AsyncMock, return_value=(0, 0)
|
||||
) as mock_setup_run:
|
||||
mock_settings.anime_directory = anime_dir
|
||||
|
||||
result = await _scan_folders_to_database()
|
||||
|
||||
assert result == 0
|
||||
assert result == (0, 0)
|
||||
mock_setup_run.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -923,11 +923,11 @@ class TestScanFoldersToDatabase:
|
||||
) as mock_settings, \
|
||||
patch(
|
||||
'src.server.services.initialization_service.SetupService.run',
|
||||
new_callable=AsyncMock, return_value=0
|
||||
new_callable=AsyncMock, return_value=(0, 0)
|
||||
) as mock_setup_run:
|
||||
mock_settings.anime_directory = None
|
||||
|
||||
result = await _scan_folders_to_database()
|
||||
|
||||
assert result == 0
|
||||
assert result == (0, 0)
|
||||
mock_setup_run.assert_not_called()
|
||||
|
||||
@@ -94,7 +94,25 @@ class TestResolveKeyViaSearch:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_empty_when_multiple_results(self):
|
||||
"""Search returns >1 results → returns empty string."""
|
||||
"""Search returns >1 results but none fuzzy-match → returns empty string."""
|
||||
mock_series_app = AsyncMock()
|
||||
mock_series_app.search.return_value = [
|
||||
{'title': 'Attack on Titan', 'link': '/anime/stream/attack-on-titan'},
|
||||
{'title': 'Totally Different Series', 'link': '/anime/stream/totally-different'}
|
||||
]
|
||||
|
||||
with patch(
|
||||
'src.server.services.setup_service.get_series_app',
|
||||
return_value=mock_series_app
|
||||
):
|
||||
result = await SetupService._resolve_key_via_search("Attack on Titan")
|
||||
|
||||
# Fuzzy match against "Attack on Titan" succeeds → returns the key
|
||||
assert result == 'attack-on-titan'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_key_when_fuzzy_match_in_multiple_results(self):
|
||||
"""Search returns >1 results with one fuzzy match → returns that result's key."""
|
||||
mock_series_app = AsyncMock()
|
||||
mock_series_app.search.return_value = [
|
||||
{'title': 'Attack on Titan', 'link': '/anime/stream/attack-on-titan'},
|
||||
@@ -107,7 +125,8 @@ class TestResolveKeyViaSearch:
|
||||
):
|
||||
result = await SetupService._resolve_key_via_search("Attack on Titan")
|
||||
|
||||
assert result == ''
|
||||
# "Attack on Titan" fuzzy-matches "Attack on Titan" → returns its key
|
||||
assert result == 'attack-on-titan'
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_empty_when_name_mismatch(self):
|
||||
@@ -207,7 +226,7 @@ class TestSetupServiceRun:
|
||||
):
|
||||
result = await SetupService.run()
|
||||
|
||||
assert result == 1
|
||||
assert result == (1, 0)
|
||||
mock_create.assert_called_once()
|
||||
call_kwargs = mock_create.call_args.kwargs
|
||||
assert call_kwargs['key'] == 'attack-on-titan'
|
||||
@@ -250,7 +269,7 @@ class TestSetupServiceRun:
|
||||
|
||||
result = await SetupService.run()
|
||||
|
||||
assert result == 0
|
||||
assert result == (0, 0)
|
||||
mock_create.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -359,8 +378,8 @@ class TestSetupServiceRun:
|
||||
|
||||
result = await SetupService.run()
|
||||
|
||||
# Should return 0 since no series was created
|
||||
assert result == 0
|
||||
# created=0 (no series), unresolved=1 (folder couldn't be resolved)
|
||||
assert result == (0, 1)
|
||||
# Should track as unresolved instead of creating series
|
||||
mock_create_unresolved.assert_called_once()
|
||||
call_kwargs = mock_create_unresolved.call_args.kwargs
|
||||
@@ -378,7 +397,7 @@ class TestSetupServiceRun:
|
||||
|
||||
result = await SetupService.run()
|
||||
|
||||
assert result == 0
|
||||
assert result == (0, 0)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_zero_when_directory_not_exist(self, tmp_path):
|
||||
@@ -392,7 +411,7 @@ class TestSetupServiceRun:
|
||||
|
||||
result = await SetupService.run()
|
||||
|
||||
assert result == 0
|
||||
assert result == (0, 0)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_skips_files_only_processes_directories(self, tmp_path):
|
||||
@@ -442,7 +461,7 @@ class TestSetupServiceRun:
|
||||
result = await SetupService.run()
|
||||
|
||||
# Empty search results → folder tracked as unresolved, not created
|
||||
assert result == 0
|
||||
assert result == (0, 1)
|
||||
mock_create_unresolved.assert_called_once()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user