feat(web): finalize Discord OAuth and platform-agnostic auth
PR Checks / test-and-build (pull_request) Successful in 5m47s
PR Checks / test-and-build (pull_request) Successful in 5m47s
- Bump version to 2.8.0 across all versioned files - Fix AuthorizedSessionServiceTests for platform-agnostic identity - Update Razor Pages to use *ForCurrentUserAsync APIs - Add backward-compatible constructors to WebGameGroup/WebGroupManager - Make DiscordOAuthOptions properties non-required for config binding Bump version → 2.8.0 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -61,7 +61,7 @@ public sealed class DiscordProjectStructureTests
|
||||
var prChecks = File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "pr-checks.yml"));
|
||||
var deploy = File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "deploy.yml"));
|
||||
|
||||
Assert.Contains("gmrelay-discord-bot:2.7.2", compose);
|
||||
Assert.Contains("gmrelay-discord-bot:2.8.0", compose);
|
||||
Assert.Contains("Discord__Token=${DISCORD_BOT_TOKEN:?Set DISCORD_BOT_TOKEN in .env}", compose);
|
||||
Assert.Contains("src/GmRelay.DiscordBot/Dockerfile", deploy);
|
||||
Assert.Contains("DISCORD_BOT_TOKEN", deploy);
|
||||
@@ -75,13 +75,13 @@ public sealed class DiscordProjectStructureTests
|
||||
{
|
||||
var repoRoot = GetRepoRoot();
|
||||
|
||||
Assert.Contains("<Version>2.7.2</Version>", File.ReadAllText(Path.Combine(repoRoot, "Directory.Build.props")));
|
||||
Assert.Contains("VERSION: 2.7.2", File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "deploy.yml")));
|
||||
Assert.Contains("gmrelay-bot:2.7.2", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains("gmrelay-web:2.7.2", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains("gmrelay-discord-bot:2.7.2", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains("<Version>2.8.0</Version>", File.ReadAllText(Path.Combine(repoRoot, "Directory.Build.props")));
|
||||
Assert.Contains("VERSION: 2.8.0", File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "deploy.yml")));
|
||||
Assert.Contains("gmrelay-bot:2.8.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains("gmrelay-web:2.8.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains("gmrelay-discord-bot:2.8.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains(
|
||||
"v2.7.2",
|
||||
"v2.8.0",
|
||||
File.ReadAllText(Path.Combine(repoRoot, "src", "GmRelay.Web", "Components", "Layout", "NavMenu.razor")));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
using GmRelay.Web.Services;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using GmRelay.Shared.Domain;
|
||||
|
||||
namespace GmRelay.Bot.Tests.Web;
|
||||
|
||||
public sealed class AuthorizedSessionServiceTests
|
||||
{
|
||||
private static IHttpContextAccessor CreateAccessor(string externalUserId, string? name = null)
|
||||
{
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, externalUserId),
|
||||
new Claim("TelegramId", externalUserId),
|
||||
new Claim("Platform", "Telegram")
|
||||
};
|
||||
if (name is not null)
|
||||
claims.Add(new Claim(ClaimTypes.Name, name));
|
||||
|
||||
var identity = new ClaimsIdentity(claims, "Test");
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var httpContext = new DefaultHttpContext { User = principal };
|
||||
return new HttpContextAccessor { HttpContext = httpContext };
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUpcomingSessionsForGmAsync_ReturnsSessions_WhenGroupBelongsToGm()
|
||||
{
|
||||
@@ -19,9 +38,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var sessions = await service.GetUpcomingSessionsForGmAsync(groupId, gmId);
|
||||
var sessions = await service.GetUpcomingSessionsForCurrentUserAsync(groupId);
|
||||
|
||||
Assert.NotNull(sessions);
|
||||
Assert.Single(sessions);
|
||||
@@ -47,9 +67,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(groupId, coGmId, GroupManagerRole.CoGm)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(coGmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var sessions = await service.GetUpcomingSessionsForGmAsync(groupId, coGmId);
|
||||
var sessions = await service.GetUpcomingSessionsForCurrentUserAsync(groupId);
|
||||
|
||||
Assert.NotNull(sessions);
|
||||
Assert.Single(sessions);
|
||||
@@ -65,9 +86,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(groupId, 42, "Alpha", 2002L)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor("1001");
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var sessions = await service.GetUpcomingSessionsForGmAsync(groupId, 1001L);
|
||||
var sessions = await service.GetUpcomingSessionsForCurrentUserAsync(groupId);
|
||||
|
||||
Assert.Null(sessions);
|
||||
}
|
||||
@@ -87,9 +109,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(sessionId, groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var session = await service.GetSessionForGmAsync(sessionId, gmId);
|
||||
var session = await service.GetSessionForCurrentUserAsync(sessionId);
|
||||
|
||||
Assert.NotNull(session);
|
||||
Assert.Equal(sessionId, session.Id);
|
||||
@@ -109,9 +132,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(sessionId, groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor("1001");
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var session = await service.GetSessionForGmAsync(sessionId, 1001L);
|
||||
var session = await service.GetSessionForCurrentUserAsync(sessionId);
|
||||
|
||||
Assert.Null(session);
|
||||
}
|
||||
@@ -130,9 +154,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(sessionId, groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor("1001");
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var action = () => service.UpdateSessionForGmAsync(sessionId, 1001L, "Updated", DateTime.UtcNow.AddDays(1), "https://example.test/b", 5);
|
||||
var action = () => service.UpdateSessionForCurrentUserAsync(sessionId, "Updated", DateTime.UtcNow.AddDays(1), "https://example.test/b", 5);
|
||||
|
||||
await Assert.ThrowsAsync<SessionAccessDeniedException>(action);
|
||||
Assert.False(store.UpdateCalled);
|
||||
@@ -154,9 +179,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(sessionId, groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.UpdateSessionForGmAsync(sessionId, gmId, "Updated", scheduledAt, "https://example.test/b", 5);
|
||||
await service.UpdateSessionForCurrentUserAsync(sessionId, "Updated", scheduledAt, "https://example.test/b", 5);
|
||||
|
||||
Assert.True(store.UpdateCalled);
|
||||
Assert.Equal(groupId, store.LastUpdatedGroupId);
|
||||
@@ -183,9 +209,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(sessionId, groupId, "Session A", originalTime, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.UpdateSessionForGmAsync(sessionId, gmId, "Updated Title", originalTime, "https://example.test/a", 4);
|
||||
await service.UpdateSessionForCurrentUserAsync(sessionId, "Updated Title", originalTime, "https://example.test/a", 4);
|
||||
|
||||
Assert.Single(store.LogEntries);
|
||||
Assert.Equal("Title", store.LogEntries[0].ChangeType);
|
||||
@@ -209,10 +236,11 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(sessionId, groupId, "Session A", originalTime, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var newTime = originalTime.AddDays(1);
|
||||
await service.UpdateSessionForGmAsync(sessionId, gmId, "Updated Title", newTime, "https://example.test/b", 5);
|
||||
await service.UpdateSessionForCurrentUserAsync(sessionId, "Updated Title", newTime, "https://example.test/b", 5);
|
||||
|
||||
Assert.Equal(4, store.LogEntries.Count);
|
||||
Assert.Contains(store.LogEntries, e => e.ChangeType == "Title");
|
||||
@@ -237,9 +265,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(sessionId, groupId, "Session A", originalTime, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.UpdateSessionForGmAsync(sessionId, gmId, "Session A", originalTime, "https://example.test/a", 4);
|
||||
await service.UpdateSessionForCurrentUserAsync(sessionId, "Session A", originalTime, "https://example.test/a", 4);
|
||||
|
||||
Assert.Empty(store.LogEntries);
|
||||
}
|
||||
@@ -259,9 +288,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(sessionId, groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var history = await service.GetSessionHistoryForGmAsync(sessionId, gmId);
|
||||
var history = await service.GetSessionHistoryForCurrentUserAsync(sessionId);
|
||||
|
||||
Assert.NotNull(history);
|
||||
Assert.Empty(history);
|
||||
@@ -281,9 +311,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(sessionId, groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor("1001");
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var history = await service.GetSessionHistoryForGmAsync(sessionId, 1001L);
|
||||
var history = await service.GetSessionHistoryForCurrentUserAsync(sessionId);
|
||||
|
||||
Assert.Null(history);
|
||||
}
|
||||
@@ -303,9 +334,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(sessionId, groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 3, 1)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.PromoteWaitlistedPlayerForGmAsync(sessionId, gmId);
|
||||
await service.PromoteWaitlistedPlayerForCurrentUserAsync(sessionId);
|
||||
|
||||
Assert.True(store.PromoteCalled);
|
||||
Assert.Equal(groupId, store.LastPromotedGroupId);
|
||||
@@ -326,9 +358,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor("1001");
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var action = () => service.UpdateBatchDetailsForGmAsync(batchId, 1001L, "Updated", "https://example.test/b");
|
||||
var action = () => service.UpdateBatchDetailsForCurrentUserAsync(batchId, "Updated", "https://example.test/b");
|
||||
|
||||
await Assert.ThrowsAsync<SessionAccessDeniedException>(action);
|
||||
Assert.False(store.UpdateBatchDetailsCalled);
|
||||
@@ -349,9 +382,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.UpdateBatchDetailsForGmAsync(batchId, gmId, "Updated", "https://example.test/b");
|
||||
await service.UpdateBatchDetailsForCurrentUserAsync(batchId, "Updated", "https://example.test/b");
|
||||
|
||||
Assert.True(store.UpdateBatchDetailsCalled);
|
||||
Assert.Equal(batchId, store.LastUpdatedBatchId);
|
||||
@@ -380,9 +414,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(groupId, coGmId, GroupManagerRole.CoGm)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(coGmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.UpdateBatchDetailsForGmAsync(batchId, coGmId, "Updated", "https://example.test/b");
|
||||
await service.UpdateBatchDetailsForCurrentUserAsync(batchId, "Updated", "https://example.test/b");
|
||||
|
||||
Assert.True(store.UpdateBatchDetailsCalled);
|
||||
Assert.Equal(batchId, store.LastUpdatedBatchId);
|
||||
@@ -400,9 +435,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(groupId, 42, "Alpha", ownerId)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(ownerId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.AddCoGmForOwnerAsync(groupId, ownerId, coGmId, "Assistant GM", "assistant");
|
||||
await service.AddCoGmForOwnerAsync(groupId, "Telegram", coGmId.ToString(), "Assistant GM", "assistant");
|
||||
|
||||
Assert.True(store.AddCoGmCalled);
|
||||
Assert.Equal(groupId, store.LastAddedCoGmGroupId);
|
||||
@@ -427,9 +463,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(groupId, coGmId, GroupManagerRole.CoGm)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(coGmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var action = () => service.AddCoGmForOwnerAsync(groupId, coGmId, newCoGmId, "Second Assistant", null);
|
||||
var action = () => service.AddCoGmForOwnerAsync(groupId, "Telegram", newCoGmId.ToString(), "Second Assistant", null);
|
||||
|
||||
await Assert.ThrowsAsync<SessionAccessDeniedException>(action);
|
||||
Assert.False(store.AddCoGmCalled);
|
||||
@@ -450,9 +487,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(groupId, coGmId, GroupManagerRole.CoGm)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(ownerId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.RemoveCoGmForOwnerAsync(groupId, ownerId, coGmId);
|
||||
await service.RemoveCoGmForOwnerAsync(groupId, "Telegram", coGmId.ToString());
|
||||
|
||||
Assert.True(store.RemoveCoGmCalled);
|
||||
Assert.Equal(groupId, store.LastRemovedCoGmGroupId);
|
||||
@@ -473,9 +511,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor("1001");
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var action = () => service.UpdateBatchNotificationModeForGmAsync(batchId, 1001L, SessionNotificationMode.GroupOnly);
|
||||
var action = () => service.UpdateBatchNotificationModeForCurrentUserAsync(batchId, SessionNotificationMode.GroupOnly);
|
||||
|
||||
await Assert.ThrowsAsync<SessionAccessDeniedException>(action);
|
||||
Assert.False(store.UpdateBatchNotificationModeCalled);
|
||||
@@ -496,9 +535,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.UpdateBatchNotificationModeForGmAsync(batchId, gmId, SessionNotificationMode.GroupOnly);
|
||||
await service.UpdateBatchNotificationModeForCurrentUserAsync(batchId, SessionNotificationMode.GroupOnly);
|
||||
|
||||
Assert.True(store.UpdateBatchNotificationModeCalled);
|
||||
Assert.Equal(batchId, store.LastUpdatedNotificationBatchId);
|
||||
@@ -521,9 +561,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var action = () => service.RescheduleBatchForGmAsync(batchId, gmId, DateTime.UtcNow.AddDays(7), intervalDays: 0);
|
||||
var action = () => service.RescheduleBatchForCurrentUserAsync(batchId, DateTime.UtcNow.AddDays(7), intervalDays: 0);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(action);
|
||||
Assert.False(store.RescheduleBatchCalled);
|
||||
@@ -545,9 +586,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.RescheduleBatchForGmAsync(batchId, gmId, firstScheduledAt, intervalDays: 14);
|
||||
await service.RescheduleBatchForCurrentUserAsync(batchId, firstScheduledAt, intervalDays: 14);
|
||||
|
||||
Assert.True(store.RescheduleBatchCalled);
|
||||
Assert.Equal(batchId, store.LastRescheduledBatchId);
|
||||
@@ -571,9 +613,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.CloneBatchForGmAsync(batchId, gmId, BatchCloneInterval.NextWeek);
|
||||
await service.CloneBatchForCurrentUserAsync(batchId, BatchCloneInterval.NextWeek);
|
||||
|
||||
Assert.True(store.CloneBatchCalled);
|
||||
Assert.Equal(batchId, store.LastClonedBatchId);
|
||||
@@ -591,11 +634,11 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(groupId, 42, "Alpha", gmId)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.CreateCampaignTemplateForGmAsync(
|
||||
await service.CreateCampaignTemplateForCurrentUserAsync(
|
||||
groupId,
|
||||
gmId,
|
||||
new CreateCampaignTemplateRequest(
|
||||
" Weekly arc ",
|
||||
" Kingmaker ",
|
||||
@@ -626,11 +669,11 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(groupId, 42, "Alpha", gmId)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.CreateCampaignTemplateForGmAsync(
|
||||
await service.CreateCampaignTemplateForCurrentUserAsync(
|
||||
groupId,
|
||||
gmId,
|
||||
new CreateCampaignTemplateRequest(
|
||||
"Open table",
|
||||
"West Marches",
|
||||
@@ -653,11 +696,11 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(groupId, 42, "Alpha", 2002L)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor("1001");
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var action = () => service.CreateCampaignTemplateForGmAsync(
|
||||
var action = () => service.CreateCampaignTemplateForCurrentUserAsync(
|
||||
groupId,
|
||||
1001L,
|
||||
new CreateCampaignTemplateRequest(
|
||||
"Weekly arc",
|
||||
"Kingmaker",
|
||||
@@ -687,9 +730,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(templateId, groupId, "Weekly arc", "Kingmaker", "https://example.test/kingmaker", 6, 7, 5, SessionNotificationModeExtensions.GroupOnlyValue, DateTime.UtcNow, DateTime.UtcNow)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor(gmId.ToString());
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
await service.CreateBatchFromCampaignTemplateForGmAsync(templateId, gmId, firstScheduledAt);
|
||||
await service.CreateBatchFromCampaignTemplateForCurrentUserAsync(templateId, firstScheduledAt);
|
||||
|
||||
Assert.True(store.CreateBatchFromTemplateCalled);
|
||||
Assert.Equal(templateId, store.LastCreatedBatchTemplateId);
|
||||
@@ -711,9 +755,10 @@ public sealed class AuthorizedSessionServiceTests
|
||||
[
|
||||
new(templateId, groupId, "Weekly arc", "Kingmaker", "https://example.test/kingmaker", 6, 7, 5, SessionNotificationModeExtensions.GroupOnlyValue, DateTime.UtcNow, DateTime.UtcNow)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
var accessor = CreateAccessor("1001");
|
||||
var service = new AuthorizedSessionService(store, accessor);
|
||||
|
||||
var action = () => service.CreateBatchFromCampaignTemplateForGmAsync(templateId, 1001L, DateTime.UtcNow.AddDays(3));
|
||||
var action = () => service.CreateBatchFromCampaignTemplateForCurrentUserAsync(templateId, DateTime.UtcNow.AddDays(3));
|
||||
|
||||
await Assert.ThrowsAsync<SessionAccessDeniedException>(action);
|
||||
Assert.False(store.CreateBatchFromTemplateCalled);
|
||||
@@ -1019,7 +1064,7 @@ public sealed class AuthorizedSessionServiceTests
|
||||
|
||||
public Task LogSessionChangeAsync(Guid sessionId, long actorTelegramId, string actorName, string changeType, string? oldValue, string? newValue)
|
||||
{
|
||||
var entry = new SessionAuditLogEntry(Guid.NewGuid(), sessionId, actorTelegramId, actorName, changeType, oldValue, newValue, DateTime.UtcNow);
|
||||
var entry = new SessionAuditLogEntry(Guid.NewGuid(), sessionId, actorTelegramId.ToString(), actorName, changeType, oldValue, newValue, DateTime.UtcNow);
|
||||
LogEntries.Add(entry);
|
||||
LastLogSessionId = sessionId;
|
||||
LastLogActorTelegramId = actorTelegramId;
|
||||
@@ -1033,12 +1078,62 @@ public sealed class AuthorizedSessionServiceTests
|
||||
public Task<List<SessionAuditLogEntry>> GetSessionHistoryAsync(Guid sessionId) =>
|
||||
Task.FromResult(LogEntries.Where(e => e.SessionId == sessionId).OrderByDescending(e => e.ChangedAt).ToList());
|
||||
|
||||
public Task<List<WebGameGroup>> GetGroupsForUserAsync(string platform, string externalUserId) =>
|
||||
Task.FromResult(groupsById.Values.Where(group => IsManager(group.Id, externalUserId)).ToList());
|
||||
|
||||
public Task<bool> IsGroupManagerAsync(Guid groupId, string platform, string externalUserId) =>
|
||||
Task.FromResult(IsManager(groupId, externalUserId));
|
||||
|
||||
public Task<bool> IsGroupOwnerAsync(Guid groupId, string platform, string externalUserId) =>
|
||||
Task.FromResult(IsOwner(groupId, externalUserId));
|
||||
|
||||
public Task AddGroupCoGmAsync(Guid groupId, string ownerPlatform, string ownerExternalUserId, string coGmPlatform, string coGmExternalUserId, string displayName, string? externalUsername)
|
||||
{
|
||||
AddCoGmCalled = true;
|
||||
LastAddedCoGmGroupId = groupId;
|
||||
LastAddedCoGmTelegramId = long.TryParse(coGmExternalUserId, out var id) ? id : null;
|
||||
LastAddedCoGmDisplayName = displayName;
|
||||
LastAddedCoGmUsername = externalUsername;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task RemoveGroupCoGmAsync(Guid groupId, string coGmPlatform, string coGmExternalUserId)
|
||||
{
|
||||
RemoveCoGmCalled = true;
|
||||
LastRemovedCoGmGroupId = groupId;
|
||||
LastRemovedCoGmTelegramId = long.TryParse(coGmExternalUserId, out var id) ? id : null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task LogSessionChangeAsync(Guid sessionId, string actorExternalUserId, string actorName, string changeType, string? oldValue, string? newValue)
|
||||
{
|
||||
var entry = new SessionAuditLogEntry(Guid.NewGuid(), sessionId, actorExternalUserId, actorName, changeType, oldValue, newValue, DateTime.UtcNow);
|
||||
LogEntries.Add(entry);
|
||||
LastLogSessionId = sessionId;
|
||||
LastLogActorTelegramId = long.TryParse(actorExternalUserId, out var id) ? (long?)id : null;
|
||||
LastLogActorName = actorName;
|
||||
LastLogChangeType = changeType;
|
||||
LastLogOldValue = oldValue;
|
||||
LastLogNewValue = newValue;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task UpsertDiscordUserAsync(string discordId, string displayName, string? avatarUrl) =>
|
||||
Task.CompletedTask;
|
||||
|
||||
private bool IsManager(Guid groupId, long telegramId) =>
|
||||
IsOwner(groupId, telegramId) ||
|
||||
managers.Any(manager => manager.GroupId == groupId && manager.TelegramId == telegramId);
|
||||
|
||||
private bool IsOwner(Guid groupId, long telegramId) =>
|
||||
groupsById.TryGetValue(groupId, out var group) && group.GmTelegramId == telegramId;
|
||||
|
||||
private bool IsManager(Guid groupId, string externalUserId) =>
|
||||
IsOwner(groupId, externalUserId) ||
|
||||
managers.Any(manager => manager.GroupId == groupId && manager.TelegramId.ToString() == externalUserId);
|
||||
|
||||
private bool IsOwner(Guid groupId, string externalUserId) =>
|
||||
groupsById.TryGetValue(groupId, out var group) && group.GmTelegramId.ToString() == externalUserId;
|
||||
}
|
||||
|
||||
private sealed record FakeGroupManager(Guid GroupId, long TelegramId, GroupManagerRole Role);
|
||||
|
||||
Reference in New Issue
Block a user