Added skorubauserservice tests

This commit is contained in:
2026-10-11 07:43:51 +02:00
parent 381d5b7e94
commit 9a0371fb08
6 changed files with 181 additions and 43 deletions
@@ -147,7 +147,7 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
if (await context.Users.AnyAsync(u => u.Email == email, cancellationToken))
return Result.Fail(ValidationError.Create("User with this email already exists."));
var createUserResult = await skorubaService.CreateUserAsync(request, userId, cancellationToken);
var createUserResult = await skorubaService.CreateUserAsync(request, cancellationToken);
if (createUserResult.IsFailed)
return Result.Fail(CreateError.Create("Failed to create user."));
@@ -7,7 +7,11 @@ public interface ISkorubaUserService
{
[Log]
[Audit]
Task<Result<string>> CreateUserAsync(CreateUserRequest request, Guid userId, CancellationToken cancellationToken = default);
Task<Result<string>> CreateUserAsync(CreateUserRequest request, CancellationToken cancellationToken = default);
[Log]
[Audit]
Task<Result> RemoveUserAsync(Guid userId, CancellationToken cancellationToken = default);
[Log]
Task<Result<UserResponse>> GetUserAsync(Guid userId, CancellationToken cancellationToken = default);
@@ -2,7 +2,7 @@
namespace PostFundManagement.Domain.Models.Security;
public sealed class UserLockOutRequest
public abstract class SkorubaUser
{
[JsonPropertyName("id")]
public string? UserId { get; set; }
@@ -35,15 +35,15 @@ public sealed class UserLockOutRequest
public DateTimeOffset? LockoutEnd { get; set; }
}
public sealed class UserLockOutRequest : SkorubaUser;
public sealed class CreateUserRequest : SkorubaUser;
public sealed class UpdateUserRequest : SkorubaUser;
public sealed record AddUserClaimRequest([Required] Guid UserId,
[Required] int ClaimId,
[Required][MaxLength(LabelLength)] string ClaimType,
[Required][MaxLength(LabelLength)] string ClaimValue);
public sealed record CreateUserRequest(string UserName, string Email, string Password, bool EmailConfirmed = true, string? PhoneNumber = null, bool PhoneNumberConfirmed = false);
public sealed record UpdateUserRequest(string UserId, string UserName, string Email, bool EmailConfirmed, string? PhoneNumber, bool PhoneNumberConfirmed, bool LockoutEnabled, DateTimeOffset? LockoutEnd, int AccessFailedCount, bool TwoFactorEnabled);
public sealed record Claim(string ClaimType, string ClaimValue, int? ClaimId = null);
public sealed record ClaimsResponse(int TotalCount, int PageSize, ICollection<Claim> Claims);
@@ -6,14 +6,14 @@ namespace PostFundManagement.Infrastructure.Security;
public class SkorubaUserService(IHttpClientFactory httpClientFactory) : ISkorubaUserService
{
public async Task<Result<string>> CreateUserAsync(CreateUserRequest request, Guid userId, CancellationToken cancellationToken = default) =>
public async Task<Result<string>> CreateUserAsync(CreateUserRequest request, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result<string>>>)(async () =>
{
var httpClient = httpClientFactory.CreateClient(SkorubaHttpClient);
var response = await httpClient.PostAsJsonAsync("api/users", request, cancellationToken);
if (!response.IsSuccessStatusCode)
return Result.Fail(CreateError.Create("Failed to create user."));
return Result.Fail(CreateError.Create(response.ReasonPhrase!));
var created = await response.Content.ReadFromJsonAsync<UserResponse>(cancellationToken: cancellationToken);
@@ -72,9 +72,9 @@ public class SkorubaUserService(IHttpClientFactory httpClientFactory) : ISkoruba
await Result.Try((Func<Task<Result>>)(async () =>
{
var httpClient = httpClientFactory.CreateClient(SkorubaHttpClient);
var payload = new { UserId = userId, ClaimType = claimType, ClaimValue = claimValue };
var payload = new { userId = userId, claimType = claimType, claimValue = claimValue };
var response = await httpClient.PostAsJsonAsync($"api/users/{userId}/claims", payload, cancellationToken);
var response = await httpClient.PostAsJsonAsync($"api/users/claims", payload, cancellationToken);
return Result.OkIf(response.IsSuccessStatusCode, CreateError.Create("Failed to add user claim."));
}));
@@ -83,9 +83,12 @@ public class SkorubaUserService(IHttpClientFactory httpClientFactory) : ISkoruba
await Result.Try((Func<Task<Result>>)(async () =>
{
var httpClient = httpClientFactory.CreateClient(SkorubaHttpClient);
var response = await httpClient.DeleteAsync($"api/users/{userId}/claims/{claimId}", cancellationToken);
var response = await httpClient.DeleteAsync($"api/users/{userId}/Claims?claimId={claimId}", cancellationToken);
return Result.OkIf(response.IsSuccessStatusCode, CreateError.Create("Failed to remove user claim."));
if(response.StatusCode == System.Net.HttpStatusCode.NotFound)
return Result.Fail(NotFoundError.Create($"Claim {claimId} for user {userId} not found."));
return Result.OkIf(response.IsSuccessStatusCode, ValidationError.Create("Failed to remove user claim."));
}));
public async Task<Result<Claim[]>> GetUserClaimsAsync(Guid userId, int page = 1, int pageSize = 50, CancellationToken cancellationToken = default) =>
@@ -103,7 +106,7 @@ public class SkorubaUserService(IHttpClientFactory httpClientFactory) : ISkoruba
await Result.Try((Func<Task<Result>>)(async () =>
{
var httpClient = httpClientFactory.CreateClient(SkorubaHttpClient);
var payload = new { UserId = userId, RoleId = roleId };
var payload = new { userId = userId, roleId = roleId };
var response = await httpClient.PostAsJsonAsync($"api/users/roles", payload, cancellationToken);
@@ -183,4 +186,15 @@ public class SkorubaUserService(IHttpClientFactory httpClientFactory) : ISkoruba
return Result.FailIf(result is null, NotFoundError.Create("Available roles could not be retrieved."))
.ToResult(result?.Roles?.ToArray() ?? []);
}));
public async Task<Result> RemoveUserAsync(Guid userId, CancellationToken cancellationToken = default) =>
await Result.Try((Func<Task<Result>>)(async () =>
{
var httpClient = httpClientFactory.CreateClient(SkorubaHttpClient);
var result = await httpClient.DeleteAsync($"api/users/{userId}", cancellationToken);
return result.StatusCode == System.Net.HttpStatusCode.NotFound
? Result.Fail(NotFoundError.Create($"User {userId} not found."))
: Result.OkIf(result.IsSuccessStatusCode, UpdateError.Create("Failed to remove user."));
}));
}
@@ -1,22 +1,11 @@
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Models.Security;
using PostFundManagement.Infrastructure.Database.Services;
using static PostFundManagement.Domain.Extensions.Constants;
namespace PostFundManagement.Tests;
/// <summary>
/// Integration tests for SkorubaUserService.GetUserRolesAsync method.
///
/// These tests hit the actual Skoruba API environment.
/// Requires the following configuration:
/// - MachineIdentitySettings with valid ClientId and ClientSecret from user secrets
/// - SkorubaApi connection string pointing to the API endpoint
/// - Network access to the Skoruba API
///
/// To run these tests:
/// dotnet test --filter "FullyQualifiedName~SkorubaUserServiceGetUserRolesIntegrationTests"
/// </summary>
public class SkorubaUserServiceGetUserRolesIntegrationTests
{
private readonly ISkorubaUserService _skorubaService;
@@ -51,9 +40,6 @@ public class SkorubaUserServiceGetUserRolesIntegrationTests
_httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
}
/// <summary>
/// Integration test: Verify HttpClient is properly configured with SkorubaHttpClient name.
/// </summary>
[DebugOnlyFact]
public void HttpClientFactory_HasSkorubaHttpClientConfigured()
{
@@ -65,15 +51,72 @@ public class SkorubaUserServiceGetUserRolesIntegrationTests
Assert.NotNull(httpClient.BaseAddress);
}
/// <summary>
/// Integration test: Verify that the service properly formats API requests.
/// Uses a fake/invalid user ID to test the HTTP request flow.
/// </summary>
[DebugOnlyFact]
public async Task CreateUserAsync_MustSucced_WithErrorMessage()
{
// Arrange
var newUser = new CreateUserRequest
{
UserName = "testuser",
Email = "testuser@example.com",
EmailConfirmed = true,
};
// Act
var result = await _skorubaService.CreateUserAsync(newUser);
// Assert
Assert.True(result.IsSuccess, result.Errors.FirstOrDefault()?.Message);
Assert.Empty(result.Errors);
}
[DebugOnlyFact]
public async Task DeleteUserAsync_MustSucced_WithErrorMessage()
{
// Arrange
var userId = Guid.Parse("3b001832-c78d-4705-9bc2-0f2f2ebb35b7");
// Act
var result = await _skorubaService.RemoveUserAsync(userId);
// Assert
Assert.True(result.IsSuccess, result.Errors.FirstOrDefault()?.Message);
Assert.Empty(result.Errors);
}
[DebugOnlyFact]
public async Task LockoutUserAsync_MustSucced_WithErrorMessage()
{
// Arrange
var userId = Guid.Parse("285df6e4-c0f9-4c7c-8da7-b3867ffe1880");
// Act
var result = await _skorubaService.SetUserLockoutAsync(userId, true);
// Assert
Assert.True(result.IsSuccess, result.Errors.FirstOrDefault()?.Message);
Assert.Empty(result.Errors);
}
[DebugOnlyFact]
public async Task UnlockUserAsync_MustSucced_WithErrorMessage()
{
// Arrange
var userId = Guid.Parse("285df6e4-c0f9-4c7c-8da7-b3867ffe1880");
// Act
var result = await _skorubaService.SetUserLockoutAsync(userId, false);
// Assert
Assert.True(result.IsSuccess, result.Errors.FirstOrDefault()?.Message);
Assert.Empty(result.Errors);
}
[DebugOnlyFact]
public async Task GetUserRolesAsync_SendsProperlyFormattedRequest()
{
// Arrange
var userId = Guid.Parse("6059f1e4-6a41-4447-a546-86ece5102004");
var userId = Guid.Parse("285df6e4-c0f9-4c7c-8da7-b3867ffe1880");
var page = 1;
var pageSize = 25;
@@ -85,9 +128,88 @@ public class SkorubaUserServiceGetUserRolesIntegrationTests
Assert.Empty(result.Errors);
}
/// <summary>
/// Integration test: Verify cancellation token is respected.
/// </summary>
[DebugOnlyFact]
public async Task AddUserRoleAsync_MustSucced_WithErrorMessage()
{
// Arrange
var userId = Guid.Parse("285df6e4-c0f9-4c7c-8da7-b3867ffe1880");
var roleId = Guid.Parse("34b03edc-cda4-4380-b7a5-3310933b75ab"); // Contributor role ID
// Act
var result = await _skorubaService.AddRoleAsync(userId, roleId);
// Assert
Assert.True(result.IsSuccess, result.Errors.FirstOrDefault()?.Message);
Assert.Empty(result.Errors);
}
[DebugOnlyFact]
public async Task RemoveUserRoleAsync_MustSucced_WithErrorMessage()
{
// Arrange
var userId = Guid.Parse("285df6e4-c0f9-4c7c-8da7-b3867ffe1880");
var roleId = Guid.Parse("34b03edc-cda4-4380-b7a5-3310933b75ab"); // Contributor role ID
// Act
var result = await _skorubaService.RemoveRoleAsync(userId, roleId);
// Assert
Assert.True(result.IsSuccess, result.Errors.FirstOrDefault()?.Message);
Assert.Empty(result.Errors);
}
[DebugOnlyFact]
public async Task GetUserClaimAsync_MustSucced_WithErrorMessage()
{
// Arrange
var userId = Guid.Parse("285df6e4-c0f9-4c7c-8da7-b3867ffe1880");
var page = 1;
var pageSize = 25;
// Act
var result = await _skorubaService.GetUserClaimsAsync(userId, page, pageSize);
// Assert
Assert.True(result.IsSuccess, result.Errors.FirstOrDefault()?.Message);
Assert.Empty(result.Errors);
}
[DebugOnlyFact]
public async Task AddUserClaimAsync_MustSucced_WithErrorMessage()
{
// Arrange
var userId = Guid.Parse("285df6e4-c0f9-4c7c-8da7-b3867ffe1880");
var claimType = "test-claim";
var claimValue = "test-value";
// Act
var result = await _skorubaService.AddClaimAsync(userId, claimType, claimValue);
// Assert
Assert.True(result.IsSuccess, result.Errors.FirstOrDefault()?.Message);
Assert.Empty(result.Errors);
}
[DebugOnlyFact]
public async Task RemoveUserClaimAsync_MustSucced_WithErrorMessage()
{
// Arrange
var userId = Guid.Parse("285df6e4-c0f9-4c7c-8da7-b3867ffe1880");
var claimType = "test-claim";
var userClaimsResult = await _skorubaService.GetUserClaimsAsync(userId);
var matchingClaim = userClaimsResult.Value?.FirstOrDefault(c => c.ClaimType == claimType);
Assert.NotNull(matchingClaim);
// Act
var result = await _skorubaService.RemoveClaimAsync(userId, matchingClaim!.ClaimId!.Value);
// Assert
Assert.True(result.IsSuccess, result.Errors.FirstOrDefault()?.Message);
Assert.Empty(result.Errors);
}
[DebugOnlyFact]
public async Task GetUserRolesAsync_RespectsCancellationToken()
{
+1 -3
View File
@@ -7,11 +7,9 @@
<File Path="PostFundManagement.snk" />
<File Path="README.md" />
</Folder>
<Folder Name="/tests/">
<Project Path="PostFundManagement.Tests/PostFundManagement.Tests.csproj" Id="ce650d27-8a68-436d-b5c1-db25f2286e85" />
</Folder>
<Project Path="PostFundManagement.Api/PostFundManagement.Api.csproj" />
<Project Path="PostFundManagement.Application/PostFundManagement.Application.csproj" Id="750c7754-53a7-4573-abed-5c436246a04d" />
<Project Path="PostFundManagement.Domain/PostFundManagement.Domain.csproj" />
<Project Path="PostFundManagement.Infrastructure/PostFundManagement.Infrastructure.csproj" />
<Project Path="PostFundManagement.Tests/PostFundManagement.Tests.csproj" Id="ce650d27-8a68-436d-b5c1-db25f2286e85" />
</Solution>