From a7fb1e3e9d7b756978e9f7e023829e444bc54d68 Mon Sep 17 00:00:00 2001 From: omgbeez <251408589+omgbeez@users.noreply.github.com> Date: Sun, 31 May 2026 17:12:13 -0400 Subject: [PATCH] fix(sabnzbd): Fix delete implementation to respect finished action --- .../Services/SabnzbdTest.cs | 58 +++++++++++++++++++ server/RdtClient.Service/Services/Sabnzbd.cs | 14 ++--- .../Controllers/SabnzbdControllerTest.cs | 44 ++++++++++++++ .../Controllers/SabnzbdController.cs | 53 ++++++++++++----- 4 files changed, 146 insertions(+), 23 deletions(-) diff --git a/server/RdtClient.Service.Test/Services/SabnzbdTest.cs b/server/RdtClient.Service.Test/Services/SabnzbdTest.cs index 55a729f6..e9211ec0 100644 --- a/server/RdtClient.Service.Test/Services/SabnzbdTest.cs +++ b/server/RdtClient.Service.Test/Services/SabnzbdTest.cs @@ -366,6 +366,64 @@ public async Task GetHistory_ShouldReturnFailedStatus_WhenTorrentHasError() Assert.Equal("Failed", result.Slots[0].Status); } + [Theory] + [InlineData(TorrentFinishedAction.RemoveAllTorrents, true, true, true, true)] + [InlineData(TorrentFinishedAction.RemoveAllTorrents, false, true, true, false)] + [InlineData(TorrentFinishedAction.RemoveRealDebrid, true, false, true, true)] + [InlineData(TorrentFinishedAction.RemoveRealDebrid, false, false, true, false)] + [InlineData(TorrentFinishedAction.RemoveClient, true, true, false, true)] + [InlineData(TorrentFinishedAction.RemoveClient, false, true, false, false)] + public async Task Delete_ShouldRespectFinishedActionAndDeleteFiles( + TorrentFinishedAction finishedAction, + Boolean deleteFiles, + Boolean expectedDeleteData, + Boolean expectedDeleteRdTorrent, + Boolean expectedDeleteLocalFiles) + { + // Arrange + var torrentId = Guid.NewGuid(); + _settings.Current.Integrations.Default.FinishedAction = finishedAction; + + _torrentsMock.Setup(t => t.GetByHash("hash1")) + .ReturnsAsync(new Torrent + { + TorrentId = torrentId, + Hash = "hash1", + Type = DownloadType.Nzb + }); + + var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings); + + // Act + await sabnzbd.Delete("hash1", deleteFiles); + + // Assert + _torrentsMock.Verify(t => t.Delete(torrentId, expectedDeleteData, expectedDeleteRdTorrent, expectedDeleteLocalFiles), Times.Once); + } + + [Fact] + public async Task Delete_ShouldNotDelete_WhenFinishedActionIsNone() + { + // Arrange + _settings.Current.Integrations.Default.FinishedAction = TorrentFinishedAction.None; + + _torrentsMock.Setup(t => t.GetByHash("hash1")) + .ReturnsAsync(new Torrent + { + TorrentId = Guid.NewGuid(), + Hash = "hash1", + Type = DownloadType.Nzb + }); + + var sabnzbd = new Sabnzbd(_loggerMock.Object, _torrentsMock.Object, _appSettings, _settings); + + // Act + await sabnzbd.Delete("hash1", true); + + // Assert + _torrentsMock.Verify(t => t.Delete(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + [Fact] public void GetConfig_ShouldReturnCorrectConfig() { diff --git a/server/RdtClient.Service/Services/Sabnzbd.cs b/server/RdtClient.Service/Services/Sabnzbd.cs index c95abc98..a84643ec 100644 --- a/server/RdtClient.Service/Services/Sabnzbd.cs +++ b/server/RdtClient.Service/Services/Sabnzbd.cs @@ -177,7 +177,7 @@ public virtual async Task AddUrl(String url, String? category, Int32? pr return result.Hash; } - public virtual async Task Delete(String hash) + public virtual async Task Delete(String hash, Boolean deleteFiles = false) { var torrent = await torrents.GetByHash(hash); @@ -189,18 +189,18 @@ public virtual async Task Delete(String hash) switch (settings.Current.Integrations.Default.FinishedAction) { case TorrentFinishedAction.RemoveAllTorrents: - logger.LogDebug("Removing nzb from debrid provider and RDT-Client, no files"); - await torrents.Delete(torrent.TorrentId, true, true, true); + logger.LogDebug("Removing nzb from debrid provider and RDT-Client, {Files}", deleteFiles ? "with files" : "no files"); + await torrents.Delete(torrent.TorrentId, true, true, deleteFiles); break; case TorrentFinishedAction.RemoveRealDebrid: - logger.LogDebug("Removing nzb from debrid provider, no files"); - await torrents.Delete(torrent.TorrentId, false, true, true); + logger.LogDebug("Removing nzb from debrid provider, {Files}", deleteFiles ? "with files" : "no files"); + await torrents.Delete(torrent.TorrentId, false, true, deleteFiles); break; case TorrentFinishedAction.RemoveClient: - logger.LogDebug("Removing nzb from client, no files"); - await torrents.Delete(torrent.TorrentId, true, false, true); + logger.LogDebug("Removing nzb from client, {Files}", deleteFiles ? "with files" : "no files"); + await torrents.Delete(torrent.TorrentId, true, false, deleteFiles); break; case TorrentFinishedAction.None: diff --git a/server/RdtClient.Web.Test/Controllers/SabnzbdControllerTest.cs b/server/RdtClient.Web.Test/Controllers/SabnzbdControllerTest.cs index 4a1a52bb..9a1e2620 100644 --- a/server/RdtClient.Web.Test/Controllers/SabnzbdControllerTest.cs +++ b/server/RdtClient.Web.Test/Controllers/SabnzbdControllerTest.cs @@ -108,6 +108,30 @@ public async Task GetQueue_WithMaAuth_ReturnsOk() Assert.IsType(result); } + [Theory] + [InlineData("?name=delete&value=hash1&del_files=1", true)] + [InlineData("?name=delete&value=hash1&del_files=true", true)] + [InlineData("?name=delete&value=hash1&del_files=0", false)] + [InlineData("?name=delete&value=hash1", false)] + public async Task Queue_Delete_PassesDeleteFilesFlag(String queryString, Boolean expectedDeleteFiles) + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.QueryString = new(queryString); + _controller.ControllerContext.HttpContext = httpContext; + + _sabnzbdMock.Setup(s => s.Delete("hash1", expectedDeleteFiles)).Returns(Task.CompletedTask); + + // Act + var result = await _controller.Queue(); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType(okResult.Value); + Assert.True(response.Status); + _sabnzbdMock.Verify(s => s.Delete("hash1", expectedDeleteFiles), Times.Once); + } + [Fact] public async Task GetHistory_ReturnsOk() { @@ -129,6 +153,26 @@ public async Task GetHistory_ReturnsOk() Assert.Equal(1, response.History.NoOfSlots); } + [Fact] + public async Task History_Delete_PassesDeleteFilesFlag() + { + // Arrange + var httpContext = new DefaultHttpContext(); + httpContext.Request.QueryString = new("?name=delete&value=hash1&del_files=1"); + _controller.ControllerContext.HttpContext = httpContext; + + _sabnzbdMock.Setup(s => s.Delete("hash1", true)).Returns(Task.CompletedTask); + + // Act + var result = await _controller.History(); + + // Assert + var okResult = Assert.IsType(result); + var response = Assert.IsType(okResult.Value); + Assert.True(response.Status); + _sabnzbdMock.Verify(s => s.Delete("hash1", true), Times.Once); + } + [Fact] public void GetVersion_HasAllowAnonymousAttribute() { diff --git a/server/RdtClient.Web/Controllers/SabnzbdController.cs b/server/RdtClient.Web/Controllers/SabnzbdController.cs index 6baf82d6..872e8f38 100644 --- a/server/RdtClient.Web/Controllers/SabnzbdController.cs +++ b/server/RdtClient.Web/Controllers/SabnzbdController.cs @@ -56,22 +56,7 @@ public async Task Queue() if (name == "delete") { - var value = GetParam("value"); - - if (String.IsNullOrWhiteSpace(value)) - { - return BadRequest(new SabnzbdResponse - { - Error = "No value specified for delete operation" - }); - } - - await sabnzbd.Delete(value ?? ""); - - return Ok(new SabnzbdResponse - { - Status = true - }); + return await DeleteDownloads(); } return Ok(new SabnzbdResponse @@ -85,6 +70,12 @@ public async Task Queue() public async Task History() { logger.LogDebug("Sabnzbd mode: history"); + var name = GetParam("name"); + + if (name == "delete") + { + return await DeleteDownloads(); + } return Ok(new SabnzbdResponse { @@ -183,6 +174,31 @@ public async Task FullStatus() }); } + private async Task DeleteDownloads() + { + var value = GetParam("value"); + + if (String.IsNullOrWhiteSpace(value)) + { + return BadRequest(new SabnzbdResponse + { + Error = "No value specified for delete operation" + }); + } + + var deleteFiles = IsTrue(GetParam("del_files")); + + foreach (var hash in value.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + await sabnzbd.Delete(hash, deleteFiles); + } + + return Ok(new SabnzbdResponse + { + Status = true + }); + } + private String? GetParam(String name) { var value = Request.Query[name].ToString(); @@ -194,4 +210,9 @@ public async Task FullStatus() return value; } + + private static Boolean IsTrue(String? value) + { + return value is "1" || Boolean.TryParse(value, out var result) && result; + } }