diff --git a/RpgRoller.Tests/Api/AuthApiTests.cs b/RpgRoller.Tests/Api/AuthApiTests.cs index 3d199d9..512f540 100644 --- a/RpgRoller.Tests/Api/AuthApiTests.cs +++ b/RpgRoller.Tests/Api/AuthApiTests.cs @@ -1,11 +1,8 @@ -using Microsoft.AspNetCore.Mvc.Testing; - namespace RpgRoller.Tests; public sealed class AuthApiTests : ApiTestBase { - public AuthApiTests(WebApplicationFactory factory) - : base(factory) + public AuthApiTests(WebApplicationFactory 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); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Api/CampaignApiTests.cs b/RpgRoller.Tests/Api/CampaignApiTests.cs index c962357..363cd96 100644 --- a/RpgRoller.Tests/Api/CampaignApiTests.cs +++ b/RpgRoller.Tests/Api/CampaignApiTests.cs @@ -1,11 +1,8 @@ -using Microsoft.AspNetCore.Mvc.Testing; - namespace RpgRoller.Tests; public sealed class CampaignApiTests : ApiTestBase { - public CampaignApiTests(WebApplicationFactory factory) - : base(factory) + public CampaignApiTests(WebApplicationFactory 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( - gmClient, - "/api/campaigns", - new CreateCampaignRequest("Alpha Campaign", "dnd5e")); + var campaign = await PostAsync(gmClient, "/api/campaigns", new("Alpha Campaign", "dnd5e")); - var gmCharacter = await PostAsync( - gmClient, - "/api/characters", - new CreateCharacterRequest("Arin", campaign.Id)); + var gmCharacter = await PostAsync(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( - gmClient, - $"/api/characters/{gmCharacter.Id}/skills", - new CreateSkillRequest("Arcana", "2d12+2", 0, false)); + var createdSkill = await PostAsync(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( - gmClient, - $"/api/skills/{createdSkill.Id}", - new UpdateSkillRequest("Arcana Mastery", "2d12+3", 0, false)); + var updatedSkill = await PutAsync(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(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( - gmClient, - "/api/campaigns", - new CreateCampaignRequest("Beta Campaign", "d6")); + var otherCampaign = await PostAsync(gmClient, "/api/campaigns", new("Beta Campaign", "d6")); - var updatedCharacter = await PutAsync( - gmClient, - $"/api/characters/{gmCharacter.Id}", - new UpdateCharacterRequest("Arin Updated", otherCampaign.Id)); + var updatedCharacter = await PutAsync(gmClient, $"/api/characters/{gmCharacter.Id}", new("Arin Updated", otherCampaign.Id)); Assert.Equal("Arin Updated", updatedCharacter.Name); Assert.Equal(otherCampaign.Id, updatedCharacter.CampaignId); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Api/FrontendHostTests.cs b/RpgRoller.Tests/Api/FrontendHostTests.cs index d7c745a..774a713 100644 --- a/RpgRoller.Tests/Api/FrontendHostTests.cs +++ b/RpgRoller.Tests/Api/FrontendHostTests.cs @@ -1,11 +1,8 @@ -using Microsoft.AspNetCore.Mvc.Testing; - namespace RpgRoller.Tests; public sealed class FrontendHostTests : ApiTestBase { - public FrontendHostTests(WebApplicationFactory factory) - : base(factory) + public FrontendHostTests(WebApplicationFactory 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); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Api/RollVisibilityApiTests.cs b/RpgRoller.Tests/Api/RollVisibilityApiTests.cs index b1e5042..5e097eb 100644 --- a/RpgRoller.Tests/Api/RollVisibilityApiTests.cs +++ b/RpgRoller.Tests/Api/RollVisibilityApiTests.cs @@ -1,11 +1,8 @@ -using Microsoft.AspNetCore.Mvc.Testing; - namespace RpgRoller.Tests; public sealed class RollVisibilityApiTests : ApiTestBase { - public RollVisibilityApiTests(WebApplicationFactory factory) - : base(factory) + public RollVisibilityApiTests(WebApplicationFactory 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( - gmClient, - "/api/campaigns", - new CreateCampaignRequest("Main", "d6")); + var campaign = await PostAsync(gmClient, "/api/campaigns", new("Main", "d6")); await RegisterAsync(playerClient, "player", "Password123", "Player"); await LoginAsync(playerClient, "player", "Password123"); - var playerCharacter = await PostAsync( - playerClient, - "/api/characters", - new CreateCharacterRequest("Rogue", campaign.Id)); + var playerCharacter = await PostAsync(playerClient, "/api/characters", new("Rogue", campaign.Id)); - var skill = await PostAsync( - playerClient, - $"/api/characters/{playerCharacter.Id}/skills", - new CreateSkillRequest("Stealth", "2D+1", 1, true)); + var skill = await PostAsync(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( - observerClient, - "/api/characters", - new CreateCharacterRequest("Watcher", campaign.Id)); + await PostAsync(observerClient, "/api/characters", new("Watcher", campaign.Id)); - var privateRoll = await PostAsync( - playerClient, - $"/api/skills/{skill.Id}/roll", - new RollSkillRequest("private")); - var publicRoll = await PostAsync( - playerClient, - $"/api/skills/{skill.Id}/roll", - new RollSkillRequest("public")); + var privateRoll = await PostAsync(playerClient, $"/api/skills/{skill.Id}/roll", new("private")); + var publicRoll = await PostAsync(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); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Api/SystemApiTests.cs b/RpgRoller.Tests/Api/SystemApiTests.cs index f0b6e48..4751dc6 100644 --- a/RpgRoller.Tests/Api/SystemApiTests.cs +++ b/RpgRoller.Tests/Api/SystemApiTests.cs @@ -1,11 +1,8 @@ -using Microsoft.AspNetCore.Mvc.Testing; - namespace RpgRoller.Tests; public sealed class SystemApiTests : ApiTestBase { - public SystemApiTests(WebApplicationFactory factory) - : base(factory) + public SystemApiTests(WebApplicationFactory 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>(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( - client, - "/api/campaigns", - new CreateCampaignRequest("SSE", "d6")); + var campaign = await PostAsync(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); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/BackendCoverageTests.cs b/RpgRoller.Tests/BackendCoverageTests.cs index 3e63bf8..97e45c4 100644 --- a/RpgRoller.Tests/BackendCoverageTests.cs +++ b/RpgRoller.Tests/BackendCoverageTests.cs @@ -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(() => method!.Invoke(null, [context])); Assert.IsType(exception.InnerException); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/GameServiceTests.cs b/RpgRoller.Tests/GameServiceTests.cs index 16ee960..437c2f9 100644 --- a/RpgRoller.Tests/GameServiceTests.cs +++ b/RpgRoller.Tests/GameServiceTests.cs @@ -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. \ No newline at end of file diff --git a/RpgRoller.Tests/GlobalUsings.cs b/RpgRoller.Tests/GlobalUsings.cs index 3da012d..d2fabb2 100644 --- a/RpgRoller.Tests/GlobalUsings.cs +++ b/RpgRoller.Tests/GlobalUsings.cs @@ -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; \ No newline at end of file diff --git a/RpgRoller.Tests/HostingCoverageTests.cs b/RpgRoller.Tests/HostingCoverageTests.cs index 9e96d1a..5321af4 100644 --- a/RpgRoller.Tests/HostingCoverageTests.cs +++ b/RpgRoller.Tests/HostingCoverageTests.cs @@ -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 - { - ["ConnectionStrings:RpgRoller"] = "Data Source=:memory:" - }) - .Build(); + var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary { ["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() - .UseSqlite(connectionString) - .Options; + var options = new DbContextOptionsBuilder().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(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(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); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/RpgRoller.Tests.csproj b/RpgRoller.Tests/RpgRoller.Tests.csproj index 9ad4433..6c2b17f 100644 --- a/RpgRoller.Tests/RpgRoller.Tests.csproj +++ b/RpgRoller.Tests/RpgRoller.Tests.csproj @@ -1,26 +1,26 @@  - - net10.0 - enable - enable - false - + + net10.0 + enable + enable + false + - - - - - - - + + + + + + + - - - + + + - - - + + + diff --git a/RpgRoller.Tests/Services/DiceRulesTests.cs b/RpgRoller.Tests/Services/DiceRulesTests.cs index cc984c0..824d49f 100644 --- a/RpgRoller.Tests/Services/DiceRulesTests.cs +++ b/RpgRoller.Tests/Services/DiceRulesTests.cs @@ -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(() => DiceRules.ToRulesetId((RulesetKind)99)); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Services/RandomDiceRollerTests.cs b/RpgRoller.Tests/Services/RandomDiceRollerTests.cs index ec4a192..c5070dd 100644 --- a/RpgRoller.Tests/Services/RandomDiceRollerTests.cs +++ b/RpgRoller.Tests/Services/RandomDiceRollerTests.cs @@ -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); } } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Services/ServiceAuthTests.cs b/RpgRoller.Tests/Services/ServiceAuthTests.cs index 04285f2..c42edb1 100644 --- a/RpgRoller.Tests/Services/ServiceAuthTests.cs +++ b/RpgRoller.Tests/Services/ServiceAuthTests.cs @@ -56,4 +56,4 @@ public sealed class ServiceAuthTests Assert.True(login.Succeeded); Assert.Equal(2, hasher.HashCalls); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Services/ServiceCampaignTests.cs b/RpgRoller.Tests/Services/ServiceCampaignTests.cs index 1f1348b..43dfeed 100644 --- a/RpgRoller.Tests/Services/ServiceCampaignTests.cs +++ b/RpgRoller.Tests/Services/ServiceCampaignTests.cs @@ -97,4 +97,4 @@ public sealed class ServiceCampaignTests Assert.Single(ownerView.Skills); Assert.Equal(ownerSkill.Id, ownerView.Skills[0].Id); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Services/ServiceD6RollTests.cs b/RpgRoller.Tests/Services/ServiceD6RollTests.cs index a51aa9f..b20b796 100644 --- a/RpgRoller.Tests/Services/ServiceD6RollTests.cs +++ b/RpgRoller.Tests/Services/ServiceD6RollTests.cs @@ -77,4 +77,4 @@ public sealed class ServiceD6RollTests Assert.Equal(0, dndSkill.WildDice); Assert.False(dndSkill.AllowFumble); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Services/ServicePersistenceTests.cs b/RpgRoller.Tests/Services/ServicePersistenceTests.cs index 445556e..39a3815 100644 --- a/RpgRoller.Tests/Services/ServicePersistenceTests.cs +++ b/RpgRoller.Tests/Services/ServicePersistenceTests.cs @@ -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); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Services/ServiceSkillRollTests.cs b/RpgRoller.Tests/Services/ServiceSkillRollTests.cs index 5b69b7a..471db2c 100644 --- a/RpgRoller.Tests/Services/ServiceSkillRollTests.cs +++ b/RpgRoller.Tests/Services/ServiceSkillRollTests.cs @@ -73,4 +73,4 @@ public sealed class ServiceSkillRollTests Assert.True(version.Succeeded); Assert.False(missingVersion.Succeeded); } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Support/ApiTestBase.cs b/RpgRoller.Tests/Support/ApiTestBase.cs index ef5bdff..2923118 100644 --- a/RpgRoller.Tests/Support/ApiTestBase.cs +++ b/RpgRoller.Tests/Support/ApiTestBase.cs @@ -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> { - private readonly WebApplicationFactory m_BaseFactory; + private sealed class FixedDiceRoller : IDiceRoller + { + public FixedDiceRoller(IEnumerable 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 m_Values; + } protected ApiTestBase(WebApplicationFactory factory) { @@ -21,28 +30,23 @@ public abstract class ApiTestBase : IClassFixture protected WebApplicationFactory CreateFactory(params int[] rollValues) { - return m_BaseFactory.WithWebHostBuilder(builder => - builder.ConfigureServices(services => - { - services.RemoveAll(); - services.AddSingleton(new FixedDiceRoller(rollValues)); + return m_BaseFactory.WithWebHostBuilder(builder => builder.ConfigureServices(services => + { + services.RemoveAll(); + services.AddSingleton(new FixedDiceRoller(rollValues)); - services.RemoveAll>(); - services.RemoveAll>(); - services.RemoveAll(); + services.RemoveAll>(); + services.RemoveAll>(); + services.RemoveAll(); - var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-tests-{Guid.NewGuid():N}.db"); - services.AddDbContextFactory(options => - options.UseSqlite($"Data Source={dbPath}")); - })); + var dbPath = Path.Combine(Path.GetTempPath(), $"rpgroller-tests-{Guid.NewGuid():N}.db"); + services.AddDbContextFactory(options => options.UseSqlite($"Data Source={dbPath}")); + })); } protected static async Task RegisterAsync(HttpClient client, string username, string password, string displayName) { - return await PostAsync( - client, - "/api/auth/register", - new RegisterRequest(username, password, displayName)); + return await PostAsync(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 return result; } - private sealed class FixedDiceRoller : IDiceRoller - { - private readonly Queue m_Values; - - public FixedDiceRoller(IEnumerable values) - { - m_Values = new Queue(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 m_BaseFactory; +} \ No newline at end of file diff --git a/RpgRoller.Tests/Support/ServiceTestSupport.cs b/RpgRoller.Tests/Support/ServiceTestSupport.cs index d7b0166..89bfd37 100644 --- a/RpgRoller.Tests/Support/ServiceTestSupport.cs +++ b/RpgRoller.Tests/Support/ServiceTestSupport.cs @@ -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 + { + 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 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 m_Values; + } + + internal sealed class SqliteDbContextFactory : IDbContextFactory, IDisposable + { + public SqliteDbContextFactory(string dbPath) + { + m_Options = new DbContextOptionsBuilder().UseSqlite($"Data Source={dbPath}").Options; + } + + public RpgRollerDbContext CreateDbContext() + { + return new(m_Options); + } + + public void Dispose() + { + } + + private readonly DbContextOptions m_Options; + } + internal static ServiceHarness CreateHarness(params int[] rollValues) { return CreateHarness(new PasswordHasher(), rollValues); @@ -26,9 +99,7 @@ internal static class ServiceTestSupport internal static ServiceHarness CreateHarnessFromPath(string dbPath, IPasswordHasher passwordHasher, params int[] rollValues) { - var options = new DbContextOptionsBuilder() - .UseSqlite($"Data Source={dbPath}") - .Options; + var options = new DbContextOptionsBuilder().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(ServiceResult 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 - { - 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 m_Values; - - public FixedDiceRoller(IEnumerable values) - { - m_Values = new Queue(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, IDisposable - { - private readonly DbContextOptions m_Options; - - public SqliteDbContextFactory(string dbPath) - { - m_Options = new DbContextOptionsBuilder() - .UseSqlite($"Data Source={dbPath}") - .Options; - } - - public RpgRollerDbContext CreateDbContext() - { - return new RpgRollerDbContext(m_Options); - } - - public void Dispose() - { - } - } -} +} \ No newline at end of file diff --git a/RpgRoller.Tests/Support/TestWebHostEnvironment.cs b/RpgRoller.Tests/Support/TestWebHostEnvironment.cs index ee1e6a2..376d69a 100644 --- a/RpgRoller.Tests/Support/TestWebHostEnvironment.cs +++ b/RpgRoller.Tests/Support/TestWebHostEnvironment.cs @@ -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(); -} +} \ No newline at end of file diff --git a/RpgRoller/Api/ApiEndpointRegistration.cs b/RpgRoller/Api/ApiEndpointRegistration.cs index 1660028..262b337 100644 --- a/RpgRoller/Api/ApiEndpointRegistration.cs +++ b/RpgRoller/Api/ApiEndpointRegistration.cs @@ -8,8 +8,7 @@ public static class ApiEndpointRegistration api.MapSystemEndpoints(); api.MapAuthEndpoints(); - var authenticatedApi = api.MapGroup(string.Empty) - .AddEndpointFilter(); + var authenticatedApi = api.MapGroup(string.Empty).AddEndpointFilter(); authenticatedApi.MapMeEndpoints(); authenticatedApi.MapCampaignEndpoints(); @@ -17,4 +16,4 @@ public static class ApiEndpointRegistration authenticatedApi.MapSkillEndpoints(); authenticatedApi.MapStateEventEndpoints(); } -} +} \ No newline at end of file diff --git a/RpgRoller/Api/ApiResultMapper.cs b/RpgRoller/Api/ApiResultMapper.cs index 482d7aa..2d05e1a 100644 --- a/RpgRoller/Api/ApiResultMapper.cs +++ b/RpgRoller/Api/ApiResultMapper.cs @@ -9,14 +9,10 @@ internal static class ApiResultMapper public static Results, BadRequest, UnauthorizedHttpResult> ToApiResult(ServiceResult result) { if (result.Succeeded) - { return TypedResults.Ok(result.Value!); - } if (result.Error!.Code == "unauthorized") - { return TypedResults.Unauthorized(); - } return TypedResults.BadRequest(new ApiError(result.Error.Message)); } @@ -25,4 +21,4 @@ internal static class ApiResultMapper { return TypedResults.BadRequest(new ApiError(error.Message)); } -} +} \ No newline at end of file diff --git a/RpgRoller/Api/AuthEndpoints.cs b/RpgRoller/Api/AuthEndpoints.cs index 620cd4b..ca20991 100644 --- a/RpgRoller/Api/AuthEndpoints.cs +++ b/RpgRoller/Api/AuthEndpoints.cs @@ -12,9 +12,7 @@ internal static class AuthEndpoints { var result = game.Register(request.Username, request.Password, request.DisplayName); if (!result.Succeeded) - { return ApiResultMapper.ToBadRequest(result.Error!); - } return TypedResults.Ok(result.Value!); }); @@ -23,11 +21,9 @@ internal static class AuthEndpoints { var result = game.Login(request.Username, request.Password); if (!result.Succeeded) - { return ApiResultMapper.ToBadRequest(result.Error!); - } - context.Response.Cookies.Append(SessionCookie.Name, result.Value.SessionToken, new CookieOptions + context.Response.Cookies.Append(SessionCookie.Name, result.Value.SessionToken, new() { HttpOnly = true, SameSite = SameSiteMode.Strict, @@ -41,9 +37,7 @@ internal static class AuthEndpoints group.MapPost("/auth/logout", (HttpContext context, IGameService game) => { if (context.TryReadSessionTokenFromCookie(out var sessionToken)) - { game.Logout(sessionToken); - } context.Response.Cookies.Delete(SessionCookie.Name); return TypedResults.NoContent(); @@ -51,4 +45,4 @@ internal static class AuthEndpoints return group; } -} +} \ No newline at end of file diff --git a/RpgRoller/Api/CampaignEndpoints.cs b/RpgRoller/Api/CampaignEndpoints.cs index 6169efe..883a81b 100644 --- a/RpgRoller/Api/CampaignEndpoints.cs +++ b/RpgRoller/Api/CampaignEndpoints.cs @@ -33,4 +33,4 @@ internal static class CampaignEndpoints return group; } -} +} \ No newline at end of file diff --git a/RpgRoller/Api/CharacterEndpoints.cs b/RpgRoller/Api/CharacterEndpoints.cs index 13832fb..a16b9fa 100644 --- a/RpgRoller/Api/CharacterEndpoints.cs +++ b/RpgRoller/Api/CharacterEndpoints.cs @@ -33,4 +33,4 @@ internal static class CharacterEndpoints return group; } -} +} \ No newline at end of file diff --git a/RpgRoller/Api/MeEndpoints.cs b/RpgRoller/Api/MeEndpoints.cs index 2f239f0..10dc246 100644 --- a/RpgRoller/Api/MeEndpoints.cs +++ b/RpgRoller/Api/MeEndpoints.cs @@ -16,4 +16,4 @@ internal static class MeEndpoints return group; } -} +} \ No newline at end of file diff --git a/RpgRoller/Api/RequestMappings.cs b/RpgRoller/Api/RequestMappings.cs index 44f2b0f..df96b39 100644 --- a/RpgRoller/Api/RequestMappings.cs +++ b/RpgRoller/Api/RequestMappings.cs @@ -2,4 +2,4 @@ namespace RpgRoller.Api; internal static class RequestMappings { -} +} \ No newline at end of file diff --git a/RpgRoller/Api/RequireSessionTokenFilter.cs b/RpgRoller/Api/RequireSessionTokenFilter.cs index 52d3e0a..09a42f0 100644 --- a/RpgRoller/Api/RequireSessionTokenFilter.cs +++ b/RpgRoller/Api/RequireSessionTokenFilter.cs @@ -5,11 +5,9 @@ internal sealed class RequireSessionTokenFilter : IEndpointFilter public ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) { if (!context.HttpContext.TryReadSessionTokenFromCookie(out var sessionToken)) - { return ValueTask.FromResult(TypedResults.Unauthorized()); - } context.HttpContext.StoreSessionToken(sessionToken); return next(context); } -} +} \ No newline at end of file diff --git a/RpgRoller/Api/SessionCookie.cs b/RpgRoller/Api/SessionCookie.cs index 9d1a0f6..f236226 100644 --- a/RpgRoller/Api/SessionCookie.cs +++ b/RpgRoller/Api/SessionCookie.cs @@ -3,4 +3,4 @@ namespace RpgRoller.Api; internal static class SessionCookie { public const string Name = "rpgroller_session"; -} +} \ No newline at end of file diff --git a/RpgRoller/Api/SessionTokenHttpContextExtensions.cs b/RpgRoller/Api/SessionTokenHttpContextExtensions.cs index af92a69..da7f3d0 100644 --- a/RpgRoller/Api/SessionTokenHttpContextExtensions.cs +++ b/RpgRoller/Api/SessionTokenHttpContextExtensions.cs @@ -2,8 +2,6 @@ namespace RpgRoller.Api; internal static class SessionTokenHttpContextExtensions { - private const string SessionTokenItemKey = "__rpgroller.session-token"; - public static bool TryReadSessionTokenFromCookie(this HttpContext context, out string sessionToken) { sessionToken = context.Request.Cookies[SessionCookie.Name] ?? string.Empty; @@ -17,13 +15,11 @@ internal static class SessionTokenHttpContextExtensions public static string GetRequiredSessionToken(this HttpContext context) { - if (context.Items.TryGetValue(SessionTokenItemKey, out var token) - && token is string sessionToken - && !string.IsNullOrWhiteSpace(sessionToken)) - { + if (context.Items.TryGetValue(SessionTokenItemKey, out var token) && token is string sessionToken && !string.IsNullOrWhiteSpace(sessionToken)) return sessionToken; - } throw new InvalidOperationException("Session token is not available in this request."); } -} + + private const string SessionTokenItemKey = "__rpgroller.session-token"; +} \ No newline at end of file diff --git a/RpgRoller/Api/SkillEndpoints.cs b/RpgRoller/Api/SkillEndpoints.cs index 1fc1b21..43cd809 100644 --- a/RpgRoller/Api/SkillEndpoints.cs +++ b/RpgRoller/Api/SkillEndpoints.cs @@ -27,4 +27,4 @@ internal static class SkillEndpoints return group; } -} +} \ No newline at end of file diff --git a/RpgRoller/Api/StateEventEndpoints.cs b/RpgRoller/Api/StateEventEndpoints.cs index a9c30e4..c73360c 100644 --- a/RpgRoller/Api/StateEventEndpoints.cs +++ b/RpgRoller/Api/StateEventEndpoints.cs @@ -7,18 +7,13 @@ internal static class StateEventEndpoints { public static RouteGroupBuilder MapStateEventEndpoints(this RouteGroupBuilder group) { - group.MapGet("/events/state", async Task ( - Guid campaignId, - HttpContext context, - IGameService game) => + group.MapGet("/events/state", async Task (Guid campaignId, HttpContext context, IGameService game) => { var sessionToken = context.GetRequiredSessionToken(); var versionResult = game.GetCampaignVersion(sessionToken, campaignId); if (!versionResult.Succeeded) { - return versionResult.Error!.Code == "unauthorized" - ? TypedResults.Unauthorized() - : TypedResults.BadRequest(new ApiError(versionResult.Error.Message)); + return versionResult.Error!.Code == "unauthorized" ? TypedResults.Unauthorized() : TypedResults.BadRequest(new ApiError(versionResult.Error.Message)); } context.Response.Headers.CacheControl = "no-cache"; @@ -37,9 +32,7 @@ internal static class StateEventEndpoints var currentVersionResult = game.GetCampaignVersion(sessionToken, campaignId); if (!currentVersionResult.Succeeded) - { break; - } if (currentVersionResult.Value != lastVersion) { @@ -47,9 +40,7 @@ internal static class StateEventEndpoints await context.Response.WriteAsync($"event: state\ndata: {{\"campaignId\":\"{campaignId}\",\"version\":{lastVersion}}}\n\n"); } else - { await context.Response.WriteAsync(": heartbeat\n\n"); - } await context.Response.Body.FlushAsync(); } @@ -63,4 +54,4 @@ internal static class StateEventEndpoints return group; } -} +} \ No newline at end of file diff --git a/RpgRoller/Api/SystemEndpoints.cs b/RpgRoller/Api/SystemEndpoints.cs index 16517c2..db973a5 100644 --- a/RpgRoller/Api/SystemEndpoints.cs +++ b/RpgRoller/Api/SystemEndpoints.cs @@ -11,4 +11,4 @@ internal static class SystemEndpoints group.MapGet("/rulesets", (IGameService game) => TypedResults.Ok(game.GetRulesets())); return group; } -} +} \ No newline at end of file diff --git a/RpgRoller/Components/App.razor b/RpgRoller/Components/App.razor index 68095a9..356b52c 100644 --- a/RpgRoller/Components/App.razor +++ b/RpgRoller/Components/App.razor @@ -1,19 +1,18 @@ -@using Microsoft.AspNetCore.Components.Web @attribute [ExcludeFromCodeCoverage] - - - + + + RpgRoller - - + + - - - + + + diff --git a/RpgRoller/Components/Pages/Home.Models.cs b/RpgRoller/Components/Pages/Home.Models.cs index ab327e8..2975fa7 100644 --- a/RpgRoller/Components/Pages/Home.Models.cs +++ b/RpgRoller/Components/Pages/Home.Models.cs @@ -1,17 +1,16 @@ namespace RpgRoller.Components.Pages; -public sealed class FormState - where TModel : new() +public sealed class FormState where TModel : new() { - public TModel Model { get; } = new(); - public Dictionary Errors { get; } = []; - public string? ErrorMessage { get; set; } - public void ResetValidation() { Errors.Clear(); ErrorMessage = null; } + + public TModel Model { get; } = new(); + public Dictionary Errors { get; } = []; + public string? ErrorMessage { get; set; } } public sealed class RegisterFormModel @@ -52,4 +51,4 @@ public enum HomeViewMode Loading, Anonymous, Workspace -} +} \ No newline at end of file diff --git a/RpgRoller/Components/Pages/Home.razor b/RpgRoller/Components/Pages/Home.razor index 4dc4fe6..9c47300 100644 --- a/RpgRoller/Components/Pages/Home.razor +++ b/RpgRoller/Components/Pages/Home.razor @@ -17,11 +17,11 @@ + LoggedIn="OnLoggedInAsync"/> break; case HomeViewMode.Workspace: - + break; } diff --git a/RpgRoller/Components/Pages/Home.razor.cs b/RpgRoller/Components/Pages/Home.razor.cs index df13749..9f48d54 100644 --- a/RpgRoller/Components/Pages/Home.razor.cs +++ b/RpgRoller/Components/Pages/Home.razor.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Components; -using RpgRoller.Components; using RpgRoller.Contracts; namespace RpgRoller.Components.Pages; @@ -8,20 +7,10 @@ namespace RpgRoller.Components.Pages; [ExcludeFromCodeCoverage] public partial class Home { - private HomeViewMode CurrentView { get; set; } = HomeViewMode.Loading; - private string? StatusMessage { get; set; } - private bool StatusIsError { get; set; } - private bool HasInitialized { get; set; } - - [Inject] - private RpgRollerApiClient ApiClient { get; set; } = default!; - protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender || HasInitialized) - { return; - } HasInitialized = true; await InitializeAsync(); @@ -78,4 +67,12 @@ public partial class Home StatusMessage = null; StatusIsError = false; } -} + + private HomeViewMode CurrentView { get; set; } = HomeViewMode.Loading; + private string? StatusMessage { get; set; } + private bool StatusIsError { get; set; } + private bool HasInitialized { get; set; } + + [Inject] + private RpgRollerApiClient ApiClient { get; set; } = null!; +} \ No newline at end of file diff --git a/RpgRoller/Components/Pages/HomeControls/AuthSection.razor b/RpgRoller/Components/Pages/HomeControls/AuthSection.razor index f82f995..75955b5 100644 --- a/RpgRoller/Components/Pages/HomeControls/AuthSection.razor +++ b/RpgRoller/Components/Pages/HomeControls/AuthSection.razor @@ -1,8 +1,3 @@ -@using System.Diagnostics.CodeAnalysis -@using RpgRoller.Components -@using RpgRoller.Components.Pages -@using RpgRoller.Contracts -

