From 95ef1f42b8eee92fc76c6cf4280e02775f4f9bb2 Mon Sep 17 00:00:00 2001 From: Cucumberrbob <128094686+Cucumberrbob@users.noreply.github.com> Date: Mon, 17 Feb 2025 22:01:16 +0000 Subject: [PATCH] Add tests for `DownloadStationDownloader` Tests: - Downloads successfully when everything goes right first time - Downloads successfully when the 5th api call is successful after 4 failures - Fails to download when 5 api calls fail in a row - Fails to download if the file has already been added to `Download Station` (when `GetInfoAsync` does not error) - FAils to download if `remotePath` cannot be parsed by `Path.GetDirectoryName()` --- .../DownloadStationDownloaderTest.cs | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 server/RdtClient.Service.Test/Services/Downloaders/DownloadStationDownloaderTest.cs diff --git a/server/RdtClient.Service.Test/Services/Downloaders/DownloadStationDownloaderTest.cs b/server/RdtClient.Service.Test/Services/Downloaders/DownloadStationDownloaderTest.cs new file mode 100644 index 0000000..8f145e2 --- /dev/null +++ b/server/RdtClient.Service.Test/Services/Downloaders/DownloadStationDownloaderTest.cs @@ -0,0 +1,213 @@ +using Moq; +using RdtClient.Service.Helpers; +using RdtClient.Service.Services.Downloaders; +using Synology.Api.Client; +using Synology.Api.Client.Apis.DownloadStation; +using Synology.Api.Client.Apis.DownloadStation.Task.Models; + +namespace RdtClient.Service.Test.Services.Downloaders; + +class Mocks +{ + public readonly String Gid; + public readonly Mock SynologyClientMock = new(); + public readonly Mock TaskEndpointMock = new(); + + public Mocks(String gid = "123456") + { + Gid = gid; + var downloadStationApiMock = new Mock(); + downloadStationApiMock.Setup(a => a.TaskEndpoint()).Returns(TaskEndpointMock.Object); + SynologyClientMock.Setup(c => c.DownloadStationApi()).Returns(downloadStationApiMock.Object); + } +} + +class FakeDelayProvider : IDelayProvider +{ + public Task Delay(Int32 milliseconds) + { + return Task.CompletedTask; + } +} + +public class DownloadStationDownloaderTest +{ + [Fact] + public async Task Download_WhenRemotePathEmpty_Throws() + { + // Arrange + var synologyClientMock = new Mock(); + var gid = Guid.NewGuid(); + + var downloadStationDownloader = new DownloadStationDownloader(gid.ToString(), + "https://fake.url/file.txt", + "", + "/path/to/file.txt", + "download-path", + synologyClientMock.Object); + + // Act + var exception = await Assert.ThrowsAsync(downloadStationDownloader.Download); + + // Assert + Assert.Contains("invalid file path", exception.Message, StringComparison.OrdinalIgnoreCase); + synologyClientMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task Download_WhenAlreadyAdded_Throws() + { + // Arrange + var mocks = new Mocks(); + mocks.TaskEndpointMock.Setup(t => t.GetInfoAsync(mocks.Gid)).ReturnsAsync(new DownloadStationTask()); + + var downloadStationDownloader = new DownloadStationDownloader(mocks.Gid, + "https://fake.url/file.txt", + "/path/on/remote/file.txt", + "/path/to/file.txt", + "download-path", + mocks.SynologyClientMock.Object); + + // Act + var exception = await Assert.ThrowsAsync(downloadStationDownloader.Download); + + // Assert + Assert.Contains("already been added", exception.Message, StringComparison.OrdinalIgnoreCase); + mocks.TaskEndpointMock.Verify(t => t.GetInfoAsync(mocks.Gid), Times.Once); + mocks.TaskEndpointMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task Download_WhenAddedSuccessfully_ReturnsGid() + { + // Arrange + var mocks = new Mocks(); + mocks.TaskEndpointMock.Setup(t => t.GetInfoAsync(mocks.Gid)).ThrowsAsync(new Exception()); + + mocks.TaskEndpointMock.Setup(t => t.ListAsync()) + .ReturnsAsync(new DownloadStationTaskListResponse + { + Total = 0, + Offset = 0 + }); + + mocks.TaskEndpointMock.Setup(t => t.CreateAsync(It.IsAny())) + .ReturnsAsync(new DownloadStationTaskCreateResponse + { + TaskId = [mocks.Gid] + }); + + var downloadStationDownloader = new DownloadStationDownloader(mocks.Gid, + "https://fake.url/file.txt", + "/path/on/remote/file.txt", + "/path/to/file.txt", + "download-path", + mocks.SynologyClientMock.Object); + + // Act + var result = await downloadStationDownloader.Download(); + + // Assert + Assert.Equal(mocks.Gid, result); + mocks.TaskEndpointMock.Verify(t => t.GetInfoAsync(mocks.Gid), Times.Once); + mocks.TaskEndpointMock.Verify(t => t.ListAsync(), Times.Once); + mocks.TaskEndpointMock.Verify(t => t.CreateAsync(It.IsAny()), Times.Once); + + mocks.TaskEndpointMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task Download_After5Tries_Throws() + { + var mocks = new Mocks(); + mocks.TaskEndpointMock.Setup(t => t.GetInfoAsync(mocks.Gid)).ThrowsAsync(new Exception()); + + var emptyListResponse = new DownloadStationTaskListResponse + { + Total = 0, + Offset = 0 + }; + + mocks.TaskEndpointMock.SetupSequence(t => t.ListAsync()) + .ReturnsAsync(emptyListResponse) + .ReturnsAsync(emptyListResponse) + .ReturnsAsync(emptyListResponse) + .ReturnsAsync(emptyListResponse) + .ReturnsAsync(emptyListResponse); + + mocks.TaskEndpointMock.SetupSequence(t => t.CreateAsync(It.IsAny())) + .ThrowsAsync(new Exception()) + .ThrowsAsync(new Exception()) + .ThrowsAsync(new Exception()) + .ThrowsAsync(new Exception()) + .ThrowsAsync(new Exception()); + + var downloadStationDownloader = new DownloadStationDownloader(mocks.Gid, + "https://fake.url/file.txt", + "/path/on/remote/file.txt", + "/path/to/file.txt", + "download-path", + mocks.SynologyClientMock.Object, + new FakeDelayProvider()); + + // Act + var exception = await Assert.ThrowsAsync(downloadStationDownloader.Download); + + // Assert + Assert.Contains("unable to download", exception.Message, StringComparison.OrdinalIgnoreCase); + mocks.TaskEndpointMock.Verify(t => t.GetInfoAsync(mocks.Gid), Times.Once); + mocks.TaskEndpointMock.Verify(t => t.ListAsync(), Times.Exactly(5)); + mocks.TaskEndpointMock.Verify(t => t.CreateAsync(It.IsAny()), Times.Exactly(5)); + + mocks.TaskEndpointMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task Download_WhenSuccessfulAfter4Tries_ReturnsGid() + { + var mocks = new Mocks(); + mocks.TaskEndpointMock.Setup(t => t.GetInfoAsync(mocks.Gid)).ThrowsAsync(new Exception()); + + var emptyListResponse = new DownloadStationTaskListResponse + { + Total = 0, + Offset = 0 + }; + + mocks.TaskEndpointMock.SetupSequence(t => t.ListAsync()) + .ReturnsAsync(emptyListResponse) + .ReturnsAsync(emptyListResponse) + .ReturnsAsync(emptyListResponse) + .ReturnsAsync(emptyListResponse) + .ReturnsAsync(emptyListResponse); + + mocks.TaskEndpointMock.SetupSequence(t => t.CreateAsync(It.IsAny())) + .ThrowsAsync(new Exception()) + .ThrowsAsync(new Exception()) + .ThrowsAsync(new Exception()) + .ThrowsAsync(new Exception()) + .ReturnsAsync(new DownloadStationTaskCreateResponse + { + TaskId = [mocks.Gid] + }); + + var downloadStationDownloader = new DownloadStationDownloader(mocks.Gid, + "https://fake.url/file.txt", + "/path/on/remote/file.txt", + "/path/to/file.txt", + "download-path", + mocks.SynologyClientMock.Object, + new FakeDelayProvider()); + + // Act + var result = await downloadStationDownloader.Download(); + + // Assert + Assert.Equal(mocks.Gid, result); + mocks.TaskEndpointMock.Verify(t => t.GetInfoAsync(mocks.Gid), Times.Once); + mocks.TaskEndpointMock.Verify(t => t.ListAsync(), Times.Exactly(5)); + mocks.TaskEndpointMock.Verify(t => t.CreateAsync(It.IsAny()), Times.Exactly(5)); + + mocks.TaskEndpointMock.VerifyNoOtherCalls(); + } +}