Added skorubauserservice tests
This commit is contained in:
@@ -147,7 +147,7 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
|
|||||||
if (await context.Users.AnyAsync(u => u.Email == email, cancellationToken))
|
if (await context.Users.AnyAsync(u => u.Email == email, cancellationToken))
|
||||||
return Result.Fail(ValidationError.Create("User with this email already exists."));
|
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)
|
if (createUserResult.IsFailed)
|
||||||
return Result.Fail(CreateError.Create("Failed to create user."));
|
return Result.Fail(CreateError.Create("Failed to create user."));
|
||||||
|
|||||||
@@ -7,7 +7,11 @@ public interface ISkorubaUserService
|
|||||||
{
|
{
|
||||||
[Log]
|
[Log]
|
||||||
[Audit]
|
[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]
|
[Log]
|
||||||
Task<Result<UserResponse>> GetUserAsync(Guid userId, CancellationToken cancellationToken = default);
|
Task<Result<UserResponse>> GetUserAsync(Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace PostFundManagement.Domain.Models.Security;
|
namespace PostFundManagement.Domain.Models.Security;
|
||||||
|
|
||||||
public sealed class UserLockOutRequest
|
public abstract class SkorubaUser
|
||||||
{
|
{
|
||||||
[JsonPropertyName("id")]
|
[JsonPropertyName("id")]
|
||||||
public string? UserId { get; set; }
|
public string? UserId { get; set; }
|
||||||
@@ -35,15 +35,15 @@ public sealed class UserLockOutRequest
|
|||||||
public DateTimeOffset? LockoutEnd { get; set; }
|
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,
|
public sealed record AddUserClaimRequest([Required] Guid UserId,
|
||||||
[Required] int ClaimId,
|
[Required] int ClaimId,
|
||||||
[Required][MaxLength(LabelLength)] string ClaimType,
|
[Required][MaxLength(LabelLength)] string ClaimType,
|
||||||
[Required][MaxLength(LabelLength)] string ClaimValue);
|
[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 Claim(string ClaimType, string ClaimValue, int? ClaimId = null);
|
||||||
|
|
||||||
public sealed record ClaimsResponse(int TotalCount, int PageSize, ICollection<Claim> Claims);
|
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 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 () =>
|
await Result.Try((Func<Task<Result<string>>>)(async () =>
|
||||||
{
|
{
|
||||||
var httpClient = httpClientFactory.CreateClient(SkorubaHttpClient);
|
var httpClient = httpClientFactory.CreateClient(SkorubaHttpClient);
|
||||||
var response = await httpClient.PostAsJsonAsync("api/users", request, cancellationToken);
|
var response = await httpClient.PostAsJsonAsync("api/users", request, cancellationToken);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
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);
|
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 () =>
|
await Result.Try((Func<Task<Result>>)(async () =>
|
||||||
{
|
{
|
||||||
var httpClient = httpClientFactory.CreateClient(SkorubaHttpClient);
|
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."));
|
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 () =>
|
await Result.Try((Func<Task<Result>>)(async () =>
|
||||||
{
|
{
|
||||||
var httpClient = httpClientFactory.CreateClient(SkorubaHttpClient);
|
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) =>
|
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 () =>
|
await Result.Try((Func<Task<Result>>)(async () =>
|
||||||
{
|
{
|
||||||
var httpClient = httpClientFactory.CreateClient(SkorubaHttpClient);
|
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);
|
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."))
|
return Result.FailIf(result is null, NotFoundError.Create("Available roles could not be retrieved."))
|
||||||
.ToResult(result?.Roles?.ToArray() ?? []);
|
.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.Application.Extensions;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Models.Security;
|
||||||
using PostFundManagement.Infrastructure.Database.Services;
|
using PostFundManagement.Infrastructure.Database.Services;
|
||||||
using static PostFundManagement.Domain.Extensions.Constants;
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
namespace PostFundManagement.Tests;
|
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
|
public class SkorubaUserServiceGetUserRolesIntegrationTests
|
||||||
{
|
{
|
||||||
private readonly ISkorubaUserService _skorubaService;
|
private readonly ISkorubaUserService _skorubaService;
|
||||||
@@ -51,9 +40,6 @@ public class SkorubaUserServiceGetUserRolesIntegrationTests
|
|||||||
_httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
|
_httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Integration test: Verify HttpClient is properly configured with SkorubaHttpClient name.
|
|
||||||
/// </summary>
|
|
||||||
[DebugOnlyFact]
|
[DebugOnlyFact]
|
||||||
public void HttpClientFactory_HasSkorubaHttpClientConfigured()
|
public void HttpClientFactory_HasSkorubaHttpClientConfigured()
|
||||||
{
|
{
|
||||||
@@ -65,15 +51,72 @@ public class SkorubaUserServiceGetUserRolesIntegrationTests
|
|||||||
Assert.NotNull(httpClient.BaseAddress);
|
Assert.NotNull(httpClient.BaseAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
[DebugOnlyFact]
|
||||||
/// Integration test: Verify that the service properly formats API requests.
|
public async Task CreateUserAsync_MustSucced_WithErrorMessage()
|
||||||
/// Uses a fake/invalid user ID to test the HTTP request flow.
|
{
|
||||||
/// </summary>
|
// 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]
|
[DebugOnlyFact]
|
||||||
public async Task GetUserRolesAsync_SendsProperlyFormattedRequest()
|
public async Task GetUserRolesAsync_SendsProperlyFormattedRequest()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var userId = Guid.Parse("6059f1e4-6a41-4447-a546-86ece5102004");
|
var userId = Guid.Parse("285df6e4-c0f9-4c7c-8da7-b3867ffe1880");
|
||||||
var page = 1;
|
var page = 1;
|
||||||
var pageSize = 25;
|
var pageSize = 25;
|
||||||
|
|
||||||
@@ -85,9 +128,88 @@ public class SkorubaUserServiceGetUserRolesIntegrationTests
|
|||||||
Assert.Empty(result.Errors);
|
Assert.Empty(result.Errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
[DebugOnlyFact]
|
||||||
/// Integration test: Verify cancellation token is respected.
|
public async Task AddUserRoleAsync_MustSucced_WithErrorMessage()
|
||||||
/// </summary>
|
{
|
||||||
|
// 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]
|
[DebugOnlyFact]
|
||||||
public async Task GetUserRolesAsync_RespectsCancellationToken()
|
public async Task GetUserRolesAsync_RespectsCancellationToken()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,11 +7,9 @@
|
|||||||
<File Path="PostFundManagement.snk" />
|
<File Path="PostFundManagement.snk" />
|
||||||
<File Path="README.md" />
|
<File Path="README.md" />
|
||||||
</Folder>
|
</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.Api/PostFundManagement.Api.csproj" />
|
||||||
<Project Path="PostFundManagement.Application/PostFundManagement.Application.csproj" Id="750c7754-53a7-4573-abed-5c436246a04d" />
|
<Project Path="PostFundManagement.Application/PostFundManagement.Application.csproj" Id="750c7754-53a7-4573-abed-5c436246a04d" />
|
||||||
<Project Path="PostFundManagement.Domain/PostFundManagement.Domain.csproj" />
|
<Project Path="PostFundManagement.Domain/PostFundManagement.Domain.csproj" />
|
||||||
<Project Path="PostFundManagement.Infrastructure/PostFundManagement.Infrastructure.csproj" />
|
<Project Path="PostFundManagement.Infrastructure/PostFundManagement.Infrastructure.csproj" />
|
||||||
|
<Project Path="PostFundManagement.Tests/PostFundManagement.Tests.csproj" Id="ce650d27-8a68-436d-b5c1-db25f2286e85" />
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
Reference in New Issue
Block a user