Update database handling code (#247)

This commit is contained in:
2026-08-25 13:42:51 -04:00
committed by GitHub
parent 03ca966708
commit 8a047e8d9f
37 changed files with 386 additions and 405 deletions

View File

@@ -38,42 +38,6 @@ public class TokenServiceValidationTests
);
}
[Fact]
public async Task ValidateAccessTokenAsync_WithValidToken_ReturnsValidatedToken()
{
// Arrange
Guid userId = Guid.NewGuid();
const string username = "testuser";
const string token = "valid-access-token";
List<Claim> claims = new()
{
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
ClaimsIdentity claimsIdentity = new(claims);
ClaimsPrincipal principal = new(claimsIdentity);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
// Act
ValidatedToken result = await _tokenService.ValidateAccessTokenAsync(token);
// Assert
result.Should().NotBeNull();
result.UserId.Should().Be(userId);
result.Username.Should().Be(username);
result.Principal.Should().NotBeNull();
result
.Principal.FindFirst(JwtRegisteredClaimNames.Sub)
?.Value.Should()
.Be(userId.ToString());
}
[Fact]
public async Task ValidateRefreshTokenAsync_WithValidToken_ReturnsValidatedToken()
{
@@ -137,24 +101,7 @@ public class TokenServiceValidationTests
}
[Fact]
public async Task ValidateAccessTokenAsync_WithInvalidToken_ThrowsUnauthorizedException()
{
// Arrange
const string token = "invalid-token";
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Invalid token"));
// Act & Assert
await FluentActions
.Invoking(async () => await _tokenService.ValidateAccessTokenAsync(token))
.Should()
.ThrowAsync<UnauthorizedException>();
}
[Fact]
public async Task ValidateAccessTokenAsync_WithExpiredToken_ThrowsUnauthorizedException()
public async Task ValidateRefreshTokenAsync_WithExpiredToken_ThrowsUnauthorizedException()
{
// Arrange
const string token = "expired-token";
@@ -165,13 +112,13 @@ public class TokenServiceValidationTests
// Act & Assert
await FluentActions
.Invoking(async () => await _tokenService.ValidateAccessTokenAsync(token))
.Invoking(async () => await _tokenService.ValidateRefreshTokenAsync(token))
.Should()
.ThrowAsync<UnauthorizedException>();
}
[Fact]
public async Task ValidateAccessTokenAsync_WithMissingUserIdClaim_ThrowsUnauthorizedException()
public async Task ValidateRefreshTokenAsync_WithMissingUserIdClaim_ThrowsUnauthorizedException()
{
// Arrange
const string username = "testuser";
@@ -193,14 +140,14 @@ public class TokenServiceValidationTests
// Act & Assert
await FluentActions
.Invoking(async () => await _tokenService.ValidateAccessTokenAsync(token))
.Invoking(async () => await _tokenService.ValidateRefreshTokenAsync(token))
.Should()
.ThrowAsync<UnauthorizedException>()
.WithMessage("*missing required claims*");
}
[Fact]
public async Task ValidateAccessTokenAsync_WithMissingUsernameClaim_ThrowsUnauthorizedException()
public async Task ValidateRefreshTokenAsync_WithMissingUsernameClaim_ThrowsUnauthorizedException()
{
// Arrange
Guid userId = Guid.NewGuid();
@@ -222,14 +169,14 @@ public class TokenServiceValidationTests
// Act & Assert
await FluentActions
.Invoking(async () => await _tokenService.ValidateAccessTokenAsync(token))
.Invoking(async () => await _tokenService.ValidateRefreshTokenAsync(token))
.Should()
.ThrowAsync<UnauthorizedException>()
.WithMessage("*missing required claims*");
}
[Fact]
public async Task ValidateAccessTokenAsync_WithMalformedUserId_ThrowsUnauthorizedException()
public async Task ValidateRefreshTokenAsync_WithMalformedUserId_ThrowsUnauthorizedException()
{
// Arrange
const string username = "testuser";
@@ -252,7 +199,7 @@ public class TokenServiceValidationTests
// Act & Assert
await FluentActions
.Invoking(async () => await _tokenService.ValidateAccessTokenAsync(token))
.Invoking(async () => await _tokenService.ValidateRefreshTokenAsync(token))
.Should()
.ThrowAsync<UnauthorizedException>()
.WithMessage("*malformed user ID*");

View File

@@ -16,7 +16,7 @@
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Jwt\Infrastructure.Jwt.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.PasswordHashing\Infrastructure.PasswordHashing.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
</ItemGroup>

View File

@@ -1,6 +1,6 @@
using System.Data.Common;
using Dapper;
using Infrastructure.Sql;
using Database.Connection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Data.SqlClient;
@@ -14,7 +14,7 @@ namespace Features.Auth.Identity;
/// existing unique indexes are already case-insensitive under SQL Server's default collation).
/// </summary>
public sealed class DapperUserStore(ISqlConnectionFactory connectionFactory)
: Repository<ApplicationUser>(connectionFactory),
: DapperRepository(connectionFactory),
IUserStore<ApplicationUser>,
IUserPasswordStore<ApplicationUser>,
IUserEmailStore<ApplicationUser>

View File

@@ -66,12 +66,6 @@ public interface ITokenService
/// </summary>
string GenerateConfirmationToken(Guid userId, string username);
/// <summary>Validates an access token's signature and expiration and extracts the caller's identity.</summary>
/// <exception cref="Domain.Exceptions.UnauthorizedException">
/// Thrown when the token is missing required claims, has a malformed user ID, or otherwise fails validation.
/// </exception>
Task<ValidatedToken> ValidateAccessTokenAsync(string token);
/// <summary>Validates a refresh token's signature and expiration and extracts the caller's identity.</summary>
/// <exception cref="Domain.Exceptions.UnauthorizedException">
/// Thrown when the token is missing required claims, has a malformed user ID, or otherwise fails validation.

View File

@@ -82,12 +82,6 @@ public class TokenService : ITokenService
);
}
/// <inheritdoc />
public async Task<ValidatedToken> ValidateAccessTokenAsync(string token)
{
return await ValidateTokenInternalAsync(token, _accessTokenSecret, "access");
}
/// <inheritdoc />
public async Task<ValidatedToken> ValidateRefreshTokenAsync(string token)
{

View File

@@ -13,7 +13,7 @@
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
</ItemGroup>

View File

@@ -13,7 +13,7 @@
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
</ItemGroup>

View File

@@ -1,8 +1,8 @@
using System.Data.Common;
using Dapper;
using Database.Connection;
using Domain.Entities;
using Domain.Exceptions;
using Infrastructure.Sql;
namespace Features.Breweries.Repository;
@@ -17,7 +17,7 @@ namespace Features.Breweries.Repository;
/// yields the UDT's serialized bytes as a plain <c>varbinary</c> value that Dapper can bind directly.
/// </remarks>
public class BreweryRepository(ISqlConnectionFactory connectionFactory)
: Repository<BreweryPost>(connectionFactory),
: Database.Connection.DapperRepository(connectionFactory),
IBreweryRepository
{
/// <inheritdoc/>

View File

@@ -13,7 +13,7 @@
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
</ItemGroup>

View File

@@ -1,9 +1,8 @@
using System.Data.Common;
using Dapper;
using Domain.Entities;
using Database.Connection;
using Domain.Exceptions;
using Features.Locations.Dtos;
using Infrastructure.Sql;
namespace Features.Locations.Repository;
@@ -11,7 +10,7 @@ namespace Features.Locations.Repository;
/// Dapper-based implementation of <see cref="ILocationRepository" />.
/// </summary>
public class LocationRepository(ISqlConnectionFactory connectionFactory)
: Repository<City>(connectionFactory),
: Database.Connection.DapperRepository(connectionFactory),
ILocationRepository
{
/// <inheritdoc />

View File

@@ -13,7 +13,7 @@
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
</ItemGroup>

View File

@@ -1,8 +1,8 @@
using System.Data.Common;
using Dapper;
using Database.Connection;
using Domain.Entities;
using Domain.Exceptions;
using Infrastructure.Sql;
namespace Features.UserManagement.Repository;
@@ -10,7 +10,7 @@ namespace Features.UserManagement.Repository;
/// Dapper-based implementation of <see cref="IUserAccountRepository" />.
/// </summary>
public class UserAccountRepository(ISqlConnectionFactory connectionFactory)
: Repository<UserAccount>(connectionFactory),
: Database.Connection.DapperRepository(connectionFactory),
IUserAccountRepository
{
/// <inheritdoc />