Files
unkin-agent a167d00b73
ci/woodpecker/pr/ci Pipeline was successful
store OAuth state in the shared cache
Keys each in-flight OpenID flow in the server's Valkey/Redis connection so a login
can start on one replica and finish on another. Falls back to an in-memory store
when no multiplexer is registered.
2026-09-19 22:47:43 +10:00

83 lines
3.0 KiB
C#

using System;
using System.Collections.Generic;
using Duende.IdentityModel.OidcClient;
using Jellyfin.Plugin.SSO_Auth.Api;
using Jellyfin.Plugin.SSO_Auth.State;
using Xunit;
namespace Jellyfin.Plugin.SSO_Auth.Tests;
public class SsoStateSerializerTests
{
[Fact]
public void RoundTrip_PreservesEveryFieldTheFlowDependsOn()
{
var original = new TimedAuthorizeState(
new AuthorizeState
{
State = "state-value",
StartUrl = "https://idp.example/authorize?code_challenge=abc",
RedirectUri = "https://jellyfin.example/sso/OID/redirect/authentik",
CodeVerifier = "code-verifier-secret"
},
new DateTime(2026, 9, 19, 10, 30, 0, DateTimeKind.Utc))
{
Valid = true,
Username = "alice",
Admin = true,
IsLinking = true,
Folders = new List<string> { "movies", "shows" },
EnableLiveTv = true,
EnableLiveTvManagement = true,
AvatarURL = "https://idp.example/avatar/alice.png"
};
var restored = SsoStateSerializer.Deserialize(SsoStateSerializer.Serialize(original));
Assert.NotNull(restored);
Assert.Equal("state-value", restored.State.State);
Assert.Equal("https://idp.example/authorize?code_challenge=abc", restored.State.StartUrl);
Assert.Equal("https://jellyfin.example/sso/OID/redirect/authentik", restored.State.RedirectUri);
// The code verifier is required to complete the token exchange on the pod that handles the callback.
Assert.Equal("code-verifier-secret", restored.State.CodeVerifier);
Assert.Equal(original.Created, restored.Created);
Assert.True(restored.Valid);
Assert.Equal("alice", restored.Username);
Assert.True(restored.Admin);
Assert.True(restored.IsLinking);
Assert.Equal(new[] { "movies", "shows" }, restored.Folders);
Assert.True(restored.EnableLiveTv);
Assert.True(restored.EnableLiveTvManagement);
Assert.Equal("https://idp.example/avatar/alice.png", restored.AvatarURL);
}
[Fact]
public void RoundTrip_PreservesNulls()
{
var original = new TimedAuthorizeState(
new AuthorizeState { State = "state-value", CodeVerifier = null, RedirectUri = null, StartUrl = null },
DateTime.UtcNow);
var restored = SsoStateSerializer.Deserialize(SsoStateSerializer.Serialize(original));
Assert.NotNull(restored);
Assert.Equal("state-value", restored.State.State);
Assert.Null(restored.State.CodeVerifier);
Assert.Null(restored.State.RedirectUri);
Assert.Null(restored.Username);
Assert.Null(restored.AvatarURL);
Assert.Null(restored.Folders);
Assert.False(restored.Valid);
}
[Theory]
[InlineData(null)]
[InlineData("")]
public void Deserialize_WithoutPayload_ReturnsNull(string payload)
{
Assert.Null(SsoStateSerializer.Deserialize(payload));
}
}