mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 17:47:22 +00:00
Migrate Auth and Emails to vertical slices (Features.Auth, Features.Emails)
Auth and Emails land together since Auth's registration/resend flows need Emails to exist first. Service.Auth's four services (Register, Login, Confirmation, Token) collapse into MediatR commands/queries in Features.Auth; TokenService stays as a slice-internal service since multiple handlers call it. Auth no longer references Emails directly — it sends SendRegistrationEmailCommand/SendResendConfirmationEmailCommand (defined in Shared.Application) which Features.Emails handles, so neither slice has a project reference on the other. Service.Emails' IEmailService becomes Features.Emails' IEmailDispatcher, simplified to take (firstName, email, token) instead of a full UserAccount since that's all it ever used. API.Specs' TestApiFactory/MockEmailService are updated to swap in the relocated interface. Also deletes Infrastructure.Repository now that Breweries, UserManagement, and Auth have all moved their repos out of it, and replaces the two now-dead docker-compose test services (repository.tests, service.auth.tests, both pointing at deleted Dockerfiles) with a single unit.tests service that runs every Features.*.Tests project via Dockerfile.tests.
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
using Apps72.Dev.Data.DbMocker;
|
||||
using FluentAssertions;
|
||||
using Features.Auth.Repository;
|
||||
|
||||
namespace Features.Auth.Tests.Repository;
|
||||
|
||||
public class AuthRepositoryTests
|
||||
{
|
||||
private static AuthRepository CreateRepo(MockDbConnection conn) =>
|
||||
new(new TestConnectionFactory(conn));
|
||||
|
||||
[Fact]
|
||||
public async Task RegisterUserAsync_CreatesUserWithCredential_ReturnsUserAccount()
|
||||
{
|
||||
var expectedUserId = Guid.NewGuid();
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "USP_RegisterUser")
|
||||
.ReturnsScalar(expectedUserId);
|
||||
|
||||
// Mock the subsequent read for the newly created user by id
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountById")
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Username", typeof(string)),
|
||||
("FirstName", typeof(string)),
|
||||
("LastName", typeof(string)),
|
||||
("Email", typeof(string)),
|
||||
("CreatedAt", typeof(DateTime)),
|
||||
("UpdatedAt", typeof(DateTime?)),
|
||||
("DateOfBirth", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
)
|
||||
.AddRow(
|
||||
expectedUserId,
|
||||
"testuser",
|
||||
"Test",
|
||||
"User",
|
||||
"test@example.com",
|
||||
DateTime.UtcNow,
|
||||
null,
|
||||
new DateTime(1990, 1, 1),
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.RegisterUserAsync(
|
||||
username: "testuser",
|
||||
firstName: "Test",
|
||||
lastName: "User",
|
||||
email: "test@example.com",
|
||||
dateOfBirth: new DateTime(1990, 1, 1),
|
||||
passwordHash: "hashedpassword123"
|
||||
);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result.UserAccountId.Should().Be(expectedUserId);
|
||||
result.Username.Should().Be("testuser");
|
||||
result.FirstName.Should().Be("Test");
|
||||
result.LastName.Should().Be("User");
|
||||
result.Email.Should().Be("test@example.com");
|
||||
result.DateOfBirth.Should().Be(new DateTime(1990, 1, 1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUserByEmailAsync_ReturnsUser_WhenExists()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Username", typeof(string)),
|
||||
("FirstName", typeof(string)),
|
||||
("LastName", typeof(string)),
|
||||
("Email", typeof(string)),
|
||||
("CreatedAt", typeof(DateTime)),
|
||||
("UpdatedAt", typeof(DateTime?)),
|
||||
("DateOfBirth", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
)
|
||||
.AddRow(
|
||||
userId,
|
||||
"emailuser",
|
||||
"Email",
|
||||
"User",
|
||||
"emailuser@example.com",
|
||||
DateTime.UtcNow,
|
||||
null,
|
||||
new DateTime(1990, 5, 15),
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetUserByEmailAsync("emailuser@example.com");
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.UserAccountId.Should().Be(userId);
|
||||
result.Username.Should().Be("emailuser");
|
||||
result.Email.Should().Be("emailuser@example.com");
|
||||
result.FirstName.Should().Be("Email");
|
||||
result.LastName.Should().Be("User");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUserByEmailAsync_ReturnsNull_WhenNotExists()
|
||||
{
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
|
||||
.ReturnsTable(MockTable.Empty());
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetUserByEmailAsync("nonexistent@example.com");
|
||||
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUserByUsernameAsync_ReturnsUser_WhenExists()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks.When(cmd =>
|
||||
cmd.CommandText == "usp_GetUserAccountByUsername"
|
||||
)
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Username", typeof(string)),
|
||||
("FirstName", typeof(string)),
|
||||
("LastName", typeof(string)),
|
||||
("Email", typeof(string)),
|
||||
("CreatedAt", typeof(DateTime)),
|
||||
("UpdatedAt", typeof(DateTime?)),
|
||||
("DateOfBirth", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
)
|
||||
.AddRow(
|
||||
userId,
|
||||
"usernameuser",
|
||||
"Username",
|
||||
"User",
|
||||
"username@example.com",
|
||||
DateTime.UtcNow,
|
||||
null,
|
||||
new DateTime(1985, 8, 20),
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetUserByUsernameAsync("usernameuser");
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.UserAccountId.Should().Be(userId);
|
||||
result.Username.Should().Be("usernameuser");
|
||||
result.Email.Should().Be("username@example.com");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUserByUsernameAsync_ReturnsNull_WhenNotExists()
|
||||
{
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks.When(cmd =>
|
||||
cmd.CommandText == "usp_GetUserAccountByUsername"
|
||||
)
|
||||
.ReturnsTable(MockTable.Empty());
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetUserByUsernameAsync("nonexistent");
|
||||
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetActiveCredentialByUserAccountIdAsync_ReturnsCredential_WhenExists()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var credentialId = Guid.NewGuid();
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks.When(cmd =>
|
||||
cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId"
|
||||
)
|
||||
.ReturnsTable(
|
||||
MockTable
|
||||
.WithColumns(
|
||||
("UserCredentialId", typeof(Guid)),
|
||||
("UserAccountId", typeof(Guid)),
|
||||
("Hash", typeof(string)),
|
||||
("CreatedAt", typeof(DateTime)),
|
||||
("Timer", typeof(byte[]))
|
||||
)
|
||||
.AddRow(
|
||||
credentialId,
|
||||
userId,
|
||||
"hashed_password_value",
|
||||
DateTime.UtcNow,
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
|
||||
|
||||
result.Should().NotBeNull();
|
||||
result!.UserCredentialId.Should().Be(credentialId);
|
||||
result.UserAccountId.Should().Be(userId);
|
||||
result.Hash.Should().Be("hashed_password_value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetActiveCredentialByUserAccountIdAsync_ReturnsNull_WhenNotExists()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks.When(cmd =>
|
||||
cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId"
|
||||
)
|
||||
.ReturnsTable(MockTable.Empty());
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
var result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
|
||||
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RotateCredentialAsync_ExecutesSuccessfully()
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var newPasswordHash = "new_hashed_password";
|
||||
var conn = new MockDbConnection();
|
||||
|
||||
conn.Mocks.When(cmd => cmd.CommandText == "USP_RotateUserCredential")
|
||||
.ReturnsScalar(1);
|
||||
|
||||
var repo = CreateRepo(conn);
|
||||
|
||||
// Should not throw
|
||||
var act = async () =>
|
||||
await repo.RotateCredentialAsync(userId, newPasswordHash);
|
||||
await act.Should().NotThrowAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Data.Common;
|
||||
using Infrastructure.Sql;
|
||||
|
||||
namespace Features.Auth.Tests.Repository;
|
||||
|
||||
internal class TestConnectionFactory(DbConnection conn) : ISqlConnectionFactory
|
||||
{
|
||||
private readonly DbConnection _conn = conn;
|
||||
|
||||
public DbConnection CreateConnection() => _conn;
|
||||
}
|
||||
Reference in New Issue
Block a user