418 lines
16 KiB
C#
418 lines
16 KiB
C#
using GmRelay.Web.Services;
|
|
|
|
namespace GmRelay.Bot.Tests.Web;
|
|
|
|
public sealed class AuthorizedSessionServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task GetUpcomingSessionsForGmAsync_ReturnsSessions_WhenGroupBelongsToGm()
|
|
{
|
|
var gmId = 1001L;
|
|
var groupId = Guid.NewGuid();
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", gmId)
|
|
],
|
|
sessions:
|
|
[
|
|
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 sessions = await service.GetUpcomingSessionsForGmAsync(groupId, gmId);
|
|
|
|
Assert.NotNull(sessions);
|
|
Assert.Single(sessions);
|
|
Assert.Equal("Session A", sessions[0].Title);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetUpcomingSessionsForGmAsync_ReturnsNull_WhenGroupBelongsToAnotherGm()
|
|
{
|
|
var groupId = Guid.NewGuid();
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", 2002L)
|
|
]);
|
|
var service = new AuthorizedSessionService(store);
|
|
|
|
var sessions = await service.GetUpcomingSessionsForGmAsync(groupId, 1001L);
|
|
|
|
Assert.Null(sessions);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSessionForGmAsync_ReturnsSession_WhenSessionBelongsToOwnedGroup()
|
|
{
|
|
var gmId = 1001L;
|
|
var groupId = Guid.NewGuid();
|
|
var sessionId = Guid.NewGuid();
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", gmId)
|
|
],
|
|
sessions:
|
|
[
|
|
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 session = await service.GetSessionForGmAsync(sessionId, gmId);
|
|
|
|
Assert.NotNull(session);
|
|
Assert.Equal(sessionId, session.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetSessionForGmAsync_ReturnsNull_WhenSessionBelongsToAnotherGm()
|
|
{
|
|
var groupId = Guid.NewGuid();
|
|
var sessionId = Guid.NewGuid();
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", 2002L)
|
|
],
|
|
sessions:
|
|
[
|
|
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 session = await service.GetSessionForGmAsync(sessionId, 1001L);
|
|
|
|
Assert.Null(session);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateSessionForGmAsync_Throws_WhenSessionBelongsToAnotherGm()
|
|
{
|
|
var groupId = Guid.NewGuid();
|
|
var sessionId = Guid.NewGuid();
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", 2002L)
|
|
],
|
|
sessions:
|
|
[
|
|
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 action = () => service.UpdateSessionForGmAsync(sessionId, 1001L, "Updated", DateTime.UtcNow.AddDays(1), "https://example.test/b", 5);
|
|
|
|
await Assert.ThrowsAsync<SessionAccessDeniedException>(action);
|
|
Assert.False(store.UpdateCalled);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateSessionForGmAsync_UpdatesOwnedSession()
|
|
{
|
|
var gmId = 1001L;
|
|
var groupId = Guid.NewGuid();
|
|
var sessionId = Guid.NewGuid();
|
|
var scheduledAt = DateTime.UtcNow.AddDays(1);
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", gmId)
|
|
],
|
|
sessions:
|
|
[
|
|
new(sessionId, groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 1, 0)
|
|
]);
|
|
var service = new AuthorizedSessionService(store);
|
|
|
|
await service.UpdateSessionForGmAsync(sessionId, gmId, "Updated", scheduledAt, "https://example.test/b", 5);
|
|
|
|
Assert.True(store.UpdateCalled);
|
|
Assert.Equal(groupId, store.LastUpdatedGroupId);
|
|
Assert.Equal(sessionId, store.LastUpdatedSessionId);
|
|
Assert.Equal("Updated", store.LastUpdatedTitle);
|
|
Assert.Equal(scheduledAt, store.LastUpdatedScheduledAt);
|
|
Assert.Equal("https://example.test/b", store.LastUpdatedJoinLink);
|
|
Assert.Equal(5, store.LastUpdatedMaxPlayers);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PromoteWaitlistedPlayerForGmAsync_PromotesOwnedSession()
|
|
{
|
|
var gmId = 1001L;
|
|
var groupId = Guid.NewGuid();
|
|
var sessionId = Guid.NewGuid();
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", gmId)
|
|
],
|
|
sessions:
|
|
[
|
|
new(sessionId, groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", Guid.NewGuid(), 10, 42, 4, 3, 1)
|
|
]);
|
|
var service = new AuthorizedSessionService(store);
|
|
|
|
await service.PromoteWaitlistedPlayerForGmAsync(sessionId, gmId);
|
|
|
|
Assert.True(store.PromoteCalled);
|
|
Assert.Equal(groupId, store.LastPromotedGroupId);
|
|
Assert.Equal(sessionId, store.LastPromotedSessionId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateBatchDetailsForGmAsync_Throws_WhenBatchBelongsToAnotherGm()
|
|
{
|
|
var batchId = Guid.NewGuid();
|
|
var groupId = Guid.NewGuid();
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", 2002L)
|
|
],
|
|
sessions:
|
|
[
|
|
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 action = () => service.UpdateBatchDetailsForGmAsync(batchId, 1001L, "Updated", "https://example.test/b");
|
|
|
|
await Assert.ThrowsAsync<SessionAccessDeniedException>(action);
|
|
Assert.False(store.UpdateBatchDetailsCalled);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateBatchDetailsForGmAsync_UpdatesOwnedBatch()
|
|
{
|
|
var gmId = 1001L;
|
|
var groupId = Guid.NewGuid();
|
|
var batchId = Guid.NewGuid();
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", gmId)
|
|
],
|
|
sessions:
|
|
[
|
|
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
|
]);
|
|
var service = new AuthorizedSessionService(store);
|
|
|
|
await service.UpdateBatchDetailsForGmAsync(batchId, gmId, "Updated", "https://example.test/b");
|
|
|
|
Assert.True(store.UpdateBatchDetailsCalled);
|
|
Assert.Equal(batchId, store.LastUpdatedBatchId);
|
|
Assert.Equal(groupId, store.LastUpdatedBatchGroupId);
|
|
Assert.Equal("Updated", store.LastUpdatedBatchTitle);
|
|
Assert.Equal("https://example.test/b", store.LastUpdatedBatchJoinLink);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RescheduleBatchForGmAsync_RejectsNonPositiveInterval()
|
|
{
|
|
var gmId = 1001L;
|
|
var groupId = Guid.NewGuid();
|
|
var batchId = Guid.NewGuid();
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", gmId)
|
|
],
|
|
sessions:
|
|
[
|
|
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 action = () => service.RescheduleBatchForGmAsync(batchId, gmId, DateTime.UtcNow.AddDays(7), intervalDays: 0);
|
|
|
|
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(action);
|
|
Assert.False(store.RescheduleBatchCalled);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RescheduleBatchForGmAsync_ReschedulesOwnedBatch()
|
|
{
|
|
var gmId = 1001L;
|
|
var groupId = Guid.NewGuid();
|
|
var batchId = Guid.NewGuid();
|
|
var firstScheduledAt = DateTime.UtcNow.AddDays(7);
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", gmId)
|
|
],
|
|
sessions:
|
|
[
|
|
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
|
]);
|
|
var service = new AuthorizedSessionService(store);
|
|
|
|
await service.RescheduleBatchForGmAsync(batchId, gmId, firstScheduledAt, intervalDays: 14);
|
|
|
|
Assert.True(store.RescheduleBatchCalled);
|
|
Assert.Equal(batchId, store.LastRescheduledBatchId);
|
|
Assert.Equal(groupId, store.LastRescheduledBatchGroupId);
|
|
Assert.Equal(firstScheduledAt, store.LastRescheduledFirstScheduledAt);
|
|
Assert.Equal(14, store.LastRescheduledIntervalDays);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CloneBatchForGmAsync_ClonesOwnedBatch()
|
|
{
|
|
var gmId = 1001L;
|
|
var groupId = Guid.NewGuid();
|
|
var batchId = Guid.NewGuid();
|
|
var store = new FakeSessionStore(
|
|
groups:
|
|
[
|
|
new(groupId, 42, "Alpha", gmId)
|
|
],
|
|
sessions:
|
|
[
|
|
new(Guid.NewGuid(), groupId, "Session A", DateTime.UtcNow, "Planned", "https://example.test/a", batchId, 10, 42, 4, 1, 0)
|
|
]);
|
|
var service = new AuthorizedSessionService(store);
|
|
|
|
await service.CloneBatchForGmAsync(batchId, gmId, BatchCloneInterval.NextWeek);
|
|
|
|
Assert.True(store.CloneBatchCalled);
|
|
Assert.Equal(batchId, store.LastClonedBatchId);
|
|
Assert.Equal(groupId, store.LastClonedBatchGroupId);
|
|
Assert.Equal(BatchCloneInterval.NextWeek, store.LastCloneInterval);
|
|
}
|
|
|
|
private sealed class FakeSessionStore(
|
|
IEnumerable<WebGameGroup>? groups = null,
|
|
IEnumerable<WebSession>? sessions = null) : ISessionStore
|
|
{
|
|
private readonly Dictionary<Guid, WebGameGroup> groupsById = groups?.ToDictionary(group => group.Id) ?? [];
|
|
private readonly Dictionary<Guid, WebSession> sessionsById = sessions?.ToDictionary(session => session.Id) ?? [];
|
|
|
|
public bool UpdateCalled { get; private set; }
|
|
public bool PromoteCalled { get; private set; }
|
|
public bool UpdateBatchDetailsCalled { get; private set; }
|
|
public bool RescheduleBatchCalled { get; private set; }
|
|
public bool CloneBatchCalled { get; private set; }
|
|
public Guid? LastUpdatedSessionId { get; private set; }
|
|
public Guid? LastUpdatedGroupId { get; private set; }
|
|
public string? LastUpdatedTitle { get; private set; }
|
|
public DateTime? LastUpdatedScheduledAt { get; private set; }
|
|
public string? LastUpdatedJoinLink { get; private set; }
|
|
public int? LastUpdatedMaxPlayers { get; private set; }
|
|
public Guid? LastPromotedSessionId { get; private set; }
|
|
public Guid? LastPromotedGroupId { get; private set; }
|
|
public Guid? LastUpdatedBatchId { get; private set; }
|
|
public Guid? LastUpdatedBatchGroupId { get; private set; }
|
|
public string? LastUpdatedBatchTitle { get; private set; }
|
|
public string? LastUpdatedBatchJoinLink { get; private set; }
|
|
public Guid? LastRescheduledBatchId { get; private set; }
|
|
public Guid? LastRescheduledBatchGroupId { get; private set; }
|
|
public DateTime? LastRescheduledFirstScheduledAt { get; private set; }
|
|
public int? LastRescheduledIntervalDays { get; private set; }
|
|
public Guid? LastClonedBatchId { get; private set; }
|
|
public Guid? LastClonedBatchGroupId { get; private set; }
|
|
public BatchCloneInterval? LastCloneInterval { get; private set; }
|
|
|
|
public Task<List<WebGameGroup>> GetGroupsForGmAsync(long gmId) =>
|
|
Task.FromResult(groupsById.Values.Where(group => group.GmTelegramId == gmId).ToList());
|
|
|
|
public Task<WebGameGroup?> GetGroupAsync(Guid groupId)
|
|
{
|
|
groupsById.TryGetValue(groupId, out var group);
|
|
return Task.FromResult(group);
|
|
}
|
|
|
|
public Task<List<WebSession>> GetUpcomingSessionsAsync(Guid groupId) =>
|
|
Task.FromResult(sessionsById.Values.Where(session => session.GroupId == groupId).ToList());
|
|
|
|
public Task<WebSession?> GetSessionAsync(Guid sessionId)
|
|
{
|
|
sessionsById.TryGetValue(sessionId, out var session);
|
|
return Task.FromResult(session);
|
|
}
|
|
|
|
public Task<WebSessionBatch?> GetBatchAsync(Guid batchId)
|
|
{
|
|
var batchSessions = sessionsById.Values
|
|
.Where(session => session.BatchId == batchId)
|
|
.OrderBy(session => session.ScheduledAt)
|
|
.ToList();
|
|
|
|
if (batchSessions.Count == 0)
|
|
{
|
|
return Task.FromResult<WebSessionBatch?>(null);
|
|
}
|
|
|
|
var firstSession = batchSessions[0];
|
|
return Task.FromResult<WebSessionBatch?>(new(
|
|
batchId,
|
|
firstSession.GroupId,
|
|
firstSession.Title,
|
|
firstSession.JoinLink,
|
|
firstSession.ScheduledAt,
|
|
batchSessions[^1].ScheduledAt,
|
|
batchSessions.Count));
|
|
}
|
|
|
|
public Task UpdateSessionAsync(Guid sessionId, Guid groupId, string title, DateTime scheduledAt, string joinLink, int? maxPlayers)
|
|
{
|
|
UpdateCalled = true;
|
|
LastUpdatedSessionId = sessionId;
|
|
LastUpdatedGroupId = groupId;
|
|
LastUpdatedTitle = title;
|
|
LastUpdatedScheduledAt = scheduledAt;
|
|
LastUpdatedJoinLink = joinLink;
|
|
LastUpdatedMaxPlayers = maxPlayers;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task PromoteWaitlistedPlayerAsync(Guid sessionId, Guid groupId)
|
|
{
|
|
PromoteCalled = true;
|
|
LastPromotedSessionId = sessionId;
|
|
LastPromotedGroupId = groupId;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task UpdateBatchDetailsAsync(Guid batchId, Guid groupId, string title, string joinLink)
|
|
{
|
|
UpdateBatchDetailsCalled = true;
|
|
LastUpdatedBatchId = batchId;
|
|
LastUpdatedBatchGroupId = groupId;
|
|
LastUpdatedBatchTitle = title;
|
|
LastUpdatedBatchJoinLink = joinLink;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task RescheduleBatchAsync(Guid batchId, Guid groupId, DateTime firstScheduledAt, int intervalDays)
|
|
{
|
|
RescheduleBatchCalled = true;
|
|
LastRescheduledBatchId = batchId;
|
|
LastRescheduledBatchGroupId = groupId;
|
|
LastRescheduledFirstScheduledAt = firstScheduledAt;
|
|
LastRescheduledIntervalDays = intervalDays;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task<WebSessionBatch> CloneBatchAsync(Guid batchId, Guid groupId, BatchCloneInterval interval)
|
|
{
|
|
CloneBatchCalled = true;
|
|
LastClonedBatchId = batchId;
|
|
LastClonedBatchGroupId = groupId;
|
|
LastCloneInterval = interval;
|
|
return Task.FromResult(new WebSessionBatch(
|
|
Guid.NewGuid(),
|
|
groupId,
|
|
"Session A",
|
|
"https://example.test/a",
|
|
DateTime.UtcNow.AddDays(7),
|
|
DateTime.UtcNow.AddDays(7),
|
|
1));
|
|
}
|
|
}
|
|
}
|