thalos-service/tests/Thalos.Service.Application.UnitTests/RefreshIdentitySessionUseCaseTests.cs
José René White Enciso 96c53d9dab feat(thalos-service): add canonical session flows
Why: provide service-side canonical login/refresh orchestration for session-based web auth.

What: add session contracts, refresh token codec with provider-agnostic secret boundary, grpc session methods, DI wiring, tests, and docs.

Rule: preserve thalos identity ownership and keep transport adapters at service edge.
2026-03-08 14:48:35 -06:00

79 lines
3.1 KiB
C#

using BuildingBlock.Identity.Contracts.Conventions;
using BuildingBlock.Identity.Contracts.Requests;
using BuildingBlock.Identity.Contracts.Responses;
using Thalos.Service.Application.Sessions;
using Thalos.Service.Application.UseCases;
using Thalos.Service.Identity.Abstractions.Contracts;
using IdentityIssueRequest = BuildingBlock.Identity.Contracts.Requests.IssueIdentityTokenRequest;
using IdentityIssueResponse = BuildingBlock.Identity.Contracts.Responses.IssueIdentityTokenResponse;
using SessionRefreshRequest = Thalos.Service.Identity.Abstractions.Contracts.RefreshIdentitySessionRequest;
namespace Thalos.Service.Application.UnitTests;
public class RefreshIdentitySessionUseCaseTests
{
[Fact]
public async Task HandleAsync_WhenRefreshTokenValid_ReissuesSessionTokens()
{
var useCase = new RefreshIdentitySessionUseCase(new FakeIssueUseCase(), new FakeSessionTokenCodec());
var response = await useCase.HandleAsync(new SessionRefreshRequest("refresh-token", "corr-1", IdentityAuthProvider.Google));
Assert.Equal("token-new", response.AccessToken);
Assert.Equal(3000, response.ExpiresInSeconds);
Assert.Equal("google-sub-1", response.SubjectId);
Assert.Equal("tenant-2", response.TenantId);
Assert.Equal("refresh-google-sub-1-tenant-2", response.RefreshToken);
}
[Fact]
public async Task HandleAsync_WhenRefreshTokenInvalid_ReturnsEmptyPayload()
{
var useCase = new RefreshIdentitySessionUseCase(new FakeIssueUseCase(), new InvalidSessionTokenCodec());
var response = await useCase.HandleAsync(new SessionRefreshRequest("bad-token", "corr-2"));
Assert.Equal(string.Empty, response.AccessToken);
Assert.Equal(0, response.ExpiresInSeconds);
Assert.Equal(string.Empty, response.RefreshToken);
}
private sealed class FakeIssueUseCase : IIssueIdentityTokenUseCase
{
public Task<IdentityIssueResponse> HandleAsync(IdentityIssueRequest request)
{
return Task.FromResult(new IdentityIssueResponse("token-new", 3000));
}
}
private sealed class FakeSessionTokenCodec : IIdentitySessionTokenCodec
{
public string Encode(IdentitySessionDescriptor descriptor)
{
return $"refresh-{descriptor.SubjectId}-{descriptor.TenantId}";
}
public bool TryDecode(string token, out IdentitySessionDescriptor descriptor)
{
descriptor = new IdentitySessionDescriptor(
"google-sub-1",
"tenant-2",
IdentityAuthProvider.Google,
DateTimeOffset.UtcNow.AddHours(1));
return true;
}
}
private sealed class InvalidSessionTokenCodec : IIdentitySessionTokenCodec
{
public string Encode(IdentitySessionDescriptor descriptor) => string.Empty;
public bool TryDecode(string token, out IdentitySessionDescriptor descriptor)
{
descriptor = new IdentitySessionDescriptor(string.Empty, string.Empty, IdentityAuthProvider.InternalJwt, DateTimeOffset.MinValue);
return false;
}
}
}