using System.Text; using System.Text.Json; using Omika.Core.Services.Mobile; namespace Omika.Tests.Mobile; /// /// Tests du TokenExpirationReader : lecture NON autoritative de l'expiration /// depuis le payload, sans vérification de signature. /// public class TokenExpirationReaderTests { private static readonly DateTimeOffset Now = new(2026, 9, 29, 12, 0, 0, TimeSpan.Zero); private static string Token(long expiresAtUtc) => Base64Url(JsonSerializer.SerializeToUtf8Bytes(new { ExpiresAtUtc = expiresAtUtc })) + ".signature-ignored"; private static string Base64Url(byte[] data) => Convert.ToBase64String(data).TrimEnd('=').Replace('+', '-').Replace('/', '_'); [Fact] public void ExpirationFuture_NestPasExpire() { string token = Token(Now.AddMinutes(5).ToUnixTimeSeconds()); Assert.False(TokenExpirationReader.IsExpired(token, Now)); } [Fact] public void ExpirationPassee_EstExpire() { string token = Token(Now.AddSeconds(-1).ToUnixTimeSeconds()); Assert.True(TokenExpirationReader.IsExpired(token, Now)); } [Fact] public void BoundaryExactNow_NestPasExpire_StrictementInferieur() { // Le serveur utilise ExpiresAtUtc < now (strict). À l'instant exact, // le token n'est PAS encore expiré. string token = Token(Now.ToUnixTimeSeconds()); Assert.False(TokenExpirationReader.IsExpired(token, Now)); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] [InlineData("pas-de-point")] [InlineData("a.b.c")] [InlineData("!base64-invalide!.sig")] [InlineData("eyJmb28iOiJiYXIifQ==.sig")] // payload sans ExpiresAtUtc public void FormatInvalide_RetourneNullEtNonExpire(string? token) { Assert.Null(TokenExpirationReader.TryReadExpiresAtUtc(token)); Assert.False(TokenExpirationReader.IsExpired(token, Now)); } [Fact] public void LitExpirationSansVerifierSignature() { string token = Token(Now.AddHours(2).ToUnixTimeSeconds()); DateTimeOffset? expires = TokenExpirationReader.TryReadExpiresAtUtc(token); Assert.NotNull(expires); Assert.Equal(Now.AddHours(2).ToUnixTimeSeconds(), expires!.Value.ToUnixTimeSeconds()); } }