Ship Prowlarr per-entry rows as opt-in, not the default (#1145)

#1140 fixed the guid-only dedup that hid results from filter-specific
indexer entries, but shipped the new behaviour on by default:
PROWLARR_COLLAPSE_DUPLICATES defaulted off, so every existing Prowlarr
user got extra rows for any release that two indexer entries both
returned, and the setting only let them opt back into what they already
had.

Default it on. The dedup key stays indexer-qualified, so the entries are
still distinct internally; collapse then merges them back to one row,
resolved by the Prowlarr priority rather than by query order as before.
The visible result set matches what users had prior to #1140, and anyone
who wants the per-entry rows (freeleech and the like) turns the setting
off.

Beyond the noisier list, the default mattered because split rows differ
only by indexer name while sharing a title, size and peer count. Two of
them have distinct source_ids, so the queue's duplicate guard does not
fire, and the second grab's find_existing() matches the first by
infohash and runs post-processing over the same download again,
delivering the book twice.

The search-side fallback in config.get(..., True) is flipped to agree
with the field default. In production the field default governs, since
the config cache is seeded from the registry and the fallback only
applies to an unregistered key. The source tests monkeypatch config.get
with a plain dict lookup, though, so the fallback is what they exercise:
leaving it False would have kept every default-behaviour test asserting
the opposite of what ships. A new test pins the two together.

The deduplication tests now opt out explicitly, since they assert the
split itself. test_collapse_off_by_default_keeps_both_rows becomes a
pair, one for the untouched setting collapsing to a single row and one
for opting out.

docs/environment-variables.md is regenerated rather than hand-edited. It
was already stale on main, so it also picks up RTORRENT_AUDIOBOOK_LABEL,
DIRECT_DOWNLOAD_LANGUAGE_FROM_PATH, and reworded IRC_SEARCH_BOT and
RTORRENT_LABEL text from earlier merges.

The rest is fallout from the ruff 0.16.0 bump in #1139, which enabled a
much larger default rule set and started formatting Python code blocks
in Markdown: _find_existing_alias_user() uses min() instead of
sorted()[0] (FURB192, currently failing Python Quality on main), and the
two READMEs get their code blocks reformatted.
This commit is contained in:
CaliBrain
2026-07-28 01:23:31 -04:00
committed by GitHub
parent a4086f5e06
commit a1367f431d
7 changed files with 90 additions and 31 deletions
+2
View File
@@ -169,6 +169,7 @@ uv run pytest tests/ --cov=shelfmark -m "not integration"
```python
from unittest.mock import MagicMock, patch
class TestMyFeature:
def test_something(self, monkeypatch):
# Mock config values
@@ -188,6 +189,7 @@ class TestMyFeature:
import pytest
from .conftest import APIClient, DownloadTracker
@pytest.mark.e2e
class TestMyEndpoint:
def test_endpoint_works(self, protected_api_client: APIClient):
+37 -4
View File
@@ -796,7 +796,14 @@ def _mam_result(indexer_id: int, indexer: str, guid: str, *, freeleech: bool = F
class TestIndexerAwareDeduplication:
"""One tracker as several Prowlarr entries must not collapse to one row (#1137)."""
"""One tracker as several Prowlarr entries must not collapse to one row (#1137).
These assert the split itself, so they turn PROWLARR_COLLAPSE_DUPLICATES off:
it ships on, which keeps the release list as it was before #1137 for everyone
who has not asked for the per-entry rows.
"""
SPLIT = {"PROWLARR_COLLAPSE_DUPLICATES": False}
ONLY_ACTIVE = 10
FREELEECH = 25
@@ -834,6 +841,7 @@ class TestIndexerAwareDeduplication:
)
],
},
config_values=self.SPLIT,
)
assert len(releases) == 2
@@ -850,6 +858,7 @@ class TestIndexerAwareDeduplication:
_mam_result(self.FREELEECH, "MyAnonamouse - Freeleech", shared_guid)
],
},
config_values=self.SPLIT,
)
source_ids = [r.source_id for r in releases]
@@ -868,6 +877,7 @@ class TestIndexerAwareDeduplication:
_mam_result(self.FREELEECH, "MyAnonamouse - Freeleech", shared_guid)
],
},
config_values=self.SPLIT,
)
assert len(releases) == 2
@@ -914,7 +924,7 @@ class TestBuildSourceId:
class TestCollapseDuplicatesSetting:
"""Opt-in one-row-per-release collapse, resolved by Prowlarr's priority."""
"""One-row-per-release collapse, on by default, resolved by Prowlarr's priority."""
ONLY_ACTIVE = 10
FREELEECH = 25
@@ -951,8 +961,8 @@ class TestCollapseDuplicatesSetting:
assert len(releases) == 1
assert releases[0].indexer == "MyAnonamouse"
def test_collapse_off_by_default_keeps_both_rows(self, monkeypatch):
releases = TestIndexerAwareDeduplication()._search(
def _search_shared_guid(self, monkeypatch, config_values):
return TestIndexerAwareDeduplication()._search(
monkeypatch,
{
self.ONLY_ACTIVE: [
@@ -962,11 +972,34 @@ class TestCollapseDuplicatesSetting:
_mam_result(self.FREELEECH, "MAM - Freeleech", "https://tracker.example/t/9")
],
},
config_values=config_values,
priorities={self.FREELEECH: 20, self.ONLY_ACTIVE: 24},
)
def test_collapse_is_on_when_the_setting_is_untouched(self, monkeypatch):
"""An upgrading user who sets nothing keeps the single row they had before #1137."""
releases = self._search_shared_guid(monkeypatch, None)
assert len(releases) == 1
assert releases[0].indexer == "MAM - Freeleech"
def test_opting_out_keeps_every_indexer_entry(self, monkeypatch):
releases = self._search_shared_guid(monkeypatch, {"PROWLARR_COLLAPSE_DUPLICATES": False})
assert len(releases) == 2
def test_the_settings_field_and_the_search_fallback_agree(self):
"""The field default is what governs in production; the search fallback only
applies to an unregistered key. They have to say the same thing.
"""
from shelfmark.release_sources.prowlarr.settings import prowlarr_config_settings
field = next(
f for f in prowlarr_config_settings() if f.key == "PROWLARR_COLLAPSE_DUPLICATES"
)
assert field.default is True
class TestBuildIndexerPriority:
"""The priority NUMBER from Prowlarr is the rank; the id is only the key."""