Reorganize tests by API and service concerns
This commit is contained in:
95
RpgRoller.Tests/Support/ApiTestBase.cs
Normal file
95
RpgRoller.Tests/Support/ApiTestBase.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using RpgRoller.Contracts;
|
||||
using RpgRoller.Data;
|
||||
using RpgRoller.Services;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
public abstract class ApiTestBase : IClassFixture<WebApplicationFactory<Program>>
|
||||
{
|
||||
private readonly WebApplicationFactory<Program> m_BaseFactory;
|
||||
|
||||
protected ApiTestBase(WebApplicationFactory<Program> factory)
|
||||
{
|
||||
m_BaseFactory = factory;
|
||||
}
|
||||
|
||||
protected WebApplicationFactory<Program> CreateFactory(params int[] rollValues)
|
||||
{
|
||||
return m_BaseFactory.WithWebHostBuilder(builder =>
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
services.RemoveAll<IDiceRoller>();
|
||||
services.AddSingleton<IDiceRoller>(new FixedDiceRoller(rollValues));
|
||||
|
||||
services.RemoveAll<DbContextOptions<RpgRollerDbContext>>();
|
||||
services.RemoveAll<IDbContextFactory<RpgRollerDbContext>>();
|
||||
services.RemoveAll<RpgRollerDbContext>();
|
||||
|
||||
var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-tests-{Guid.NewGuid():N}.db");
|
||||
services.AddDbContextFactory<RpgRollerDbContext>(options => options.UseSqlite($"Data Source={dbPath}"));
|
||||
}));
|
||||
}
|
||||
|
||||
protected static async Task<UserSummary> RegisterAsync(HttpClient client, string username, string password, string displayName)
|
||||
{
|
||||
return await PostAsync<RegisterRequest, UserSummary>(
|
||||
client,
|
||||
"/api/auth/register",
|
||||
new RegisterRequest(username, password, displayName));
|
||||
}
|
||||
|
||||
protected static async Task LoginAsync(HttpClient client, string username, string password)
|
||||
{
|
||||
var response = await client.PostAsJsonAsync("/api/auth/login", new LoginRequest(username, password));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
protected static async Task<TResponse> PostAsync<TRequest, TResponse>(HttpClient client, string uri, TRequest payload)
|
||||
{
|
||||
var response = await client.PostAsJsonAsync(uri, payload);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<TResponse>();
|
||||
Assert.NotNull(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static async Task<TResponse> PutAsync<TRequest, TResponse>(HttpClient client, string uri, TRequest payload)
|
||||
{
|
||||
var response = await client.PutAsJsonAsync(uri, payload);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<TResponse>();
|
||||
Assert.NotNull(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static async Task<TResponse> GetAsync<TResponse>(HttpClient client, string uri)
|
||||
{
|
||||
var response = await client.GetAsync(uri);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<TResponse>();
|
||||
Assert.NotNull(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private sealed class FixedDiceRoller : IDiceRoller
|
||||
{
|
||||
private readonly Queue<int> m_Values;
|
||||
|
||||
public FixedDiceRoller(IEnumerable<int> values)
|
||||
{
|
||||
m_Values = new Queue<int>(values);
|
||||
}
|
||||
|
||||
public int Roll(int sides)
|
||||
{
|
||||
var next = m_Values.Count > 0 ? m_Values.Dequeue() : 1;
|
||||
return Math.Clamp(next, 1, sides);
|
||||
}
|
||||
}
|
||||
}
|
||||
129
RpgRoller.Tests/Support/ServiceTestSupport.cs
Normal file
129
RpgRoller.Tests/Support/ServiceTestSupport.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RpgRoller.Data;
|
||||
using RpgRoller.Domain;
|
||||
using RpgRoller.Services;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
internal static class ServiceTestSupport
|
||||
{
|
||||
internal static ServiceHarness CreateHarness(params int[] rollValues)
|
||||
{
|
||||
return CreateHarness(new PasswordHasher<UserAccount>(), rollValues);
|
||||
}
|
||||
|
||||
internal static ServiceHarness CreateHarness(IPasswordHasher<UserAccount> passwordHasher, params int[] rollValues)
|
||||
{
|
||||
var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-servicetests-{Guid.NewGuid():N}.db");
|
||||
return CreateHarnessFromPath(dbPath, passwordHasher, rollValues);
|
||||
}
|
||||
|
||||
internal static ServiceHarness CreateHarnessFromPath(string dbPath, params int[] rollValues)
|
||||
{
|
||||
return CreateHarnessFromPath(dbPath, new PasswordHasher<UserAccount>(), rollValues);
|
||||
}
|
||||
|
||||
internal static ServiceHarness CreateHarnessFromPath(string dbPath, IPasswordHasher<UserAccount> passwordHasher, params int[] rollValues)
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<RpgRollerDbContext>()
|
||||
.UseSqlite($"Data Source={dbPath}")
|
||||
.Options;
|
||||
|
||||
using (var db = new RpgRollerDbContext(options))
|
||||
{
|
||||
db.Database.EnsureCreated();
|
||||
}
|
||||
|
||||
var factory = new SqliteDbContextFactory(dbPath);
|
||||
var service = new GameService(factory, passwordHasher, new FixedDiceRoller(rollValues));
|
||||
return new ServiceHarness(service, factory, dbPath);
|
||||
}
|
||||
|
||||
internal static T GetValue<T>(ServiceResult<T> result)
|
||||
{
|
||||
Assert.True(result.Succeeded);
|
||||
Assert.NotNull(result.Value);
|
||||
return result.Value!;
|
||||
}
|
||||
|
||||
internal sealed class ServiceHarness : IDisposable
|
||||
{
|
||||
private readonly SqliteDbContextFactory m_Factory;
|
||||
|
||||
internal ServiceHarness(GameService service, SqliteDbContextFactory factory, string dbPath)
|
||||
{
|
||||
Service = service;
|
||||
m_Factory = factory;
|
||||
DbPath = dbPath;
|
||||
}
|
||||
|
||||
public GameService Service { get; }
|
||||
public string DbPath { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_Factory.Dispose();
|
||||
}
|
||||
|
||||
public RpgRollerDbContext CreateDbContext()
|
||||
{
|
||||
return m_Factory.CreateDbContext();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RehashingPasswordHasher : IPasswordHasher<UserAccount>
|
||||
{
|
||||
public int HashCalls { get; private set; }
|
||||
|
||||
public string HashPassword(UserAccount user, string password)
|
||||
{
|
||||
HashCalls += 1;
|
||||
return $"hash:{HashCalls}:{password}";
|
||||
}
|
||||
|
||||
public PasswordVerificationResult VerifyHashedPassword(UserAccount user, string hashedPassword, string providedPassword)
|
||||
{
|
||||
return providedPassword == "Password123"
|
||||
? PasswordVerificationResult.SuccessRehashNeeded
|
||||
: PasswordVerificationResult.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FixedDiceRoller : IDiceRoller
|
||||
{
|
||||
private readonly Queue<int> m_Values;
|
||||
|
||||
public FixedDiceRoller(IEnumerable<int> values)
|
||||
{
|
||||
m_Values = new Queue<int>(values);
|
||||
}
|
||||
|
||||
public int Roll(int sides)
|
||||
{
|
||||
var next = m_Values.Count > 0 ? m_Values.Dequeue() : 1;
|
||||
return Math.Clamp(next, 1, sides);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SqliteDbContextFactory : IDbContextFactory<RpgRollerDbContext>, IDisposable
|
||||
{
|
||||
private readonly DbContextOptions<RpgRollerDbContext> m_Options;
|
||||
|
||||
public SqliteDbContextFactory(string dbPath)
|
||||
{
|
||||
m_Options = new DbContextOptionsBuilder<RpgRollerDbContext>()
|
||||
.UseSqlite($"Data Source={dbPath}")
|
||||
.Options;
|
||||
}
|
||||
|
||||
public RpgRollerDbContext CreateDbContext()
|
||||
{
|
||||
return new RpgRollerDbContext(m_Options);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
14
RpgRoller.Tests/Support/TestWebHostEnvironment.cs
Normal file
14
RpgRoller.Tests/Support/TestWebHostEnvironment.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
internal sealed class TestWebHostEnvironment : IWebHostEnvironment
|
||||
{
|
||||
public string ApplicationName { get; set; } = "RpgRoller.Tests";
|
||||
public IFileProvider WebRootFileProvider { get; set; } = new NullFileProvider();
|
||||
public string WebRootPath { get; set; } = string.Empty;
|
||||
public string EnvironmentName { get; set; } = "Development";
|
||||
public string ContentRootPath { get; set; } = string.Empty;
|
||||
public IFileProvider ContentRootFileProvider { get; set; } = new NullFileProvider();
|
||||
}
|
||||
Reference in New Issue
Block a user