feat(web): dashboard session deletion and E2E coverage for issue #150
Deploy Telegram Bot / build-and-push (push) Failing after 28m28s
Deploy Telegram Bot / scan-images (push) Has been skipped
Deploy Telegram Bot / deploy (push) Has been skipped

- Add DeleteSessionAsync to ISessionStore/SessionService (unpublish portfolio card,
  remove bot-created empty forum topic, update batch message).
- Add DeleteSessionForCurrentUserAsync to AuthorizedSessionService with audit log.
- Add delete button + confirmation dialog to GroupDetails.razor.
- Extend dashboard Playwright tests with edit persistence and delete verification.
- Update AuthorizedSessionServiceTests with delete authorization coverage.
- Mark issue #150 as done in tests/e2e/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 13:05:48 +03:00
parent 892f39401c
commit 40fc435bda
14 changed files with 1140 additions and 28 deletions
@@ -765,6 +765,58 @@ public sealed class AuthorizedSessionServiceTests
Assert.False(store.CreateBatchFromTemplateCalled);
}
[Fact]
public async Task DeleteSessionForCurrentUserAsync_Deletes_WhenSessionBelongsToManager()
{
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 accessor = CreateAccessor(gmId.ToString(System.Globalization.CultureInfo.InvariantCulture));
var service = new AuthorizedSessionService(store, accessor);
await service.DeleteSessionForCurrentUserAsync(sessionId);
Assert.True(store.DeleteCalled);
Assert.Equal(sessionId, store.LastDeletedSessionId);
Assert.Equal(groupId, store.LastDeletedGroupId);
Assert.Null(await store.GetSessionAsync(sessionId));
}
[Fact]
public async Task DeleteSessionForCurrentUserAsync_Throws_WhenSessionDoesNotBelongToManager()
{
var gmId = 1001L;
var otherGmId = 2002L;
var groupId = Guid.NewGuid();
var otherGroupId = Guid.NewGuid();
var sessionId = Guid.NewGuid();
var store = new FakeSessionStore(
groups:
[
new(groupId, 42, "Alpha", gmId),
new(otherGroupId, 43, "Beta", otherGmId)
],
sessions:
[
new(sessionId, otherGroupId, "Session B", DateTime.UtcNow, "Planned", "https://example.test/b", Guid.NewGuid(), 10, 43, 4, 1, 0)
]);
var accessor = CreateAccessor(gmId.ToString(System.Globalization.CultureInfo.InvariantCulture));
var service = new AuthorizedSessionService(store, accessor);
await Assert.ThrowsAsync<SessionAccessDeniedException>(
() => service.DeleteSessionForCurrentUserAsync(sessionId));
Assert.False(store.DeleteCalled);
}
private sealed class FakeSessionStore(
IEnumerable<WebGameGroup>? groups = null,
IEnumerable<WebSession>? sessions = null,
@@ -796,6 +848,9 @@ public sealed class AuthorizedSessionServiceTests
public DateTime? LastUpdatedScheduledAt { get; private set; }
public string? LastUpdatedJoinLink { get; private set; }
public int? LastUpdatedMaxPlayers { get; private set; }
public bool DeleteCalled { get; private set; }
public Guid? LastDeletedSessionId { get; private set; }
public Guid? LastDeletedGroupId { get; private set; }
public Guid? LastPromotedSessionId { get; private set; }
public Guid? LastPromotedGroupId { get; private set; }
public Guid? LastUpdatedBatchId { get; private set; }
@@ -1036,6 +1091,15 @@ public sealed class AuthorizedSessionServiceTests
return Task.CompletedTask;
}
public Task<string?> DeleteSessionAsync(Guid sessionId, Guid groupId)
{
DeleteCalled = true;
LastDeletedSessionId = sessionId;
LastDeletedGroupId = groupId;
sessionsById.Remove(sessionId);
return Task.FromResult<string?>("Deleted session");
}
public Task PromoteWaitlistedPlayerAsync(Guid sessionId, Guid groupId)
{
PromoteCalled = true;