RpgRoller

Register or log in to join a campaign session.

@@ -19,19 +14,22 @@ }
- + @if (RegisterState.Errors.TryGetValue("username", out var registerUsernameError)) {

@registerUsernameError

} - + @if (RegisterState.Errors.TryGetValue("displayName", out var registerDisplayNameError)) {

@registerDisplayNameError

} - + @if (RegisterState.Errors.TryGetValue("password", out var registerPasswordError)) {

@registerPasswordError

@@ -48,13 +46,15 @@ } - + @if (LoginState.Errors.TryGetValue("username", out var loginUsernameError)) {

@loginUsernameError

} - + @if (LoginState.Errors.TryGetValue("password", out var loginPasswordError)) {

@loginPasswordError

diff --git a/RpgRoller/Components/Pages/HomeControls/AuthSection.razor.cs b/RpgRoller/Components/Pages/HomeControls/AuthSection.razor.cs index 1015eaf..f6e345a 100644 --- a/RpgRoller/Components/Pages/HomeControls/AuthSection.razor.cs +++ b/RpgRoller/Components/Pages/HomeControls/AuthSection.razor.cs @@ -1,113 +1,75 @@ -using Microsoft.AspNetCore.Components; -using RpgRoller.Components.Pages; -using RpgRoller.Components; -using RpgRoller.Contracts; using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components; +using RpgRoller.Contracts; namespace RpgRoller.Components.Pages.HomeControls; [ExcludeFromCodeCoverage] public partial class AuthSection { - [Inject] - private RpgRollerApiClient ApiClient { get; set; } = default!; - - private FormState RegisterState { get; } = new(); - private FormState LoginState { get; } = new(); - private bool IsSubmitting { get; set; } - - [Parameter] - public string? StatusMessage { get; set; } - - [Parameter] - public bool StatusIsError { get; set; } - - [Parameter] - public EventCallback LoggedIn { get; set; } - private async Task SubmitRegisterAsync() { RegisterState.ResetValidation(); - + var model = RegisterState.Model; if (string.IsNullOrWhiteSpace(model.Username)) - { RegisterState.Errors["username"] = "Username is required."; - } - + if (string.IsNullOrWhiteSpace(model.DisplayName)) - { RegisterState.Errors["displayName"] = "Display name is required."; - } - + if (string.IsNullOrWhiteSpace(model.Password) || model.Password.Length < 8) - { RegisterState.Errors["password"] = "Password must be at least 8 characters."; - } - + if (RegisterState.Errors.Count > 0) { RegisterState.ErrorMessage = "Resolve validation issues before submitting."; return; } - + IsSubmitting = true; try { - _ = await ApiClient.RequestAsync( - "POST", - "/api/auth/register", - new RegisterRequest(model.Username.Trim(), model.Password, model.DisplayName.Trim())); - + _ = await ApiClient.RequestAsync("POST", "/api/auth/register", new RegisterRequest(model.Username.Trim(), model.Password, model.DisplayName.Trim())); + model.Password = string.Empty; RegisterState.ErrorMessage = "Registration successful. You can log in now."; } catch (ApiRequestException ex) { if (ex.Message.Contains("already taken", StringComparison.OrdinalIgnoreCase)) - { RegisterState.Errors["username"] = "Username is already taken. Choose another one."; - } else - { RegisterState.ErrorMessage = ex.Message; - } } finally { IsSubmitting = false; } } - + private async Task SubmitLoginAsync() { LoginState.ResetValidation(); - + var model = LoginState.Model; if (string.IsNullOrWhiteSpace(model.Username)) - { LoginState.Errors["username"] = "Username is required."; - } - + if (string.IsNullOrWhiteSpace(model.Password)) - { LoginState.Errors["password"] = "Password is required."; - } - + if (LoginState.Errors.Count > 0) { LoginState.ErrorMessage = "Resolve validation issues before submitting."; return; } - + IsSubmitting = true; try { - _ = await ApiClient.RequestAsync( - "POST", - "/api/auth/login", - new LoginRequest(model.Username.Trim(), model.Password)); - + _ = await ApiClient.RequestAsync("POST", "/api/auth/login", new LoginRequest(model.Username.Trim(), model.Password)); + model.Password = string.Empty; await LoggedIn.InvokeAsync(); } @@ -120,4 +82,20 @@ public partial class AuthSection IsSubmitting = false; } } -} + + [Inject] + private RpgRollerApiClient ApiClient { get; set; } = null!; + + private FormState RegisterState { get; } = new(); + private FormState LoginState { get; } = new(); + private bool IsSubmitting { get; set; } + + [Parameter] + public string? StatusMessage { get; set; } + + [Parameter] + public bool StatusIsError { get; set; } + + [Parameter] + public EventCallback LoggedIn { get; set; } +} \ No newline at end of file diff --git a/RpgRoller/Components/Pages/HomeControls/CampaignLogPanel.razor b/RpgRoller/Components/Pages/HomeControls/CampaignLogPanel.razor index a47a783..d03a681 100644 --- a/RpgRoller/Components/Pages/HomeControls/CampaignLogPanel.razor +++ b/RpgRoller/Components/Pages/HomeControls/CampaignLogPanel.razor @@ -1,11 +1,12 @@ -@using System.Diagnostics.CodeAnalysis -@using RpgRoller.Contracts -