Added unit tests
parent
3f0412e7c5
commit
25baaa0a67
|
@ -31,6 +31,8 @@ namespace Tuitio.Application.Services
|
|||
|
||||
public async Task<LoginResult> Login(string userName, string password)
|
||||
{
|
||||
ValidateCredentials(userName, password);
|
||||
|
||||
var passwordHash = _hashingService.HashSha256(password);
|
||||
var user = await _userRepository.GetUser(userName, passwordHash);
|
||||
if (user == null)
|
||||
|
@ -69,6 +71,15 @@ namespace Tuitio.Application.Services
|
|||
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)
|
||||
{
|
||||
if (user == null)
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
// 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.Repositories;
|
||||
using Tuitio.Domain.Repositories;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Tuitio.Domain.Data
|
||||
{
|
||||
public static class DependencyInjectionExtensions
|
||||
{
|
||||
public static void AddDataAccess(this IServiceCollection services)
|
||||
public static void AddDataAccessServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IUserRepository, UserRepository>();
|
||||
}
|
||||
|
||||
public static void AddDataAccess(this IServiceCollection services)
|
||||
{
|
||||
services.AddDataAccessServices();
|
||||
services
|
||||
.AddDbContextPool<TuitioDbContext>(
|
||||
(serviceProvider, options) =>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
using MediatR;
|
||||
// Copyright (c) 2020 Tudor Stanciu
|
||||
|
||||
using MediatR;
|
||||
using MediatR.Pipeline;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -6,6 +8,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Data.Common;
|
||||
using Tuitio.Domain.Data;
|
||||
using Tuitio.Domain.Data.DbContexts;
|
||||
using Xunit;
|
||||
|
||||
|
@ -65,6 +68,7 @@ namespace Tuitio.Application.Tests.Fixtures
|
|||
options.EnableDetailedErrors();
|
||||
});
|
||||
|
||||
services.AddDataAccessServices();
|
||||
services.AddApplicationServices();
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
@ -92,6 +96,7 @@ namespace Tuitio.Application.Tests.Fixtures
|
|||
{
|
||||
using var dbContext = ServiceProvider.GetRequiredService<TuitioDbContext>();
|
||||
|
||||
#region UserStatus
|
||||
dbContext.UserStatuses.AddRange(new Domain.Entities.UserStatus[]
|
||||
{
|
||||
new Domain.Entities.UserStatus()
|
||||
|
@ -113,6 +118,23 @@ namespace Tuitio.Application.Tests.Fixtures
|
|||
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();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using Tuitio.Application.Services;
|
||||
// Copyright (c) 2020 Tudor Stanciu
|
||||
|
||||
using Tuitio.Application.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace Tuitio.Application.Tests
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
// Copyright (c) 2020 Tudor Stanciu
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Tuitio.Application.Tests.Fixtures;
|
||||
using Tuitio.Domain.Data.DbContexts;
|
||||
|
|
|
@ -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.Tests.Fixtures;
|
||||
using Tuitio.Domain.Entities;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using Tuitio.Application.Stores;
|
||||
// Copyright (c) 2020 Tudor Stanciu
|
||||
|
||||
using Tuitio.Application.Stores;
|
||||
using Tuitio.Domain.Models;
|
||||
using Xunit;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue