Code cleanup
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
public sealed class AuthApiTests : ApiTestBase
|
||||
{
|
||||
public AuthApiTests(WebApplicationFactory<Program> factory)
|
||||
: base(factory)
|
||||
public AuthApiTests(WebApplicationFactory<Program> factory) : base(factory)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -13,7 +10,7 @@ public sealed class AuthApiTests : ApiTestBase
|
||||
public async Task RegisterLoginAndMeFlow_WorksWithDuplicateUsernameGuard()
|
||||
{
|
||||
using var factory = CreateFactory(4, 4, 4);
|
||||
using var client = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
using var client = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||
|
||||
var registerResult = await RegisterAsync(client, "alice", "Password123", "Alice");
|
||||
Assert.Equal("alice", registerResult.Username);
|
||||
@@ -32,4 +29,4 @@ public sealed class AuthApiTests : ApiTestBase
|
||||
var invalidLogin = await client.PostAsJsonAsync("/api/auth/login", new LoginRequest("alice", "wrong-password"));
|
||||
Assert.Equal(HttpStatusCode.BadRequest, invalidLogin.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
public sealed class CampaignApiTests : ApiTestBase
|
||||
{
|
||||
public CampaignApiTests(WebApplicationFactory<Program> factory)
|
||||
: base(factory)
|
||||
public CampaignApiTests(WebApplicationFactory<Program> factory) : base(factory)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -13,44 +10,30 @@ public sealed class CampaignApiTests : ApiTestBase
|
||||
public async Task CampaignCharacterAndSkillFlow_EnforcesRulesetValidation()
|
||||
{
|
||||
using var factory = CreateFactory(6, 6, 6);
|
||||
using var gmClient = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
using var gmClient = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||
|
||||
await RegisterAsync(gmClient, "gm", "Password123", "Game Master");
|
||||
await LoginAsync(gmClient, "gm", "Password123");
|
||||
|
||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(
|
||||
gmClient,
|
||||
"/api/campaigns",
|
||||
new CreateCampaignRequest("Alpha Campaign", "dnd5e"));
|
||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Alpha Campaign", "dnd5e"));
|
||||
|
||||
var gmCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(
|
||||
gmClient,
|
||||
"/api/characters",
|
||||
new CreateCharacterRequest("Arin", campaign.Id));
|
||||
var gmCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(gmClient, "/api/characters", new("Arin", campaign.Id));
|
||||
|
||||
var activateResponse = await gmClient.PostAsync($"/api/characters/{gmCharacter.Id}/activate", null);
|
||||
Assert.Equal(HttpStatusCode.OK, activateResponse.StatusCode);
|
||||
|
||||
var createdSkill = await PostAsync<CreateSkillRequest, SkillSummary>(
|
||||
gmClient,
|
||||
$"/api/characters/{gmCharacter.Id}/skills",
|
||||
new CreateSkillRequest("Arcana", "2d12+2", 0, false));
|
||||
var createdSkill = await PostAsync<CreateSkillRequest, SkillSummary>(gmClient, $"/api/characters/{gmCharacter.Id}/skills", new("Arcana", "2d12+2", 0, false));
|
||||
Assert.Equal("2d12+2", createdSkill.DiceRollDefinition);
|
||||
Assert.Equal(0, createdSkill.WildDice);
|
||||
Assert.False(createdSkill.AllowFumble);
|
||||
|
||||
var updatedSkill = await PutAsync<UpdateSkillRequest, SkillSummary>(
|
||||
gmClient,
|
||||
$"/api/skills/{createdSkill.Id}",
|
||||
new UpdateSkillRequest("Arcana Mastery", "2d12+3", 0, false));
|
||||
var updatedSkill = await PutAsync<UpdateSkillRequest, SkillSummary>(gmClient, $"/api/skills/{createdSkill.Id}", new("Arcana Mastery", "2d12+3", 0, false));
|
||||
Assert.Equal("Arcana Mastery", updatedSkill.Name);
|
||||
Assert.Equal("2d12+3", updatedSkill.DiceRollDefinition);
|
||||
Assert.Equal(0, updatedSkill.WildDice);
|
||||
Assert.False(updatedSkill.AllowFumble);
|
||||
|
||||
var invalidSkill = await gmClient.PostAsJsonAsync(
|
||||
$"/api/characters/{gmCharacter.Id}/skills",
|
||||
new CreateSkillRequest("Broken", "5D+4", 0, false));
|
||||
var invalidSkill = await gmClient.PostAsJsonAsync($"/api/characters/{gmCharacter.Id}/skills", new CreateSkillRequest("Broken", "5D+4", 0, false));
|
||||
Assert.Equal(HttpStatusCode.BadRequest, invalidSkill.StatusCode);
|
||||
|
||||
var details = await GetAsync<CampaignDetails>(gmClient, $"/api/campaigns/{campaign.Id}");
|
||||
@@ -62,17 +45,11 @@ public sealed class CampaignApiTests : ApiTestBase
|
||||
Assert.Single(currentCampaignCharacters);
|
||||
Assert.Equal(gmCharacter.Id, currentCampaignCharacters[0].Id);
|
||||
|
||||
var otherCampaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(
|
||||
gmClient,
|
||||
"/api/campaigns",
|
||||
new CreateCampaignRequest("Beta Campaign", "d6"));
|
||||
var otherCampaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Beta Campaign", "d6"));
|
||||
|
||||
var updatedCharacter = await PutAsync<UpdateCharacterRequest, CharacterSummary>(
|
||||
gmClient,
|
||||
$"/api/characters/{gmCharacter.Id}",
|
||||
new UpdateCharacterRequest("Arin Updated", otherCampaign.Id));
|
||||
var updatedCharacter = await PutAsync<UpdateCharacterRequest, CharacterSummary>(gmClient, $"/api/characters/{gmCharacter.Id}", new("Arin Updated", otherCampaign.Id));
|
||||
|
||||
Assert.Equal("Arin Updated", updatedCharacter.Name);
|
||||
Assert.Equal(otherCampaign.Id, updatedCharacter.CampaignId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
public sealed class FrontendHostTests : ApiTestBase
|
||||
{
|
||||
public FrontendHostTests(WebApplicationFactory<Program> factory)
|
||||
: base(factory)
|
||||
public FrontendHostTests(WebApplicationFactory<Program> factory) : base(factory)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -13,7 +10,7 @@ public sealed class FrontendHostTests : ApiTestBase
|
||||
public async Task RootPath_ServesBlazorFrontendShell()
|
||||
{
|
||||
using var factory = CreateFactory(1);
|
||||
using var client = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
using var client = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||
var response = await client.GetAsync("/");
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
@@ -21,4 +18,4 @@ public sealed class FrontendHostTests : ApiTestBase
|
||||
Assert.Contains("_framework/blazor.web.js", html);
|
||||
Assert.Contains("Connecting...", html);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
public sealed class RollVisibilityApiTests : ApiTestBase
|
||||
{
|
||||
public RollVisibilityApiTests(WebApplicationFactory<Program> factory)
|
||||
: base(factory)
|
||||
public RollVisibilityApiTests(WebApplicationFactory<Program> factory) : base(factory)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -13,47 +10,29 @@ public sealed class RollVisibilityApiTests : ApiTestBase
|
||||
public async Task RollVisibilityAndAuthorization_AreEnforced()
|
||||
{
|
||||
using var factory = CreateFactory(4, 3, 5, 2, 6);
|
||||
using var gmClient = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
using var playerClient = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
using var observerClient = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
using var outsiderClient = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
using var gmClient = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||
using var playerClient = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||
using var observerClient = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||
using var outsiderClient = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||
|
||||
await RegisterAsync(gmClient, "gm", "Password123", "GM");
|
||||
await LoginAsync(gmClient, "gm", "Password123");
|
||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(
|
||||
gmClient,
|
||||
"/api/campaigns",
|
||||
new CreateCampaignRequest("Main", "d6"));
|
||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(gmClient, "/api/campaigns", new("Main", "d6"));
|
||||
|
||||
await RegisterAsync(playerClient, "player", "Password123", "Player");
|
||||
await LoginAsync(playerClient, "player", "Password123");
|
||||
var playerCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(
|
||||
playerClient,
|
||||
"/api/characters",
|
||||
new CreateCharacterRequest("Rogue", campaign.Id));
|
||||
var playerCharacter = await PostAsync<CreateCharacterRequest, CharacterSummary>(playerClient, "/api/characters", new("Rogue", campaign.Id));
|
||||
|
||||
var skill = await PostAsync<CreateSkillRequest, SkillSummary>(
|
||||
playerClient,
|
||||
$"/api/characters/{playerCharacter.Id}/skills",
|
||||
new CreateSkillRequest("Stealth", "2D+1", 1, true));
|
||||
var skill = await PostAsync<CreateSkillRequest, SkillSummary>(playerClient, $"/api/characters/{playerCharacter.Id}/skills", new("Stealth", "2D+1", 1, true));
|
||||
Assert.Equal(1, skill.WildDice);
|
||||
Assert.True(skill.AllowFumble);
|
||||
|
||||
await RegisterAsync(observerClient, "observer", "Password123", "Observer");
|
||||
await LoginAsync(observerClient, "observer", "Password123");
|
||||
await PostAsync<CreateCharacterRequest, CharacterSummary>(
|
||||
observerClient,
|
||||
"/api/characters",
|
||||
new CreateCharacterRequest("Watcher", campaign.Id));
|
||||
await PostAsync<CreateCharacterRequest, CharacterSummary>(observerClient, "/api/characters", new("Watcher", campaign.Id));
|
||||
|
||||
var privateRoll = await PostAsync<RollSkillRequest, RollResult>(
|
||||
playerClient,
|
||||
$"/api/skills/{skill.Id}/roll",
|
||||
new RollSkillRequest("private"));
|
||||
var publicRoll = await PostAsync<RollSkillRequest, RollResult>(
|
||||
playerClient,
|
||||
$"/api/skills/{skill.Id}/roll",
|
||||
new RollSkillRequest("public"));
|
||||
var privateRoll = await PostAsync<RollSkillRequest, RollResult>(playerClient, $"/api/skills/{skill.Id}/roll", new("private"));
|
||||
var publicRoll = await PostAsync<RollSkillRequest, RollResult>(playerClient, $"/api/skills/{skill.Id}/roll", new("public"));
|
||||
|
||||
Assert.Equal("private", privateRoll.Visibility);
|
||||
Assert.Equal("public", publicRoll.Visibility);
|
||||
@@ -77,15 +56,11 @@ public sealed class RollVisibilityApiTests : ApiTestBase
|
||||
var forbiddenCampaign = await outsiderClient.GetAsync($"/api/campaigns/{campaign.Id}");
|
||||
Assert.Equal(HttpStatusCode.BadRequest, forbiddenCampaign.StatusCode);
|
||||
|
||||
var invalidVisibility = await playerClient.PostAsJsonAsync(
|
||||
$"/api/skills/{skill.Id}/roll",
|
||||
new RollSkillRequest("hidden"));
|
||||
var invalidVisibility = await playerClient.PostAsJsonAsync($"/api/skills/{skill.Id}/roll", new RollSkillRequest("hidden"));
|
||||
Assert.Equal(HttpStatusCode.BadRequest, invalidVisibility.StatusCode);
|
||||
|
||||
using var anonymousClient = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
var unauthorizedCampaignCreate = await anonymousClient.PostAsJsonAsync(
|
||||
"/api/campaigns",
|
||||
new CreateCampaignRequest("Nope", "d6"));
|
||||
using var anonymousClient = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||
var unauthorizedCampaignCreate = await anonymousClient.PostAsJsonAsync("/api/campaigns", new CreateCampaignRequest("Nope", "d6"));
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, unauthorizedCampaignCreate.StatusCode);
|
||||
|
||||
var invalidSessionRequest = new HttpRequestMessage(HttpMethod.Get, "/api/campaigns");
|
||||
@@ -93,4 +68,4 @@ public sealed class RollVisibilityApiTests : ApiTestBase
|
||||
var unauthorizedWithInvalidSession = await anonymousClient.SendAsync(invalidSessionRequest);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, unauthorizedWithInvalidSession.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
public sealed class SystemApiTests : ApiTestBase
|
||||
{
|
||||
public SystemApiTests(WebApplicationFactory<Program> factory)
|
||||
: base(factory)
|
||||
public SystemApiTests(WebApplicationFactory<Program> factory) : base(factory)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -13,7 +10,7 @@ public sealed class SystemApiTests : ApiTestBase
|
||||
public async Task RulesetAndSseEndpoints_ReturnExpectedResponses()
|
||||
{
|
||||
using var factory = CreateFactory(2, 2, 2);
|
||||
using var client = factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
using var client = factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||
|
||||
var rulesets = await GetAsync<IReadOnlyList<RulesetDefinition>>(client, "/api/rulesets");
|
||||
Assert.Equal(2, rulesets.Count);
|
||||
@@ -21,13 +18,10 @@ public sealed class SystemApiTests : ApiTestBase
|
||||
await RegisterAsync(client, "sse", "Password123", "Sse User");
|
||||
await LoginAsync(client, "sse", "Password123");
|
||||
|
||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(
|
||||
client,
|
||||
"/api/campaigns",
|
||||
new CreateCampaignRequest("SSE", "d6"));
|
||||
var campaign = await PostAsync<CreateCampaignRequest, CampaignSummary>(client, "/api/campaigns", new("SSE", "d6"));
|
||||
|
||||
var sseResponse = await client.GetAsync($"/api/events/state?campaignId={campaign.Id}", HttpCompletionOption.ResponseHeadersRead);
|
||||
Assert.Equal(HttpStatusCode.OK, sseResponse.StatusCode);
|
||||
Assert.Equal("text/event-stream", sseResponse.Content.Headers.ContentType?.MediaType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,11 @@ public sealed class BackendCoverageTests
|
||||
var extensionsType = typeof(Program).Assembly.GetType("RpgRoller.Api.SessionTokenHttpContextExtensions");
|
||||
Assert.NotNull(extensionsType);
|
||||
|
||||
var method = extensionsType!.GetMethod(
|
||||
"GetRequiredSessionToken",
|
||||
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
var method = extensionsType!.GetMethod("GetRequiredSessionToken", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
Assert.NotNull(method);
|
||||
|
||||
var context = new DefaultHttpContext();
|
||||
var exception = Assert.Throws<TargetInvocationException>(() => method!.Invoke(null, [context]));
|
||||
Assert.IsType<InvalidOperationException>(exception.InnerException);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
// Service-level tests were split by concern under RpgRoller.Tests/Services.
|
||||
// Service-level tests were split by concern under RpgRoller.Tests/Services.
|
||||
@@ -4,4 +4,4 @@ global using System.Net.Http.Json;
|
||||
global using Microsoft.AspNetCore.Mvc.Testing;
|
||||
global using RpgRoller.Contracts;
|
||||
global using RpgRoller.Domain;
|
||||
global using RpgRoller.Services;
|
||||
global using RpgRoller.Services;
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RpgRoller.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using RpgRoller.Data;
|
||||
using RpgRoller.Hosting;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
@@ -13,22 +13,14 @@ public sealed class HostingCoverageTests
|
||||
public void AddRpgRollerCore_WithInMemoryConnectionString_RegistersCoreServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:RpgRoller"] = "Data Source=:memory:"
|
||||
})
|
||||
.Build();
|
||||
var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?> { ["ConnectionStrings:RpgRoller"] = "Data Source=:memory:" }).Build();
|
||||
|
||||
var environment = new TestWebHostEnvironment
|
||||
{
|
||||
ContentRootPath = Path.GetTempPath()
|
||||
};
|
||||
var environment = new TestWebHostEnvironment { ContentRootPath = Path.GetTempPath() };
|
||||
|
||||
services.AddRpgRollerCore(configuration, environment);
|
||||
|
||||
Assert.Contains(services, d => d.ServiceType == typeof(RpgRoller.Services.IGameService));
|
||||
Assert.Contains(services, d => d.ServiceType == typeof(RpgRoller.Services.IDiceRoller));
|
||||
Assert.Contains(services, d => d.ServiceType == typeof(IGameService));
|
||||
Assert.Contains(services, d => d.ServiceType == typeof(IDiceRoller));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -41,63 +33,60 @@ public sealed class HostingCoverageTests
|
||||
{
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText =
|
||||
"""
|
||||
CREATE TABLE "Users" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_Users" PRIMARY KEY,
|
||||
"Username" TEXT NOT NULL,
|
||||
"UsernameNormalized" TEXT NOT NULL,
|
||||
"PasswordHash" TEXT NOT NULL,
|
||||
"DisplayName" TEXT NOT NULL,
|
||||
"ActiveCharacterId" TEXT NULL
|
||||
);
|
||||
command.CommandText = """
|
||||
CREATE TABLE "Users" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_Users" PRIMARY KEY,
|
||||
"Username" TEXT NOT NULL,
|
||||
"UsernameNormalized" TEXT NOT NULL,
|
||||
"PasswordHash" TEXT NOT NULL,
|
||||
"DisplayName" TEXT NOT NULL,
|
||||
"ActiveCharacterId" TEXT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "Sessions" (
|
||||
"Token" TEXT NOT NULL CONSTRAINT "PK_Sessions" PRIMARY KEY,
|
||||
"UserId" TEXT NOT NULL,
|
||||
"CreatedAtUtc" TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE "Sessions" (
|
||||
"Token" TEXT NOT NULL CONSTRAINT "PK_Sessions" PRIMARY KEY,
|
||||
"UserId" TEXT NOT NULL,
|
||||
"CreatedAtUtc" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "Campaigns" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_Campaigns" PRIMARY KEY,
|
||||
"GmUserId" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL,
|
||||
"Ruleset" TEXT NOT NULL,
|
||||
"Version" INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE "Campaigns" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_Campaigns" PRIMARY KEY,
|
||||
"GmUserId" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL,
|
||||
"Ruleset" TEXT NOT NULL,
|
||||
"Version" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "Characters" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_Characters" PRIMARY KEY,
|
||||
"OwnerUserId" TEXT NOT NULL,
|
||||
"CampaignId" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE "Characters" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_Characters" PRIMARY KEY,
|
||||
"OwnerUserId" TEXT NOT NULL,
|
||||
"CampaignId" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "Skills" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_Skills" PRIMARY KEY,
|
||||
"CharacterId" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL,
|
||||
"DiceRollDefinition" TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE "Skills" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_Skills" PRIMARY KEY,
|
||||
"CharacterId" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL,
|
||||
"DiceRollDefinition" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "RollLogEntries" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_RollLogEntries" PRIMARY KEY,
|
||||
"CampaignId" TEXT NOT NULL,
|
||||
"CharacterId" TEXT NOT NULL,
|
||||
"SkillId" TEXT NOT NULL,
|
||||
"RollerUserId" TEXT NOT NULL,
|
||||
"Visibility" TEXT NOT NULL,
|
||||
"Result" INTEGER NOT NULL,
|
||||
"Breakdown" TEXT NOT NULL,
|
||||
"TimestampUtc" TEXT NOT NULL
|
||||
);
|
||||
""";
|
||||
CREATE TABLE "RollLogEntries" (
|
||||
"Id" TEXT NOT NULL CONSTRAINT "PK_RollLogEntries" PRIMARY KEY,
|
||||
"CampaignId" TEXT NOT NULL,
|
||||
"CharacterId" TEXT NOT NULL,
|
||||
"SkillId" TEXT NOT NULL,
|
||||
"RollerUserId" TEXT NOT NULL,
|
||||
"Visibility" TEXT NOT NULL,
|
||||
"Result" INTEGER NOT NULL,
|
||||
"Breakdown" TEXT NOT NULL,
|
||||
"TimestampUtc" TEXT NOT NULL
|
||||
);
|
||||
""";
|
||||
_ = command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
var options = new DbContextOptionsBuilder<RpgRollerDbContext>()
|
||||
.UseSqlite(connectionString)
|
||||
.Options;
|
||||
var options = new DbContextOptionsBuilder<RpgRollerDbContext>().UseSqlite(connectionString).Options;
|
||||
|
||||
using (var db = new RpgRollerDbContext(options))
|
||||
{
|
||||
@@ -112,9 +101,7 @@ public sealed class HostingCoverageTests
|
||||
using var tableInfoReader = tableInfoCommand.ExecuteReader();
|
||||
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
while (tableInfoReader.Read())
|
||||
{
|
||||
columns.Add(tableInfoReader.GetString(1));
|
||||
}
|
||||
|
||||
Assert.Contains("WildDice", columns);
|
||||
Assert.Contains("AllowFumble", columns);
|
||||
@@ -124,9 +111,7 @@ public sealed class HostingCoverageTests
|
||||
using var rollTableInfoReader = rollTableInfoCommand.ExecuteReader();
|
||||
var rollColumns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
while (rollTableInfoReader.Read())
|
||||
{
|
||||
rollColumns.Add(rollTableInfoReader.GetString(1));
|
||||
}
|
||||
|
||||
Assert.Contains("Dice", rollColumns);
|
||||
|
||||
@@ -145,4 +130,4 @@ public sealed class HostingCoverageTests
|
||||
var rollDiceHistoryCount = Convert.ToInt32(rollDiceHistoryCommand.ExecuteScalar());
|
||||
Assert.Equal(1, rollDiceHistoryCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
|
||||
<PackageReference Include="xunit" Version="2.9.3"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RpgRoller\RpgRoller.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RpgRoller\RpgRoller.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using RpgRoller.Services;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
public sealed class DiceRulesTests
|
||||
@@ -33,4 +31,4 @@ public sealed class DiceRulesTests
|
||||
Assert.Equal("dnd5e", DiceRules.ToRulesetId(RulesetKind.Dnd5e));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => DiceRules.ToRulesetId((RulesetKind)99));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using RpgRoller.Services;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
public sealed class RandomDiceRollerTests
|
||||
@@ -14,4 +12,4 @@ public sealed class RandomDiceRollerTests
|
||||
Assert.InRange(value, 1, 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,4 +56,4 @@ public sealed class ServiceAuthTests
|
||||
Assert.True(login.Succeeded);
|
||||
Assert.Equal(2, hasher.HashCalls);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,4 +97,4 @@ public sealed class ServiceCampaignTests
|
||||
Assert.Single(ownerView.Skills);
|
||||
Assert.Equal(ownerSkill.Id, ownerView.Skills[0].Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,4 +77,4 @@ public sealed class ServiceD6RollTests
|
||||
Assert.Equal(0, dndSkill.WildDice);
|
||||
Assert.False(dndSkill.AllowFumble);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,4 +91,4 @@ public sealed class ServicePersistenceTests
|
||||
Assert.False(invalidExpressionHarness.Service.RollSkill(ownerSession, skill.Id, "public").Succeeded);
|
||||
Assert.False(service.GetCampaignLog(string.Empty, campaign.Id).Succeeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,4 +73,4 @@ public sealed class ServiceSkillRollTests
|
||||
Assert.True(version.Succeeded);
|
||||
Assert.False(missingVersion.Succeeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,27 @@
|
||||
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;
|
||||
private sealed class FixedDiceRoller : IDiceRoller
|
||||
{
|
||||
public FixedDiceRoller(IEnumerable<int> values)
|
||||
{
|
||||
m_Values = new(values);
|
||||
}
|
||||
|
||||
public int Roll(int sides)
|
||||
{
|
||||
var next = m_Values.Count > 0 ? m_Values.Dequeue() : 1;
|
||||
return Math.Clamp(next, 1, sides);
|
||||
}
|
||||
|
||||
private readonly Queue<int> m_Values;
|
||||
}
|
||||
|
||||
protected ApiTestBase(WebApplicationFactory<Program> factory)
|
||||
{
|
||||
@@ -21,28 +30,23 @@ public abstract class ApiTestBase : IClassFixture<WebApplicationFactory<Program>
|
||||
|
||||
protected WebApplicationFactory<Program> CreateFactory(params int[] rollValues)
|
||||
{
|
||||
return m_BaseFactory.WithWebHostBuilder(builder =>
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
services.RemoveAll<IDiceRoller>();
|
||||
services.AddSingleton<IDiceRoller>(new FixedDiceRoller(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>();
|
||||
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}"));
|
||||
}));
|
||||
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));
|
||||
return await PostAsync<RegisterRequest, UserSummary>(client, "/api/auth/register", new(username, password, displayName));
|
||||
}
|
||||
|
||||
protected static async Task LoginAsync(HttpClient client, string username, string password)
|
||||
@@ -78,19 +82,5 @@ public abstract class ApiTestBase : IClassFixture<WebApplicationFactory<Program>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
private readonly WebApplicationFactory<Program> m_BaseFactory;
|
||||
}
|
||||
@@ -1,13 +1,86 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RpgRoller.Data;
|
||||
using RpgRoller.Domain;
|
||||
using RpgRoller.Services;
|
||||
|
||||
namespace RpgRoller.Tests;
|
||||
|
||||
internal static class ServiceTestSupport
|
||||
{
|
||||
internal sealed class ServiceHarness : IDisposable
|
||||
{
|
||||
internal ServiceHarness(GameService service, SqliteDbContextFactory factory, string dbPath)
|
||||
{
|
||||
Service = service;
|
||||
m_Factory = factory;
|
||||
DbPath = dbPath;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
m_Factory.Dispose();
|
||||
}
|
||||
|
||||
public RpgRollerDbContext CreateDbContext()
|
||||
{
|
||||
return m_Factory.CreateDbContext();
|
||||
}
|
||||
|
||||
public GameService Service { get; }
|
||||
public string DbPath { get; }
|
||||
private readonly SqliteDbContextFactory m_Factory;
|
||||
}
|
||||
|
||||
internal sealed class RehashingPasswordHasher : IPasswordHasher<UserAccount>
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public int HashCalls { get; private set; }
|
||||
}
|
||||
|
||||
private sealed class FixedDiceRoller : IDiceRoller
|
||||
{
|
||||
public FixedDiceRoller(IEnumerable<int> values)
|
||||
{
|
||||
m_Values = new(values);
|
||||
}
|
||||
|
||||
public int Roll(int sides)
|
||||
{
|
||||
var next = m_Values.Count > 0 ? m_Values.Dequeue() : 1;
|
||||
return Math.Clamp(next, 1, sides);
|
||||
}
|
||||
|
||||
private readonly Queue<int> m_Values;
|
||||
}
|
||||
|
||||
internal sealed class SqliteDbContextFactory : IDbContextFactory<RpgRollerDbContext>, IDisposable
|
||||
{
|
||||
public SqliteDbContextFactory(string dbPath)
|
||||
{
|
||||
m_Options = new DbContextOptionsBuilder<RpgRollerDbContext>().UseSqlite($"Data Source={dbPath}").Options;
|
||||
}
|
||||
|
||||
public RpgRollerDbContext CreateDbContext()
|
||||
{
|
||||
return new(m_Options);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private readonly DbContextOptions<RpgRollerDbContext> m_Options;
|
||||
}
|
||||
|
||||
internal static ServiceHarness CreateHarness(params int[] rollValues)
|
||||
{
|
||||
return CreateHarness(new PasswordHasher<UserAccount>(), rollValues);
|
||||
@@ -26,9 +99,7 @@ internal static class ServiceTestSupport
|
||||
|
||||
internal static ServiceHarness CreateHarnessFromPath(string dbPath, IPasswordHasher<UserAccount> passwordHasher, params int[] rollValues)
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<RpgRollerDbContext>()
|
||||
.UseSqlite($"Data Source={dbPath}")
|
||||
.Options;
|
||||
var options = new DbContextOptionsBuilder<RpgRollerDbContext>().UseSqlite($"Data Source={dbPath}").Options;
|
||||
|
||||
using (var db = new RpgRollerDbContext(options))
|
||||
{
|
||||
@@ -37,7 +108,7 @@ internal static class ServiceTestSupport
|
||||
|
||||
var factory = new SqliteDbContextFactory(dbPath);
|
||||
var service = new GameService(factory, passwordHasher, new FixedDiceRoller(rollValues));
|
||||
return new ServiceHarness(service, factory, dbPath);
|
||||
return new(service, factory, dbPath);
|
||||
}
|
||||
|
||||
internal static T GetValue<T>(ServiceResult<T> result)
|
||||
@@ -46,84 +117,4 @@ internal static class ServiceTestSupport
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,4 +11,4 @@ internal sealed class TestWebHostEnvironment : IWebHostEnvironment
|
||||
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