From e98ecfcb2ab7bf726c3025a2c1f5069b0d79af8a Mon Sep 17 00:00:00 2001 From: Flaminel Date: Fri, 16 May 2025 20:48:52 +0300 Subject: [PATCH] fix #9 --- .../ContentBlocker/ContentBlockerConfig.cs | 15 +++-- .../DownloadCleaner/CleanCategory.cs | 11 ++-- .../DownloadCleaner/DownloadCleanerConfig.cs | 17 +++-- .../DownloadClient/ClientConfig.cs | 32 +++------- .../DownloadClient/DelugeConfig.cs | 24 ------- .../DownloadClient/DownloadClientConfig.cs | 4 +- .../DownloadClient/QBitConfig.cs | 26 -------- .../DownloadClient/TransmissionConfig.cs | 26 -------- .../Controllers/DownloadClientsController.cs | 16 ++--- .../Controllers/HealthCheckController.cs | 8 +-- .../Health/HealthCheckServiceFixture.cs | 15 +++-- .../Health/HealthCheckServiceTests.cs | 64 +++++++++---------- .../Http/DynamicHttpClientProviderFixture.cs | 6 +- .../JsonConfigurationProvider.cs | 2 +- .../Health/ClientHealthChangedEventArgs.cs | 4 +- .../Health/HealthCheckService.cs | 20 +++--- code/Infrastructure/Health/HealthStatus.cs | 2 +- .../Health/IHealthCheckService.cs | 8 +-- .../DownloadClient/DownloadService.cs | 2 +- .../DownloadClient/DownloadServiceFactory.cs | 4 +- .../Factory/DownloadClientFactory.cs | 10 +-- .../Factory/IDownloadClientFactory.cs | 4 +- .../DownloadClient/IDownloadService.cs | 2 +- 23 files changed, 123 insertions(+), 199 deletions(-) delete mode 100644 code/Common/Configuration/DownloadClient/DelugeConfig.cs delete mode 100644 code/Common/Configuration/DownloadClient/QBitConfig.cs delete mode 100644 code/Common/Configuration/DownloadClient/TransmissionConfig.cs diff --git a/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs b/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs index 90d1283c..e578cacc 100644 --- a/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs +++ b/code/Common/Configuration/ContentBlocker/ContentBlockerConfig.cs @@ -1,5 +1,6 @@ using Common.Configuration.Arr; using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; namespace Common.Configuration.ContentBlocker; @@ -10,22 +11,28 @@ public sealed record ContentBlockerConfig : IJobConfig public bool Enabled { get; init; } // Trigger configuration - [ConfigurationKeyName("CRON_EXPRESSION")] + [JsonProperty("cron_expression")] public string CronExpression { get; init; } = "0 */30 * ? * *"; // Default: every 30 minutes // Privacy settings - [ConfigurationKeyName("IGNORE_PRIVATE")] + [JsonProperty("ignore_private")] public bool IgnorePrivate { get; init; } - [ConfigurationKeyName("DELETE_PRIVATE")] + [JsonProperty("delete_private")] public bool DeletePrivate { get; init; } - [ConfigurationKeyName("IGNORED_DOWNLOADS_PATH")] + // TODO + [JsonProperty("IGNORED_DOWNLOADS_PATH")] public string? IgnoredDownloadsPath { get; init; } // Blocklist settings moved from ArrConfig + [JsonProperty("sonarr")] public BlocklistSettings Sonarr { get; init; } = new(); + + [JsonProperty("radarr")] public BlocklistSettings Radarr { get; init; } = new(); + + [JsonProperty("lidarr")] public BlocklistSettings Lidarr { get; init; } = new(); public void Validate() diff --git a/code/Common/Configuration/DownloadCleaner/CleanCategory.cs b/code/Common/Configuration/DownloadCleaner/CleanCategory.cs index 48574cfe..0a659109 100644 --- a/code/Common/Configuration/DownloadCleaner/CleanCategory.cs +++ b/code/Common/Configuration/DownloadCleaner/CleanCategory.cs @@ -1,28 +1,31 @@ using Common.Exceptions; using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; namespace Common.Configuration.DownloadCleaner; public sealed record CleanCategory : IConfig { public required string Name { get; init; } + + // TODO add clean type (category, tag, etc) and make it scoped to a client and rename this to cleanobject or something /// /// Max ratio before removing a download. /// - [ConfigurationKeyName("MAX_RATIO")] + [JsonProperty("max_ratio")] public required double MaxRatio { get; init; } = -1; /// /// Min number of hours to seed before removing a download, if the ratio has been met. /// - [ConfigurationKeyName("MIN_SEED_TIME")] - public required double MinSeedTime { get; init; } = 0; + [JsonProperty("min_seed_time")] + public required double MinSeedTime { get; init; } /// /// Number of hours to seed before removing a download. /// - [ConfigurationKeyName("MAX_SEED_TIME")] + [JsonProperty("max_seed_time")] public required double MaxSeedTime { get; init; } = -1; public void Validate() diff --git a/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs b/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs index 42651ee0..3ec34bf3 100644 --- a/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs +++ b/code/Common/Configuration/DownloadCleaner/DownloadCleanerConfig.cs @@ -1,5 +1,6 @@ using Common.Exceptions; using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; namespace Common.Configuration.DownloadCleaner; @@ -10,27 +11,29 @@ public sealed record DownloadCleanerConfig : IJobConfig public bool Enabled { get; init; } // Trigger configuration - [ConfigurationKeyName("CRON_EXPRESSION")] + [JsonProperty("cron_expression")] public string CronExpression { get; init; } = "0 */20 * ? * *"; // Default: every 20 minutes public List? Categories { get; init; } - [ConfigurationKeyName("DELETE_PRIVATE")] + [JsonProperty("delete_private")] public bool DeletePrivate { get; init; } - [ConfigurationKeyName("IGNORED_DOWNLOADS_PATH")] + // TODO + [JsonProperty("ignored_downloads_path")] public string? IgnoredDownloadsPath { get; init; } - [ConfigurationKeyName("UNLINKED_TARGET_CATEGORY")] + [JsonProperty("unlinked_target_category")] public string UnlinkedTargetCategory { get; init; } = "cleanuperr-unlinked"; - [ConfigurationKeyName("UNLINKED_USE_TAG")] + [JsonProperty("unlinked_use_tag")] public bool UnlinkedUseTag { get; init; } - [ConfigurationKeyName("UNLINKED_IGNORED_ROOT_DIR")] + [JsonProperty("unlinked_ignored_root_dir")] public string UnlinkedIgnoredRootDir { get; init; } = string.Empty; - [ConfigurationKeyName("UNLINKED_CATEGORIES")] + // TODO rename to unlinked objects and add type (category, tag, etc) + [JsonProperty("unlinked_categories")] public List? UnlinkedCategories { get; init; } public void Validate() diff --git a/code/Common/Configuration/DownloadClient/ClientConfig.cs b/code/Common/Configuration/DownloadClient/ClientConfig.cs index d2500b3c..56b8401a 100644 --- a/code/Common/Configuration/DownloadClient/ClientConfig.cs +++ b/code/Common/Configuration/DownloadClient/ClientConfig.cs @@ -1,5 +1,6 @@ using Common.Enums; using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; namespace Common.Configuration.DownloadClient; @@ -8,10 +9,15 @@ namespace Common.Configuration.DownloadClient; /// public sealed record ClientConfig { + /// + /// Whether this client is enabled + /// + public bool Enabled { get; init; } = true; + /// /// Unique identifier for this client /// - public string Id { get; init; } = Guid.NewGuid().ToString("N"); + public Guid Id { get; init; } = Guid.NewGuid(); /// /// Friendly name for this client @@ -38,32 +44,12 @@ public sealed record ClientConfig /// public string Password { get; init; } = string.Empty; - /// - /// Default category to use - /// - public string Category { get; init; } = string.Empty; - - /// - /// Path to download directory - /// - public string Path { get; init; } = string.Empty; - - /// - /// Whether this client is enabled - /// - public bool Enabled { get; init; } = true; - /// /// The base URL path component, used by clients like Transmission and Deluge /// - [ConfigurationKeyName("URL_BASE")] + [JsonProperty("url_base")] public string UrlBase { get; init; } = string.Empty; - /// - /// Use HTTPS protocol - /// - public bool UseHttps { get; init; } = false; - /// /// The computed full URL for the client /// @@ -74,7 +60,7 @@ public sealed record ClientConfig /// public void Validate() { - if (string.IsNullOrWhiteSpace(Id)) + if (Id == Guid.Empty) { throw new InvalidOperationException("Client ID cannot be empty"); } diff --git a/code/Common/Configuration/DownloadClient/DelugeConfig.cs b/code/Common/Configuration/DownloadClient/DelugeConfig.cs deleted file mode 100644 index 59033a28..00000000 --- a/code/Common/Configuration/DownloadClient/DelugeConfig.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Common.Exceptions; -using Microsoft.Extensions.Configuration; - -namespace Common.Configuration.DownloadClient; - -public sealed record DelugeConfig : IConfig -{ - public const string SectionName = "Deluge"; - - public Uri? Url { get; init; } - - [ConfigurationKeyName("URL_BASE")] - public string UrlBase { get; init; } = string.Empty; - - public string? Password { get; init; } - - public void Validate() - { - if (Url is null) - { - throw new ValidationException($"{nameof(Url)} is empty"); - } - } -} \ No newline at end of file diff --git a/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs b/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs index 4c9d0584..48e84145 100644 --- a/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs +++ b/code/Common/Configuration/DownloadClient/DownloadClientConfig.cs @@ -12,7 +12,7 @@ public sealed record DownloadClientConfig : IConfig /// /// The client id /// The client configuration or null if not found - public ClientConfig? GetClientConfig(string id) + public ClientConfig? GetClientConfig(Guid id) { return Clients.FirstOrDefault(c => c.Id == id); } @@ -46,7 +46,7 @@ public sealed record DownloadClientConfig : IConfig // Validate each client configuration foreach (var client in Clients) { - if (string.IsNullOrWhiteSpace(client.Id)) + if (client.Id == Guid.Empty) { throw new InvalidOperationException("Client ID cannot be empty"); } diff --git a/code/Common/Configuration/DownloadClient/QBitConfig.cs b/code/Common/Configuration/DownloadClient/QBitConfig.cs deleted file mode 100644 index 7de0fafb..00000000 --- a/code/Common/Configuration/DownloadClient/QBitConfig.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Common.Exceptions; -using Microsoft.Extensions.Configuration; - -namespace Common.Configuration.DownloadClient; - -public sealed class QBitConfig : IConfig -{ - public const string SectionName = "qBittorrent"; - - public Uri? Url { get; init; } - - [ConfigurationKeyName("URL_BASE")] - public string UrlBase { get; init; } = string.Empty; - - public string? Username { get; init; } - - public string? Password { get; init; } - - public void Validate() - { - if (Url is null) - { - throw new ValidationException($"{nameof(Url)} is empty"); - } - } -} \ No newline at end of file diff --git a/code/Common/Configuration/DownloadClient/TransmissionConfig.cs b/code/Common/Configuration/DownloadClient/TransmissionConfig.cs deleted file mode 100644 index 4d30b626..00000000 --- a/code/Common/Configuration/DownloadClient/TransmissionConfig.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Common.Exceptions; -using Microsoft.Extensions.Configuration; - -namespace Common.Configuration.DownloadClient; - -public record TransmissionConfig : IConfig -{ - public const string SectionName = "Transmission"; - - public Uri? Url { get; init; } - - [ConfigurationKeyName("URL_BASE")] - public string UrlBase { get; init; } = "transmission"; - - public string? Username { get; init; } - - public string? Password { get; init; } - - public void Validate() - { - if (Url is null) - { - throw new ValidationException($"{nameof(Url)} is empty"); - } - } -} \ No newline at end of file diff --git a/code/Executable/Controllers/DownloadClientsController.cs b/code/Executable/Controllers/DownloadClientsController.cs index c2ce60ed..b3a1370c 100644 --- a/code/Executable/Controllers/DownloadClientsController.cs +++ b/code/Executable/Controllers/DownloadClientsController.cs @@ -57,8 +57,8 @@ public class DownloadClientsController : ControllerBase /// /// Gets a specific download client by ID /// - [HttpGet("{id}")] - public async Task GetClient(string id) + [HttpGet("{id:guid}")] + public async Task GetClient(Guid id) { try { @@ -133,8 +133,8 @@ public class DownloadClientsController : ControllerBase /// /// Updates an existing download client /// - [HttpPut("{id}")] - public async Task UpdateClient(string id, [FromBody] ClientConfig clientConfig) + [HttpPut("{id:guid}")] + public async Task UpdateClient(Guid id, [FromBody] ClientConfig clientConfig) { try { @@ -184,8 +184,8 @@ public class DownloadClientsController : ControllerBase /// /// Deletes a download client /// - [HttpDelete("{id}")] - public async Task DeleteClient(string id) + [HttpDelete("{id:guid}")] + public async Task DeleteClient(Guid id) { try { @@ -226,8 +226,8 @@ public class DownloadClientsController : ControllerBase /// /// Tests connection to a download client /// - [HttpPost("{id}/test")] - public async Task TestConnection(string id) + [HttpPost("{id:guid}/test")] + public async Task TestConnection(Guid id) { try { diff --git a/code/Executable/Controllers/HealthCheckController.cs b/code/Executable/Controllers/HealthCheckController.cs index e883f6e2..caf7f47c 100644 --- a/code/Executable/Controllers/HealthCheckController.cs +++ b/code/Executable/Controllers/HealthCheckController.cs @@ -45,8 +45,8 @@ public class HealthCheckController : ControllerBase /// /// Gets the health status of a specific download client /// - [HttpGet("{id}")] - public IActionResult GetClientHealth(string id) + [HttpGet("{id:guid}")] + public IActionResult GetClientHealth(Guid id) { try { @@ -86,8 +86,8 @@ public class HealthCheckController : ControllerBase /// /// Triggers a health check for a specific download client /// - [HttpPost("check/{id}")] - public async Task CheckClientHealth(string id) + [HttpPost("check/{id:guid}")] + public async Task CheckClientHealth(Guid id) { try { diff --git a/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs b/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs index fa575dc0..48a8dfdf 100644 --- a/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs +++ b/code/Infrastructure.Tests/Health/HealthCheckServiceFixture.cs @@ -24,6 +24,7 @@ public class HealthCheckServiceFixture : IDisposable ConfigManager = Substitute.For(); ClientFactory = Substitute.For(); MockClient = Substitute.For(); + Guid clientId = Guid.NewGuid(); // Set up test download client config DownloadClientConfig = new DownloadClientConfig @@ -32,7 +33,7 @@ public class HealthCheckServiceFixture : IDisposable { new() { - Id = "qbit1", + Id = clientId, Name = "Test QBittorrent", Type = DownloadClientType.QBittorrent, Enabled = true, @@ -41,7 +42,7 @@ public class HealthCheckServiceFixture : IDisposable }, new() { - Id = "transmission1", + Id = Guid.NewGuid(), Name = "Test Transmission", Type = DownloadClientType.Transmission, Enabled = true, @@ -50,7 +51,7 @@ public class HealthCheckServiceFixture : IDisposable }, new() { - Id = "disabled1", + Id = Guid.NewGuid(), Name = "Disabled Client", Type = DownloadClientType.QBittorrent, Enabled = false, @@ -59,8 +60,8 @@ public class HealthCheckServiceFixture : IDisposable }; // Set up the mock client factory - ClientFactory.GetClient(Arg.Any()).Returns(MockClient); - MockClient.GetClientId().Returns("qbit1"); + ClientFactory.GetClient(Arg.Any()).Returns(MockClient); + MockClient.GetClientId().Returns(clientId); // Set up mock config manager ConfigManager.GetDownloadClientConfigAsync().Returns(DownloadClientConfig); @@ -71,13 +72,13 @@ public class HealthCheckServiceFixture : IDisposable return new HealthCheckService(Logger, ConfigManager, ClientFactory); } - public void SetupHealthyClient(string clientId) + public void SetupHealthyClient(Guid clientId) { // Setup a client that will successfully login MockClient.LoginAsync().Returns(Task.CompletedTask); } - public void SetupUnhealthyClient(string clientId, string errorMessage = "Failed to connect") + public void SetupUnhealthyClient(Guid clientId, string errorMessage = "Failed to connect") { // Setup a client that will fail to login MockClient.LoginAsync().Throws(new Exception(errorMessage)); diff --git a/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs b/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs index 2b90f560..df448f71 100644 --- a/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs +++ b/code/Infrastructure.Tests/Health/HealthCheckServiceTests.cs @@ -19,15 +19,15 @@ public class HealthCheckServiceTests : IClassFixture { // Arrange var sut = _fixture.CreateSut(); - _fixture.SetupHealthyClient("qbit1"); + _fixture.SetupHealthyClient(new Guid("00000000-0000-0000-0000-000000000001")); // Act - var result = await sut.CheckClientHealthAsync("qbit1"); + var result = await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001")); // Assert result.ShouldSatisfyAllConditions( () => result.IsHealthy.ShouldBeTrue(), - () => result.ClientId.ShouldBe("qbit1"), + () => result.ClientId.ShouldBe(new Guid("00000000-0000-0000-0000-000000000001")), () => result.ErrorMessage.ShouldBeNull(), () => result.LastChecked.ShouldBeInRange(DateTime.UtcNow.AddSeconds(-10), DateTime.UtcNow) ); @@ -38,15 +38,15 @@ public class HealthCheckServiceTests : IClassFixture { // Arrange var sut = _fixture.CreateSut(); - _fixture.SetupUnhealthyClient("qbit1", "Connection refused"); + _fixture.SetupUnhealthyClient(new Guid("00000000-0000-0000-0000-000000000001"), "Connection refused"); // Act - var result = await sut.CheckClientHealthAsync("qbit1"); + var result = await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001")); // Assert result.ShouldSatisfyAllConditions( () => result.IsHealthy.ShouldBeFalse(), - () => result.ClientId.ShouldBe("qbit1"), + () => result.ClientId.ShouldBe(new Guid("00000000-0000-0000-0000-000000000001")), () => result.ErrorMessage.ShouldContain("Connection refused"), () => result.LastChecked.ShouldBeInRange(DateTime.UtcNow.AddSeconds(-10), DateTime.UtcNow) ); @@ -64,12 +64,12 @@ public class HealthCheckServiceTests : IClassFixture ); // Act - var result = await sut.CheckClientHealthAsync("non-existent"); + var result = await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000010")); // Assert result.ShouldSatisfyAllConditions( () => result.IsHealthy.ShouldBeFalse(), - () => result.ClientId.ShouldBe("non-existent"), + () => result.ClientId.ShouldBe(new Guid("00000000-0000-0000-0000-000000000010")), () => result.ErrorMessage.ShouldContain("not found"), () => result.LastChecked.ShouldBeInRange(DateTime.UtcNow.AddSeconds(-10), DateTime.UtcNow) ); @@ -80,18 +80,18 @@ public class HealthCheckServiceTests : IClassFixture { // Arrange var sut = _fixture.CreateSut(); - _fixture.SetupHealthyClient("qbit1"); - _fixture.SetupUnhealthyClient("transmission1"); + _fixture.SetupHealthyClient(new Guid("00000000-0000-0000-0000-000000000001")); + _fixture.SetupUnhealthyClient(new Guid("00000000-0000-0000-0000-000000000002")); // Act var results = await sut.CheckAllClientsHealthAsync(); // Assert results.Count.ShouldBe(2); // Only enabled clients - results.Keys.ShouldContain("qbit1"); - results.Keys.ShouldContain("transmission1"); - results["qbit1"].IsHealthy.ShouldBeTrue(); - results["transmission1"].IsHealthy.ShouldBeFalse(); + results.Keys.ShouldContain(new Guid("00000000-0000-0000-0000-000000000001")); + results.Keys.ShouldContain(new Guid("00000000-0000-0000-0000-000000000002")); + results[new Guid("00000000-0000-0000-0000-000000000001")].IsHealthy.ShouldBeTrue(); + results[new Guid("00000000-0000-0000-0000-000000000002")].IsHealthy.ShouldBeFalse(); } [Fact] @@ -99,23 +99,23 @@ public class HealthCheckServiceTests : IClassFixture { // Arrange var sut = _fixture.CreateSut(); - _fixture.SetupHealthyClient("qbit1"); + _fixture.SetupHealthyClient(new Guid("00000000-0000-0000-0000-000000000001")); ClientHealthChangedEventArgs? capturedArgs = null; sut.ClientHealthChanged += (_, args) => capturedArgs = args; // Act - first check establishes initial state - var firstResult = await sut.CheckClientHealthAsync("qbit1"); + var firstResult = await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001")); // Setup client to be unhealthy for second check - _fixture.SetupUnhealthyClient("qbit1"); + _fixture.SetupUnhealthyClient(new Guid("00000000-0000-0000-0000-000000000001")); // Act - second check changes state - var secondResult = await sut.CheckClientHealthAsync("qbit1"); + var secondResult = await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001")); // Assert capturedArgs.ShouldNotBeNull(); - capturedArgs.ClientId.ShouldBe("qbit1"); + capturedArgs.ClientId.ShouldBe(new Guid("00000000-0000-0000-0000-000000000001")); capturedArgs.Status.IsHealthy.ShouldBeFalse(); capturedArgs.IsDegraded.ShouldBeTrue(); capturedArgs.IsRecovered.ShouldBeFalse(); @@ -126,18 +126,18 @@ public class HealthCheckServiceTests : IClassFixture { // Arrange var sut = _fixture.CreateSut(); - _fixture.SetupHealthyClient("qbit1"); + _fixture.SetupHealthyClient(new Guid("00000000-0000-0000-0000-000000000001")); // Perform a check to cache the status - await sut.CheckClientHealthAsync("qbit1"); + await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001")); // Act - var result = sut.GetClientHealth("qbit1"); + var result = sut.GetClientHealth(new Guid("00000000-0000-0000-0000-000000000001")); // Assert result.ShouldNotBeNull(); result.IsHealthy.ShouldBeTrue(); - result.ClientId.ShouldBe("qbit1"); + result.ClientId.ShouldBe(new Guid("00000000-0000-0000-0000-000000000001")); } [Fact] @@ -147,7 +147,7 @@ public class HealthCheckServiceTests : IClassFixture var sut = _fixture.CreateSut(); // Act - var result = sut.GetClientHealth("qbit1"); + var result = sut.GetClientHealth(new Guid("00000000-0000-0000-0000-000000000001")); // Assert result.ShouldBeNull(); @@ -158,21 +158,21 @@ public class HealthCheckServiceTests : IClassFixture { // Arrange var sut = _fixture.CreateSut(); - _fixture.SetupHealthyClient("qbit1"); - _fixture.SetupUnhealthyClient("transmission1"); + _fixture.SetupHealthyClient(new Guid("00000000-0000-0000-0000-000000000001")); + _fixture.SetupUnhealthyClient(new Guid("00000000-0000-0000-0000-000000000002")); // Perform checks to cache statuses - await sut.CheckClientHealthAsync("qbit1"); - await sut.CheckClientHealthAsync("transmission1"); + await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000001")); + await sut.CheckClientHealthAsync(new Guid("00000000-0000-0000-0000-000000000002")); // Act var results = sut.GetAllClientHealth(); // Assert results.Count.ShouldBe(2); - results.Keys.ShouldContain("qbit1"); - results.Keys.ShouldContain("transmission1"); - results["qbit1"].IsHealthy.ShouldBeTrue(); - results["transmission1"].IsHealthy.ShouldBeFalse(); + results.Keys.ShouldContain(new Guid("00000000-0000-0000-0000-000000000001")); + results.Keys.ShouldContain(new Guid("00000000-0000-0000-0000-000000000002")); + results[new Guid("00000000-0000-0000-0000-000000000001")].IsHealthy.ShouldBeTrue(); + results[new Guid("00000000-0000-0000-0000-000000000002")].IsHealthy.ShouldBeFalse(); } } diff --git a/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs b/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs index 43a59ba6..99dc7037 100644 --- a/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs +++ b/code/Infrastructure.Tests/Http/DynamicHttpClientProviderFixture.cs @@ -34,7 +34,7 @@ public class DynamicHttpClientProviderFixture : IDisposable { return new ClientConfig { - Id = "qbit-test", + Id = Guid.NewGuid(), Name = "QBit Test", Type = DownloadClientType.QBittorrent, Enabled = true, @@ -48,7 +48,7 @@ public class DynamicHttpClientProviderFixture : IDisposable { return new ClientConfig { - Id = "transmission-test", + Id = Guid.NewGuid(), Name = "Transmission Test", Type = DownloadClientType.Transmission, Enabled = true, @@ -63,7 +63,7 @@ public class DynamicHttpClientProviderFixture : IDisposable { return new ClientConfig { - Id = "deluge-test", + Id = Guid.NewGuid(), Name = "Deluge Test", Type = DownloadClientType.Deluge, Enabled = true, diff --git a/code/Infrastructure/Configuration/JsonConfigurationProvider.cs b/code/Infrastructure/Configuration/JsonConfigurationProvider.cs index d344fe38..99250b12 100644 --- a/code/Infrastructure/Configuration/JsonConfigurationProvider.cs +++ b/code/Infrastructure/Configuration/JsonConfigurationProvider.cs @@ -79,7 +79,7 @@ public class JsonConfigurationProvider if (!File.Exists(fullPath)) { - _logger.LogWarning("Configuration file does not exist: {file}", fullPath); + _logger.LogDebug("Configuration file does not exist: {file}", fullPath); return new T(); } diff --git a/code/Infrastructure/Health/ClientHealthChangedEventArgs.cs b/code/Infrastructure/Health/ClientHealthChangedEventArgs.cs index 1f944a7b..30e9892e 100644 --- a/code/Infrastructure/Health/ClientHealthChangedEventArgs.cs +++ b/code/Infrastructure/Health/ClientHealthChangedEventArgs.cs @@ -8,7 +8,7 @@ public class ClientHealthChangedEventArgs : EventArgs /// /// Gets the client ID /// - public string ClientId { get; } + public Guid ClientId { get; } /// /// Gets the health status @@ -31,7 +31,7 @@ public class ClientHealthChangedEventArgs : EventArgs /// The client ID /// The current health status /// The previous health status, if any - public ClientHealthChangedEventArgs(string clientId, HealthStatus status, HealthStatus? previousStatus) + public ClientHealthChangedEventArgs(Guid clientId, HealthStatus status, HealthStatus? previousStatus) { ClientId = clientId; Status = status; diff --git a/code/Infrastructure/Health/HealthCheckService.cs b/code/Infrastructure/Health/HealthCheckService.cs index 13417f9a..152a2482 100644 --- a/code/Infrastructure/Health/HealthCheckService.cs +++ b/code/Infrastructure/Health/HealthCheckService.cs @@ -14,7 +14,7 @@ public class HealthCheckService : IHealthCheckService private readonly ILogger _logger; private readonly IConfigManager _configManager; private readonly IDownloadClientFactory _clientFactory; - private readonly Dictionary _healthStatuses = new(); + private readonly Dictionary _healthStatuses = new(); private readonly object _lockObject = new(); /// @@ -39,7 +39,7 @@ public class HealthCheckService : IHealthCheckService } /// - public async Task CheckClientHealthAsync(string clientId) + public async Task CheckClientHealthAsync(Guid clientId) { _logger.LogDebug("Checking health for client {clientId}", clientId); @@ -128,7 +128,7 @@ public class HealthCheckService : IHealthCheckService } /// - public async Task> CheckAllClientsHealthAsync() + public async Task> CheckAllClientsHealthAsync() { _logger.LogDebug("Checking health for all enabled clients"); @@ -139,11 +139,11 @@ public class HealthCheckService : IHealthCheckService if (config == null) { _logger.LogWarning("Download client configuration not found"); - return new Dictionary(); + return new Dictionary(); } var enabledClients = config.GetEnabledClients(); - var results = new Dictionary(); + var results = new Dictionary(); // Check health of each enabled client foreach (var clientConfig in enabledClients) @@ -157,12 +157,12 @@ public class HealthCheckService : IHealthCheckService catch (Exception ex) { _logger.LogError(ex, "Error checking health for all clients"); - return new Dictionary(); + return new Dictionary(); } } /// - public HealthStatus? GetClientHealth(string clientId) + public HealthStatus? GetClientHealth(Guid clientId) { lock (_lockObject) { @@ -171,15 +171,15 @@ public class HealthCheckService : IHealthCheckService } /// - public IDictionary GetAllClientHealth() + public IDictionary GetAllClientHealth() { lock (_lockObject) { - return new Dictionary(_healthStatuses); + return new Dictionary(_healthStatuses); } } - private async Task GetClientConfigAsync(string clientId) + private async Task GetClientConfigAsync(Guid clientId) { var config = await _configManager.GetDownloadClientConfigAsync(); return config?.GetClientConfig(clientId); diff --git a/code/Infrastructure/Health/HealthStatus.cs b/code/Infrastructure/Health/HealthStatus.cs index 7f36a63b..5a5c2d94 100644 --- a/code/Infrastructure/Health/HealthStatus.cs +++ b/code/Infrastructure/Health/HealthStatus.cs @@ -28,7 +28,7 @@ public class HealthStatus /// /// Gets or sets the client ID /// - public string ClientId { get; set; } = string.Empty; + public Guid ClientId { get; set; } = Guid.Empty; /// /// Gets or sets the client name diff --git a/code/Infrastructure/Health/IHealthCheckService.cs b/code/Infrastructure/Health/IHealthCheckService.cs index 1b334385..e799747c 100644 --- a/code/Infrastructure/Health/IHealthCheckService.cs +++ b/code/Infrastructure/Health/IHealthCheckService.cs @@ -15,24 +15,24 @@ public interface IHealthCheckService /// /// The client ID to check /// The health status of the client - Task CheckClientHealthAsync(string clientId); + Task CheckClientHealthAsync(Guid clientId); /// /// Checks the health of all enabled clients /// /// A dictionary of client IDs to health statuses - Task> CheckAllClientsHealthAsync(); + Task> CheckAllClientsHealthAsync(); /// /// Gets the current health status of a client /// /// The client ID /// The current health status, or null if the client hasn't been checked - HealthStatus? GetClientHealth(string clientId); + HealthStatus? GetClientHealth(Guid clientId); /// /// Gets the current health status of all clients that have been checked /// /// A dictionary of client IDs to health statuses - IDictionary GetAllClientHealth(); + IDictionary GetAllClientHealth(); } diff --git a/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs index a58c3b1f..9eeff067 100644 --- a/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/DownloadService.cs @@ -70,7 +70,7 @@ public abstract class DownloadService : IDownloadService } /// - public string GetClientId() + public Guid GetClientId() { return _clientConfig.Id; } diff --git a/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs b/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs index bc94d152..524744ff 100644 --- a/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs +++ b/code/Infrastructure/Verticals/DownloadClient/DownloadServiceFactory.cs @@ -33,9 +33,9 @@ public sealed class DownloadServiceFactory /// /// The client ID to create a service for /// An implementation of IDownloadService or null if the client is not available - public IDownloadService? GetDownloadService(string clientId) + public IDownloadService? GetDownloadService(Guid clientId) { - var config = _configManager.GetDownloadClientConfigAsync().GetAwaiter().GetResult(); + var config = _configManager.GetDownloadClientConfig(); if (config == null) { diff --git a/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs b/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs index 7c06831a..a337570b 100644 --- a/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs +++ b/code/Infrastructure/Verticals/DownloadClient/Factory/DownloadClientFactory.cs @@ -25,7 +25,7 @@ public class DownloadClientFactory : IDownloadClientFactory private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; private readonly IConfigManager _configManager; - private readonly ConcurrentDictionary _clients = new(); + private readonly ConcurrentDictionary _clients = new(); public DownloadClientFactory( ILogger logger, @@ -38,9 +38,9 @@ public class DownloadClientFactory : IDownloadClientFactory } /// - public IDownloadService GetClient(string clientId) + public IDownloadService GetClient(Guid clientId) { - if (string.IsNullOrWhiteSpace(clientId)) + if (clientId == Guid.Empty) { throw new ArgumentException("Client ID cannot be empty", nameof(clientId)); } @@ -73,7 +73,7 @@ public class DownloadClientFactory : IDownloadClientFactory } /// - public void RefreshClient(string clientId) + public void RefreshClient(Guid clientId) { if (_clients.TryRemove(clientId, out var service)) { @@ -100,7 +100,7 @@ public class DownloadClientFactory : IDownloadClientFactory } } - private IDownloadService CreateClient(string clientId) + private IDownloadService CreateClient(Guid clientId) { var downloadClientConfig = _configManager.GetConfiguration("downloadclients.json") ?? new DownloadClientConfig(); diff --git a/code/Infrastructure/Verticals/DownloadClient/Factory/IDownloadClientFactory.cs b/code/Infrastructure/Verticals/DownloadClient/Factory/IDownloadClientFactory.cs index 64997a19..a6e0c201 100644 --- a/code/Infrastructure/Verticals/DownloadClient/Factory/IDownloadClientFactory.cs +++ b/code/Infrastructure/Verticals/DownloadClient/Factory/IDownloadClientFactory.cs @@ -12,7 +12,7 @@ public interface IDownloadClientFactory /// /// The client ID /// The download service for the specified client - IDownloadService GetClient(string clientId); + IDownloadService GetClient(Guid clientId); /// /// Gets all enabled download clients @@ -31,7 +31,7 @@ public interface IDownloadClientFactory /// Refreshes a specific client instance (disposes and recreates) /// /// The client ID to refresh - void RefreshClient(string clientId); + void RefreshClient(Guid clientId); /// /// Refreshes all client instances (disposes and recreates) diff --git a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs index 721eb37c..36fa9af2 100644 --- a/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs +++ b/code/Infrastructure/Verticals/DownloadClient/IDownloadService.cs @@ -13,7 +13,7 @@ public interface IDownloadService : IDisposable /// Gets the unique identifier for this download client /// /// The client ID - string GetClientId(); + Guid GetClientId(); /// /// Initializes the download service with client-specific configuration