Added unit testing with xunit
parent
919ab053bc
commit
3f0412e7c5
|
@ -8,6 +8,7 @@ namespace Tuitio.Domain.Data.DbContexts
|
||||||
{
|
{
|
||||||
public class TuitioDbContext : DbContext
|
public class TuitioDbContext : DbContext
|
||||||
{
|
{
|
||||||
|
public DbSet<UserStatus> UserStatuses{ get; set; }
|
||||||
public DbSet<AppUser> Users { get; set; }
|
public DbSet<AppUser> Users { get; set; }
|
||||||
public DbSet<UserToken> UserTokens { get; set; }
|
public DbSet<UserToken> UserTokens { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,7 @@ namespace Tuitio.Extensions
|
||||||
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>));
|
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>));
|
||||||
|
|
||||||
// AutoMapper
|
// AutoMapper
|
||||||
services.AddAutoMapper(
|
services.AddAutoMapper(typeof(Application.Mappings.MappingProfile).Assembly);
|
||||||
typeof(Application.Mappings.MappingProfile).Assembly);
|
|
||||||
|
|
||||||
// Swagger
|
// Swagger
|
||||||
services.AddSwagger("Tuitio API", AuthorizationType.None);
|
services.AddSwagger("Tuitio API", AuthorizationType.None);
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
using MediatR;
|
||||||
|
using MediatR.Pipeline;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Data.Common;
|
||||||
|
using Tuitio.Domain.Data.DbContexts;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Tuitio.Application.Tests.Fixtures
|
||||||
|
{
|
||||||
|
public class DependencyInjectionFixture : IAsyncLifetime
|
||||||
|
{
|
||||||
|
private readonly Guid _prefix;
|
||||||
|
private readonly List<SqliteConnection> _connections;
|
||||||
|
public readonly IServiceProvider ServiceProvider;
|
||||||
|
|
||||||
|
public DependencyInjectionFixture()
|
||||||
|
{
|
||||||
|
_prefix = Guid.NewGuid();
|
||||||
|
_connections = new List<SqliteConnection>();
|
||||||
|
ServiceProvider = BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DisposeAsync()
|
||||||
|
{
|
||||||
|
foreach (var con in _connections)
|
||||||
|
await con.DisposeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
await PrepareDbAsync();
|
||||||
|
SeedData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IServiceProvider BuildServiceProvider()
|
||||||
|
{
|
||||||
|
var configurationBuilder = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
|
.AddJsonFile("appsettings.tuitio.json", optional: true, reloadOnChange: false);
|
||||||
|
var configuration = configurationBuilder.Build();
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddSingleton<IConfiguration>(configuration);
|
||||||
|
services.AddLogging(builder =>
|
||||||
|
{
|
||||||
|
builder.SetMinimumLevel(LogLevel.Warning);
|
||||||
|
builder.AddFilter("Microsoft.*", LogLevel.Warning);
|
||||||
|
});
|
||||||
|
|
||||||
|
// MediatR
|
||||||
|
services.AddMediatR(typeof(Application.CommandHandlers.AccountLoginHandler).Assembly);
|
||||||
|
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>));
|
||||||
|
|
||||||
|
// AutoMapper
|
||||||
|
services.AddAutoMapper(typeof(Application.Mappings.MappingProfile).Assembly);
|
||||||
|
|
||||||
|
services.AddDbContext<TuitioDbContext>(options =>
|
||||||
|
{
|
||||||
|
options.UseSqlite(GetOrAddInMemoryDatabase("TuitioDbContext"));
|
||||||
|
options.EnableSensitiveDataLogging();
|
||||||
|
options.EnableDetailedErrors();
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddApplicationServices();
|
||||||
|
|
||||||
|
var provider = services.BuildServiceProvider();
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DbConnection GetOrAddInMemoryDatabase(string code)
|
||||||
|
{
|
||||||
|
var connection = new SqliteConnection($"Data Source={_prefix}{code};Mode=Memory;Cache=shared");
|
||||||
|
_connections.Add(connection);
|
||||||
|
connection.Open();
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PrepareDbAsync()
|
||||||
|
{
|
||||||
|
var result = await ServiceProvider.GetRequiredService<TuitioDbContext>().Database.EnsureCreatedAsync();
|
||||||
|
if (!result) throw new Exception("TuitioDbContext exists");
|
||||||
|
|
||||||
|
var script = await File.ReadAllTextAsync("seed.sql");
|
||||||
|
await ServiceProvider.GetRequiredService<TuitioDbContext>().Database.ExecuteSqlRawAsync(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SeedData()
|
||||||
|
{
|
||||||
|
using var dbContext = ServiceProvider.GetRequiredService<TuitioDbContext>();
|
||||||
|
|
||||||
|
dbContext.UserStatuses.AddRange(new Domain.Entities.UserStatus[]
|
||||||
|
{
|
||||||
|
new Domain.Entities.UserStatus()
|
||||||
|
{
|
||||||
|
StatusId = 1,
|
||||||
|
StatusCode = "ACTIVE",
|
||||||
|
StatusName = "Active"
|
||||||
|
},
|
||||||
|
new Domain.Entities.UserStatus()
|
||||||
|
{
|
||||||
|
StatusId = 2,
|
||||||
|
StatusCode = "INACTIVE",
|
||||||
|
StatusName = "Inactive"
|
||||||
|
},
|
||||||
|
new Domain.Entities.UserStatus()
|
||||||
|
{
|
||||||
|
StatusId = 3,
|
||||||
|
StatusCode = "BLOCKED",
|
||||||
|
StatusName = "Blocked"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
using Tuitio.Application.Services;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Tuitio.Application.Tests
|
||||||
|
{
|
||||||
|
public class HashingServiceTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void HashSha256_ShouldReturnCorrectHash()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = "9B8769A4A742959A2D0298C36FB70623F2DFACDA8436237DF08D8DFD5B37374C";
|
||||||
|
var hashingService = new HashingService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actual = hashingService.HashSha256("pass123");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Tuitio.Application.Tests.Fixtures;
|
||||||
|
using Tuitio.Domain.Data.DbContexts;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Tuitio.Application.Tests
|
||||||
|
{
|
||||||
|
public class LocalSqliteDatabaseTests : IClassFixture<DependencyInjectionFixture>, IDisposable
|
||||||
|
{
|
||||||
|
private readonly IServiceScope _tuitioScope;
|
||||||
|
|
||||||
|
public LocalSqliteDatabaseTests(DependencyInjectionFixture fixture)
|
||||||
|
{
|
||||||
|
_tuitioScope = fixture.ServiceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_tuitioScope.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EnsureExpectedDataInLocalDbTest()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var _dbContext = _tuitioScope.ServiceProvider.GetRequiredService<TuitioDbContext>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var statuses = await _dbContext.UserStatuses.ToArrayAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotEmpty(statuses);
|
||||||
|
Assert.True(statuses.All(z => !string.IsNullOrWhiteSpace(z.StatusCode)));
|
||||||
|
Assert.Contains(statuses, z => z.StatusCode == "ACTIVE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Tuitio.Application.Services.Abstractions;
|
||||||
|
using Tuitio.Application.Tests.Fixtures;
|
||||||
|
using Tuitio.Domain.Entities;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Tuitio.Application.Tests
|
||||||
|
{
|
||||||
|
public class TokenServiceTests : IClassFixture<DependencyInjectionFixture>, IDisposable
|
||||||
|
{
|
||||||
|
private readonly IServiceScope _tuitioScope;
|
||||||
|
private readonly AppUser _userMock;
|
||||||
|
|
||||||
|
public TokenServiceTests(DependencyInjectionFixture fixture)
|
||||||
|
{
|
||||||
|
_tuitioScope = fixture.ServiceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope();
|
||||||
|
_userMock = MockAppUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppUser MockAppUser()
|
||||||
|
{
|
||||||
|
var user = new AppUser()
|
||||||
|
{
|
||||||
|
UserId = 0,
|
||||||
|
UserName = "tuitio.test",
|
||||||
|
Password = "9B8769A4A742959A2D0298C36FB70623F2DFACDA8436237DF08D8DFD5B37374C", //pass123
|
||||||
|
Email = "tuitio.test@test.test",
|
||||||
|
FirstName = "tuitio",
|
||||||
|
LastName = "test",
|
||||||
|
StatusId = 1,
|
||||||
|
FailedLoginAttempts = 0,
|
||||||
|
SecurityStamp = "A93650FF-1FC4-4999-BAB6-3EEB174F6892",
|
||||||
|
CreationDate = DateTime.Now
|
||||||
|
};
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_tuitioScope.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TokenGenerationTest()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var _tokenService = _tuitioScope.ServiceProvider.GetRequiredService<ITokenService>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var token = _tokenService.GenerateToken(_userMock);
|
||||||
|
var raw = _tokenService.GenerateTokenRaw(token);
|
||||||
|
var extracted = _tokenService.ExtractToken(raw);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(token);
|
||||||
|
Assert.NotNull(raw);
|
||||||
|
Assert.NotNull(extracted);
|
||||||
|
|
||||||
|
Assert.True(_userMock.UserName == extracted.UserName);
|
||||||
|
Assert.True(_userMock.SecurityStamp == extracted.SecurityStamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
@ -7,7 +7,23 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<None Remove="appsettings.tuitio.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="appsettings.tuitio.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="$(AutoMapperExtensionsPackageVersion)" />
|
||||||
|
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="$(MediatRPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.14" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||||
|
<PackageReference Include="Moq" Version="4.18.4" />
|
||||||
<PackageReference Include="xunit" Version="2.4.2" />
|
<PackageReference Include="xunit" Version="2.4.2" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
@ -17,6 +33,13 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\src\Tuitio.Application\Tuitio.Application.csproj" />
|
<ProjectReference Include="..\..\..\src\Tuitio.Application\Tuitio.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\src\Tuitio.Domain.Data\Tuitio.Domain.Data.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="seed.sql">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Restrictions": {
|
||||||
|
"MaxFailedLoginAttempts": 5
|
||||||
|
},
|
||||||
|
"Token": {
|
||||||
|
"ValidityInMinutes": 43800
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
SELECT 'empty'
|
||||||
|
|
Loading…
Reference in New Issue