Added unit tests

master
Tudor Stanciu 2023-03-13 18:36:59 +02:00
parent 3f0412e7c5
commit 25baaa0a67
9 changed files with 318 additions and 10 deletions

View File

@ -31,6 +31,8 @@ namespace Tuitio.Application.Services
public async Task<LoginResult> Login(string userName, string password) public async Task<LoginResult> Login(string userName, string password)
{ {
ValidateCredentials(userName, password);
var passwordHash = _hashingService.HashSha256(password); var passwordHash = _hashingService.HashSha256(password);
var user = await _userRepository.GetUser(userName, passwordHash); var user = await _userRepository.GetUser(userName, passwordHash);
if (user == null) if (user == null)
@ -69,6 +71,15 @@ namespace Tuitio.Application.Services
return token; return token;
} }
private void ValidateCredentials(string userName, string password)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentException($"Value cannot be null or empty string.", nameof(userName));
if (string.IsNullOrEmpty(password))
throw new ArgumentException($"Value cannot be null or empty string.", nameof(password));
}
private bool ValidateUser(AppUser user) private bool ValidateUser(AppUser user)
{ {
if (user == null) if (user == null)

View File

@ -1,20 +1,24 @@
// Copyright (c) 2020 Tudor Stanciu // Copyright (c) 2020 Tudor Stanciu
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Tuitio.Domain.Data.DbContexts; using Tuitio.Domain.Data.DbContexts;
using Tuitio.Domain.Data.Repositories; using Tuitio.Domain.Data.Repositories;
using Tuitio.Domain.Repositories; using Tuitio.Domain.Repositories;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
namespace Tuitio.Domain.Data namespace Tuitio.Domain.Data
{ {
public static class DependencyInjectionExtensions public static class DependencyInjectionExtensions
{ {
public static void AddDataAccess(this IServiceCollection services) public static void AddDataAccessServices(this IServiceCollection services)
{ {
services.AddScoped<IUserRepository, UserRepository>(); services.AddScoped<IUserRepository, UserRepository>();
}
public static void AddDataAccess(this IServiceCollection services)
{
services.AddDataAccessServices();
services services
.AddDbContextPool<TuitioDbContext>( .AddDbContextPool<TuitioDbContext>(
(serviceProvider, options) => (serviceProvider, options) =>

View File

@ -0,0 +1,66 @@
// Copyright (c) 2020 Tudor Stanciu
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Tuitio.Application.CommandHandlers;
using Tuitio.Application.Tests.Fixtures;
using Tuitio.PublishedLanguage.Constants;
using Xunit;
namespace Tuitio.Application.Tests
{
public class CommandHandlerTests : IClassFixture<DependencyInjectionFixture>, IDisposable
{
private readonly IServiceScope _tuitioScope;
private readonly IMediator _mediator;
public CommandHandlerTests(DependencyInjectionFixture fixture)
{
_tuitioScope = fixture.ServiceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
_mediator = _tuitioScope.ServiceProvider.GetRequiredService<IMediator>();
}
public void Dispose()
{
_tuitioScope.Dispose();
}
[Fact]
public async Task AccountLoginHandler_Handle_Success()
{
// Arrange
var userName = "tuitio.user";
var password = "pass123";
var command = new AccountLoginHandler.Command(userName, password);
// Act
var result = await _mediator.Send(command);
// Assert
Assert.NotNull(result);
Assert.NotNull(result.Result);
Assert.Null(result.Error);
Assert.NotEmpty(result.Result.Token);
Assert.True(result.Result.ExpiresIn > 0, "Token expiration must be a positive number.");
}
[Fact]
public async Task AccountLoginHandler_Handle_Failed()
{
// Arrange
var userName = "tuitio.user";
var password = "wrong_password";
var command = new AccountLoginHandler.Command(userName, password);
// Act
var result = await _mediator.Send(command);
// Assert
Assert.NotNull(result);
Assert.Null(result.Result);
Assert.NotNull(result.Error);
Assert.NotEmpty(result.Error);
Assert.Equal(EnvelopeError.BAD_CREDENTIALS, result.Error);
}
}
}

View File

@ -1,4 +1,6 @@
using MediatR; // Copyright (c) 2020 Tudor Stanciu
using MediatR;
using MediatR.Pipeline; using MediatR.Pipeline;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -6,6 +8,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Data.Common; using System.Data.Common;
using Tuitio.Domain.Data;
using Tuitio.Domain.Data.DbContexts; using Tuitio.Domain.Data.DbContexts;
using Xunit; using Xunit;
@ -65,6 +68,7 @@ namespace Tuitio.Application.Tests.Fixtures
options.EnableDetailedErrors(); options.EnableDetailedErrors();
}); });
services.AddDataAccessServices();
services.AddApplicationServices(); services.AddApplicationServices();
var provider = services.BuildServiceProvider(); var provider = services.BuildServiceProvider();
@ -92,6 +96,7 @@ namespace Tuitio.Application.Tests.Fixtures
{ {
using var dbContext = ServiceProvider.GetRequiredService<TuitioDbContext>(); using var dbContext = ServiceProvider.GetRequiredService<TuitioDbContext>();
#region UserStatus
dbContext.UserStatuses.AddRange(new Domain.Entities.UserStatus[] dbContext.UserStatuses.AddRange(new Domain.Entities.UserStatus[]
{ {
new Domain.Entities.UserStatus() new Domain.Entities.UserStatus()
@ -113,6 +118,23 @@ namespace Tuitio.Application.Tests.Fixtures
StatusName = "Blocked" StatusName = "Blocked"
} }
}); });
#endregion
#region Users
dbContext.Users.Add(new Domain.Entities.AppUser()
{
UserId = 1,
UserName = "tuitio.user",
Password = "9B8769A4A742959A2D0298C36FB70623F2DFACDA8436237DF08D8DFD5B37374C", // pass123
FirstName = "Tuitio",
LastName = "User",
Email = "tuitio.user@tuitio.lab",
SecurityStamp = "A93650FF-1FC4-4999-BAB6-3EEB174F6892",
StatusId = 1,
CreationDate = DateTime.Now,
FailedLoginAttempts = 0
});
#endregion
dbContext.SaveChanges(); dbContext.SaveChanges();
} }

View File

@ -1,4 +1,6 @@
using Tuitio.Application.Services; // Copyright (c) 2020 Tudor Stanciu
using Tuitio.Application.Services;
using Xunit; using Xunit;
namespace Tuitio.Application.Tests namespace Tuitio.Application.Tests

View File

@ -1,4 +1,6 @@
using Microsoft.EntityFrameworkCore; // Copyright (c) 2020 Tudor Stanciu
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Tuitio.Application.Tests.Fixtures; using Tuitio.Application.Tests.Fixtures;
using Tuitio.Domain.Data.DbContexts; using Tuitio.Domain.Data.DbContexts;

View File

@ -1,4 +1,6 @@
using Microsoft.Extensions.DependencyInjection; // Copyright (c) 2020 Tudor Stanciu
using Microsoft.Extensions.DependencyInjection;
using Tuitio.Application.Services.Abstractions; using Tuitio.Application.Services.Abstractions;
using Tuitio.Application.Tests.Fixtures; using Tuitio.Application.Tests.Fixtures;
using Tuitio.Domain.Entities; using Tuitio.Domain.Entities;

View File

@ -1,4 +1,6 @@
using Tuitio.Application.Stores; // Copyright (c) 2020 Tudor Stanciu
using Tuitio.Application.Stores;
using Tuitio.Domain.Models; using Tuitio.Domain.Models;
using Xunit; using Xunit;

View File

@ -0,0 +1,197 @@
// Copyright (c) 2020 Tudor Stanciu
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Tuitio.Application.Services.Abstractions;
using Tuitio.Application.Tests.Fixtures;
using Tuitio.Domain.Data.DbContexts;
using Tuitio.Domain.Models.Account;
using Xunit;
namespace Tuitio.Application.Tests
{
public class UserServiceTests : IClassFixture<DependencyInjectionFixture>, IDisposable
{
private readonly IServiceScope _tuitioScope;
private readonly IUserService _userService;
public UserServiceTests(DependencyInjectionFixture fixture)
{
_tuitioScope = fixture.ServiceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
_userService = _tuitioScope.ServiceProvider.GetRequiredService<IUserService>();
}
public void Dispose()
{
_tuitioScope.Dispose();
}
[Fact]
public async Task Login_ShouldThrowArgumentExceptionIfUserNameIsNullOrEmptyString()
{
// Arrange
var userName = "";
var password = "";
// Act
// Assert
await Assert.ThrowsAsync<ArgumentException>(nameof(userName), () => _userService.Login(userName, password));
await Assert.ThrowsAsync<ArgumentException>(nameof(userName), () => _userService.Login(null, password));
}
[Fact]
public async Task Login_ShouldThrowArgumentExceptionIfPasswordIsNullOrEmptyString()
{
// Arrange
var userName = "tuitio.test.user";
var password = "";
// Act
// Assert
await Assert.ThrowsAsync<ArgumentException>(nameof(password), () => _userService.Login(userName, password));
await Assert.ThrowsAsync<ArgumentException>(nameof(password), () => _userService.Login(userName, null));
}
[Fact]
public async Task Login_ShouldReturnAValidToken()
{
// Arrange
var userName = "tuitio.user";
var password = "pass123";
// Act
var result = await _userService.Login(userName, password);
// Assert
Assert.NotNull(result);
Assert.NotNull(result.Token);
Assert.NotEmpty(result.Raw);
Assert.Equal(userName, result.Token.UserName);
Assert.True(result.Token.TokenId != Guid.Empty, "Token id cannot be an empty guid.");
Assert.NotEmpty(result.Token.LockStamp);
Assert.True(result.Token.ExpiresIn > 0, "Token expiration must be a positive number.");
Assert.True((DateTime.UtcNow - result.Token.CreatedAt).TotalMinutes <= 1, "Token creation date must be within the last minute.");
}
[Fact]
public async Task Login_ShouldSetCorrectLastLoginDate()
{
// Arrange
var userName = "tuitio.user";
var password = "pass123";
var dbContext = _tuitioScope.ServiceProvider.GetRequiredService<TuitioDbContext>();
await _userService.Login(userName, password);
// Act
var userFromDb = await dbContext.Users.FirstOrDefaultAsync(z => z.UserName == userName);
// Assert
Assert.NotNull(userFromDb);
Assert.True(userFromDb.LastLoginDate.HasValue, "Last login date cannot be null after user login");
Assert.True((DateTime.UtcNow - userFromDb.LastLoginDate.Value).TotalMinutes <= 1, "Last login date must be within the last minute.");
}
[Fact]
public async Task Login_ShouldSaveTokenInDatabase()
{
// Arrange
var userName = "tuitio.user";
var password = "pass123";
var dbContext = _tuitioScope.ServiceProvider.GetRequiredService<TuitioDbContext>();
var loginResult = await _userService.Login(userName, password);
// Act
var userTokenFromDb = await dbContext.UserTokens.FirstOrDefaultAsync(z => z.TokenId == loginResult.Token.TokenId);
// Assert
Assert.NotNull(userTokenFromDb);
Assert.True(loginResult.Token.TokenId != Guid.Empty, "Token id cannot be an empty guid.");
Assert.True((DateTime.UtcNow - userTokenFromDb.ValidFrom).TotalMinutes <= 1, "Token valid from date must be within the last minute.");
Assert.True(userTokenFromDb.ValidUntil > DateTime.UtcNow, "Token valid until date must be greater than the current date.");
}
[Fact]
public async Task Login_ShouldReturnNullResultForWrongCredentials()
{
// Arrange
var userName = "tuitio.user";
var password = "wrong_password";
// Act
var result = await _userService.Login(userName, password);
// Assert
Assert.Null(result);
}
[Fact]
public async Task Authorize_ShouldReturnAValidToken()
{
// Arrange
var userName = "tuitio.user";
var password = "pass123";
var loginResult = await _userService.Login(userName, password);
// Act
var result = _userService.Authorize(loginResult.Raw);
// Assert
Assert.NotNull(result);
Assert.Equal(userName, result.UserName);
Assert.NotNull(result.SecurityStamp);
Assert.NotNull(result.LockStamp);
Assert.True(result.TokenId != Guid.Empty, "Token id cannot be an empty guid.");
Assert.True(result.ExpiresIn > 0, "Token expiration must be a positive number.");
}
[Fact]
public void Authorize_ShouldReturnNull()
{
// Arrange
var unauthorizedToken = "unauthorized-token";
// Act
var result = _userService.Authorize(unauthorizedToken);
// Assert
Assert.Null(result);
}
[Fact]
public async Task Logout_ShouldSuccessfullyLogoutTheUser()
{
// Arrange
var userName = "tuitio.user";
var password = "pass123";
var loginResult = await _userService.Login(userName, password);
// Act
LogoutResult result;
using (var scope = _tuitioScope.ServiceProvider.CreateScope())
{
var _newUserService = scope.ServiceProvider.GetRequiredService<IUserService>();
result = await _newUserService.Logout(loginResult.Raw);
}
// Assert
Assert.NotNull(result);
Assert.Equal(userName, result.UserName);
Assert.True((DateTime.UtcNow - result.LogoutDate).TotalMinutes <= 1, "Logout date must be within the last minute.");
}
[Fact]
public async Task Logout_ShouldReturnNullResultForUnauthenticatedToken()
{
// Arrange
var unauthenticatedToken = "unauthenticated-token";
// Act
var result = await _userService.Logout(unauthenticatedToken);
// Assert
Assert.Null(result);
}
}
}