Add option to change the category instead of deleting queue items#602
Conversation
✅ Actions performedReview triggered.
|
Code Review by Qodo
1. QueueRuleDto.ChangeCategory undocumented
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Greptile SummaryThis PR adds a
Confidence Score: 4/5The core category-change path is correctly implemented, but two unresolved issues from previous review rounds remain open: the Seeker search queue is populated even when changeCategory=true (causing a duplicate download), and usenet queue items are silently left in the usenet client with no *arr tracking when changeCategory=true (because the *arr API's changeCategory parameter is torrent-only). The feature logic from evaluator through to the *arr API query string is sound, and the mutual-exclusion validation between changeCategory and deletePrivate is properly enforced on both frontend and backend. The two open issues from prior review iterations — Seeker enqueuing a fresh search while the original torrent keeps downloading, and usenet downloads being abandoned in the client — are genuine behavioral defects on the changed paths that should be addressed before merging. QueueItemRemover.cs (Seeker search not guarded on changeCategory) and QueueCleaner.cs (no protocol guard for usenet when changeCategory=true) Important Files Changed
Sequence DiagramsequenceDiagram
participant QC as QueueCleaner
participant Eval as QueueRuleEvaluator
participant GH as GenericHandler
participant QIR as QueueItemRemover
participant Arr as ArrClient
QC->>Eval: EvaluateStallRulesAsync(torrent)
Eval-->>QC: (ShouldRemove, Reason, DeleteFromClient, ChangeCategory)
QC->>QC: changeCategory = result.ChangeCategory
QC->>GH: PublishQueueItemRemoveRequest(..., changeCategory)
GH->>QIR: RemoveQueueItemAsync(request)
QIR->>Arr: DeleteQueueItemAsync(..., removeFromClient, changeCategory)
Arr->>Arr: GetQueueDeleteUrlQuery(removeFromClient, changeCategory)
Note over Arr: changeCategory=true → blocklist=true&skipRedownload=true&changeCategory=true&removeFromClient=false
Arr-->>QIR: HTTP DELETE response
QIR->>QIR: PublishQueueItemDeleted(RemoveFromClient=false, reason)
Note over QIR: ChangeCategory not passed — fires removed-from-arr notification
Reviews (2): Last reviewed commit: "add stalled-rule ChangeCategory tests fo..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
code/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cs (1)
45-59: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winExtract identical
GetQueueDeleteUrlQueryimplementation to baseArrClient— currently duplicated across 6 client classes.This method is copy-pasted identically in
RadarrClient,SonarrClient,ReadarrClient,LidarrClient,WhisparrV2Client, andWhisparrV3Client. All share the same branching logic. Convert the abstract method inArrClientto avirtualimplementation with a default body, then remove all six overrides. Subclasses can still override if their API diverges.♻️ Suggested change in `ArrClient.cs`
-protected abstract string GetQueueDeleteUrlQuery(bool removeFromClient, bool changeCategory); +protected virtual string GetQueueDeleteUrlQuery(bool removeFromClient, bool changeCategory) +{ + string query = "blocklist=true&skipRedownload=true&"; + + if (changeCategory) + { + query += "changeCategory=true&removeFromClient=false"; + return query; + } + + query += "changeCategory=false"; + query += removeFromClient ? "&removeFromClient=true" : "&removeFromClient=false"; + return query; +}Then remove the identical overrides from all six client classes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cs` around lines 45 - 59, The duplicated GetQueueDeleteUrlQuery method appears verbatim in RadarrClient, SonarrClient, ReadarrClient, LidarrClient, WhisparrV2Client and WhisparrV3Client; move that implementation into the base ArrClient by changing the abstract declaration to a virtual method with the current body, then delete the identical overrides from the six subclasses so they inherit the shared logic (they can still override later if needed). Ensure the method signature in ArrClient matches the existing overrides (GetQueueDeleteUrlQuery(bool removeFromClient, bool changeCategory)) and run tests to confirm no behavioral changes.
♻️ Duplicate comments (1)
code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cs (1)
47-61: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winDuplicate of
RadarrClient.GetQueueDeleteUrlQuery— see the refactor suggestion onRadarrClient.cs.The implementation here is byte-for-byte identical to
RadarrClient's override. Consolidating intoArrClientas described in theRadarrClient.cscomment resolves both.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cs` around lines 47 - 61, This override of GetQueueDeleteUrlQuery in WhisparrV2Client is identical to RadarrClient.GetQueueDeleteUrlQuery; remove the duplicate from WhisparrV2Client and implement the shared logic once in the base ArrClient (e.g., add a protected virtual or concrete GetQueueDeleteUrlQuery implementation there), then have both WhisparrV2Client and RadarrClient inherit/override only if behavior diverges; reference the method name GetQueueDeleteUrlQuery and the classes WhisparrV2Client, RadarrClient, and ArrClient when making the refactor.
🧹 Nitpick comments (6)
code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DelugeServiceTests.cs (1)
416-498: ⚡ Quick winSame
result.ChangeCategoryassertion gap as inUTorrentServiceTests.cs.Add
result.ChangeCategory.ShouldBeFalse()to bothSlowDownload_MatchesRule_RemovesFromQueue(line 453) andStalledDownload_MatchesRule_RemovesFromQueue(line 497), and aChangeCategory = truepropagation test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DelugeServiceTests.cs` around lines 416 - 498, The tests SlowDownload_MatchesRule_RemovesFromQueue and StalledDownload_MatchesRule_RemovesFromQueue are missing assertions for result.ChangeCategory; add result.ChangeCategory.ShouldBeFalse() to both tests to mirror UTorrentServiceTests expectations, and add an additional unit test that sets the rule evaluator to return ChangeCategory = true (e.g., Arrange EvaluateSlowRulesAsync / EvaluateStallRulesAsync to return (true, DeleteReason.*, true, true)), call ShouldRemoveFromArrQueueAsync on the SUT, and assert result.ChangeCategory.ShouldBeTrue() along with the existing DeleteReason and DeleteFromClient checks so ChangeCategory propagation is covered.code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/UTorrentServiceTests.cs (1)
521-613: ⚡ Quick win
result.ChangeCategoryis never asserted —ChangeCategorypropagation is untested.
SlowDownload_MatchesRule_RemovesFromQueueandStalledDownload_MatchesRule_RemovesFromQueueboth verifyShouldRemove,DeleteReason, andDeleteFromClient, but neither asserts the newChangeCategoryfield. If the assignment at line 73 ofUTorrentServiceQC.cswere accidentally removed, all tests would still pass. The same gap exists inTransmissionServiceTests.csandDelugeServiceTests.cs.Consider adding:
result.ChangeCategory.ShouldBeFalse()to the existingChangeCategory = falsescenarios.- A new scenario where the evaluator returns
ChangeCategory = trueand the result propagates it correctly.✅ Suggested additions to existing tests + new test
result.ShouldRemove.ShouldBeTrue(); result.DeleteReason.ShouldBe(DeleteReason.SlowSpeed); result.DeleteFromClient.ShouldBeTrue(); +result.ChangeCategory.ShouldBeFalse();result.ShouldRemove.ShouldBeTrue(); result.DeleteReason.ShouldBe(DeleteReason.Stalled); result.DeleteFromClient.ShouldBeTrue(); +result.ChangeCategory.ShouldBeFalse();New test inside
ShouldRemoveFromArrQueueAsync_SlowAndStalledScenarios:[Fact] public async Task SlowDownload_ChangeCategoryRule_PropagatesChangeCategoryFlag() { const string hash = "test-hash"; var sut = _fixture.CreateSut(); var torrentItem = new UTorrentItem { Hash = hash, Name = "Test Torrent", Status = 9, DownloadSpeed = 1000 }; var torrentProperties = new UTorrentProperties { Hash = hash, Pex = 1, Trackers = "" }; _fixture.ClientWrapper.GetTorrentAsync(hash).Returns(torrentItem); _fixture.ClientWrapper.GetTorrentPropertiesAsync(hash).Returns(torrentProperties); _fixture.ClientWrapper.GetTorrentFilesAsync(hash).Returns(new List<UTorrentFile> { new UTorrentFile { Name = "file1.mkv", Priority = 1, Index = 0, Size = 1000, Downloaded = 500 } }); // ChangeCategory=true → deleteFromClient must be false (rule gates it) _fixture.RuleEvaluator .EvaluateSlowRulesAsync(Arg.Any<UTorrentItemWrapper>()) .Returns((true, DeleteReason.SlowSpeed, false, true)); var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>()); result.ShouldRemove.ShouldBeTrue(); result.DeleteReason.ShouldBe(DeleteReason.SlowSpeed); result.DeleteFromClient.ShouldBeFalse(); result.ChangeCategory.ShouldBeTrue(); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/UTorrentServiceTests.cs` around lines 521 - 613, The tests for UTorrentService (SlowDownload_MatchesRule_RemovesFromQueue and StalledDownload_MatchesRule_RemovesFromQueue) are missing assertions for the new ChangeCategory propagation; update those tests to assert result.ChangeCategory.ShouldBeFalse() (since the RuleEvaluator returns ChangeCategory=false), and add a new test that stubs RuleEvaluator.EvaluateSlowRulesAsync (or EvaluateStallRulesAsync) to return ChangeCategory=true and verifies that ShouldRemove is true, DeleteReason matches, ChangeCategory is true and DeleteFromClient is false (rule gates deletion when ChangeCategory=true). Apply the same additional ChangeCategory assertions and a positive ChangeCategory propagation test pattern to TransmissionServiceTests and DelugeServiceTests, targeting the ShouldRemoveFromArrQueueAsync flows and the EvaluateSlowRulesAsync/EvaluateStallRulesAsync call sites.code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/TransmissionServiceTests.cs (1)
617-729: ⚡ Quick winSame
result.ChangeCategoryassertion gap as inUTorrentServiceTests.cs.Add
result.ChangeCategory.ShouldBeFalse()to bothSlowDownload_MatchesRule_RemovesFromQueue(line 668) andStalledDownload_MatchesRule_RemovesFromQueue(line 727), and aChangeCategory = truepropagation test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/TransmissionServiceTests.cs` around lines 617 - 729, The two TransmissionServiceTests (SlowDownload_MatchesRule_RemovesFromQueue and StalledDownload_MatchesRule_RemovesFromQueue) are missing assertions for ChangeCategory; add result.ChangeCategory.ShouldBeFalse() to both tests after the other ShouldBe* assertions, and add an additional unit test that stubs RuleEvaluator.EvaluateSlowRulesAsync (or EvaluateStallRulesAsync) to return (true, DeleteReason.SlowSpeed, true, true) (or the stalled equivalent) and asserts that result.ChangeCategory is true so ChangeCategory propagation is covered; locate the tests by their method names and update assertions accordingly.code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs (1)
426-444: ⚡ Quick winAdd a
changeCategoryparameter toCreateRemoveRequestand a corresponding forwarding test.The helper always produces a
ChangeCategory = falserequest (unsetbooldefault), soChangeCategory = trueis never exercised. There is aRemoveQueueItemAsync_PassesCorrectRemoveFromClientFlagtheory test but no analogous theory forChangeCategory.✅ Suggested additions
private QueueItemRemoveRequest<SearchItem> CreateRemoveRequest( InstanceType instanceType = InstanceType.Sonarr, bool removeFromClient = true, DeleteReason deleteReason = DeleteReason.Stalled, - bool skipSearch = false) + bool skipSearch = false, + bool changeCategory = false) { var instance = GetOrCreateArrInstance(instanceType); return new QueueItemRemoveRequest<SearchItem> { Instance = instance, SearchItem = new SearchItem { Id = 123 }, Record = CreateQueueRecord(), RemoveFromClient = removeFromClient, + ChangeCategory = changeCategory, DeleteReason = deleteReason, SkipSearch = skipSearch, JobRunId = _jobRunId }; }And add a forwarding theory alongside
RemoveQueueItemAsync_PassesCorrectRemoveFromClientFlag:[Theory] [InlineData(true)] [InlineData(false)] public async Task RemoveQueueItemAsync_PassesCorrectChangeCategoryFlag(bool changeCategory) { var request = CreateRemoveRequest(changeCategory: changeCategory); await _queueItemRemover.RemoveQueueItemAsync(request); await _arrClient.Received(1).DeleteQueueItemAsync( Arg.Any<ArrInstance>(), Arg.Any<QueueRecord>(), Arg.Any<bool>(), Arg.Is<bool>(x => x == changeCategory), Arg.Any<DeleteReason>()); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs` around lines 426 - 444, Add a new bool parameter changeCategory (default false) to CreateRemoveRequest and set the returned QueueItemRemoveRequest's ChangeCategory property to that value so tests can exercise both true/false; then add a forwarding theory test RemoveQueueItemAsync_PassesCorrectChangeCategoryFlag that calls CreateRemoveRequest(changeCategory: changeCategory), invokes _queueItemRemover.RemoveQueueItemAsync(request), and verifies _arrClient.Received(1).DeleteQueueItemAsync(..., Arg.Is<bool>(x => x == changeCategory), ...) to assert the flag is forwarded.code/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cs (1)
45-59: ⚡ Quick win
GetQueueDeleteUrlQueryis duplicated across allArrClientsubclasses — consolidate into the base.The body of
GetQueueDeleteUrlQueryis byte-for-byte identical inLidarrClient,SonarrClient, andRadarrClient(per the context snippet). Since all *arr apps accept the same query parameters ("All inputs are the same in all apps"), there is no per-client variation. The abstract method inArrClientshould becomevirtualwith this shared body as the default; subclasses that genuinely diverge can still override.♻️ Suggested change in `ArrClient.cs`
-protected abstract string GetQueueDeleteUrlQuery(bool removeFromClient, bool changeCategory); +protected virtual string GetQueueDeleteUrlQuery(bool removeFromClient, bool changeCategory) +{ + string query = "blocklist=true&skipRedownload=true&"; + + if (changeCategory) + { + query += "changeCategory=true&removeFromClient=false"; + return query; + } + + query += "changeCategory=false"; + query += removeFromClient ? "&removeFromClient=true" : "&removeFromClient=false"; + + return query; +}Then remove the identical
overridefromLidarrClient,SonarrClient, andRadarrClient(and any other client with the same body).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cs` around lines 45 - 59, The GetQueueDeleteUrlQuery implementation is identical across ArrClient subclasses; move the shared body into the base ArrClient by changing the abstract declaration of GetQueueDeleteUrlQuery to a virtual method that returns the same query string ("blocklist=true&skipRedownload=true&..." with changeCategory/removeFromClient logic) so it becomes the default behavior in ArrClient, then remove the duplicate overrides from LidarrClient, SonarrClient, RadarrClient (and any other subclass with the same body); keep the method virtual so subclasses that need different behavior can still override it.code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/QueueCleanerTests.cs (1)
1387-1413: ⚡ Quick winThe first
ChangeCategorytest's assertion onRemoveFromClient == falseis not conclusive.
DownloadCheckResultis constructed without settingDeleteFromClient(it defaults tofalse). This meansRemoveFromClient=falsein the assertion would be satisfied even if theChangeCategoryoverride logic were removed entirely. The test doesn't actually prove thatChangeCategory=trueforcesRemoveFromClienttofalse.Compare this with the second ChangeCategory test (lines 1458–1466), which correctly uses
DeleteFromClient = truealongsideChangeCategory = trueso theRemoveFromClient == falseassertion is only satisfiable due to the override.♻️ Suggested fix — make the FailedImport path conclusive
Add
DeleteFromClient = trueto theDownloadCheckResult(or use whatever mechanism controlsRemoveFromClientin the failed-import path) so the assertion proves the override:.Returns(new DownloadCheckResult { Found = true, ShouldRemove = false, - IsPrivate = true + IsPrivate = true, + DeleteFromClient = true // ensure RemoveFromClient would be true without ChangeCategory override });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/QueueCleanerTests.cs` around lines 1387 - 1413, The test is inconclusive because the mocked DownloadCheckResult leaves DeleteFromClient at its default false, so asserting RemoveFromClient == false doesn't prove ChangeCategory forces the override; update the mock returned by ShouldRemoveFromArrQueueAsync to set DeleteFromClient = true on the DownloadCheckResult (in the arrangement before calling CreateSut/ExecuteAsync) so the assertion on QueueItemRemoveRequest<SeriesSearchItem> (r => r.ChangeCategory == true && r.RemoveFromClient == false && r.DeleteReason == DeleteReason.FailedImport) only passes if the ChangeCategory override logic actually flips RemoveFromClient to false.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@code/backend/Cleanuparr.Infrastructure/Features/Arr/ReadarrClient.cs`:
- Around line 47-53: The query string in ReadarrClient.cs is built with
"blocklist=true&skipRedownload=true&" before checking changeCategory, causing
blocklist to be sent even when changeCategory is true; update the logic in the
method that constructs the string (the local variable query and the
changeCategory branch) so that when changeCategory == true the returned query
excludes blocklist (e.g., only include
changeCategory=true&removeFromClient=false or skipRedownload as appropriate),
otherwise include blocklist=true&skipRedownload=true; ensure the code paths that
return query (the changeCategory branch and the default path) produce the
correct mutually exclusive parameter sets.
In `@code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs`:
- Around line 48-54: The query string for WhisparrV3Client is always initialized
with "blocklist=true&skipRedownload=true&", so when changeCategory is true the
code returns "blocklist=...&changeCategory=..." and wrongly blocklists items;
update the logic in WhisparrV3Client (where the local variable query is built
and the changeCategory branch returns) to mirror RadarrClient: do not include
the blocklist parameter when changeCategory is true—build the base query
conditionally (e.g., initialize query without blocklist when changeCategory) or
remove the blocklist token before returning the changeCategory path so the
returned query contains changeCategory=true&removeFromClient=false plus
skipRedownload if appropriate.
---
Outside diff comments:
In `@code/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cs`:
- Around line 45-59: The duplicated GetQueueDeleteUrlQuery method appears
verbatim in RadarrClient, SonarrClient, ReadarrClient, LidarrClient,
WhisparrV2Client and WhisparrV3Client; move that implementation into the base
ArrClient by changing the abstract declaration to a virtual method with the
current body, then delete the identical overrides from the six subclasses so
they inherit the shared logic (they can still override later if needed). Ensure
the method signature in ArrClient matches the existing overrides
(GetQueueDeleteUrlQuery(bool removeFromClient, bool changeCategory)) and run
tests to confirm no behavioral changes.
---
Duplicate comments:
In `@code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cs`:
- Around line 47-61: This override of GetQueueDeleteUrlQuery in WhisparrV2Client
is identical to RadarrClient.GetQueueDeleteUrlQuery; remove the duplicate from
WhisparrV2Client and implement the shared logic once in the base ArrClient
(e.g., add a protected virtual or concrete GetQueueDeleteUrlQuery implementation
there), then have both WhisparrV2Client and RadarrClient inherit/override only
if behavior diverges; reference the method name GetQueueDeleteUrlQuery and the
classes WhisparrV2Client, RadarrClient, and ArrClient when making the refactor.
---
Nitpick comments:
In
`@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DelugeServiceTests.cs`:
- Around line 416-498: The tests SlowDownload_MatchesRule_RemovesFromQueue and
StalledDownload_MatchesRule_RemovesFromQueue are missing assertions for
result.ChangeCategory; add result.ChangeCategory.ShouldBeFalse() to both tests
to mirror UTorrentServiceTests expectations, and add an additional unit test
that sets the rule evaluator to return ChangeCategory = true (e.g., Arrange
EvaluateSlowRulesAsync / EvaluateStallRulesAsync to return (true,
DeleteReason.*, true, true)), call ShouldRemoveFromArrQueueAsync on the SUT, and
assert result.ChangeCategory.ShouldBeTrue() along with the existing DeleteReason
and DeleteFromClient checks so ChangeCategory propagation is covered.
In
`@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/TransmissionServiceTests.cs`:
- Around line 617-729: The two TransmissionServiceTests
(SlowDownload_MatchesRule_RemovesFromQueue and
StalledDownload_MatchesRule_RemovesFromQueue) are missing assertions for
ChangeCategory; add result.ChangeCategory.ShouldBeFalse() to both tests after
the other ShouldBe* assertions, and add an additional unit test that stubs
RuleEvaluator.EvaluateSlowRulesAsync (or EvaluateStallRulesAsync) to return
(true, DeleteReason.SlowSpeed, true, true) (or the stalled equivalent) and
asserts that result.ChangeCategory is true so ChangeCategory propagation is
covered; locate the tests by their method names and update assertions
accordingly.
In
`@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/UTorrentServiceTests.cs`:
- Around line 521-613: The tests for UTorrentService
(SlowDownload_MatchesRule_RemovesFromQueue and
StalledDownload_MatchesRule_RemovesFromQueue) are missing assertions for the new
ChangeCategory propagation; update those tests to assert
result.ChangeCategory.ShouldBeFalse() (since the RuleEvaluator returns
ChangeCategory=false), and add a new test that stubs
RuleEvaluator.EvaluateSlowRulesAsync (or EvaluateStallRulesAsync) to return
ChangeCategory=true and verifies that ShouldRemove is true, DeleteReason
matches, ChangeCategory is true and DeleteFromClient is false (rule gates
deletion when ChangeCategory=true). Apply the same additional ChangeCategory
assertions and a positive ChangeCategory propagation test pattern to
TransmissionServiceTests and DelugeServiceTests, targeting the
ShouldRemoveFromArrQueueAsync flows and the
EvaluateSlowRulesAsync/EvaluateStallRulesAsync call sites.
In
`@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs`:
- Around line 426-444: Add a new bool parameter changeCategory (default false)
to CreateRemoveRequest and set the returned QueueItemRemoveRequest's
ChangeCategory property to that value so tests can exercise both true/false;
then add a forwarding theory test
RemoveQueueItemAsync_PassesCorrectChangeCategoryFlag that calls
CreateRemoveRequest(changeCategory: changeCategory), invokes
_queueItemRemover.RemoveQueueItemAsync(request), and verifies
_arrClient.Received(1).DeleteQueueItemAsync(..., Arg.Is<bool>(x => x ==
changeCategory), ...) to assert the flag is forwarded.
In
`@code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/QueueCleanerTests.cs`:
- Around line 1387-1413: The test is inconclusive because the mocked
DownloadCheckResult leaves DeleteFromClient at its default false, so asserting
RemoveFromClient == false doesn't prove ChangeCategory forces the override;
update the mock returned by ShouldRemoveFromArrQueueAsync to set
DeleteFromClient = true on the DownloadCheckResult (in the arrangement before
calling CreateSut/ExecuteAsync) so the assertion on
QueueItemRemoveRequest<SeriesSearchItem> (r => r.ChangeCategory == true &&
r.RemoveFromClient == false && r.DeleteReason == DeleteReason.FailedImport) only
passes if the ChangeCategory override logic actually flips RemoveFromClient to
false.
In `@code/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cs`:
- Around line 45-59: The GetQueueDeleteUrlQuery implementation is identical
across ArrClient subclasses; move the shared body into the base ArrClient by
changing the abstract declaration of GetQueueDeleteUrlQuery to a virtual method
that returns the same query string ("blocklist=true&skipRedownload=true&..."
with changeCategory/removeFromClient logic) so it becomes the default behavior
in ArrClient, then remove the duplicate overrides from LidarrClient,
SonarrClient, RadarrClient (and any other subclass with the same body); keep the
method virtual so subclasses that need different behavior can still override it.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 94d878df-0ecd-4f5a-bb5d-ddc68847f31d
📒 Files selected for processing (45)
code/backend/Cleanuparr.Api/Features/QueueCleaner/Contracts/Requests/QueueRuleDto.cscode/backend/Cleanuparr.Api/Features/QueueCleaner/Controllers/QueueRulesController.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DelugeServiceTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/QBitServiceTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/RTorrentServiceTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/TransmissionServiceTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/UTorrentServiceTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/MalwareBlockerIntegrationTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/Integration/QueueCleanerIntegrationTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/QueueCleanerTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Services/QueueRuleEvaluatorTests.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/Interfaces/IArrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/ReadarrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/SonarrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cscode/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Deluge/DelugeServiceQC.cscode/backend/Cleanuparr.Infrastructure/Features/DownloadClient/DownloadCheckResult.cscode/backend/Cleanuparr.Infrastructure/Features/DownloadClient/QBittorrent/QBitServiceQC.cscode/backend/Cleanuparr.Infrastructure/Features/DownloadClient/RTorrent/RTorrentServiceQC.cscode/backend/Cleanuparr.Infrastructure/Features/DownloadClient/Transmission/TransmissionServiceQC.cscode/backend/Cleanuparr.Infrastructure/Features/DownloadClient/UTorrent/UTorrentServiceQC.cscode/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/Models/QueueItemRemoveRequest.cscode/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/QueueItemRemover.cscode/backend/Cleanuparr.Infrastructure/Features/Jobs/GenericHandler.cscode/backend/Cleanuparr.Infrastructure/Features/Jobs/QueueCleaner.cscode/backend/Cleanuparr.Infrastructure/Services/Interfaces/IQueueRuleEvaluator.cscode/backend/Cleanuparr.Infrastructure/Services/QueueRuleEvaluator.cscode/backend/Cleanuparr.Persistence.Tests/Models/Configuration/QueueCleaner/FailedImportConfigTests.cscode/backend/Cleanuparr.Persistence.Tests/Models/Configuration/QueueCleaner/QueueRuleTests.cscode/backend/Cleanuparr.Persistence/Migrations/Data/20260504193343_AddQueueCleanerChangeCategory.Designer.cscode/backend/Cleanuparr.Persistence/Migrations/Data/20260504193343_AddQueueCleanerChangeCategory.cscode/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cscode/backend/Cleanuparr.Persistence/Models/Configuration/QueueCleaner/FailedImportConfig.cscode/backend/Cleanuparr.Persistence/Models/Configuration/QueueCleaner/QueueRule.cscode/frontend/src/app/core/services/documentation.service.tscode/frontend/src/app/features/settings/queue-cleaner/queue-cleaner.component.htmlcode/frontend/src/app/features/settings/queue-cleaner/queue-cleaner.component.tscode/frontend/src/app/shared/models/queue-cleaner-config.model.tscode/frontend/src/app/shared/models/queue-rule.model.tsdocs/docs/configuration/queue-cleaner/index.mdx
There was a problem hiding this comment.
🧹 Nitpick comments (3)
code/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cs (1)
162-168: ⚡ Quick winAdd XML docs for the updated public API method.
Line 162 introduces a changed public signature (
changeCategory) but this method has no XML documentation (or/// <inheritdoc/>). Please add docs so the new parameter contract is discoverable from implementation as well.As per coding guidelines, "Add XML documentation comments for public APIs in C#".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cs` around lines 162 - 168, Add XML documentation comments for the public method DeleteQueueItemAsync so the new parameter contract is discoverable: add a <summary> describing what DeleteQueueItemAsync does, and <param> tags for arrInstance, record, removeFromClient, changeCategory (explain what true/false means), and deleteReason; include a <returns> describing the returned Task and any <exception> tags if the method throws specific exceptions. Ensure the doc sits immediately above the DeleteQueueItemAsync declaration and either fully documents the parameters or uses <inheritdoc/> with an explicit <param name="changeCategory"> entry to cover the new parameter.code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/UTorrentServiceTests.cs (1)
568-615: ⚡ Quick winAdd a stalled-rule variant where
ChangeCategory = truefor symmetry.You covered this for slow rules, but not for stalled rules in this class. A mirrored test would close the remaining path-level gap.
✅ Suggested test addition
+ [Fact] + public async Task StalledDownload_RuleWithChangeCategory_PropagatesChangeCategoryFlag() + { + const string hash = "test-hash"; + var sut = _fixture.CreateSut(); + + var torrentItem = new UTorrentItem + { + Hash = hash, + Name = "Test Torrent", + Status = 9, + DownloadSpeed = 0, + ETA = 0 + }; + + var torrentProperties = new UTorrentProperties + { + Hash = hash, + Pex = 1, + Trackers = "" + }; + + _fixture.ClientWrapper.GetTorrentAsync(hash).Returns(torrentItem); + _fixture.ClientWrapper.GetTorrentPropertiesAsync(hash).Returns(torrentProperties); + _fixture.ClientWrapper.GetTorrentFilesAsync(hash).Returns(new List<UTorrentFile> + { + new UTorrentFile { Name = "file1.mkv", Priority = 1, Index = 0, Size = 1000, Downloaded = 500 } + }); + + _fixture.RuleEvaluator + .EvaluateStallRulesAsync(Arg.Any<UTorrentItemWrapper>()) + .Returns((true, DeleteReason.Stalled, false, true)); + + var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>()); + + result.ShouldRemove.ShouldBeTrue(); + result.DeleteReason.ShouldBe(DeleteReason.Stalled); + result.DeleteFromClient.ShouldBeFalse(); + result.ChangeCategory.ShouldBeTrue(); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/UTorrentServiceTests.cs` around lines 568 - 615, Add a mirrored unit test in UTorrentServiceTests that exercises the stalled-rule path where ChangeCategory is true: create a new test similar to StalledDownload_MatchesRule_RemovesFromQueue but have the mocked RuleEvaluator.EvaluateStallRulesAsync(Arg.Any<UTorrentItemWrapper>()) return (true, DeleteReason.Stalled, true, true) and assert result.ShouldRemove == true, result.DeleteReason == DeleteReason.Stalled, result.DeleteFromClient == true and result.ChangeCategory == true; ensure the test uses the same setup (UTorrentItem, UTorrentProperties, UTorrentFile list) and calls ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>()) on the SUT.code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/QBitServiceTests.cs (1)
836-950: ⚡ Quick winMissing stall-path equivalent of
SlowDownload_RuleWithChangeCategory_PropagatesChangeCategoryFlag.A
StalledDownload_RuleWithChangeCategory_PropagatesChangeCategoryFlagtest is absent. Since stall-rule evaluation is a separate code path throughEvaluateStallRulesAsync, theChangeCategorypropagation from that branch is not exercised by the new test.✅ Suggested addition (mirrors the new slow test)
+ [Fact] + public async Task StalledDownload_RuleWithChangeCategory_PropagatesChangeCategoryFlag() + { + // Arrange + const string hash = "test-hash"; + var sut = _fixture.CreateSut(); + + var torrentInfo = new TorrentInfo + { + Hash = hash, + Name = "Test Torrent", + State = TorrentState.StalledDownload + }; + + _fixture.ClientWrapper + .GetTorrentListAsync(Arg.Is<TorrentListQuery>(q => q.Hashes != null && q.Hashes.Contains(hash))) + .Returns(new[] { torrentInfo }); + + _fixture.ClientWrapper + .GetTorrentTrackersAsync(hash) + .Returns(Array.Empty<TorrentTracker>()); + + var properties = new TorrentProperties + { + AdditionalData = new Dictionary<string, JToken> + { + { "is_private", JToken.FromObject(false) } + } + }; + + _fixture.ClientWrapper + .GetTorrentPropertiesAsync(hash) + .Returns(properties); + + _fixture.ClientWrapper + .GetTorrentContentsAsync(hash) + .Returns(new[] + { + new TorrentContent { Index = 0, Priority = TorrentContentPriority.Normal } + }); + + _fixture.RuleEvaluator + .EvaluateStallRulesAsync(Arg.Any<QBitItemWrapper>()) + .Returns((true, DeleteReason.Stalled, false, true)); + + // Act + var result = await sut.ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>()); + + // Assert + result.ShouldRemove.ShouldBeTrue(); + result.DeleteReason.ShouldBe(DeleteReason.Stalled); + result.DeleteFromClient.ShouldBeFalse(); + result.ChangeCategory.ShouldBeTrue(); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/QBitServiceTests.cs` around lines 836 - 950, Add a test named StalledDownload_RuleWithChangeCategory_PropagatesChangeCategoryFlag in the ShouldRemoveFromArrQueueAsync_StalledDownloadScenarios class that mirrors the SlowDownload_RuleWithChangeCategory_PropagatesChangeCategoryFlag test but sets TorrentState = TorrentState.StalledDownload and configures RuleEvaluator.EvaluateStallRulesAsync to return (true, DeleteReason.Stalled, true, true); call ShouldRemoveFromArrQueueAsync(hash, ...) on the SUT and assert result.ShouldRemove is true, result.DeleteReason == DeleteReason.Stalled, result.DeleteFromClient is true and result.ChangeCategory is true so the stalled-rule branch (EvaluateStallRulesAsync) propagates the ChangeCategory flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/QBitServiceTests.cs`:
- Around line 836-950: Add a test named
StalledDownload_RuleWithChangeCategory_PropagatesChangeCategoryFlag in the
ShouldRemoveFromArrQueueAsync_StalledDownloadScenarios class that mirrors the
SlowDownload_RuleWithChangeCategory_PropagatesChangeCategoryFlag test but sets
TorrentState = TorrentState.StalledDownload and configures
RuleEvaluator.EvaluateStallRulesAsync to return (true, DeleteReason.Stalled,
true, true); call ShouldRemoveFromArrQueueAsync(hash, ...) on the SUT and assert
result.ShouldRemove is true, result.DeleteReason == DeleteReason.Stalled,
result.DeleteFromClient is true and result.ChangeCategory is true so the
stalled-rule branch (EvaluateStallRulesAsync) propagates the ChangeCategory
flag.
In
`@code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/UTorrentServiceTests.cs`:
- Around line 568-615: Add a mirrored unit test in UTorrentServiceTests that
exercises the stalled-rule path where ChangeCategory is true: create a new test
similar to StalledDownload_MatchesRule_RemovesFromQueue but have the mocked
RuleEvaluator.EvaluateStallRulesAsync(Arg.Any<UTorrentItemWrapper>()) return
(true, DeleteReason.Stalled, true, true) and assert result.ShouldRemove == true,
result.DeleteReason == DeleteReason.Stalled, result.DeleteFromClient == true and
result.ChangeCategory == true; ensure the test uses the same setup
(UTorrentItem, UTorrentProperties, UTorrentFile list) and calls
ShouldRemoveFromArrQueueAsync(hash, Array.Empty<string>()) on the SUT.
In `@code/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cs`:
- Around line 162-168: Add XML documentation comments for the public method
DeleteQueueItemAsync so the new parameter contract is discoverable: add a
<summary> describing what DeleteQueueItemAsync does, and <param> tags for
arrInstance, record, removeFromClient, changeCategory (explain what true/false
means), and deleteReason; include a <returns> describing the returned Task and
any <exception> tags if the method throws specific exceptions. Ensure the doc
sits immediately above the DeleteQueueItemAsync declaration and either fully
documents the parameters or uses <inheritdoc/> with an explicit <param
name="changeCategory"> entry to cover the new parameter.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 36f091fd-bbc3-494d-9e87-fa3d3e4d5e0a
📒 Files selected for processing (17)
code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DelugeServiceTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/QBitServiceTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/RTorrentServiceTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/TransmissionServiceTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/UTorrentServiceTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cscode/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/QueueCleanerTests.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/ArrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/Interfaces/IArrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/ReadarrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/SonarrClient.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cscode/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cscode/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/Models/QueueItemRemoveRequest.csdocs/docs/configuration/queue-cleaner/index.mdx
💤 Files with no reviewable changes (6)
- code/backend/Cleanuparr.Infrastructure/Features/Arr/ReadarrClient.cs
- code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV2Client.cs
- code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs
- code/backend/Cleanuparr.Infrastructure/Features/Arr/LidarrClient.cs
- code/backend/Cleanuparr.Infrastructure/Features/Arr/SonarrClient.cs
- code/backend/Cleanuparr.Infrastructure/Features/Arr/RadarrClient.cs
🚧 Files skipped from review as they are similar to previous changes (8)
- code/backend/Cleanuparr.Infrastructure/Features/Arr/Interfaces/IArrClient.cs
- docs/docs/configuration/queue-cleaner/index.mdx
- code/backend/Cleanuparr.Infrastructure/Features/DownloadRemover/Models/QueueItemRemoveRequest.cs
- code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/RTorrentServiceTests.cs
- code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadRemover/QueueItemRemoverTests.cs
- code/backend/Cleanuparr.Infrastructure.Tests/Features/Jobs/QueueCleanerTests.cs
- code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/TransmissionServiceTests.cs
- code/backend/Cleanuparr.Infrastructure.Tests/Features/DownloadClient/DelugeServiceTests.cs
|
@greptileai review |
Relates to #452 #591