Standardize arr response DTOs (#819)

This commit is contained in:
Flaminel authored and GitHub committed 2026-09-29 13:04:02 +03:00
1 parent ba1aab81fc
commit 044e7ae7d2
9 files changed
+47 -67

No files matched your search

@@ -4,7 +4,7 @@ using Cleanuparr.Api.Features.Arr.Controllers;
using Cleanuparr.Api.Tests.TestHelpers;
using Cleanuparr.Domain.Enums;
using Cleanuparr.Infrastructure.Events.Interfaces;
using Cleanuparr.Infrastructure.Features.Arr.Dtos;
using Cleanuparr.Api.Features.Arr.Contracts.Responses;
using Cleanuparr.Infrastructure.Features.Arr.Interfaces;
using Cleanuparr.Persistence;
using Cleanuparr.Persistence.Models.Configuration.Arr;
@@ -60,7 +60,7 @@ public class ArrConfigControllerTests : IDisposable
// Assert
var ok = result.ShouldBeOfType<OkObjectResult>();
var dto = ok.Value.ShouldBeOfType<ArrConfigDto>();
var dto = ok.Value.ShouldBeOfType<ArrConfigResponse>();
dto.Type.ShouldBe(type);
}
@@ -79,7 +79,7 @@ public class ArrConfigControllerTests : IDisposable
// Assert
var ok = result.ShouldBeOfType<OkObjectResult>();
var dto = ok.Value.ShouldBeOfType<ArrConfigDto>();
var dto = ok.Value.ShouldBeOfType<ArrConfigResponse>();
dto.Instances[0].Name.ShouldBe("a");
dto.Instances[1].Name.ShouldBe("z");
}
@@ -137,7 +137,7 @@ public class ArrConfigControllerTests : IDisposable
// Assert
var created = result.ShouldBeOfType<CreatedAtActionResult>();
var dto = created.Value.ShouldBeOfType<ArrInstanceDto>();
var dto = created.Value.ShouldBeOfType<ArrInstanceResponse>();
dto.Name.ShouldBe("test");
var sonarrConfig = await _dataContext.ArrConfigs
.Include(c => c.Instances)
@@ -427,12 +427,12 @@ public class ArrConfigControllerTests : IDisposable
// Act + Assert: create
IActionResult created = await DispatchCreate(type, request);
ArrInstanceDto dto = created.ShouldBeOfType<CreatedAtActionResult>().Value.ShouldBeOfType<ArrInstanceDto>();
ArrInstanceResponse dto = created.ShouldBeOfType<CreatedAtActionResult>().Value.ShouldBeOfType<ArrInstanceResponse>();
// Act + Assert: update
Guid id = dto.Id.ShouldNotBeNull();
IActionResult updated = await DispatchUpdate(type, id, request with { Name = "rewired" });
updated.ShouldBeOfType<OkObjectResult>().Value.ShouldBeOfType<ArrInstanceDto>().Name.ShouldBe("rewired");
updated.ShouldBeOfType<OkObjectResult>().Value.ShouldBeOfType<ArrInstanceResponse>().Name.ShouldBe("rewired");
// Act + Assert: connection test
IActionResult tested = await DispatchTest(type, new TestArrInstanceRequest
@@ -2,7 +2,7 @@ using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Cleanuparr.Api.Json;
using Cleanuparr.Domain.Enums;
using Cleanuparr.Infrastructure.Features.Arr.Dtos;
using Cleanuparr.Api.Features.Arr.Contracts.Responses;
using Cleanuparr.Persistence.Models.Configuration;
using Cleanuparr.Persistence.Models.Configuration.Arr;
using Cleanuparr.Persistence.Models.Configuration.Notification;
@@ -74,7 +74,7 @@ public class SensitiveDataResolverTests
public void ArrInstance_NullApiKey_RemainsNull()
{
// ApiKey is required, but let's test with the DTO which might handle null
var dto = new ArrInstanceDto
var dto = new ArrInstanceResponse
{
Name = "Sonarr",
Url = "http://sonarr:8989",
@@ -90,12 +90,12 @@ public class SensitiveDataResolverTests
#endregion
#region ArrInstanceDto
#region ArrInstanceResponse
[Fact]
public void ArrInstanceDto_ApiKey_IsMasked()
{
var dto = new ArrInstanceDto
var dto = new ArrInstanceResponse
{
Id = Guid.NewGuid(),
Name = "Radarr",
@@ -0,0 +1,14 @@
using Cleanuparr.Domain.Enums;
namespace Cleanuparr.Api.Features.Arr.Contracts.Responses;
public sealed record ArrConfigResponse
{
public required Guid Id { get; init; }
public required InstanceType Type { get; init; }
public short FailedImportMaxStrikes { get; init; } = -1;
public List<ArrInstanceResponse> Instances { get; init; } = [];
}
@@ -1,31 +1,31 @@
using System.ComponentModel.DataAnnotations;
using Cleanuparr.Shared.Attributes;
namespace Cleanuparr.Infrastructure.Features.Arr.Dtos;
namespace Cleanuparr.Api.Features.Arr.Contracts.Responses;
/// <summary>
/// DTO for Arr instances that can handle both existing (with ID) and new (without ID) instances
/// Response shape for Arr instances, covering both existing (with ID) and new (without ID) instances.
/// </summary>
public record ArrInstanceDto
public sealed record ArrInstanceResponse
{
/// <summary>
/// ID for existing instances, null for new instances
/// </summary>
public Guid? Id { get; init; }
public bool Enabled { get; init; } = true;
public float Version { get; set; }
public float Version { get; init; }
[Required]
public required string Name { get; init; }
[Required]
public required string Url { get; init; }
[Required]
[SensitiveData]
public required string ApiKey { get; init; }
public string? ExternalUrl { get; init; }
}
}
@@ -2,7 +2,7 @@
using Cleanuparr.Api.Features.Arr.Contracts.Requests;
using Cleanuparr.Domain.Enums;
using Cleanuparr.Infrastructure.Events.Interfaces;
using Cleanuparr.Infrastructure.Features.Arr.Dtos;
using Cleanuparr.Api.Features.Arr.Contracts.Responses;
using Cleanuparr.Infrastructure.Health;
using Cleanuparr.Persistence;
using Cleanuparr.Persistence.Models.Configuration.Arr;
@@ -413,7 +413,7 @@ public sealed class ArrConfigController : ControllerBase
}
}
private static ArrConfigDto ToDto(ArrConfig config) => new()
private static ArrConfigResponse ToDto(ArrConfig config) => new()
{
Id = config.Id,
Type = config.Type,
@@ -421,7 +421,7 @@ public sealed class ArrConfigController : ControllerBase
Instances = config.Instances.Select(ToDto).ToList(),
};
private static ArrInstanceDto ToDto(ArrInstance instance) => new()
private static ArrInstanceResponse ToDto(ArrInstance instance) => new()
{
Id = instance.Id,
Enabled = instance.Enabled,
@@ -102,7 +102,7 @@ public sealed class AccountController : ControllerBase
_logger.LogInformation("Password changed for user {Username}", user.Username);
return Ok(new { message = "Password changed" });
return Ok(new { Message = "Password changed" });
}
finally
{
@@ -158,7 +158,7 @@ public sealed class AccountController : ControllerBase
_logger.LogInformation("Username changed from {PreviousUsername} to {Username}",
previousUsername.SanitizeForLog(), newUsername.SanitizeForLog());
return Ok(new { message = "Username changed" });
return Ok(new { Message = "Username changed" });
}
finally
{
@@ -303,7 +303,7 @@ public sealed class AccountController : ControllerBase
_logger.LogInformation("2FA enabled for user {Username}", user.Username);
return Ok(new { message = "2FA enabled" });
return Ok(new { Message = "2FA enabled" });
}
finally
{
@@ -382,7 +382,7 @@ public sealed class AccountController : ControllerBase
_logger.LogInformation("2FA disabled for user {Username}", user.Username);
return Ok(new { message = "2FA disabled" });
return Ok(new { Message = "2FA disabled" });
}
[HttpGet("api-key")]
@@ -491,7 +491,7 @@ public sealed class AccountController : ControllerBase
_logger.LogInformation("Plex account unlinked for user {Username}", user.Username);
return Ok(new { message = "Plex account unlinked" });
return Ok(new { Message = "Plex account unlinked" });
}
[HttpGet("oidc")]
@@ -524,7 +524,7 @@ public sealed class AccountController : ControllerBase
user.UpdatedAt = _timeProvider.GetUtcNow();
await _usersContext.SaveChangesAsync();
return Ok(new { message = "OIDC configuration updated" });
return Ok(new { Message = "OIDC configuration updated" });
}
finally
{
@@ -637,7 +637,7 @@ public sealed class AccountController : ControllerBase
_logger.LogInformation("OIDC account unlinked for user {Username}", user.Username);
return Ok(new { message = "OIDC account unlinked" });
return Ok(new { Message = "OIDC account unlinked" });
}
finally
{
@@ -198,7 +198,7 @@ public sealed class AuthController : ControllerBase
_logger.LogInformation("2FA enabled for user {Username}", user.Username);
return Ok(new { message = "2FA verified and enabled" });
return Ok(new { Message = "2FA verified and enabled" });
}
finally
{
@@ -229,7 +229,7 @@ public sealed class AuthController : ControllerBase
_logger.LogInformation("Setup completed for user {Username}", user.Username);
return Ok(new { message = "Setup complete" });
return Ok(new { Message = "Setup complete" });
}
finally
{
@@ -420,7 +420,7 @@ public sealed class AuthController : ControllerBase
await _usersContext.SaveChangesAsync();
}
return Ok(new { message = "Logged out" });
return Ok(new { Message = "Logged out" });
}
finally
{
@@ -1,14 +0,0 @@
using Cleanuparr.Domain.Enums;
namespace Cleanuparr.Infrastructure.Features.Arr.Dtos;
public class ArrConfigDto
{
public Guid Id { get; set; }
public required InstanceType Type { get; set; }
public short FailedImportMaxStrikes { get; set; } = -1;
public List<ArrInstanceDto> Instances { get; set; } = [];
}
@@ -1,20 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Cleanuparr.Infrastructure.Features.Arr.Dtos;
/// <summary>
/// DTO for creating new Arr instances without requiring an ID
/// </summary>
public record CreateArrInstanceDto
{
public bool Enabled { get; init; } = true;
[Required]
public required string Name { get; init; }
[Required]
public required string Url { get; init; }
[Required]
public required string ApiKey { get; init; }
}