mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-10-28 02:38:55 -04:00
Rename validation exception and fix its references (#842)
This commit is contained in:
1 parent
40291e974f
commit
f1b4bb674c
80 files changed
+409
-413
No files matched your search
@@ -132,7 +132,7 @@ public class ArrConfigControllerTests : IDisposable
|
||||
var request = new UpdateArrConfigRequest { FailedImportMaxStrikes = strikes };
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ValidationException>(() => _controller.UpdateSonarrConfig(request));
|
||||
await Should.ThrowAsync<CustomValidationException>(() => _controller.UpdateSonarrConfig(request));
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -177,7 +177,7 @@ public class ArrConfigControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.CreateSonarrInstance(request));
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -4,6 +4,7 @@ using Cleanuparr.Api.Features.DownloadCleaner.Controllers;
|
||||
using Cleanuparr.Api.Tests.Features.DownloadCleaner.TestHelpers;
|
||||
using Cleanuparr.Api.Tests.TestHelpers;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence;
|
||||
using Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
@@ -12,7 +13,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Api.Tests.Features.DownloadCleaner;
|
||||
|
||||
@@ -83,7 +83,7 @@ public class DeadTorrentConfigControllerTests : IDisposable
|
||||
{
|
||||
var client = SeedingRulesTestDataFactory.AddDownloadClient(_dataContext);
|
||||
|
||||
await Should.ThrowAsync<ValidationException>(() => _controller.UpdateDeadTorrentConfig(client.Id, ValidRequest(maxStrikes: 2)));
|
||||
await Should.ThrowAsync<CustomValidationException>(() => _controller.UpdateDeadTorrentConfig(client.Id, ValidRequest(maxStrikes: 2)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ public class DownloadCleanerConfigControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.UpdateDownloadCleanerConfig(request));
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -269,7 +269,7 @@ public class SeedingRulesControllerTests : IDisposable
|
||||
var client = SeedingRulesTestDataFactory.AddDownloadClient(_dataContext);
|
||||
var request = CreateValidRequest(categories: []);
|
||||
|
||||
await Should.ThrowAsync<ValidationException>(() => _controller.CreateSeedingRule(client.Id, request));
|
||||
await Should.ThrowAsync<CustomValidationException>(() => _controller.CreateSeedingRule(client.Id, request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -338,7 +338,7 @@ public class SeedingRulesControllerTests : IDisposable
|
||||
DownloadClientConfig client = SeedingRulesTestDataFactory.AddDownloadClient(_dataContext);
|
||||
SeedingRuleRequest request = CreateValidRequest(action: SeedingRuleAction.Unknown);
|
||||
|
||||
await Should.ThrowAsync<ValidationException>(() => _controller.CreateSeedingRule(client.Id, request));
|
||||
await Should.ThrowAsync<CustomValidationException>(() => _controller.CreateSeedingRule(client.Id, request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -347,7 +347,7 @@ public class SeedingRulesControllerTests : IDisposable
|
||||
DownloadClientConfig client = SeedingRulesTestDataFactory.AddDownloadClient(_dataContext);
|
||||
SeedingRuleRequest request = CreateValidRequest(action: (SeedingRuleAction)5);
|
||||
|
||||
await Should.ThrowAsync<ValidationException>(() => _controller.CreateSeedingRule(client.Id, request));
|
||||
await Should.ThrowAsync<CustomValidationException>(() => _controller.CreateSeedingRule(client.Id, request));
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
@@ -444,7 +444,7 @@ public class SeedingRulesControllerTests : IDisposable
|
||||
// Both maxRatio and maxSeedTime negative → validation failure
|
||||
var request = CreateValidRequest(maxRatio: -1, maxSeedTime: -1);
|
||||
|
||||
await Should.ThrowAsync<ValidationException>(() => _controller.UpdateSeedingRule(rule.Id, request));
|
||||
await Should.ThrowAsync<CustomValidationException>(() => _controller.UpdateSeedingRule(rule.Id, request));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -474,7 +474,7 @@ public class SeedingRulesControllerTests : IDisposable
|
||||
|
||||
SeedingRuleRequest request = CreateValidRequest(action: SeedingRuleAction.Unknown);
|
||||
|
||||
await Should.ThrowAsync<ValidationException>(() => _controller.UpdateSeedingRule(rule.Id, request));
|
||||
await Should.ThrowAsync<CustomValidationException>(() => _controller.UpdateSeedingRule(rule.Id, request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -485,7 +485,7 @@ public class SeedingRulesControllerTests : IDisposable
|
||||
|
||||
SeedingRuleRequest request = CreateValidRequest(action: (SeedingRuleAction)5);
|
||||
|
||||
await Should.ThrowAsync<ValidationException>(() => _controller.UpdateSeedingRule(rule.Id, request));
|
||||
await Should.ThrowAsync<CustomValidationException>(() => _controller.UpdateSeedingRule(rule.Id, request));
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
+2
-2
@@ -164,7 +164,7 @@ public class UnlinkedConfigControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
await Should.ThrowAsync<ValidationException>(
|
||||
await Should.ThrowAsync<CustomValidationException>(
|
||||
() => _controller.UpdateUnlinkedConfig(client.Id, dto));
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ public class UnlinkedConfigControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
await Should.ThrowAsync<ValidationException>(
|
||||
await Should.ThrowAsync<CustomValidationException>(
|
||||
() => _controller.UpdateUnlinkedConfig(client.Id, dto));
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -139,7 +139,7 @@ public class DownloadClientControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
Cleanuparr.Domain.Exceptions.ValidationException exception = await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
Cleanuparr.Domain.Exceptions.CustomValidationException exception = await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.CreateDownloadClientConfig(request));
|
||||
exception.Message.ShouldBe("Use either an API key or a username and password, not both");
|
||||
}
|
||||
@@ -204,7 +204,7 @@ public class DownloadClientControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert — Validate throws, controller's generic catch logs and re-throws
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.CreateDownloadClientConfig(request));
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ public class DownloadClientControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.CreateDownloadClientConfig(request));
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ public class DownloadClientControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.CreateDownloadClientConfig(request));
|
||||
}
|
||||
|
||||
@@ -339,7 +339,7 @@ public class DownloadClientControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.UpdateDownloadClientConfig(client.Id, request));
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ public class DownloadClientControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.UpdateDownloadClientConfig(client.Id, request));
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ public class DownloadClientControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.UpdateDownloadClientConfig(client.Id, request));
|
||||
}
|
||||
|
||||
@@ -399,7 +399,7 @@ public class DownloadClientControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
Cleanuparr.Domain.Exceptions.ValidationException exception = await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
Cleanuparr.Domain.Exceptions.CustomValidationException exception = await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.UpdateDownloadClientConfig(client.Id, request));
|
||||
exception.Message.ShouldContain("cannot be changed");
|
||||
|
||||
|
||||
+1
-1
@@ -118,7 +118,7 @@ public class MalwareBlockerConfigControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.UpdateMalwareBlockerConfig(request));
|
||||
await _jobManagementService.DidNotReceive().StartJob(Arg.Any<JobType>(), Arg.Any<Cleanuparr.Infrastructure.Models.JobSchedule?>(), Arg.Any<string?>());
|
||||
}
|
||||
|
||||
+2
-2
@@ -106,7 +106,7 @@ public class QueueCleanerConfigControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act / Assert
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.UpdateQueueCleanerConfig(request));
|
||||
await _jobManagementService.DidNotReceive().StartJob(Arg.Any<JobType>(), Arg.Any<Cleanuparr.Infrastructure.Models.JobSchedule?>(), Arg.Any<string?>());
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public class QueueCleanerConfigControllerTests : IDisposable
|
||||
};
|
||||
|
||||
// Act + Assert
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.ValidationException>(
|
||||
await Should.ThrowAsync<Cleanuparr.Domain.Exceptions.CustomValidationException>(
|
||||
() => _controller.UpdateQueueCleanerConfig(request));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using Cleanuparr.Api.Features.Seeker.Contracts.Responses;
|
||||
using Cleanuparr.Api.Features.Seeker.Controllers;
|
||||
using Cleanuparr.Api.Tests.Features.Seeker.TestHelpers;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Infrastructure.Services.Interfaces;
|
||||
using Cleanuparr.Persistence;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Seeker;
|
||||
@@ -11,7 +12,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Api.Tests.Features.Seeker;
|
||||
|
||||
@@ -115,7 +115,7 @@ public class SeekerConfigControllerTests : IDisposable
|
||||
]
|
||||
};
|
||||
|
||||
await Should.ThrowAsync<ValidationException>(() => _controller.UpdateSeekerConfig(request));
|
||||
await Should.ThrowAsync<CustomValidationException>(() => _controller.UpdateSeekerConfig(request));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -3,12 +3,12 @@ using Cleanuparr.Api.Features.DownloadClient.Contracts.Requests;
|
||||
using Cleanuparr.Api.Features.Auth.Contracts.Requests;
|
||||
using Cleanuparr.Api.Features.General.Contracts.Requests;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Auth;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Arr;
|
||||
using Cleanuparr.Persistence.Models.Configuration;
|
||||
using Cleanuparr.Shared.Helpers;
|
||||
using Shouldly;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Api.Tests.Features.SensitiveData;
|
||||
|
||||
@@ -90,7 +90,7 @@ public class SensitiveDataInputTests
|
||||
Version = 4,
|
||||
};
|
||||
|
||||
Should.Throw<ValidationException>(() => request.ToEntity(Guid.NewGuid()));
|
||||
Should.Throw<CustomValidationException>(() => request.ToEntity(Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -122,7 +122,7 @@ public class SensitiveDataInputTests
|
||||
Version = 4,
|
||||
};
|
||||
|
||||
Should.Throw<ValidationException>(() => request.ToTestInstance());
|
||||
Should.Throw<CustomValidationException>(() => request.ToTestInstance());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -228,7 +228,7 @@ public class SensitiveDataInputTests
|
||||
Password = Placeholder,
|
||||
};
|
||||
|
||||
Should.Throw<ValidationException>(() => request.Validate());
|
||||
Should.Throw<CustomValidationException>(() => request.Validate());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -274,7 +274,7 @@ public class SensitiveDataInputTests
|
||||
};
|
||||
|
||||
request.Validate();
|
||||
Should.Throw<ValidationException>(() => request.ToTestConfig());
|
||||
Should.Throw<CustomValidationException>(() => request.ToTestConfig());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -47,7 +47,7 @@ public class GlobalExceptionHandlerTests
|
||||
[Fact]
|
||||
public async Task ValidationException_MapsTo400_WithMessageAsDetail()
|
||||
{
|
||||
(bool handled, HttpContext context, ProblemDetails problemDetails) = await Handle(new ValidationException("Name is required"));
|
||||
(bool handled, HttpContext context, ProblemDetails problemDetails) = await Handle(new CustomValidationException("Name is required"));
|
||||
|
||||
handled.ShouldBeTrue();
|
||||
context.Response.StatusCode.ShouldBe(StatusCodes.Status400BadRequest);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Arr;
|
||||
using Cleanuparr.Shared.Helpers;
|
||||
|
||||
@@ -29,7 +28,7 @@ public sealed record ArrInstanceRequest
|
||||
{
|
||||
if (ApiKey.IsPlaceholder())
|
||||
{
|
||||
throw new ValidationException("API key is required when creating a new instance");
|
||||
throw new CustomValidationException("API key is required when creating a new instance");
|
||||
}
|
||||
|
||||
return new()
|
||||
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Arr;
|
||||
using Cleanuparr.Shared.Helpers;
|
||||
|
||||
@@ -26,7 +25,7 @@ public sealed record TestArrInstanceRequest
|
||||
|
||||
if (apiKey.IsPlaceholder())
|
||||
{
|
||||
throw new ValidationException("API key cannot be a placeholder value");
|
||||
throw new CustomValidationException("API key cannot be a placeholder value");
|
||||
}
|
||||
|
||||
return new()
|
||||
|
||||
+2
-4
@@ -1,8 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
using Cleanuparr.Api.Features.DownloadCleaner.Contracts.Requests;
|
||||
using Cleanuparr.Api.Features.DownloadCleaner.Contracts.Responses;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Infrastructure.Services.Interfaces;
|
||||
using Cleanuparr.Infrastructure.Utilities;
|
||||
using Cleanuparr.Persistence;
|
||||
@@ -11,7 +10,6 @@ using Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Cleanuparr.Api.Features.DownloadCleaner.Controllers;
|
||||
|
||||
@@ -113,7 +111,7 @@ public sealed class DownloadCleanerConfigController : ControllerBase
|
||||
{
|
||||
if (newConfigDto is null)
|
||||
{
|
||||
throw new ValidationException("Request body cannot be null");
|
||||
throw new CustomValidationException("Request body cannot be null");
|
||||
}
|
||||
|
||||
// Validate cron expression format
|
||||
|
||||
+8
-8
@@ -35,42 +35,42 @@ public sealed record CreateDownloadClientRequest
|
||||
{
|
||||
if (TypeName is DownloadClientTypeName.Unknown || !Enum.IsDefined(TypeName))
|
||||
{
|
||||
throw new ValidationException("Unsupported download client type");
|
||||
throw new CustomValidationException("Unsupported download client type");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
throw new ValidationException("Client name cannot be empty");
|
||||
throw new CustomValidationException("Client name cannot be empty");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Host))
|
||||
{
|
||||
throw new ValidationException("Host cannot be empty");
|
||||
throw new CustomValidationException("Host cannot be empty");
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(Host, UriKind.RelativeOrAbsolute, out _))
|
||||
{
|
||||
throw new ValidationException("Host is not a valid URL");
|
||||
throw new CustomValidationException("Host is not a valid URL");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ExternalUrl) && !Uri.TryCreate(ExternalUrl, UriKind.RelativeOrAbsolute, out _))
|
||||
{
|
||||
throw new ValidationException("External URL is not a valid URL");
|
||||
throw new CustomValidationException("External URL is not a valid URL");
|
||||
}
|
||||
|
||||
if (Password.IsPlaceholder())
|
||||
{
|
||||
throw new ValidationException("Password cannot be a placeholder value");
|
||||
throw new CustomValidationException("Password cannot be a placeholder value");
|
||||
}
|
||||
|
||||
if (ApiKey.IsPlaceholder())
|
||||
{
|
||||
throw new ValidationException("API key cannot be a placeholder value");
|
||||
throw new CustomValidationException("API key cannot be a placeholder value");
|
||||
}
|
||||
|
||||
if (TypeName.RequiresAuthField(DownloadClientAuthField.ApiKey) && string.IsNullOrWhiteSpace(ApiKey))
|
||||
{
|
||||
throw new ValidationException("API key cannot be empty");
|
||||
throw new CustomValidationException("API key cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -27,28 +27,28 @@ public sealed record TestDownloadClientRequest
|
||||
{
|
||||
if (TypeName is DownloadClientTypeName.Unknown || !Enum.IsDefined(TypeName))
|
||||
{
|
||||
throw new ValidationException("Unsupported download client type");
|
||||
throw new CustomValidationException("Unsupported download client type");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Host))
|
||||
{
|
||||
throw new ValidationException("Host cannot be empty");
|
||||
throw new CustomValidationException("Host cannot be empty");
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(Host, UriKind.RelativeOrAbsolute, out _))
|
||||
{
|
||||
throw new ValidationException("Host is not a valid URL");
|
||||
throw new CustomValidationException("Host is not a valid URL");
|
||||
}
|
||||
|
||||
if (TypeName.RequiresAuthField(DownloadClientAuthField.ApiKey) && string.IsNullOrWhiteSpace(ApiKey) && !ApiKey.IsPlaceholder())
|
||||
{
|
||||
throw new ValidationException("API key cannot be empty");
|
||||
throw new CustomValidationException("API key cannot be empty");
|
||||
}
|
||||
|
||||
if (TypeName.SupportsApiKeyAndCredentials() && !string.IsNullOrWhiteSpace(ApiKey) &&
|
||||
(!string.IsNullOrWhiteSpace(Username) || !string.IsNullOrWhiteSpace(Password)))
|
||||
{
|
||||
throw new ValidationException("Use either an API key or a username and password, not both");
|
||||
throw new CustomValidationException("Use either an API key or a username and password, not both");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,12 +59,12 @@ public sealed record TestDownloadClientRequest
|
||||
|
||||
if (password.IsPlaceholder())
|
||||
{
|
||||
throw new ValidationException("Password cannot be a placeholder value");
|
||||
throw new CustomValidationException("Password cannot be a placeholder value");
|
||||
}
|
||||
|
||||
if (apiKey.IsPlaceholder())
|
||||
{
|
||||
throw new ValidationException("API key cannot be a placeholder value");
|
||||
throw new CustomValidationException("API key cannot be a placeholder value");
|
||||
}
|
||||
|
||||
return new()
|
||||
|
||||
+6
-6
@@ -35,32 +35,32 @@ public sealed record UpdateDownloadClientRequest
|
||||
{
|
||||
if (TypeName is DownloadClientTypeName.Unknown || !Enum.IsDefined(TypeName))
|
||||
{
|
||||
throw new ValidationException("Unsupported download client type");
|
||||
throw new CustomValidationException("Unsupported download client type");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
throw new ValidationException("Client name cannot be empty");
|
||||
throw new CustomValidationException("Client name cannot be empty");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Host))
|
||||
{
|
||||
throw new ValidationException("Host cannot be empty");
|
||||
throw new CustomValidationException("Host cannot be empty");
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(Host, UriKind.RelativeOrAbsolute, out _))
|
||||
{
|
||||
throw new ValidationException("Host is not a valid URL");
|
||||
throw new CustomValidationException("Host is not a valid URL");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(ExternalUrl) && !Uri.TryCreate(ExternalUrl, UriKind.RelativeOrAbsolute, out _))
|
||||
{
|
||||
throw new ValidationException("External URL is not a valid URL");
|
||||
throw new CustomValidationException("External URL is not a valid URL");
|
||||
}
|
||||
|
||||
if (TypeName.RequiresAuthField(DownloadClientAuthField.ApiKey) && string.IsNullOrWhiteSpace(ApiKey) && !ApiKey.IsPlaceholder())
|
||||
{
|
||||
throw new ValidationException("API key cannot be empty");
|
||||
throw new CustomValidationException("API key cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ public sealed class DownloadClientController : ControllerBase
|
||||
|
||||
if (updatedClient.TypeName != existingClient.TypeName)
|
||||
{
|
||||
throw new ValidationException("Download client type cannot be changed");
|
||||
throw new CustomValidationException("Download client type cannot be changed");
|
||||
}
|
||||
|
||||
var clientToPersist = updatedClient.ApplyTo(existingClient);
|
||||
|
||||
@@ -107,7 +107,7 @@ public sealed class SeekerConfigController : ControllerBase
|
||||
|
||||
if (request.ProactiveSearchEnabled && !request.Instances.Any(i => i.Enabled))
|
||||
{
|
||||
throw new Domain.Exceptions.ValidationException(
|
||||
throw new Domain.Exceptions.CustomValidationException(
|
||||
"At least one instance must be enabled when proactive search is enabled");
|
||||
}
|
||||
|
||||
|
||||
@@ -238,16 +238,16 @@ public class BackgroundJobManager : IHostedService
|
||||
|
||||
if (triggerValue > Constants.TriggerMaxLimit)
|
||||
{
|
||||
throw new ValidationException($"{cronExpression} should have a fire time of maximum {Constants.TriggerMaxLimit.TotalHours} hours");
|
||||
throw new CustomValidationException($"{cronExpression} should have a fire time of maximum {Constants.TriggerMaxLimit.TotalHours} hours");
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(SeekerJob) && triggerValue < Constants.SeekerMinLimit)
|
||||
{
|
||||
throw new ValidationException($"{cronExpression} should have a fire time of minimum {Constants.SeekerMinLimit.TotalMinutes} minutes");
|
||||
throw new CustomValidationException($"{cronExpression} should have a fire time of minimum {Constants.SeekerMinLimit.TotalMinutes} minutes");
|
||||
}
|
||||
else if (typeof(T) != typeof(MalwareBlocker) && triggerValue < Constants.TriggerMinLimit)
|
||||
{
|
||||
throw new ValidationException($"{cronExpression} should have a fire time of minimum {Constants.TriggerMinLimit.TotalSeconds} seconds");
|
||||
throw new CustomValidationException($"{cronExpression} should have a fire time of minimum {Constants.TriggerMinLimit.TotalSeconds} seconds");
|
||||
}
|
||||
|
||||
if (triggerValue > StaticConfiguration.TriggerValue)
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class GlobalExceptionHandler : IExceptionHandler
|
||||
{
|
||||
(int status, string title, string detail) = exception switch
|
||||
{
|
||||
ValidationException => (StatusCodes.Status400BadRequest, "Validation failed", exception.Message),
|
||||
CustomValidationException => (StatusCodes.Status400BadRequest, "Validation failed", exception.Message),
|
||||
NotificationTestException => (StatusCodes.Status400BadRequest, "Notification test failed", exception.Message),
|
||||
RateLimitException => (StatusCodes.Status429TooManyRequests, "Too many requests", exception.Message),
|
||||
_ => (StatusCodes.Status500InternalServerError, "An error occurred", "An unexpected error occurred"),
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Cleanuparr.Domain.Exceptions;
|
||||
|
||||
public sealed class CustomValidationException : Exception
|
||||
{
|
||||
public CustomValidationException()
|
||||
{
|
||||
}
|
||||
|
||||
public CustomValidationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public CustomValidationException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace Cleanuparr.Domain.Exceptions;
|
||||
|
||||
public sealed class ValidationException : Exception
|
||||
{
|
||||
public ValidationException()
|
||||
{
|
||||
}
|
||||
|
||||
public ValidationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ValidationException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Infrastructure.Models;
|
||||
using Cleanuparr.Infrastructure.Utilities;
|
||||
using Shouldly;
|
||||
@@ -134,7 +134,7 @@ public class CronExpressionConverterTests
|
||||
var schedule = new JobSchedule { Every = value, Type = unit };
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ValidationException>(() => CronExpressionConverter.ConvertToCronExpression(schedule));
|
||||
Should.Throw<CustomValidationException>(() => CronExpressionConverter.ConvertToCronExpression(schedule));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
||||
@@ -27,7 +27,7 @@ public class CronValidationHelperTests
|
||||
public void ValidateCronExpression_InvalidSyntax_ThrowsValidationException(string cronExpression)
|
||||
{
|
||||
// Act & Assert
|
||||
var exception = Should.Throw<ValidationException>(
|
||||
var exception = Should.Throw<CustomValidationException>(
|
||||
() => CronValidationHelper.ValidateCronExpression(cronExpression));
|
||||
exception.Message.ShouldContain("Invalid cron expression");
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public class CronValidationHelperTests
|
||||
public void ValidateCronExpression_NullOrEmpty_ThrowsValidationException(string? cronExpression)
|
||||
{
|
||||
// Act & Assert
|
||||
var exception = Should.Throw<ValidationException>(
|
||||
var exception = Should.Throw<CustomValidationException>(
|
||||
() => CronValidationHelper.ValidateCronExpression(cronExpression!));
|
||||
exception.Message.ShouldContain("cannot be null or empty");
|
||||
}
|
||||
@@ -52,7 +52,7 @@ public class CronValidationHelperTests
|
||||
public void ValidateCronExpression_TriggersTooFast_ThrowsValidationException(string cronExpression)
|
||||
{
|
||||
// Act & Assert - minimum is 30 seconds
|
||||
var exception = Should.Throw<ValidationException>(
|
||||
var exception = Should.Throw<CustomValidationException>(
|
||||
() => CronValidationHelper.ValidateCronExpression(cronExpression));
|
||||
exception.Message.ShouldContain("minimum");
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class CronValidationHelperTests
|
||||
public void ValidateCronExpression_TriggersTooSlow_ThrowsValidationException(string cronExpression)
|
||||
{
|
||||
// Act & Assert - maximum is 6 hours
|
||||
var exception = Should.Throw<ValidationException>(
|
||||
var exception = Should.Throw<CustomValidationException>(
|
||||
() => CronValidationHelper.ValidateCronExpression(cronExpression));
|
||||
exception.Message.ShouldContain("maximum");
|
||||
}
|
||||
@@ -104,7 +104,7 @@ public class CronValidationHelperTests
|
||||
const string cronExpression = "*/5 * * * * ?";
|
||||
|
||||
// Act & Assert - should fail because null uses default limits (30 second minimum)
|
||||
var exception = Should.Throw<ValidationException>(
|
||||
var exception = Should.Throw<CustomValidationException>(
|
||||
() => CronValidationHelper.ValidateCronExpression(cronExpression, null));
|
||||
exception.Message.ShouldContain("minimum");
|
||||
}
|
||||
@@ -119,7 +119,7 @@ public class CronValidationHelperTests
|
||||
const string cronExpression = "*/5 * * * * ?";
|
||||
|
||||
// Act & Assert
|
||||
var exception = Should.Throw<ValidationException>(
|
||||
var exception = Should.Throw<CustomValidationException>(
|
||||
() => CronValidationHelper.ValidateCronExpression(cronExpression, jobType));
|
||||
exception.Message.ShouldContain("minimum");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Infrastructure.Utilities;
|
||||
|
||||
namespace Cleanuparr.Infrastructure.Models;
|
||||
@@ -30,13 +30,13 @@ public class JobSchedule
|
||||
/// <summary>
|
||||
/// Validates the job schedule against the predefined valid values
|
||||
/// </summary>
|
||||
/// <exception cref="ValidationException">Thrown when the value is not valid for the selected unit</exception>
|
||||
/// <exception cref="CustomValidationException">Thrown when the value is not valid for the selected unit</exception>
|
||||
public void Validate()
|
||||
{
|
||||
if (!ScheduleOptions.IsValidValue(Type, Every))
|
||||
{
|
||||
var validValues = string.Join(", ", ScheduleOptions.GetValidValues(Type));
|
||||
throw new ValidationException($"Invalid value for {Type}: {Every}. Valid values are: {validValues}");
|
||||
throw new CustomValidationException($"Invalid value for {Type}: {Every}. Valid values are: {validValues}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Infrastructure.Models;
|
||||
using Quartz;
|
||||
|
||||
@@ -24,7 +24,7 @@ public static class CronExpressionConverter
|
||||
if (!ScheduleOptions.IsValidValue(schedule.Type, schedule.Every))
|
||||
{
|
||||
var validValues = string.Join(", ", ScheduleOptions.GetValidValues(schedule.Type));
|
||||
throw new ValidationException($"Invalid value for {schedule.Type}: {schedule.Every}. Valid values are: {validValues}");
|
||||
throw new CustomValidationException($"Invalid value for {schedule.Type}: {schedule.Every}. Valid values are: {validValues}");
|
||||
}
|
||||
|
||||
// Cron format: Seconds Minutes Hours Day-of-month Month Day-of-week Year
|
||||
|
||||
@@ -16,18 +16,18 @@ public static class CronValidationHelper
|
||||
/// Validates a cron expression against the application's trigger limits
|
||||
/// </summary>
|
||||
/// <param name="cronExpression">The cron expression to validate</param>
|
||||
/// <exception cref="ValidationException">Thrown when the cron expression is invalid or violates trigger limits</exception>
|
||||
/// <exception cref="CustomValidationException">Thrown when the cron expression is invalid or violates trigger limits</exception>
|
||||
public static void ValidateCronExpression(string cronExpression, JobType? jobType = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(cronExpression))
|
||||
{
|
||||
throw new ValidationException("Cron expression cannot be null or empty");
|
||||
throw new CustomValidationException("Cron expression cannot be null or empty");
|
||||
}
|
||||
|
||||
// First check if it's a valid cron expression
|
||||
if (!CronExpression.IsValidExpression(cronExpression))
|
||||
{
|
||||
throw new ValidationException($"Invalid cron expression: {cronExpression}");
|
||||
throw new CustomValidationException($"Invalid cron expression: {cronExpression}");
|
||||
}
|
||||
|
||||
// Validate the trigger timing limits
|
||||
@@ -43,34 +43,34 @@ public static class CronValidationHelper
|
||||
|
||||
if (nextFireTimes.Count < 2)
|
||||
{
|
||||
throw new ValidationException($"Could not compute fire times for cron expression: {cronExpression}");
|
||||
throw new CustomValidationException($"Could not compute fire times for cron expression: {cronExpression}");
|
||||
}
|
||||
|
||||
TimeSpan triggerValue = nextFireTimes[1] - nextFireTimes[0];
|
||||
|
||||
if (triggerValue > Constants.TriggerMaxLimit)
|
||||
{
|
||||
throw new ValidationException($"{cronExpression} should have a fire time of maximum {Constants.TriggerMaxLimit.TotalHours} hours");
|
||||
throw new CustomValidationException($"{cronExpression} should have a fire time of maximum {Constants.TriggerMaxLimit.TotalHours} hours");
|
||||
}
|
||||
|
||||
if (jobType is JobType.Seeker && triggerValue < Constants.SeekerMinLimit)
|
||||
{
|
||||
throw new ValidationException($"{cronExpression} should have a fire time of minimum {Constants.SeekerMinLimit.TotalMinutes} minutes");
|
||||
throw new CustomValidationException($"{cronExpression} should have a fire time of minimum {Constants.SeekerMinLimit.TotalMinutes} minutes");
|
||||
}
|
||||
|
||||
if (jobType is not JobType.MalwareBlocker && triggerValue < Constants.TriggerMinLimit)
|
||||
{
|
||||
throw new ValidationException($"{cronExpression} should have a fire time of minimum {Constants.TriggerMinLimit.TotalSeconds} seconds");
|
||||
throw new CustomValidationException($"{cronExpression} should have a fire time of minimum {Constants.TriggerMinLimit.TotalSeconds} seconds");
|
||||
}
|
||||
}
|
||||
catch (ValidationException)
|
||||
catch (CustomValidationException)
|
||||
{
|
||||
// Re-throw validation exceptions as-is
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ValidationException($"Error validating cron expression '{cronExpression}'", ex);
|
||||
throw new CustomValidationException($"Error validating cron expression '{cronExpression}'", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Arr;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.Arr;
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class ArrConfigTests
|
||||
FailedImportMaxStrikes = strikes,
|
||||
};
|
||||
|
||||
ValidationException exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
CustomValidationException exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Failed import max strikes must be -1 or greater");
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public sealed class ArrConfigTests
|
||||
FailedImportMaxStrikes = strikes,
|
||||
};
|
||||
|
||||
ValidationException exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
CustomValidationException exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("The minimum value for failed import max strikes must be 3");
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.BlacklistSync;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.BlacklistSync;
|
||||
|
||||
@@ -46,7 +46,7 @@ public sealed class BlacklistSyncConfigTests
|
||||
BlacklistPath = null
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Blacklist sync is enabled but the path is not configured");
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public sealed class BlacklistSyncConfigTests
|
||||
BlacklistPath = ""
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Blacklist sync is enabled but the path is not configured");
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed class BlacklistSyncConfigTests
|
||||
BlacklistPath = " "
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Blacklist sync is enabled but the path is not configured");
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public sealed class BlacklistSyncConfigTests
|
||||
BlacklistPath = "/non/existent/path/blacklist.txt"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Blacklist path must be a valid URL or an existing local file path");
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ public sealed class BlacklistSyncConfigTests
|
||||
BlacklistPath = "not-a-valid-url-or-path"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Blacklist path must be a valid URL or an existing local file path");
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public sealed class BlacklistSyncConfigTests
|
||||
BlacklistPath = url
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Blacklist path must be a valid URL or an existing local file path");
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.DownloadCleaner;
|
||||
|
||||
@@ -60,7 +60,7 @@ public sealed class DeadTorrentConfigTests
|
||||
MaxStrikes = 3
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Dead torrent target category is required");
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ public sealed class DeadTorrentConfigTests
|
||||
MaxStrikes = 3
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("No dead torrent categories configured");
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public sealed class DeadTorrentConfigTests
|
||||
MaxStrikes = 3
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("The dead torrent target category should not be present in dead torrent categories");
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public sealed class DeadTorrentConfigTests
|
||||
MaxStrikes = 3
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Empty dead torrent category filter found");
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ public sealed class DeadTorrentConfigTests
|
||||
MaxStrikes = strikes
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Dead torrent max strikes must be at least 3");
|
||||
}
|
||||
}
|
||||
+11
-11
@@ -1,9 +1,9 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration;
|
||||
using Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.DownloadCleaner;
|
||||
|
||||
@@ -64,7 +64,7 @@ public sealed class OrphanedFilesConfigTests
|
||||
{
|
||||
var config = new OrphanedFilesConfig { Enabled = true, ScanDirectories = [], OrphanedDirectory = "/downloads/orphaned" };
|
||||
|
||||
Should.Throw<ValidationException>(() => config.Validate())
|
||||
Should.Throw<CustomValidationException>(() => config.Validate())
|
||||
.Message.ShouldContain("scan directory");
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public sealed class OrphanedFilesConfigTests
|
||||
OrphanedDirectory = string.Empty,
|
||||
};
|
||||
|
||||
Should.Throw<ValidationException>(() => config.Validate())
|
||||
Should.Throw<CustomValidationException>(() => config.Validate())
|
||||
.Message.ShouldContain("Orphaned directory");
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public sealed class OrphanedFilesConfigTests
|
||||
OrphanedDirectory = "/downloads/orphaned-b",
|
||||
};
|
||||
|
||||
Should.Throw<ValidationException>(() => config.Validate([sibling]))
|
||||
Should.Throw<CustomValidationException>(() => config.Validate([sibling]))
|
||||
.Message.ShouldContain("overlap");
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public sealed class OrphanedFilesConfigTests
|
||||
OrphanedDirectory = "/downloads/orphaned-b",
|
||||
};
|
||||
|
||||
Should.Throw<ValidationException>(() => config.Validate([sibling]))
|
||||
Should.Throw<CustomValidationException>(() => config.Validate([sibling]))
|
||||
.Message.ShouldContain("overlap");
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ public sealed class OrphanedFilesConfigTests
|
||||
OrphanedDirectory = "/orphaned-b",
|
||||
};
|
||||
|
||||
Should.Throw<ValidationException>(() => config.Validate([sibling]))
|
||||
Should.Throw<CustomValidationException>(() => config.Validate([sibling]))
|
||||
.Message.ShouldContain("overlap");
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public sealed class OrphanedFilesConfigTests
|
||||
OrphanedDirectory = "/downloads/orphaned",
|
||||
};
|
||||
|
||||
Should.Throw<ValidationException>(() => config.Validate([sibling]))
|
||||
Should.Throw<CustomValidationException>(() => config.Validate([sibling]))
|
||||
.Message.ShouldContain("overlap");
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ public sealed class OrphanedFilesConfigTests
|
||||
OrphanedDirectory = "/orphaned-b",
|
||||
};
|
||||
|
||||
Should.Throw<ValidationException>(() => config.Validate([sibling]))
|
||||
Should.Throw<CustomValidationException>(() => config.Validate([sibling]))
|
||||
.Message.ShouldContain("overlap");
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ public sealed class OrphanedFilesConfigTests
|
||||
OrphanedDirectory = "/orphaned-b",
|
||||
};
|
||||
|
||||
Should.Throw<ValidationException>(() => config.Validate([sibling]));
|
||||
Should.Throw<CustomValidationException>(() => config.Validate([sibling]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -273,7 +273,7 @@ public sealed class OrphanedFilesConfigTests
|
||||
|
||||
var otherClient = MakeDownloadClient("Other", "/data/downloads/movies");
|
||||
|
||||
Should.Throw<ValidationException>(() => config.Validate([], [otherClient]))
|
||||
Should.Throw<CustomValidationException>(() => config.Validate([], [otherClient]))
|
||||
.Message.ShouldContain("overlap");
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ public sealed class OrphanedFilesConfigTests
|
||||
|
||||
var otherClient = MakeDownloadClient("Other", "/data/other-downloads/movies");
|
||||
|
||||
Should.Throw<ValidationException>(() => config.Validate([], [otherClient]))
|
||||
Should.Throw<CustomValidationException>(() => config.Validate([], [otherClient]))
|
||||
.Message.ShouldContain("overlap");
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -1,8 +1,8 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.DownloadCleaner;
|
||||
|
||||
@@ -141,7 +141,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("At least one category must be specified");
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Rule name can not be empty");
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Rule name can not be empty");
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Rule name can not be empty");
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Either max ratio or max seed time must be set to a non-negative value");
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Either max ratio or max seed time must be set to a non-negative value");
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Max ratio must be -1 (disabled) or a non-negative number");
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Max seed time must be -1 (disabled) or a non-negative number");
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Min seed time can not be negative");
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Min seed time can not be negative");
|
||||
}
|
||||
|
||||
@@ -386,7 +386,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Min seeders can not be less than 0");
|
||||
}
|
||||
|
||||
@@ -445,7 +445,7 @@ public sealed class QBitSeedingRuleTests
|
||||
DeleteSourceFiles = true
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Max inactive days must be -1 (disabled) or a non-negative number");
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.DownloadCleaner;
|
||||
|
||||
@@ -64,7 +64,7 @@ public sealed class UnlinkedConfigTests
|
||||
Categories = ["movies"]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Unlinked target category is required");
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public sealed class UnlinkedConfigTests
|
||||
Categories = []
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("No unlinked categories configured");
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public sealed class UnlinkedConfigTests
|
||||
Categories = ["movies", "cleanuparr-unlinked"]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("The unlinked target category should not be present in unlinked categories");
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public sealed class UnlinkedConfigTests
|
||||
Categories = ["movies", ""]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Empty unlinked category filter found");
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ public sealed class UnlinkedConfigTests
|
||||
IgnoredRootDirs = ["/non/existent/path/that/should/not/exist"]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldContain("root directory does not exist");
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -1,8 +1,8 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration;
|
||||
|
||||
@@ -53,7 +53,7 @@ public sealed class DownloadClientConfigTests
|
||||
Host = new Uri("http://localhost:8080")
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Client name cannot be empty");
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public sealed class DownloadClientConfigTests
|
||||
Host = new Uri("http://localhost:8080")
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Client name cannot be empty");
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public sealed class DownloadClientConfigTests
|
||||
Host = new Uri("http://localhost:8080")
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Client name cannot be empty");
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public sealed class DownloadClientConfigTests
|
||||
Host = null
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Host cannot be empty");
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public sealed class DownloadClientConfigTests
|
||||
DownloadDirectoryTarget = null
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Both download directory source and target must be set, or both must be empty");
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ public sealed class DownloadClientConfigTests
|
||||
DownloadDirectoryTarget = "/data/downloads"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Both download directory source and target must be set, or both must be empty");
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ public sealed class DownloadClientConfigTests
|
||||
ApiKey = null
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("API key cannot be empty");
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ public sealed class DownloadClientConfigTests
|
||||
Password = "pass"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Use either an API key or a username and password, not both");
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ public sealed class DownloadClientConfigTests
|
||||
Username = "admin"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Use either an API key or a username and password, not both");
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.General;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.General;
|
||||
|
||||
@@ -29,7 +29,7 @@ public sealed class GeneralConfigTests
|
||||
HttpTimeout = 0,
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("HTTP_TIMEOUT must be greater than 0");
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public sealed class GeneralConfigTests
|
||||
}
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Log rolling size cannot exceed 100 MB");
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.General;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.General;
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed class LoggingConfigTests
|
||||
RollingSizeMB = 101
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Log rolling size cannot exceed 100 MB");
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public sealed class LoggingConfigTests
|
||||
RetainedFileCount = 51
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Log retained file count cannot exceed 50");
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ public sealed class LoggingConfigTests
|
||||
TimeLimitHours = 1441
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Log time limit cannot exceed 60 days");
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ public sealed class LoggingConfigTests
|
||||
ArchiveRetainedCount = 101
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Log archive retained count cannot exceed 100");
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ public sealed class LoggingConfigTests
|
||||
ArchiveTimeLimitHours = 1441
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Log archive time limit cannot exceed 60 days");
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ public sealed class LoggingConfigTests
|
||||
ArchiveTimeLimitHours = 0
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Archiving is enabled, but no retention policy is set. Please set either a retained file count or time limit");
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Auth;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Auth;
|
||||
|
||||
@@ -65,7 +65,7 @@ public sealed class OidcConfigTests
|
||||
ClientId = "my-client"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("OIDC Issuer URL is required when OIDC is enabled");
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public sealed class OidcConfigTests
|
||||
ClientId = "my-client"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("OIDC Issuer URL must be a valid absolute URL");
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ public sealed class OidcConfigTests
|
||||
ClientId = "my-client"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("OIDC Issuer URL must use HTTPS");
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public sealed class OidcConfigTests
|
||||
ClientId = clientId
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("OIDC Client ID is required when OIDC is enabled");
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ public sealed class OidcConfigTests
|
||||
ProviderName = providerName
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("OIDC Provider Name is required when OIDC is enabled");
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ public sealed class OidcConfigTests
|
||||
AuthorizedSubject = "some-subject"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("OIDC must be enabled to use exclusive mode");
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ public sealed class OidcConfigTests
|
||||
ClientId = "my-client"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("OIDC Issuer URL must use HTTPS");
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -1,8 +1,8 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.MalwareBlocker;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.MalwareBlocker;
|
||||
|
||||
@@ -54,7 +54,7 @@ public sealed class ContentBlockerConfigTests
|
||||
Whisparr = new BlocklistSettings { Enabled = false }
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("At least one blocklist must be configured when Malware Blocker is enabled");
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public sealed class ContentBlockerConfigTests
|
||||
}
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Sonarr blocklist is enabled but path is not specified");
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ public sealed class ContentBlockerConfigTests
|
||||
}
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Radarr blocklist is enabled but path is not specified");
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ public sealed class ContentBlockerConfigTests
|
||||
}
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Lidarr blocklist is enabled but path is not specified");
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ public sealed class ContentBlockerConfigTests
|
||||
}
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldContain("Readarr blocklist does not exist");
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ public sealed class ContentBlockerConfigTests
|
||||
}
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldContain("Whisparr blocklist does not exist");
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ public sealed class ContentBlockerConfigTests
|
||||
}
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldContain("Sonarr blocklist does not exist");
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ public sealed class ContentBlockerConfigTests
|
||||
}
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Radarr blocklist is enabled but path is not specified");
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.Notification;
|
||||
|
||||
@@ -142,7 +142,7 @@ public sealed class AppriseConfigTests
|
||||
Key = "my-config-key"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Apprise server URL is required for API mode");
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ public sealed class AppriseConfigTests
|
||||
Key = "my-config-key"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Apprise server URL must be a valid HTTP or HTTPS URL");
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ public sealed class AppriseConfigTests
|
||||
Key = key ?? string.Empty
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Apprise configuration key is required for API mode");
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ public sealed class AppriseConfigTests
|
||||
Key = "a"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Apprise configuration key must be at least 2 characters long");
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ public sealed class AppriseConfigTests
|
||||
ServiceUrls = serviceUrls
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("At least one service URL is required for CLI mode");
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.Notification;
|
||||
|
||||
@@ -75,7 +75,7 @@ public sealed class DiscordConfigTests
|
||||
WebhookUrl = webhookUrl ?? string.Empty
|
||||
};
|
||||
|
||||
var ex = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var ex = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
ex.Message.ShouldContain("required");
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public sealed class DiscordConfigTests
|
||||
WebhookUrl = webhookUrl
|
||||
};
|
||||
|
||||
var ex = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var ex = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
ex.Message.ShouldContain("valid Discord webhook URL");
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ public sealed class DiscordConfigTests
|
||||
AvatarUrl = "not-a-valid-url"
|
||||
};
|
||||
|
||||
var ex = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var ex = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
ex.Message.ShouldContain("valid URL");
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.Notification;
|
||||
|
||||
@@ -80,7 +80,7 @@ public sealed class GotifyConfigTests
|
||||
ApplicationToken = "test-token"
|
||||
};
|
||||
|
||||
var ex = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var ex = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
ex.Message.ShouldContain("required");
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ public sealed class GotifyConfigTests
|
||||
ApplicationToken = "test-token"
|
||||
};
|
||||
|
||||
var ex = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var ex = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
ex.Message.ShouldContain("valid HTTP or HTTPS URL");
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ public sealed class GotifyConfigTests
|
||||
ApplicationToken = token ?? string.Empty
|
||||
};
|
||||
|
||||
var ex = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var ex = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
ex.Message.ShouldContain("required");
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public sealed class GotifyConfigTests
|
||||
Priority = priority
|
||||
};
|
||||
|
||||
var ex = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var ex = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
ex.Message.ShouldContain("Priority");
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.Notification;
|
||||
|
||||
@@ -91,7 +91,7 @@ public sealed class NotifiarrConfigTests
|
||||
ChannelId = "123456789012345678"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Notifiarr API key is required");
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ public sealed class NotifiarrConfigTests
|
||||
ChannelId = "123456789012345678"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Notifiarr API key must be at least 10 characters long");
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public sealed class NotifiarrConfigTests
|
||||
ChannelId = channelId ?? string.Empty
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Discord channel ID is required");
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ public sealed class NotifiarrConfigTests
|
||||
ChannelId = channelId
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Discord channel ID must be a valid numeric ID");
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.Notification;
|
||||
|
||||
@@ -248,7 +248,7 @@ public sealed class NtfyConfigTests
|
||||
AuthenticationType = NtfyAuthenticationType.None
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("ntfy server URL is required");
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ public sealed class NtfyConfigTests
|
||||
AuthenticationType = NtfyAuthenticationType.None
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("ntfy server URL must be a valid HTTP or HTTPS URL");
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ public sealed class NtfyConfigTests
|
||||
AuthenticationType = NtfyAuthenticationType.None
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("At least one ntfy topic is required");
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ public sealed class NtfyConfigTests
|
||||
AuthenticationType = NtfyAuthenticationType.None
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("At least one ntfy topic is required");
|
||||
}
|
||||
|
||||
@@ -328,7 +328,7 @@ public sealed class NtfyConfigTests
|
||||
Password = "pass"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Username is required for Basic Auth");
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ public sealed class NtfyConfigTests
|
||||
Password = password
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Password is required for Basic Auth");
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ public sealed class NtfyConfigTests
|
||||
AccessToken = accessToken
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Access token is required for Token authentication");
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -1,8 +1,8 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.Notification;
|
||||
|
||||
@@ -215,7 +215,7 @@ public sealed class PushoverConfigTests
|
||||
Priority = PushoverPriority.Normal
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Pushover API token is required");
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ public sealed class PushoverConfigTests
|
||||
Priority = PushoverPriority.Normal
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Pushover user key is required");
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ public sealed class PushoverConfigTests
|
||||
Expire = 3600
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Retry interval must be at least 30 seconds for emergency priority");
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ public sealed class PushoverConfigTests
|
||||
Expire = expire
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Expire time is required for emergency priority");
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ public sealed class PushoverConfigTests
|
||||
Expire = 10801
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Expire time cannot exceed 10800 seconds (3 hours)");
|
||||
}
|
||||
|
||||
@@ -382,7 +382,7 @@ public sealed class PushoverConfigTests
|
||||
Devices = [longDeviceName]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe($"Device name '{longDeviceName}' exceeds 25 character limit");
|
||||
}
|
||||
|
||||
@@ -416,7 +416,7 @@ public sealed class PushoverConfigTests
|
||||
Devices = [deviceName]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe($"Device name '{deviceName}' contains invalid characters. Only letters, numbers, underscores, and hyphens are allowed.");
|
||||
}
|
||||
|
||||
@@ -489,7 +489,7 @@ public sealed class PushoverConfigTests
|
||||
Sound = sound
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Sound name cannot be empty or whitespace when specified");
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.Notification;
|
||||
|
||||
@@ -151,7 +151,7 @@ public sealed class TelegramConfigTests
|
||||
ChatId = "123456789"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Telegram bot token is required");
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ public sealed class TelegramConfigTests
|
||||
ChatId = "123456789"
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Telegram bot token must be at least 10 characters long");
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ public sealed class TelegramConfigTests
|
||||
ChatId = chatId ?? string.Empty
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Telegram chat ID is required");
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ public sealed class TelegramConfigTests
|
||||
ChatId = chatId
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Telegram chat ID must be a valid integer (negative IDs allowed for groups)");
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ public sealed class TelegramConfigTests
|
||||
TopicId = topicId
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Telegram topic ID must be a valid integer when specified");
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -1,8 +1,8 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.QueueCleaner;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.QueueCleaner;
|
||||
|
||||
@@ -76,7 +76,7 @@ public sealed class FailedImportConfigTests
|
||||
Patterns = ["pattern"]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("The minimum value for failed imports max strikes must be 3");
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public sealed class FailedImportConfigTests
|
||||
Patterns = []
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("At least one pattern must be specified when using the Include pattern mode");
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ public sealed class FailedImportConfigTests
|
||||
Patterns = []
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("The minimum value for failed imports max strikes must be 3");
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ public sealed class FailedImportConfigTests
|
||||
ForceImportMaxTries = 0,
|
||||
};
|
||||
|
||||
ValidationException exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
CustomValidationException exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Force import max tries must be at least 1");
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ public sealed class FailedImportConfigTests
|
||||
ForceImportMaxTries = 5001,
|
||||
};
|
||||
|
||||
ValidationException exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
CustomValidationException exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Force import max tries must be at most 5000");
|
||||
}
|
||||
|
||||
@@ -305,7 +305,7 @@ public sealed class FailedImportConfigTests
|
||||
DeletePrivate = true,
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Cannot enable both deletion and category changing");
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -1,8 +1,8 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.QueueCleaner;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.QueueCleaner;
|
||||
|
||||
@@ -84,7 +84,7 @@ public sealed class QueueCleanerConfigTests
|
||||
DownloadingMetadataMaxStrikes = maxStrikes
|
||||
};
|
||||
|
||||
ValidationException exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
CustomValidationException exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("the minimum value for downloading metadata max strikes must be 3");
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ public sealed class QueueCleanerConfigTests
|
||||
Errored = new ErroredConfig { MaxStrikes = maxStrikes }
|
||||
};
|
||||
|
||||
ValidationException exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
CustomValidationException exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("the minimum value for errored max strikes must be 3");
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ public sealed class QueueCleanerConfigTests
|
||||
Errored = new ErroredConfig { MaxStrikes = 3, DeletePrivate = true, ChangeCategory = true }
|
||||
};
|
||||
|
||||
ValidationException exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
CustomValidationException exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Cannot enable both deletion and category changing");
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ public sealed class QueueCleanerConfigTests
|
||||
}
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("The minimum value for failed imports max strikes must be 3");
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ public sealed class QueueCleanerConfigTests
|
||||
]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Rule name cannot be empty");
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ public sealed class QueueCleanerConfigTests
|
||||
]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Duplicate stall rule names found");
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ public sealed class QueueCleanerConfigTests
|
||||
]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Rule name cannot be empty");
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ public sealed class QueueCleanerConfigTests
|
||||
]
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldBe("Duplicate slow rule names found");
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -1,8 +1,8 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.QueueCleaner;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.QueueCleaner;
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed class QueueRuleTests
|
||||
MaxCompletionPercentage = 100
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Rule name cannot be empty");
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed class QueueRuleTests
|
||||
MaxCompletionPercentage = 100
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Rule name cannot be empty");
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public sealed class QueueRuleTests
|
||||
MaxCompletionPercentage = 100
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Rule name cannot be empty");
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public sealed class QueueRuleTests
|
||||
MaxCompletionPercentage = 100
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Max strikes must be at least 3");
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ public sealed class QueueRuleTests
|
||||
MaxCompletionPercentage = 100
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Minimum completion percentage must be between 0 and 100");
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ public sealed class QueueRuleTests
|
||||
MaxCompletionPercentage = maxCompletionPercentage
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Maximum completion percentage must be between 1 and 100");
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ public sealed class QueueRuleTests
|
||||
MaxCompletionPercentage = 0
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Maximum completion percentage must be greater than 0");
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ public sealed class QueueRuleTests
|
||||
MaxCompletionPercentage = 25
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Maximum completion percentage must be greater than or equal to the minimum completion percentage");
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ public sealed class QueueRuleTests
|
||||
DeletePrivateTorrentsFromClient = true,
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Cannot enable both deletion and category changing");
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.QueueCleaner;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.QueueCleaner;
|
||||
|
||||
@@ -89,7 +89,7 @@ public sealed class SlowRuleTests
|
||||
};
|
||||
|
||||
// Base class validation runs first
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Max strikes must be at least 3");
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public sealed class SlowRuleTests
|
||||
MaxTimeHours = -1
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Maximum time cannot be negative");
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ public sealed class SlowRuleTests
|
||||
MaxTimeHours = 0
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Either minimum speed or maximum time must be specified");
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ public sealed class SlowRuleTests
|
||||
MaxTimeHours = 0
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Either minimum speed or maximum time must be specified");
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public sealed class SlowRuleTests
|
||||
MaxTimeHours = 0
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Invalid minimum speed format");
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ public sealed class SlowRuleTests
|
||||
IgnoreAboveSize = ignoreAboveSize
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldContain("invalid value for slow ignore above size");
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.QueueCleaner;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.QueueCleaner;
|
||||
|
||||
@@ -108,7 +108,7 @@ public sealed class StallRuleTests
|
||||
};
|
||||
|
||||
// Base class validation runs first
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldBe("Max strikes must be at least 3");
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ public sealed class StallRuleTests
|
||||
MinimumProgress = minimumProgress
|
||||
};
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => rule.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => rule.Validate());
|
||||
exception.Message.ShouldContain("Invalid minimum progress value");
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration.Seeker;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Tests.Models.Configuration.Seeker;
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed class SeekerConfigTests
|
||||
{
|
||||
var config = new SeekerConfig { SearchInterval = 1 };
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldContain("at least");
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed class SeekerConfigTests
|
||||
{
|
||||
var config = new SeekerConfig { SearchInterval = 361 };
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldContain("at most");
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public sealed class SeekerConfigTests
|
||||
{
|
||||
var config = new SeekerConfig { SearchInterval = interval };
|
||||
|
||||
var exception = Should.Throw<ValidationException>(() => config.Validate());
|
||||
var exception = Should.Throw<CustomValidationException>(() => config.Validate());
|
||||
exception.Message.ShouldContain("Invalid search interval");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Shared.Attributes;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Auth;
|
||||
|
||||
@@ -66,7 +66,7 @@ public sealed record OidcConfig
|
||||
{
|
||||
if (ExclusiveMode && !Enabled)
|
||||
{
|
||||
throw new ValidationException("OIDC must be enabled to use exclusive mode");
|
||||
throw new CustomValidationException("OIDC must be enabled to use exclusive mode");
|
||||
}
|
||||
|
||||
if (!Enabled)
|
||||
@@ -76,40 +76,40 @@ public sealed record OidcConfig
|
||||
|
||||
if (string.IsNullOrWhiteSpace(IssuerUrl))
|
||||
{
|
||||
throw new ValidationException("OIDC Issuer URL is required when OIDC is enabled");
|
||||
throw new CustomValidationException("OIDC Issuer URL is required when OIDC is enabled");
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(IssuerUrl, UriKind.Absolute, out var issuerUri))
|
||||
{
|
||||
throw new ValidationException("OIDC Issuer URL must be a valid absolute URL");
|
||||
throw new CustomValidationException("OIDC Issuer URL must be a valid absolute URL");
|
||||
}
|
||||
|
||||
// Enforce HTTPS except for localhost (development)
|
||||
if (issuerUri.Scheme != "https" && !IsLocalhost(issuerUri))
|
||||
{
|
||||
throw new ValidationException("OIDC Issuer URL must use HTTPS");
|
||||
throw new CustomValidationException("OIDC Issuer URL must use HTTPS");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ClientId))
|
||||
{
|
||||
throw new ValidationException("OIDC Client ID is required when OIDC is enabled");
|
||||
throw new CustomValidationException("OIDC Client ID is required when OIDC is enabled");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ProviderName))
|
||||
{
|
||||
throw new ValidationException("OIDC Provider Name is required when OIDC is enabled");
|
||||
throw new CustomValidationException("OIDC Provider Name is required when OIDC is enabled");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(RedirectUrl))
|
||||
{
|
||||
if (!Uri.TryCreate(RedirectUrl, UriKind.Absolute, out var redirectUri))
|
||||
{
|
||||
throw new ValidationException("OIDC Redirect URL must be a valid absolute URL");
|
||||
throw new CustomValidationException("OIDC Redirect URL must be a valid absolute URL");
|
||||
}
|
||||
|
||||
if (redirectUri.Scheme is not ("http" or "https"))
|
||||
{
|
||||
throw new ValidationException("OIDC Redirect URL must use HTTP or HTTPS");
|
||||
throw new CustomValidationException("OIDC Redirect URL must use HTTP or HTTPS");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.Arr;
|
||||
|
||||
@@ -21,12 +21,12 @@ public class ArrConfig : IConfig
|
||||
{
|
||||
if (FailedImportMaxStrikes < -1)
|
||||
{
|
||||
throw new ValidationException("Failed import max strikes must be -1 or greater");
|
||||
throw new CustomValidationException("Failed import max strikes must be -1 or greater");
|
||||
}
|
||||
|
||||
if (FailedImportMaxStrikes is > 0 and < 3)
|
||||
{
|
||||
throw new ValidationException("The minimum value for failed import max strikes must be 3");
|
||||
throw new CustomValidationException("The minimum value for failed import max strikes must be 3");
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.BlacklistSync;
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed record BlacklistSyncConfig : IJobConfig
|
||||
|
||||
if (string.IsNullOrWhiteSpace(BlacklistPath))
|
||||
{
|
||||
throw new ValidationException("Blacklist sync is enabled but the path is not configured");
|
||||
throw new CustomValidationException("Blacklist sync is enabled but the path is not configured");
|
||||
}
|
||||
|
||||
bool isValidPath = Uri.TryCreate(BlacklistPath, UriKind.Absolute, out var uri) &&
|
||||
@@ -37,7 +37,7 @@ public sealed record BlacklistSyncConfig : IJobConfig
|
||||
|
||||
if (!isValidPath)
|
||||
{
|
||||
throw new ValidationException("Blacklist path must be a valid URL or an existing local file path");
|
||||
throw new CustomValidationException("Blacklist path must be a valid URL or an existing local file path");
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
|
||||
@@ -45,27 +45,27 @@ public sealed record DeadTorrentConfig : IConfig
|
||||
|
||||
if (string.IsNullOrWhiteSpace(TargetCategory))
|
||||
{
|
||||
throw new ValidationException("Dead torrent target category is required");
|
||||
throw new CustomValidationException("Dead torrent target category is required");
|
||||
}
|
||||
|
||||
if (Categories.Count is 0)
|
||||
{
|
||||
throw new ValidationException("No dead torrent categories configured");
|
||||
throw new CustomValidationException("No dead torrent categories configured");
|
||||
}
|
||||
|
||||
if (Categories.Contains(TargetCategory, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ValidationException("The dead torrent target category should not be present in dead torrent categories");
|
||||
throw new CustomValidationException("The dead torrent target category should not be present in dead torrent categories");
|
||||
}
|
||||
|
||||
if (Categories.Any(string.IsNullOrWhiteSpace))
|
||||
{
|
||||
throw new ValidationException("Empty dead torrent category filter found");
|
||||
throw new CustomValidationException("Empty dead torrent category filter found");
|
||||
}
|
||||
|
||||
if (MaxStrikes < 3)
|
||||
{
|
||||
throw new ValidationException("Dead torrent max strikes must be at least 3");
|
||||
throw new CustomValidationException("Dead torrent max strikes must be at least 3");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
|
||||
@@ -13,7 +13,7 @@ public interface IInactivityFilterable
|
||||
{
|
||||
if (MaxInactiveDays is < 0 and not -1)
|
||||
{
|
||||
throw new ValidationException("Max inactive days must be -1 (disabled) or a non-negative number");
|
||||
throw new CustomValidationException("Max inactive days must be -1 (disabled) or a non-negative number");
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
|
||||
@@ -13,7 +13,7 @@ public interface ISeedersFilterable
|
||||
{
|
||||
if (MinSeeders < 0)
|
||||
{
|
||||
throw new ValidationException("Min seeders can not be less than 0");
|
||||
throw new CustomValidationException("Min seeders can not be less than 0");
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -1,5 +1,5 @@
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
|
||||
@@ -51,37 +51,37 @@ public interface ISeedingRule : IConfig
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name.Trim()))
|
||||
{
|
||||
throw new ValidationException("Rule name can not be empty");
|
||||
throw new CustomValidationException("Rule name can not be empty");
|
||||
}
|
||||
|
||||
if (Categories.Count == 0)
|
||||
{
|
||||
throw new ValidationException("At least one category must be specified");
|
||||
throw new CustomValidationException("At least one category must be specified");
|
||||
}
|
||||
|
||||
if (MaxRatio < 0 && MaxSeedTime < 0)
|
||||
{
|
||||
throw new ValidationException("Either max ratio or max seed time must be set to a non-negative value");
|
||||
throw new CustomValidationException("Either max ratio or max seed time must be set to a non-negative value");
|
||||
}
|
||||
|
||||
if (MaxRatio is < 0 and not -1)
|
||||
{
|
||||
throw new ValidationException("Max ratio must be -1 (disabled) or a non-negative number");
|
||||
throw new CustomValidationException("Max ratio must be -1 (disabled) or a non-negative number");
|
||||
}
|
||||
|
||||
if (MaxSeedTime is < 0 and not -1)
|
||||
{
|
||||
throw new ValidationException("Max seed time must be -1 (disabled) or a non-negative number");
|
||||
throw new CustomValidationException("Max seed time must be -1 (disabled) or a non-negative number");
|
||||
}
|
||||
|
||||
if (MinSeedTime < 0)
|
||||
{
|
||||
throw new ValidationException("Min seed time can not be negative");
|
||||
throw new CustomValidationException("Min seed time can not be negative");
|
||||
}
|
||||
|
||||
if (Action is not SeedingRuleAction.Delete and not SeedingRuleAction.Stop)
|
||||
{
|
||||
throw new ValidationException("Unknown seeding rule action");
|
||||
throw new CustomValidationException("Unknown seeding rule action");
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
|
||||
@@ -87,12 +87,12 @@ public sealed record OrphanedFilesConfig : IConfig
|
||||
|
||||
if (ScanDirectories.Count == 0)
|
||||
{
|
||||
throw new ValidationException("At least one scan directory is required when orphaned files cleanup is enabled for this client");
|
||||
throw new CustomValidationException("At least one scan directory is required when orphaned files cleanup is enabled for this client");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(OrphanedDirectory))
|
||||
{
|
||||
throw new ValidationException("Orphaned directory is required when orphaned files cleanup is enabled for this client");
|
||||
throw new CustomValidationException("Orphaned directory is required when orphaned files cleanup is enabled for this client");
|
||||
}
|
||||
|
||||
foreach (var scanDir in ScanDirectories)
|
||||
@@ -130,13 +130,13 @@ public sealed record OrphanedFilesConfig : IConfig
|
||||
|
||||
if (string.Equals(normalizedScan, normalizedOrphaned, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ValidationException(
|
||||
throw new CustomValidationException(
|
||||
$"Orphaned directory '{normalizedOrphaned}' cannot equal scan directory '{normalizedScan}'.");
|
||||
}
|
||||
|
||||
if (normalizedScan.StartsWith(normalizedOrphaned + sep, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ValidationException(
|
||||
throw new CustomValidationException(
|
||||
$"Orphaned directory '{normalizedOrphaned}' cannot be an ancestor of scan directory '{normalizedScan}'.");
|
||||
}
|
||||
}
|
||||
@@ -171,7 +171,7 @@ public sealed record OrphanedFilesConfig : IConfig
|
||||
|| a.StartsWith(b + sep, StringComparison.OrdinalIgnoreCase)
|
||||
|| b.StartsWith(a + sep, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ValidationException(
|
||||
throw new CustomValidationException(
|
||||
$"Path overlap detected: {aLabel} '{a}' overlaps with {bLabel} '{b}'. Scan directories and orphaned directories must not overlap across clients.");
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.DownloadCleaner;
|
||||
|
||||
@@ -33,29 +33,29 @@ public sealed record UnlinkedConfig : IConfig
|
||||
|
||||
if (string.IsNullOrWhiteSpace(TargetCategory))
|
||||
{
|
||||
throw new ValidationException("Unlinked target category is required");
|
||||
throw new CustomValidationException("Unlinked target category is required");
|
||||
}
|
||||
|
||||
if (Categories.Count is 0)
|
||||
{
|
||||
throw new ValidationException("No unlinked categories configured");
|
||||
throw new CustomValidationException("No unlinked categories configured");
|
||||
}
|
||||
|
||||
if (Categories.Contains(TargetCategory, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ValidationException("The unlinked target category should not be present in unlinked categories");
|
||||
throw new CustomValidationException("The unlinked target category should not be present in unlinked categories");
|
||||
}
|
||||
|
||||
if (Categories.Any(string.IsNullOrWhiteSpace))
|
||||
{
|
||||
throw new ValidationException("Empty unlinked category filter found");
|
||||
throw new CustomValidationException("Empty unlinked category filter found");
|
||||
}
|
||||
|
||||
foreach (var dir in IgnoredRootDirs.Where(d => !string.IsNullOrEmpty(d)))
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
throw new ValidationException($"{dir} root directory does not exist or is not accessible (check permissions)");
|
||||
throw new CustomValidationException($"{dir} root directory does not exist or is not accessible (check permissions)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,28 +103,28 @@ public sealed record DownloadClientConfig
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
throw new ValidationException($"Client name cannot be empty");
|
||||
throw new CustomValidationException($"Client name cannot be empty");
|
||||
}
|
||||
|
||||
if (Host is null)
|
||||
{
|
||||
throw new ValidationException($"Host cannot be empty");
|
||||
throw new CustomValidationException($"Host cannot be empty");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(DownloadDirectorySource) != !string.IsNullOrWhiteSpace(DownloadDirectoryTarget))
|
||||
{
|
||||
throw new ValidationException("Both download directory source and target must be set, or both must be empty");
|
||||
throw new CustomValidationException("Both download directory source and target must be set, or both must be empty");
|
||||
}
|
||||
|
||||
if (TypeName.RequiresAuthField(DownloadClientAuthField.ApiKey) && string.IsNullOrWhiteSpace(ApiKey))
|
||||
{
|
||||
throw new ValidationException("API key cannot be empty");
|
||||
throw new CustomValidationException("API key cannot be empty");
|
||||
}
|
||||
|
||||
if (TypeName.SupportsApiKeyAndCredentials() && !string.IsNullOrWhiteSpace(ApiKey) &&
|
||||
(!string.IsNullOrWhiteSpace(Username) || !string.IsNullOrWhiteSpace(Password)))
|
||||
{
|
||||
throw new ValidationException("Use either an API key or a username and password, not both");
|
||||
throw new CustomValidationException("Use either an API key or a username and password, not both");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public sealed record AuthConfig : IConfig
|
||||
{
|
||||
if (!IsValidIpOrCidr(entry))
|
||||
{
|
||||
throw new ValidationException($"Invalid IP address or CIDR range: {entry}");
|
||||
throw new CustomValidationException($"Invalid IP address or CIDR range: {entry}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Shared.Helpers;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.General;
|
||||
|
||||
@@ -51,32 +51,32 @@ public sealed record GeneralConfig : IConfig
|
||||
{
|
||||
if (HttpTimeout is 0)
|
||||
{
|
||||
throw new ValidationException("HTTP_TIMEOUT must be greater than 0");
|
||||
throw new CustomValidationException("HTTP_TIMEOUT must be greater than 0");
|
||||
}
|
||||
|
||||
if (StrikeInactivityWindowHours is 0)
|
||||
{
|
||||
throw new ValidationException("STRIKE_INACTIVITY_WINDOW_HOURS must be greater than 0");
|
||||
throw new CustomValidationException("STRIKE_INACTIVITY_WINDOW_HOURS must be greater than 0");
|
||||
}
|
||||
|
||||
if (StrikeInactivityWindowHours > 168)
|
||||
{
|
||||
throw new ValidationException("STRIKE_INACTIVITY_WINDOW_HOURS must be less than or equal to 168");
|
||||
throw new CustomValidationException("STRIKE_INACTIVITY_WINDOW_HOURS must be less than or equal to 168");
|
||||
}
|
||||
|
||||
if (HistoryRetentionDays is 0)
|
||||
{
|
||||
throw new ValidationException("HISTORY_RETENTION_DAYS must be greater than 0");
|
||||
throw new CustomValidationException("HISTORY_RETENTION_DAYS must be greater than 0");
|
||||
}
|
||||
|
||||
if (HistoryRetentionDays > 3650)
|
||||
{
|
||||
throw new ValidationException("HISTORY_RETENTION_DAYS must be less than or equal to 3650");
|
||||
throw new CustomValidationException("HISTORY_RETENTION_DAYS must be less than or equal to 3650");
|
||||
}
|
||||
|
||||
if (ConnectivityCheckEnabled && ConnectivityCheckUrls.Count is 0)
|
||||
{
|
||||
throw new ValidationException("At least one connectivity check URL is required when the internet connectivity check is enabled");
|
||||
throw new CustomValidationException("At least one connectivity check URL is required when the internet connectivity check is enabled");
|
||||
}
|
||||
|
||||
Log.Validate();
|
||||
|
||||
@@ -27,32 +27,32 @@ public sealed record LoggingConfig : IConfig
|
||||
{
|
||||
if (RollingSizeMB > 100)
|
||||
{
|
||||
throw new ValidationException("Log rolling size cannot exceed 100 MB");
|
||||
throw new CustomValidationException("Log rolling size cannot exceed 100 MB");
|
||||
}
|
||||
|
||||
if (RetainedFileCount > 50)
|
||||
{
|
||||
throw new ValidationException("Log retained file count cannot exceed 50");
|
||||
throw new CustomValidationException("Log retained file count cannot exceed 50");
|
||||
}
|
||||
|
||||
if (TimeLimitHours > 1440) // 24 * 60
|
||||
{
|
||||
throw new ValidationException("Log time limit cannot exceed 60 days");
|
||||
throw new CustomValidationException("Log time limit cannot exceed 60 days");
|
||||
}
|
||||
|
||||
if (ArchiveRetainedCount > 100)
|
||||
{
|
||||
throw new ValidationException("Log archive retained count cannot exceed 100");
|
||||
throw new CustomValidationException("Log archive retained count cannot exceed 100");
|
||||
}
|
||||
|
||||
if (ArchiveTimeLimitHours > 1440) // 24 * 60
|
||||
{
|
||||
throw new ValidationException("Log archive time limit cannot exceed 60 days");
|
||||
throw new CustomValidationException("Log archive time limit cannot exceed 60 days");
|
||||
}
|
||||
|
||||
if (ArchiveRetainedCount is 0 && ArchiveTimeLimitHours is 0 && ArchiveEnabled)
|
||||
{
|
||||
throw new ValidationException("Archiving is enabled, but no retention policy is set. Please set either a retained file count or time limit");
|
||||
throw new CustomValidationException("Archiving is enabled, but no retention policy is set. Please set either a retained file count or time limit");
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.MalwareBlocker;
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed record ContentBlockerConfig : IJobConfig
|
||||
|
||||
if (!Sonarr.Enabled && !Radarr.Enabled && !Lidarr.Enabled && !Readarr.Enabled && !Whisparr.Enabled && !Sportarr.Enabled && !LazyLibrarian.Enabled)
|
||||
{
|
||||
throw new ValidationException("At least one blocklist must be configured when Malware Blocker is enabled");
|
||||
throw new CustomValidationException("At least one blocklist must be configured when Malware Blocker is enabled");
|
||||
}
|
||||
|
||||
ValidateBlocklistSettings(Sonarr, "Sonarr");
|
||||
@@ -70,7 +70,7 @@ public sealed record ContentBlockerConfig : IJobConfig
|
||||
|
||||
if (string.IsNullOrWhiteSpace(settings.BlocklistPath))
|
||||
{
|
||||
throw new ValidationException($"{context} blocklist is enabled but path is not specified");
|
||||
throw new CustomValidationException($"{context} blocklist is enabled but path is not specified");
|
||||
}
|
||||
|
||||
if (Uri.TryCreate(settings.BlocklistPath, UriKind.Absolute, out var uri) &&
|
||||
@@ -81,7 +81,7 @@ public sealed record ContentBlockerConfig : IJobConfig
|
||||
|
||||
if (!File.Exists(settings.BlocklistPath))
|
||||
{
|
||||
throw new ValidationException($"{context} blocklist does not exist: {settings.BlocklistPath}");
|
||||
throw new CustomValidationException($"{context} blocklist does not exist: {settings.BlocklistPath}");
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -2,9 +2,9 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration;
|
||||
using Cleanuparr.Shared.Attributes;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
|
||||
@@ -86,22 +86,22 @@ public sealed record AppriseConfig : IConfig
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Url))
|
||||
{
|
||||
throw new ValidationException("Apprise server URL is required for API mode");
|
||||
throw new CustomValidationException("Apprise server URL is required for API mode");
|
||||
}
|
||||
|
||||
if (Uri is null)
|
||||
{
|
||||
throw new ValidationException("Apprise server URL must be a valid HTTP or HTTPS URL");
|
||||
throw new CustomValidationException("Apprise server URL must be a valid HTTP or HTTPS URL");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(Key))
|
||||
{
|
||||
throw new ValidationException("Apprise configuration key is required for API mode");
|
||||
throw new CustomValidationException("Apprise configuration key is required for API mode");
|
||||
}
|
||||
|
||||
if (Key.Length < 2)
|
||||
{
|
||||
throw new ValidationException("Apprise configuration key must be at least 2 characters long");
|
||||
throw new CustomValidationException("Apprise configuration key must be at least 2 characters long");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public sealed record AppriseConfig : IConfig
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ServiceUrls))
|
||||
{
|
||||
throw new ValidationException("At least one service URL is required for CLI mode");
|
||||
throw new CustomValidationException("At least one service URL is required for CLI mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration;
|
||||
using Cleanuparr.Shared.Attributes;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
|
||||
@@ -38,19 +38,19 @@ public sealed record DiscordConfig : IConfig
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(WebhookUrl))
|
||||
{
|
||||
throw new ValidationException("Discord webhook URL is required");
|
||||
throw new CustomValidationException("Discord webhook URL is required");
|
||||
}
|
||||
|
||||
if (!WebhookUrl.StartsWith("https://discord.com/api/webhooks/", StringComparison.OrdinalIgnoreCase) &&
|
||||
!WebhookUrl.StartsWith("https://discordapp.com/api/webhooks/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ValidationException("Discord webhook URL must be a valid Discord webhook URL");
|
||||
throw new CustomValidationException("Discord webhook URL must be a valid Discord webhook URL");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(AvatarUrl) &&
|
||||
!Uri.TryCreate(AvatarUrl, UriKind.Absolute, out var uri))
|
||||
{
|
||||
throw new ValidationException("Avatar URL must be a valid URL");
|
||||
throw new CustomValidationException("Avatar URL must be a valid URL");
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Shared.Attributes;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
|
||||
@@ -37,23 +37,23 @@ public sealed record GotifyConfig : IConfig
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ServerUrl))
|
||||
{
|
||||
throw new ValidationException("Gotify server URL is required");
|
||||
throw new CustomValidationException("Gotify server URL is required");
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(ServerUrl, UriKind.Absolute, out var uri) ||
|
||||
(uri.Scheme != "http" && uri.Scheme != "https"))
|
||||
{
|
||||
throw new ValidationException("Gotify server URL must be a valid HTTP or HTTPS URL");
|
||||
throw new CustomValidationException("Gotify server URL must be a valid HTTP or HTTPS URL");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ApplicationToken))
|
||||
{
|
||||
throw new ValidationException("Gotify application token is required");
|
||||
throw new CustomValidationException("Gotify application token is required");
|
||||
}
|
||||
|
||||
if (Priority < 0 || Priority > 10)
|
||||
{
|
||||
throw new ValidationException("Priority must be between 0 and 10");
|
||||
throw new CustomValidationException("Priority must be between 0 and 10");
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration;
|
||||
using Cleanuparr.Shared.Attributes;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
|
||||
@@ -37,22 +37,22 @@ public sealed record NotifiarrConfig : IConfig
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ApiKey))
|
||||
{
|
||||
throw new ValidationException("Notifiarr API key is required");
|
||||
throw new CustomValidationException("Notifiarr API key is required");
|
||||
}
|
||||
|
||||
if (ApiKey.Length < 10)
|
||||
{
|
||||
throw new ValidationException("Notifiarr API key must be at least 10 characters long");
|
||||
throw new CustomValidationException("Notifiarr API key must be at least 10 characters long");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ChannelId))
|
||||
{
|
||||
throw new ValidationException("Discord channel ID is required");
|
||||
throw new CustomValidationException("Discord channel ID is required");
|
||||
}
|
||||
|
||||
if (!ulong.TryParse(ChannelId, out _))
|
||||
{
|
||||
throw new ValidationException("Discord channel ID must be a valid numeric ID");
|
||||
throw new CustomValidationException("Discord channel ID must be a valid numeric ID");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration;
|
||||
using Cleanuparr.Shared.Attributes;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
|
||||
@@ -71,17 +71,17 @@ public sealed record NtfyConfig : IConfig
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ServerUrl))
|
||||
{
|
||||
throw new ValidationException("ntfy server URL is required");
|
||||
throw new CustomValidationException("ntfy server URL is required");
|
||||
}
|
||||
|
||||
if (Uri == null)
|
||||
{
|
||||
throw new ValidationException("ntfy server URL must be a valid HTTP or HTTPS URL");
|
||||
throw new CustomValidationException("ntfy server URL must be a valid HTTP or HTTPS URL");
|
||||
}
|
||||
|
||||
if (!Topics.Any(t => !string.IsNullOrWhiteSpace(t)))
|
||||
{
|
||||
throw new ValidationException("At least one ntfy topic is required");
|
||||
throw new CustomValidationException("At least one ntfy topic is required");
|
||||
}
|
||||
|
||||
ValidateAuthentication();
|
||||
@@ -105,18 +105,18 @@ public sealed record NtfyConfig : IConfig
|
||||
case NtfyAuthenticationType.BasicAuth:
|
||||
if (string.IsNullOrWhiteSpace(Username))
|
||||
{
|
||||
throw new ValidationException("Username is required for Basic Auth");
|
||||
throw new CustomValidationException("Username is required for Basic Auth");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Password))
|
||||
{
|
||||
throw new ValidationException("Password is required for Basic Auth");
|
||||
throw new CustomValidationException("Password is required for Basic Auth");
|
||||
}
|
||||
break;
|
||||
|
||||
case NtfyAuthenticationType.AccessToken:
|
||||
if (string.IsNullOrWhiteSpace(AccessToken))
|
||||
{
|
||||
throw new ValidationException("Access token is required for Token authentication");
|
||||
throw new CustomValidationException("Access token is required for Token authentication");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+9
-9
@@ -2,8 +2,8 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.RegularExpressions;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Shared.Attributes;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
|
||||
@@ -102,29 +102,29 @@ public sealed partial record PushoverConfig : IConfig
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ApiToken))
|
||||
{
|
||||
throw new ValidationException("Pushover API token is required");
|
||||
throw new CustomValidationException("Pushover API token is required");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(UserKey))
|
||||
{
|
||||
throw new ValidationException("Pushover user key is required");
|
||||
throw new CustomValidationException("Pushover user key is required");
|
||||
}
|
||||
|
||||
if (Priority == PushoverPriority.Emergency)
|
||||
{
|
||||
if (!Retry.HasValue || Retry.Value < 30)
|
||||
{
|
||||
throw new ValidationException("Retry interval must be at least 30 seconds for emergency priority");
|
||||
throw new CustomValidationException("Retry interval must be at least 30 seconds for emergency priority");
|
||||
}
|
||||
|
||||
if (!Expire.HasValue || Expire.Value < 1)
|
||||
{
|
||||
throw new ValidationException("Expire time is required for emergency priority");
|
||||
throw new CustomValidationException("Expire time is required for emergency priority");
|
||||
}
|
||||
|
||||
if (Expire.Value > 10800)
|
||||
{
|
||||
throw new ValidationException("Expire time cannot exceed 10800 seconds (3 hours)");
|
||||
throw new CustomValidationException("Expire time cannot exceed 10800 seconds (3 hours)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,19 +133,19 @@ public sealed partial record PushoverConfig : IConfig
|
||||
{
|
||||
if (device.Length > 25)
|
||||
{
|
||||
throw new ValidationException($"Device name '{device}' exceeds 25 character limit");
|
||||
throw new CustomValidationException($"Device name '{device}' exceeds 25 character limit");
|
||||
}
|
||||
|
||||
if (!DeviceNameRegex().IsMatch(device))
|
||||
{
|
||||
throw new ValidationException($"Device name '{device}' contains invalid characters. Only letters, numbers, underscores, and hyphens are allowed.");
|
||||
throw new CustomValidationException($"Device name '{device}' contains invalid characters. Only letters, numbers, underscores, and hyphens are allowed.");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate sound - if provided, must not be whitespace-only
|
||||
if (Sound is not null && Sound.Length > 0 && string.IsNullOrWhiteSpace(Sound))
|
||||
{
|
||||
throw new ValidationException("Sound name cannot be empty or whitespace when specified");
|
||||
throw new CustomValidationException("Sound name cannot be empty or whitespace when specified");
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,9 +1,9 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Persistence.Models.Configuration;
|
||||
using Cleanuparr.Shared.Attributes;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.Notification;
|
||||
|
||||
@@ -45,27 +45,27 @@ public sealed record TelegramConfig : IConfig
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(BotToken))
|
||||
{
|
||||
throw new ValidationException("Telegram bot token is required");
|
||||
throw new CustomValidationException("Telegram bot token is required");
|
||||
}
|
||||
|
||||
if (BotToken.Length < 10)
|
||||
{
|
||||
throw new ValidationException("Telegram bot token must be at least 10 characters long");
|
||||
throw new CustomValidationException("Telegram bot token must be at least 10 characters long");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ChatId))
|
||||
{
|
||||
throw new ValidationException("Telegram chat ID is required");
|
||||
throw new CustomValidationException("Telegram chat ID is required");
|
||||
}
|
||||
|
||||
if (!IsChatIdValid(ChatId))
|
||||
{
|
||||
throw new ValidationException("Telegram chat ID must be a valid integer (negative IDs allowed for groups)");
|
||||
throw new CustomValidationException("Telegram chat ID must be a valid integer (negative IDs allowed for groups)");
|
||||
}
|
||||
|
||||
if (!IsTopicValid(TopicId))
|
||||
{
|
||||
throw new ValidationException("Telegram topic ID must be a valid integer when specified");
|
||||
throw new CustomValidationException("Telegram topic ID must be a valid integer when specified");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -21,12 +21,12 @@ public sealed record ErroredConfig
|
||||
{
|
||||
if (MaxStrikes is > 0 and < 3)
|
||||
{
|
||||
throw new ValidationException("the minimum value for errored max strikes must be 3");
|
||||
throw new CustomValidationException("the minimum value for errored max strikes must be 3");
|
||||
}
|
||||
|
||||
if (ChangeCategory && DeletePrivate)
|
||||
{
|
||||
throw new ValidationException("Cannot enable both deletion and category changing");
|
||||
throw new CustomValidationException("Cannot enable both deletion and category changing");
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -36,27 +36,27 @@ public sealed record FailedImportConfig
|
||||
{
|
||||
if (MaxStrikes is > 0 and < 3)
|
||||
{
|
||||
throw new ValidationException("The minimum value for failed imports max strikes must be 3");
|
||||
throw new CustomValidationException("The minimum value for failed imports max strikes must be 3");
|
||||
}
|
||||
|
||||
if (MaxStrikes >= 3 && PatternMode is PatternMode.Include && Patterns.Count is 0)
|
||||
{
|
||||
throw new ValidationException("At least one pattern must be specified when using the Include pattern mode");
|
||||
throw new CustomValidationException("At least one pattern must be specified when using the Include pattern mode");
|
||||
}
|
||||
|
||||
if (ForceImport && ForceImportMaxTries is 0)
|
||||
{
|
||||
throw new ValidationException("Force import max tries must be at least 1");
|
||||
throw new CustomValidationException("Force import max tries must be at least 1");
|
||||
}
|
||||
|
||||
if (ForceImport && ForceImportMaxTries > 5000)
|
||||
{
|
||||
throw new ValidationException("Force import max tries must be at most 5000");
|
||||
throw new CustomValidationException("Force import max tries must be at most 5000");
|
||||
}
|
||||
|
||||
if (ChangeCategory && DeletePrivate)
|
||||
{
|
||||
throw new ValidationException("Cannot enable both deletion and category changing");
|
||||
throw new CustomValidationException("Cannot enable both deletion and category changing");
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -40,7 +40,7 @@ public sealed record QueueCleanerConfig : IJobConfig
|
||||
|
||||
if (DownloadingMetadataMaxStrikes is > 0 and < 3)
|
||||
{
|
||||
throw new Cleanuparr.Domain.Exceptions.ValidationException("the minimum value for downloading metadata max strikes must be 3");
|
||||
throw new Cleanuparr.Domain.Exceptions.CustomValidationException("the minimum value for downloading metadata max strikes must be 3");
|
||||
}
|
||||
|
||||
foreach (var rule in StallRules)
|
||||
@@ -57,13 +57,13 @@ public sealed record QueueCleanerConfig : IJobConfig
|
||||
var stallNames = StallRules.Where(r => r.Enabled).Select(r => r.Name).ToList();
|
||||
if (stallNames.Count != stallNames.Distinct().Count())
|
||||
{
|
||||
throw new Cleanuparr.Domain.Exceptions.ValidationException("Duplicate stall rule names found");
|
||||
throw new Cleanuparr.Domain.Exceptions.CustomValidationException("Duplicate stall rule names found");
|
||||
}
|
||||
|
||||
var slowNames = SlowRules.Where(r => r.Enabled).Select(r => r.Name).ToList();
|
||||
if (slowNames.Count != slowNames.Distinct().Count())
|
||||
{
|
||||
throw new Cleanuparr.Domain.Exceptions.ValidationException("Duplicate slow rule names found");
|
||||
throw new Cleanuparr.Domain.Exceptions.CustomValidationException("Duplicate slow rule names found");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,37 +37,37 @@ public abstract record QueueRule : IConfig
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Name))
|
||||
{
|
||||
throw new Cleanuparr.Domain.Exceptions.ValidationException("Rule name cannot be empty");
|
||||
throw new Cleanuparr.Domain.Exceptions.CustomValidationException("Rule name cannot be empty");
|
||||
}
|
||||
|
||||
if (MaxStrikes < 3)
|
||||
{
|
||||
throw new Cleanuparr.Domain.Exceptions.ValidationException("Max strikes must be at least 3");
|
||||
throw new Cleanuparr.Domain.Exceptions.CustomValidationException("Max strikes must be at least 3");
|
||||
}
|
||||
|
||||
if (MinCompletionPercentage > 100)
|
||||
{
|
||||
throw new Cleanuparr.Domain.Exceptions.ValidationException("Minimum completion percentage must be between 0 and 100");
|
||||
throw new Cleanuparr.Domain.Exceptions.CustomValidationException("Minimum completion percentage must be between 0 and 100");
|
||||
}
|
||||
|
||||
if (MaxCompletionPercentage == 0)
|
||||
{
|
||||
throw new Cleanuparr.Domain.Exceptions.ValidationException("Maximum completion percentage must be greater than 0");
|
||||
throw new Cleanuparr.Domain.Exceptions.CustomValidationException("Maximum completion percentage must be greater than 0");
|
||||
}
|
||||
|
||||
if (MaxCompletionPercentage > 100)
|
||||
{
|
||||
throw new Cleanuparr.Domain.Exceptions.ValidationException("Maximum completion percentage must be between 1 and 100");
|
||||
throw new Cleanuparr.Domain.Exceptions.CustomValidationException("Maximum completion percentage must be between 1 and 100");
|
||||
}
|
||||
|
||||
if (MaxCompletionPercentage < MinCompletionPercentage)
|
||||
{
|
||||
throw new Cleanuparr.Domain.Exceptions.ValidationException("Maximum completion percentage must be greater than or equal to the minimum completion percentage");
|
||||
throw new Cleanuparr.Domain.Exceptions.CustomValidationException("Maximum completion percentage must be greater than or equal to the minimum completion percentage");
|
||||
}
|
||||
|
||||
if (ChangeCategory && DeletePrivateTorrentsFromClient)
|
||||
{
|
||||
throw new Cleanuparr.Domain.Exceptions.ValidationException("Cannot enable both deletion and category changing");
|
||||
throw new Cleanuparr.Domain.Exceptions.CustomValidationException("Cannot enable both deletion and category changing");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,12 +50,12 @@ public sealed record SlowRule : QueueRule
|
||||
|
||||
if (MaxStrikes < 3)
|
||||
{
|
||||
throw new ValidationException("Slow rule max strikes must be at least 3");
|
||||
throw new CustomValidationException("Slow rule max strikes must be at least 3");
|
||||
}
|
||||
|
||||
if (MaxTimeHours < 0)
|
||||
{
|
||||
throw new ValidationException("Maximum time cannot be negative");
|
||||
throw new CustomValidationException("Maximum time cannot be negative");
|
||||
}
|
||||
|
||||
bool hasMinSpeed = !string.IsNullOrEmpty(MinSpeed);
|
||||
@@ -63,19 +63,19 @@ public sealed record SlowRule : QueueRule
|
||||
|
||||
if (!hasMinSpeed && !hasMaxTime)
|
||||
{
|
||||
throw new ValidationException("Either minimum speed or maximum time must be specified");
|
||||
throw new CustomValidationException("Either minimum speed or maximum time must be specified");
|
||||
}
|
||||
|
||||
if (hasMinSpeed && !ByteSize.TryParse(MinSpeed, out _))
|
||||
{
|
||||
throw new ValidationException("Invalid minimum speed format");
|
||||
throw new CustomValidationException("Invalid minimum speed format");
|
||||
}
|
||||
|
||||
bool isIgnoreAboveSizeSet = !string.IsNullOrEmpty(IgnoreAboveSize);
|
||||
|
||||
if (isIgnoreAboveSizeSet && ByteSize.TryParse(IgnoreAboveSize, out _) is false)
|
||||
{
|
||||
throw new ValidationException($"invalid value for slow ignore above size: {IgnoreAboveSize}");
|
||||
throw new CustomValidationException($"invalid value for slow ignore above size: {IgnoreAboveSize}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,19 +37,19 @@ public sealed record StallRule : QueueRule
|
||||
|
||||
if (MaxStrikes < 3)
|
||||
{
|
||||
throw new ValidationException("Stall rule max strikes must be at least 3");
|
||||
throw new CustomValidationException("Stall rule max strikes must be at least 3");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(MinimumProgress))
|
||||
{
|
||||
if (!ByteSize.TryParse(MinimumProgress, out var parsed))
|
||||
{
|
||||
throw new ValidationException($"Invalid minimum progress value: {MinimumProgress}");
|
||||
throw new CustomValidationException($"Invalid minimum progress value: {MinimumProgress}");
|
||||
}
|
||||
|
||||
if (parsed.HasValue && parsed.Value.Bytes < 0)
|
||||
{
|
||||
throw new ValidationException("Minimum progress must be zero or greater");
|
||||
throw new CustomValidationException("Minimum progress must be zero or greater");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Cleanuparr.Domain.Enums;
|
||||
using Cleanuparr.Domain.Exceptions;
|
||||
using Cleanuparr.Shared.Helpers;
|
||||
using ValidationException = Cleanuparr.Domain.Exceptions.ValidationException;
|
||||
|
||||
namespace Cleanuparr.Persistence.Models.Configuration.Seeker;
|
||||
|
||||
@@ -53,24 +53,24 @@ public sealed record SeekerConfig : IConfig
|
||||
{
|
||||
if (SearchInterval < Constants.MinSearchIntervalMinutes)
|
||||
{
|
||||
throw new ValidationException(
|
||||
throw new CustomValidationException(
|
||||
$"{nameof(SearchInterval)} must be at least {Constants.MinSearchIntervalMinutes} minute(s)");
|
||||
}
|
||||
|
||||
if (SearchInterval > Constants.MaxSearchIntervalMinutes)
|
||||
{
|
||||
throw new ValidationException(
|
||||
throw new CustomValidationException(
|
||||
$"{nameof(SearchInterval)} must be at most {Constants.MaxSearchIntervalMinutes} minutes");
|
||||
}
|
||||
|
||||
if (!new List<int> { 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60, 120, 180, 240, 360 }.Contains(SearchInterval))
|
||||
{
|
||||
throw new ValidationException($"Invalid search interval {SearchInterval}");
|
||||
throw new CustomValidationException($"Invalid search interval {SearchInterval}");
|
||||
}
|
||||
|
||||
if (PostReleaseGraceHours is < 0 or > 72)
|
||||
{
|
||||
throw new ValidationException($"{nameof(PostReleaseGraceHours)} must be between 0 and 72");
|
||||
throw new CustomValidationException($"{nameof(PostReleaseGraceHours)} must be between 0 and 72");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user