thalos-bff/tests/Thalos.Bff.Application.UnitTests/GoogleOidcFlowServiceTests.cs
2026-03-31 16:02:08 -06:00

99 lines
4.0 KiB
C#

using Thalos.Bff.Application.Security.Oidc;
namespace Thalos.Bff.Application.UnitTests;
public class GoogleOidcFlowServiceTests
{
[Fact]
public void BuildStartContext_WhenReturnUrlAllowed_PreservesCallerReturnUrl()
{
var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret");
var context = service.BuildStartContext("https://furniture-display-demo.dream-views.com/dashboard", "tenant-1");
Assert.Equal("https://furniture-display-demo.dream-views.com/dashboard", context.Payload.ReturnUrl);
Assert.Equal("tenant-1", context.Payload.TenantId);
Assert.Contains("code_challenge_method=S256", context.AuthorizationUrl);
Assert.Contains("state=", context.AuthorizationUrl);
Assert.Contains("nonce=", context.AuthorizationUrl);
}
[Fact]
public void BuildStartContext_WhenReturnUrlNotAllowed_UsesDefaultReturnUrl()
{
var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret");
var context = service.BuildStartContext("https://malicious.example.com/redirect", "tenant-1");
Assert.Equal("https://auth.dream-views.com/", context.Payload.ReturnUrl);
}
[Fact]
public void TryValidateCallbackState_WhenStateMatchesAndNotExpired_ReturnsTrue()
{
var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret");
var start = service.BuildStartContext("https://auth.dream-views.com/", "tenant-1");
var ok = service.TryValidateCallbackState(start.EncodedState, start.Payload.State, out var payload);
Assert.True(ok);
Assert.Equal(start.Payload.CodeVerifier, payload.CodeVerifier);
Assert.Equal(start.Payload.TenantId, payload.TenantId);
}
[Fact]
public void TryValidateCallbackState_WhenStateTampered_ReturnsFalse()
{
var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret");
var start = service.BuildStartContext("https://auth.dream-views.com/", "tenant-1");
var ok = service.TryValidateCallbackState(start.EncodedState, $"{start.Payload.State}-tamper", out _);
Assert.False(ok);
}
[Fact]
public void BuildFailureRedirectUrl_WhenReturnUrlAllowed_PreservesCallerHostAndAddsErrorContext()
{
var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret");
var redirectUrl = service.BuildFailureRedirectUrl(
"https://furniture-display-demo.dream-views.com/dashboard",
"oidc_exchange_failed",
"corr-123");
Assert.StartsWith("https://furniture-display-demo.dream-views.com/dashboard", redirectUrl, StringComparison.Ordinal);
Assert.Contains("authError=oidc_exchange_failed", redirectUrl, StringComparison.Ordinal);
Assert.Contains("correlationId=corr-123", redirectUrl, StringComparison.Ordinal);
}
[Fact]
public void BuildFailureRedirectUrl_WhenReturnUrlNotAllowed_UsesDefaultReturnUrl()
{
var service = new GoogleOidcFlowService(CreateOptions(), "state-signing-secret");
var redirectUrl = service.BuildFailureRedirectUrl(
"https://malicious.example.com/redirect",
"oidc_state_invalid",
"corr-456");
Assert.StartsWith("https://auth.dream-views.com/", redirectUrl, StringComparison.Ordinal);
Assert.Contains("authError=oidc_state_invalid", redirectUrl, StringComparison.Ordinal);
}
private static GoogleOidcOptions CreateOptions()
{
return new GoogleOidcOptions(
"client-id-1",
"client-secret-1",
"https://auth.dream-views.com/api/identity/oidc/google/callback",
"https://accounts.google.com/o/oauth2/v2/auth",
"https://oauth2.googleapis.com/token",
"openid profile email",
"https://auth.dream-views.com/",
"demo-tenant",
["auth.dream-views.com", "furniture-display-demo.dream-views.com"],
TimeSpan.FromMinutes(10));
}
}