using Microsoft.AspNetCore.Http; using RpgRoller.Components; namespace RpgRoller.Tests; public sealed class WorkspaceQueryServiceTests { private sealed class StubGameService : IGameService { public IReadOnlyList GetRulesets() { throw new NotSupportedException(); } public ServiceResult Register(string username, string password, string displayName) { throw new NotSupportedException(); } public ServiceResult<(UserSummary User, string SessionToken)> Login(string username, string password) { throw new NotSupportedException(); } public void Logout(string sessionToken) { throw new NotSupportedException(); } public UserSummary? GetUserBySession(string sessionToken) { throw new NotSupportedException(); } public ServiceResult GetMe(string sessionToken) { return GetMeHandler(sessionToken); } public ServiceResult CreateCampaign(string sessionToken, string name, string rulesetId) { throw new NotSupportedException(); } public ServiceResult> GetCampaigns(string sessionToken) { return GetCampaignsHandler(sessionToken); } public ServiceResult> GetCharacterCampaignOptions(string sessionToken) { throw new NotSupportedException(); } public ServiceResult GetCampaign(string sessionToken, Guid campaignId) { throw new NotSupportedException(); } public ServiceResult DeleteCampaign(string sessionToken, Guid campaignId) { throw new NotSupportedException(); } public ServiceResult> GetUsernames(string sessionToken) { throw new NotSupportedException(); } public ServiceResult> GetUsers(string sessionToken) { throw new NotSupportedException(); } public ServiceResult UpdateUserRoles(string sessionToken, Guid userId, IReadOnlyList roles) { throw new NotSupportedException(); } public ServiceResult DeleteUser(string sessionToken, Guid userId) { throw new NotSupportedException(); } public ServiceResult CreateCharacter(string sessionToken, string name, Guid campaignId) { throw new NotSupportedException(); } public ServiceResult UpdateCharacter(string sessionToken, Guid characterId, string name, Guid? campaignId, string? ownerUsername = null) { throw new NotSupportedException(); } public ServiceResult DeleteCharacter(string sessionToken, Guid characterId) { throw new NotSupportedException(); } public ServiceResult ActivateCharacter(string sessionToken, Guid characterId) { throw new NotSupportedException(); } public ServiceResult> GetOwnCharacters(string sessionToken) { throw new NotSupportedException(); } public ServiceResult CreateSkillGroup(string sessionToken, Guid characterId, string name, string diceRollDefinition, int wildDice, bool allowFumble, int? fumbleRange = null) { throw new NotSupportedException(); } public ServiceResult UpdateSkillGroup(string sessionToken, Guid skillGroupId, string name, string diceRollDefinition, int wildDice, bool allowFumble, int? fumbleRange = null) { throw new NotSupportedException(); } public ServiceResult DeleteSkillGroup(string sessionToken, Guid skillGroupId) { throw new NotSupportedException(); } public ServiceResult CreateSkill(string sessionToken, Guid characterId, string name, string diceRollDefinition, int wildDice, bool allowFumble, Guid? skillGroupId = null, int? fumbleRange = null) { throw new NotSupportedException(); } public ServiceResult UpdateSkill(string sessionToken, Guid skillId, string name, string diceRollDefinition, int wildDice, bool allowFumble, Guid? skillGroupId = null, int? fumbleRange = null) { throw new NotSupportedException(); } public ServiceResult DeleteSkill(string sessionToken, Guid skillId) { throw new NotSupportedException(); } public ServiceResult GetCharacterSheet(string sessionToken, Guid characterId) { throw new NotSupportedException(); } public ServiceResult RollSkill(string sessionToken, Guid skillId, string visibility) { throw new NotSupportedException(); } public ServiceResult RollCustom(string sessionToken, Guid characterId, string expression, string visibility) { throw new NotSupportedException(); } public ServiceResult> GetCampaignLog(string sessionToken, Guid campaignId) { throw new NotSupportedException(); } public ServiceResult GetCampaignLogPage(string sessionToken, Guid campaignId, Guid? afterRollId = null, int? limit = null) { throw new NotSupportedException(); } public ServiceResult GetRollDetail(string sessionToken, Guid rollId) { throw new NotSupportedException(); } public ServiceResult GetCampaignStateSnapshot(string sessionToken, Guid campaignId) { throw new NotSupportedException(); } public Func> GetMeHandler { get; init; } = _ => ServiceResult.Failure("unexpected_call", "Unexpected GetMe call."); public Func>> GetCampaignsHandler { get; init; } = _ => ServiceResult>.Failure("unexpected_call", "Unexpected GetCampaigns call."); } [Fact] public void SessionTokenAccessor_ReadsSessionCookieFromHttpContext() { var httpContext = new DefaultHttpContext(); httpContext.Request.Headers.Cookie = "rpgroller_session=session-token"; var accessor = new HttpContextAccessor { HttpContext = httpContext }; var sessionTokenAccessor = new WorkspaceSessionTokenAccessor(accessor); Assert.Equal("session-token", sessionTokenAccessor.GetRequiredSessionToken()); } [Fact] public async Task GetCampaignsAsync_UsesCapturedSessionToken() { var service = new StubGameService { GetCampaignsHandler = sessionToken => { Assert.Equal("server-session", sessionToken); return ServiceResult>.Success([new(Guid.NewGuid(), "Alpha", "d6", new(Guid.NewGuid(), "GM"), 1)]); } }; var queryService = new WorkspaceQueryService(service, CreateSessionTokenAccessor("server-session")); var campaigns = await queryService.GetCampaignsAsync(); Assert.Single(campaigns); } [Fact] public async Task GetMeAsync_MapsUnauthorizedServiceResultToApiRequestException() { var service = new StubGameService { GetMeHandler = _ => ServiceResult.Failure("unauthorized", "You must be logged in.") }; var queryService = new WorkspaceQueryService(service, CreateSessionTokenAccessor("expired-session")); var exception = await Assert.ThrowsAsync(queryService.GetMeAsync); Assert.Equal(401, exception.StatusCode); Assert.Equal("You must be logged in.", exception.Message); Assert.Equal("unauthorized", exception.ErrorCode); } private static WorkspaceSessionTokenAccessor CreateSessionTokenAccessor(string sessionToken) { var httpContext = new DefaultHttpContext(); httpContext.Request.Headers.Cookie = $"rpgroller_session={sessionToken}"; return new(new HttpContextAccessor { HttpContext = httpContext }); } }