Add queue deduplication to prevent duplicate entries

- In-memory dedup in add_to_queue() using _pending_by_episode dict
- Batch-local dedup via seen_in_batch set (handles duplicates within single call)
- Database unique index on episode_id via __table_args__
- 5-minute cooldown in _auto_download_missing() to prevent rapid re-triggers
- Updated _add_to_pending_queue() and _remove_from_pending_queue() to track episode keys
- Added TestQueueDeduplication with 4 test cases
- Updated DEVELOPMENT.md and TESTING.md with queue dedup docs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-23 21:27:41 +02:00
parent 24ea12bbaf
commit 31eb0026cf
7 changed files with 275 additions and 158 deletions

View File

@@ -834,3 +834,111 @@ class TestRemoveEpisodeFromMissingList:
# Episode 2 should be removed from in-memory missing list
assert 2 not in serie.episodeDict[1]
assert serie.episodeDict[1] == [1, 3]
class TestQueueDeduplication:
"""Test queue deduplication to prevent duplicate entries."""
@pytest.mark.asyncio
async def test_add_same_episode_twice_creates_only_one_entry(
self, download_service
):
"""Test that adding the same episode twice only creates one queue entry."""
episodes = [EpisodeIdentifier(season=1, episode=1)]
# Add same episode twice
ids1 = await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=episodes,
)
ids2 = await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=episodes,
)
# Should only have one entry
assert len(download_service._pending_queue) == 1
# First call creates one ID
assert len(ids1) == 1
# Second call creates zero IDs (deduplicated)
assert len(ids2) == 0
@pytest.mark.asyncio
async def test_add_different_episodes_creates_separate_entries(
self, download_service
):
"""Test that different episodes create separate queue entries."""
episodes1 = [EpisodeIdentifier(season=1, episode=1)]
episodes2 = [EpisodeIdentifier(season=1, episode=2)]
ids1 = await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=episodes1,
)
ids2 = await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=episodes2,
)
# Should have two separate entries
assert len(download_service._pending_queue) == 2
assert len(ids1) == 1
assert len(ids2) == 1
# IDs should be different
assert ids1[0] != ids2[0]
@pytest.mark.asyncio
async def test_add_same_episode_different_series_creates_entries(
self, download_service
):
"""Test that same episode in different series creates separate entries."""
episodes = [EpisodeIdentifier(season=1, episode=1)]
ids1 = await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series1",
serie_name="Test Series 1",
episodes=episodes,
)
ids2 = await download_service.add_to_queue(
serie_id="series-2",
serie_folder="series2",
serie_name="Test Series 2",
episodes=episodes,
)
# Should have two separate entries (different series)
assert len(download_service._pending_queue) == 2
assert len(ids1) == 1
assert len(ids2) == 1
@pytest.mark.asyncio
async def test_add_multiple_episodes_with_duplicates_filters_correctly(
self, download_service
):
"""Test that adding multiple episodes with some duplicates filters correctly."""
episodes = [
EpisodeIdentifier(season=1, episode=1),
EpisodeIdentifier(season=1, episode=2),
EpisodeIdentifier(season=1, episode=1), # duplicate
EpisodeIdentifier(season=1, episode=3),
]
ids1 = await download_service.add_to_queue(
serie_id="series-1",
serie_folder="series",
serie_name="Test Series",
episodes=episodes,
)
# Should only have 3 entries (1, 2, 3) - one filtered out
assert len(download_service._pending_queue) == 3
assert len(ids1) == 3