feat: support co-gm group delegation
This commit is contained in:
@@ -28,6 +28,34 @@ public sealed class AuthorizedSessionServiceTests
|
||||
Assert.Equal("Session A", sessions[0].Title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUpcomingSessionsForGmAsync_ReturnsSessions_WhenUserIsCoGm()
|
||||
{
|
||||
var ownerId = 1001L;
|
||||
var coGmId = 2002L;
|
||||
var groupId = Guid.NewGuid();
|
||||
var store = new FakeSessionStore(
|
||||
groups:
|
||||
[
|
||||
new(groupId, 42, "Alpha", ownerId)
|
||||
],
|
||||
sessions:
|
||||
[
|
||||
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
||||
],
|
||||
managers:
|
||||
[
|
||||
new(groupId, coGmId, GroupManagerRole.CoGm)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
|
||||
var sessions = await service.GetUpcomingSessionsForGmAsync(groupId, coGmId);
|
||||
|
||||
Assert.NotNull(sessions);
|
||||
Assert.Single(sessions);
|
||||
Assert.Equal("Session A", sessions[0].Title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUpcomingSessionsForGmAsync_ReturnsNull_WhenGroupBelongsToAnotherGm()
|
||||
{
|
||||
@@ -211,6 +239,105 @@ public sealed class AuthorizedSessionServiceTests
|
||||
Assert.Equal("https://example.test/b", store.LastUpdatedBatchJoinLink);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateBatchDetailsForGmAsync_UpdatesBatch_WhenUserIsCoGm()
|
||||
{
|
||||
var ownerId = 1001L;
|
||||
var coGmId = 2002L;
|
||||
var groupId = Guid.NewGuid();
|
||||
var batchId = Guid.NewGuid();
|
||||
var store = new FakeSessionStore(
|
||||
groups:
|
||||
[
|
||||
new(groupId, 42, "Alpha", ownerId)
|
||||
],
|
||||
sessions:
|
||||
[
|
||||
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
||||
],
|
||||
managers:
|
||||
[
|
||||
new(groupId, coGmId, GroupManagerRole.CoGm)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
|
||||
await service.UpdateBatchDetailsForGmAsync(batchId, coGmId, "Updated", "https://example.test/b");
|
||||
|
||||
Assert.True(store.UpdateBatchDetailsCalled);
|
||||
Assert.Equal(batchId, store.LastUpdatedBatchId);
|
||||
Assert.Equal(groupId, store.LastUpdatedBatchGroupId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddCoGmForOwnerAsync_AddsCoGm_WhenUserIsOwner()
|
||||
{
|
||||
var ownerId = 1001L;
|
||||
var coGmId = 2002L;
|
||||
var groupId = Guid.NewGuid();
|
||||
var store = new FakeSessionStore(
|
||||
groups:
|
||||
[
|
||||
new(groupId, 42, "Alpha", ownerId)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
|
||||
await service.AddCoGmForOwnerAsync(groupId, ownerId, coGmId, "Assistant GM", "assistant");
|
||||
|
||||
Assert.True(store.AddCoGmCalled);
|
||||
Assert.Equal(groupId, store.LastAddedCoGmGroupId);
|
||||
Assert.Equal(coGmId, store.LastAddedCoGmTelegramId);
|
||||
Assert.Equal("Assistant GM", store.LastAddedCoGmDisplayName);
|
||||
Assert.Equal("assistant", store.LastAddedCoGmUsername);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddCoGmForOwnerAsync_Throws_WhenUserIsCoGm()
|
||||
{
|
||||
var ownerId = 1001L;
|
||||
var coGmId = 2002L;
|
||||
var newCoGmId = 3003L;
|
||||
var groupId = Guid.NewGuid();
|
||||
var store = new FakeSessionStore(
|
||||
groups:
|
||||
[
|
||||
new(groupId, 42, "Alpha", ownerId)
|
||||
],
|
||||
managers:
|
||||
[
|
||||
new(groupId, coGmId, GroupManagerRole.CoGm)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
|
||||
var action = () => service.AddCoGmForOwnerAsync(groupId, coGmId, newCoGmId, "Second Assistant", null);
|
||||
|
||||
await Assert.ThrowsAsync<SessionAccessDeniedException>(action);
|
||||
Assert.False(store.AddCoGmCalled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RemoveCoGmForOwnerAsync_RemovesCoGm_WhenUserIsOwner()
|
||||
{
|
||||
var ownerId = 1001L;
|
||||
var coGmId = 2002L;
|
||||
var groupId = Guid.NewGuid();
|
||||
var store = new FakeSessionStore(
|
||||
groups:
|
||||
[
|
||||
new(groupId, 42, "Alpha", ownerId)
|
||||
],
|
||||
managers:
|
||||
[
|
||||
new(groupId, coGmId, GroupManagerRole.CoGm)
|
||||
]);
|
||||
var service = new AuthorizedSessionService(store);
|
||||
|
||||
await service.RemoveCoGmForOwnerAsync(groupId, ownerId, coGmId);
|
||||
|
||||
Assert.True(store.RemoveCoGmCalled);
|
||||
Assert.Equal(groupId, store.LastRemovedCoGmGroupId);
|
||||
Assert.Equal(coGmId, store.LastRemovedCoGmTelegramId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateBatchNotificationModeForGmAsync_Throws_WhenBatchBelongsToAnotherGm()
|
||||
{
|
||||
@@ -335,10 +462,12 @@ public sealed class AuthorizedSessionServiceTests
|
||||
|
||||
private sealed class FakeSessionStore(
|
||||
IEnumerable<WebGameGroup>? groups = null,
|
||||
IEnumerable<WebSession>? sessions = null) : ISessionStore
|
||||
IEnumerable<WebSession>? sessions = null,
|
||||
IEnumerable<FakeGroupManager>? managers = null) : ISessionStore
|
||||
{
|
||||
private readonly Dictionary<Guid, WebGameGroup> groupsById = groups?.ToDictionary(group => group.Id) ?? [];
|
||||
private readonly Dictionary<Guid, WebSession> sessionsById = sessions?.ToDictionary(session => session.Id) ?? [];
|
||||
private readonly List<FakeGroupManager> managers = managers?.ToList() ?? [];
|
||||
|
||||
public bool UpdateCalled { get; private set; }
|
||||
public bool PromoteCalled { get; private set; }
|
||||
@@ -346,6 +475,8 @@ public sealed class AuthorizedSessionServiceTests
|
||||
public bool UpdateBatchNotificationModeCalled { get; private set; }
|
||||
public bool RescheduleBatchCalled { get; private set; }
|
||||
public bool CloneBatchCalled { get; private set; }
|
||||
public bool AddCoGmCalled { get; private set; }
|
||||
public bool RemoveCoGmCalled { get; private set; }
|
||||
public Guid? LastUpdatedSessionId { get; private set; }
|
||||
public Guid? LastUpdatedGroupId { get; private set; }
|
||||
public string? LastUpdatedTitle { get; private set; }
|
||||
@@ -368,9 +499,15 @@ public sealed class AuthorizedSessionServiceTests
|
||||
public Guid? LastClonedBatchId { get; private set; }
|
||||
public Guid? LastClonedBatchGroupId { get; private set; }
|
||||
public BatchCloneInterval? LastCloneInterval { get; private set; }
|
||||
public Guid? LastAddedCoGmGroupId { get; private set; }
|
||||
public long? LastAddedCoGmTelegramId { get; private set; }
|
||||
public string? LastAddedCoGmDisplayName { get; private set; }
|
||||
public string? LastAddedCoGmUsername { get; private set; }
|
||||
public Guid? LastRemovedCoGmGroupId { get; private set; }
|
||||
public long? LastRemovedCoGmTelegramId { get; private set; }
|
||||
|
||||
public Task<List<WebGameGroup>> GetGroupsForGmAsync(long gmId) =>
|
||||
Task.FromResult(groupsById.Values.Where(group => group.GmTelegramId == gmId).ToList());
|
||||
Task.FromResult(groupsById.Values.Where(group => IsManager(group.Id, gmId)).ToList());
|
||||
|
||||
public Task<WebGameGroup?> GetGroupAsync(Guid groupId)
|
||||
{
|
||||
@@ -378,6 +515,36 @@ public sealed class AuthorizedSessionServiceTests
|
||||
return Task.FromResult(group);
|
||||
}
|
||||
|
||||
public Task<bool> IsGroupManagerAsync(Guid groupId, long telegramId) =>
|
||||
Task.FromResult(IsManager(groupId, telegramId));
|
||||
|
||||
public Task<bool> IsGroupOwnerAsync(Guid groupId, long telegramId) =>
|
||||
Task.FromResult(IsOwner(groupId, telegramId));
|
||||
|
||||
public Task<List<WebGroupManager>> GetGroupManagersAsync(Guid groupId)
|
||||
{
|
||||
if (!groupsById.TryGetValue(groupId, out var group))
|
||||
{
|
||||
return Task.FromResult(new List<WebGroupManager>());
|
||||
}
|
||||
|
||||
var result = new List<WebGroupManager>
|
||||
{
|
||||
new(group.GmTelegramId, "Owner GM", null, GroupManagerRoleExtensions.OwnerValue, DateTime.UtcNow)
|
||||
};
|
||||
|
||||
result.AddRange(managers
|
||||
.Where(manager => manager.GroupId == groupId)
|
||||
.Select(manager => new WebGroupManager(
|
||||
manager.TelegramId,
|
||||
$"Co-GM {manager.TelegramId}",
|
||||
null,
|
||||
manager.Role.ToDatabaseValue(),
|
||||
DateTime.UtcNow)));
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task<List<WebSession>> GetUpcomingSessionsAsync(Guid groupId) =>
|
||||
Task.FromResult(sessionsById.Values.Where(session => session.GroupId == groupId).ToList());
|
||||
|
||||
@@ -474,5 +641,32 @@ public sealed class AuthorizedSessionServiceTests
|
||||
DateTime.UtcNow.AddDays(7),
|
||||
1));
|
||||
}
|
||||
|
||||
public Task AddGroupCoGmAsync(Guid groupId, long ownerTelegramId, long coGmTelegramId, string displayName, string? telegramUsername)
|
||||
{
|
||||
AddCoGmCalled = true;
|
||||
LastAddedCoGmGroupId = groupId;
|
||||
LastAddedCoGmTelegramId = coGmTelegramId;
|
||||
LastAddedCoGmDisplayName = displayName;
|
||||
LastAddedCoGmUsername = telegramUsername;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task RemoveGroupCoGmAsync(Guid groupId, long coGmTelegramId)
|
||||
{
|
||||
RemoveCoGmCalled = true;
|
||||
LastRemovedCoGmGroupId = groupId;
|
||||
LastRemovedCoGmTelegramId = coGmTelegramId;
|
||||
return 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 sealed record FakeGroupManager(Guid GroupId, long TelegramId, GroupManagerRole Role);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user