yavsc/src/PostIt.Tests/SchemeUrlDetectorTests.cs
Paul Schneider 514549c5f9 postit: traceable OIDC login UX + session persistence + 2nd-instance early exit
- OidcLoginPhase enum + IProgress<OidcLoginPhase> on
  YavscApiClient.LoginInteractiveAsync, surfaced in the UI as
  PhaseLabel (FR). Lets operators see where the flow actually
  stalls, in particular whether the postit://callback ever arrives
  on the running instance.
- YavscApiClient.TrySilentLoginAsync: silent refresh at boot.
  Returns false (and purges the store) when the refresh token is
  rejected by the OP.
- App.OnFrameworkInitializationCompleted auto-routes: HomePage is
  the navigation root; on Opened the app calls TrySilentLoginAsync
  and pushes MainPage if a session is restored. Logout pops back
  to HomePage via the new persistent SessionStatusBanner (Connecté
  / Déconnecté + Logout button).
- PostIt.Desktop.Program.Main now detects the postit://callback
  URL BEFORE Avalonia boots, hands it off via SingleInstance, and
  exits. Stops the 2nd PostIt instance from flashing its own
  MainWindow while the 1st instance is still waiting on the named
  pipe. The check in App.OnFrameworkInitializationCompleted is
  kept as belt-and-braces defence-in-depth.
- SchemeUrlDetector: pure platform-independent detector extracted
  for unit testing.
- Tests: 7 new SchemeUrlDetectorTests + 5 new phase / silent
  refresh tests in YavscApiClientTests.
2026-06-27 12:47:52 +01:00

79 lines
2.4 KiB
C#

using PostIt.Services;
using Xunit;
namespace PostIt.Tests;
/// <summary>
/// Tests for the platform-independent scheme-URL detector. The
/// detector is the first guard against the OS launching a fresh
/// PostIt instance with the postit://callback URL — it must match
/// even when Avalonia has not booted, otherwise the 2nd instance
/// flashes its own MainWindow before shutting down.
/// </summary>
public class SchemeUrlDetectorTests
{
[Fact]
public void FindCallbackUrl_returns_null_when_no_args()
{
Assert.Null(SchemeUrlDetector.FindCallbackUrl(System.Array.Empty<string>()));
}
[Fact]
public void FindCallbackUrl_returns_null_when_no_postit_arg_present()
{
var args = new[]
{
"/usr/bin/postit-desktop",
"--some-flag",
"value",
};
Assert.Null(SchemeUrlDetector.FindCallbackUrl(args));
}
[Fact]
public void FindCallbackUrl_returns_url_when_postit_scheme_present()
{
var args = new[]
{
"/usr/bin/postit-desktop",
"postit://callback?code=abc&state=xyz",
};
var hit = SchemeUrlDetector.FindCallbackUrl(args);
Assert.Equal("postit://callback?code=abc&state=xyz", hit);
}
[Fact]
public void FindCallbackUrl_is_case_insensitive_on_scheme()
{
var args = new[] { "POSTIT://callback?code=abc" };
Assert.Equal("POSTIT://callback?code=abc", SchemeUrlDetector.FindCallbackUrl(args));
}
[Fact]
public void FindCallbackUrl_ignores_args_that_mention_scheme_without_prefix()
{
// "postit-something://x" must NOT match — the prefix is the
// scheme followed by "://", nothing else.
var args = new[] { "postit-something://callback?code=abc" };
Assert.Null(SchemeUrlDetector.FindCallbackUrl(args));
}
[Fact]
public void FindCallbackUrl_returns_first_match_when_multiple_present()
{
// Defensive: an OS shouldn't hand us two URLs in argv, but if
// it ever does we want a deterministic answer (first).
var args = new[]
{
"postit://callback?code=first",
"postit://callback?code=second",
};
Assert.Equal("postit://callback?code=first", SchemeUrlDetector.FindCallbackUrl(args));
}
[Fact]
public void FindCallbackUrl_returns_null_for_null_args()
{
Assert.Null(SchemeUrlDetector.FindCallbackUrl(null!));
}
}