Add initial implementation of API, database, and user management components.

This commit is contained in:
2026-03-15 23:17:51 +01:00
commit 0543120f3b
53 changed files with 2241 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
using MikrocopApi.Dtos;
using MikrocopApi.Exceptions;
using MikrocopApi.Services;
using MikrocopDb.Entities;
using MikrocopDb.Repositories;
using Moq;
namespace MikrocopTests;
[TestFixture]
public sealed class AuthServiceTests
{
[Test]
public async Task LoginAsync_ReturnsToken_WhenCredentialsAreValid()
{
var user = CreateUser();
var repositoryMock = new Mock<IUserRepository>(MockBehavior.Strict);
var hashingServiceMock = new Mock<IPasswordHashingService>(MockBehavior.Strict);
var jwtTokenServiceMock = new Mock<IJwtTokenService>(MockBehavior.Strict);
var request = new LoginRequestDto { UserName = user.UserName, Password = "ValidPassword!123" };
var expectedExpiry = DateTime.UtcNow.AddHours(1);
repositoryMock
.Setup(x => x.GetByUserNameAsync(request.UserName, It.IsAny<CancellationToken>()))
.ReturnsAsync(user);
hashingServiceMock
.Setup(x => x.VerifyPassword(request.Password, user.PasswordHash, user.PasswordSalt))
.Returns(true);
jwtTokenServiceMock
.Setup(x => x.Generate(user))
.Returns(("token-value", expectedExpiry));
var sut = new AuthService(repositoryMock.Object, hashingServiceMock.Object, jwtTokenServiceMock.Object);
var result = await sut.LoginAsync(request);
Assert.That(result.AccessToken, Is.EqualTo("token-value"));
Assert.That(result.ExpiresAtUtc, Is.EqualTo(expectedExpiry));
Assert.That(result.TokenType, Is.EqualTo("Bearer"));
repositoryMock.VerifyAll();
hashingServiceMock.VerifyAll();
jwtTokenServiceMock.VerifyAll();
}
[Test]
public void LoginAsync_ThrowsUnauthorized_WhenUserDoesNotExist()
{
var repositoryMock = new Mock<IUserRepository>(MockBehavior.Strict);
var hashingServiceMock = new Mock<IPasswordHashingService>(MockBehavior.Strict);
var jwtTokenServiceMock = new Mock<IJwtTokenService>(MockBehavior.Strict);
var request = new LoginRequestDto { UserName = "missing", Password = "ValidPassword!123" };
repositoryMock
.Setup(x => x.GetByUserNameAsync(request.UserName, It.IsAny<CancellationToken>()))
.ReturnsAsync((UserEntity?)null);
var sut = new AuthService(repositoryMock.Object, hashingServiceMock.Object, jwtTokenServiceMock.Object);
Assert.ThrowsAsync<UnauthorizedException>(() => sut.LoginAsync(request));
repositoryMock.VerifyAll();
hashingServiceMock.VerifyNoOtherCalls();
jwtTokenServiceMock.VerifyNoOtherCalls();
}
[Test]
public void LoginAsync_ThrowsUnauthorized_WhenPasswordIsInvalid()
{
var user = CreateUser();
var repositoryMock = new Mock<IUserRepository>(MockBehavior.Strict);
var hashingServiceMock = new Mock<IPasswordHashingService>(MockBehavior.Strict);
var jwtTokenServiceMock = new Mock<IJwtTokenService>(MockBehavior.Strict);
var request = new LoginRequestDto { UserName = user.UserName, Password = "WrongPassword!123" };
repositoryMock
.Setup(x => x.GetByUserNameAsync(request.UserName, It.IsAny<CancellationToken>()))
.ReturnsAsync(user);
hashingServiceMock
.Setup(x => x.VerifyPassword(request.Password, user.PasswordHash, user.PasswordSalt))
.Returns(false);
var sut = new AuthService(repositoryMock.Object, hashingServiceMock.Object, jwtTokenServiceMock.Object);
Assert.ThrowsAsync<UnauthorizedException>(() => sut.LoginAsync(request));
repositoryMock.VerifyAll();
hashingServiceMock.VerifyAll();
jwtTokenServiceMock.VerifyNoOtherCalls();
}
private static UserEntity CreateUser()
{
return new UserEntity
{
Id = Guid.NewGuid(),
UserName = "test-user",
FullName = "Test User",
Email = "test@example.com",
MobileNumber = "+38640111222",
Language = "en",
Culture = "en-US",
PasswordHash = "hash",
PasswordSalt = "salt"
};
}
}