From e4331b17f84461e1999300cbbb43dd07c3a4781b Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 23 Aug 2024 17:45:45 +0100 Subject: [PATCH 1/5] Use DateTimeOffset for claims fields --- NATS.Jwt.Tests/Models/JwtClaimsDataTests.cs | 19 ++++---- .../Models/NatsAccountClaimsTests.cs | 6 +-- .../Models/NatsActivationClaimsTests.cs | 7 +-- .../NatsAuthorizationRequestClaimsTests.cs | 7 +-- .../NatsAuthorizationResponseClaimsTests.cs | 7 +-- .../Models/NatsGenericFieldsClaimsTests.cs | 7 +-- .../Models/NatsOperatorClaimsTests.cs | 7 +-- NATS.Jwt.Tests/Models/NatsUserClaimsTests.cs | 6 +-- .../NatsJsonDateTimeOffsetConverter.cs | 44 +++++++++++++++++++ NATS.Jwt/Models/JwtClaimsData.cs | 11 +++-- NATS.Jwt/NatsJwt.cs | 2 +- NATS.Jwt/PublicAPI.Unshipped.txt | 6 +-- 12 files changed, 92 insertions(+), 37 deletions(-) create mode 100644 NATS.Jwt/Internal/NatsJsonDateTimeOffsetConverter.cs diff --git a/NATS.Jwt.Tests/Models/JwtClaimsDataTests.cs b/NATS.Jwt.Tests/Models/JwtClaimsDataTests.cs index d1e61e2..06304a9 100644 --- a/NATS.Jwt.Tests/Models/JwtClaimsDataTests.cs +++ b/NATS.Jwt.Tests/Models/JwtClaimsDataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The NATS Authors. // Licensed under the Apache License, Version 2.0. +using System; using System.Text.Json; using NATS.Jwt.Models; using Xunit; @@ -16,12 +17,12 @@ public void SerializeDeserialize_FullJwtClaimsData_ShouldSucceed() { Audience = "test_audience", Id = "test_id", - IssuedAt = 1609459200, // 2021-01-01 + IssuedAt = DateTimeOffset.FromUnixTimeSeconds(1609459200), // 2021-01-01 Issuer = "test_issuer", Name = "Test JWT", Subject = "test_subject", - Expires = 1735689600, // 2025-01-01 - NotBefore = 1609459200, // 2021-01-01 + Expires = DateTimeOffset.FromUnixTimeSeconds(1735689600), // 2025-01-01 + NotBefore = DateTimeOffset.FromUnixTimeSeconds(1609459200), // 2021-01-01 }; string json = JsonSerializer.Serialize(claims); @@ -53,10 +54,10 @@ public void SerializeDeserialize_MinimalJwtClaimsData_ShouldSucceed() Assert.Equal(claims.Issuer, deserialized.Issuer); Assert.Null(deserialized.Audience); Assert.Null(deserialized.Id); - Assert.Equal(0, deserialized.IssuedAt); + Assert.Null(deserialized.IssuedAt); Assert.Null(deserialized.Name); - Assert.Equal(0, deserialized.Expires); - Assert.Equal(0, deserialized.NotBefore); + Assert.Null(deserialized.Expires); + Assert.Null(deserialized.NotBefore); } [Fact] @@ -76,10 +77,10 @@ public void Deserialize_ExtraFields_ShouldIgnore() Assert.Equal("test_issuer", deserialized.Issuer); Assert.Null(deserialized.Audience); Assert.Null(deserialized.Id); - Assert.Equal(0, deserialized.IssuedAt); + Assert.Null(deserialized.IssuedAt); Assert.Null(deserialized.Name); - Assert.Equal(0, deserialized.Expires); - Assert.Equal(0, deserialized.NotBefore); + Assert.Null(deserialized.Expires); + Assert.Null(deserialized.NotBefore); } [Fact] diff --git a/NATS.Jwt.Tests/Models/NatsAccountClaimsTests.cs b/NATS.Jwt.Tests/Models/NatsAccountClaimsTests.cs index 89e53df..40ff7e6 100644 --- a/NATS.Jwt.Tests/Models/NatsAccountClaimsTests.cs +++ b/NATS.Jwt.Tests/Models/NatsAccountClaimsTests.cs @@ -20,9 +20,9 @@ public void SerializeDeserialize_FullNatsAccountClaims_ShouldSucceed() Issuer = "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", Name = "Full Test Account", Audience = "test_audience", - Expires = 1735689600, // 2025-01-01 - IssuedAt = 1609459200, // 2021-01-01 - NotBefore = 1609459200, // 2021-01-01 + Expires = DateTimeOffset.FromUnixTimeSeconds(1735689600), // 2025-01-01 + IssuedAt = DateTimeOffset.FromUnixTimeSeconds(1609459200), // 2021-01-01 + NotBefore = DateTimeOffset.FromUnixTimeSeconds(1609459200), // 2021-01-01 Id = "jti_test", Account = new NatsAccount { diff --git a/NATS.Jwt.Tests/Models/NatsActivationClaimsTests.cs b/NATS.Jwt.Tests/Models/NatsActivationClaimsTests.cs index bc0e169..7a7be12 100644 --- a/NATS.Jwt.Tests/Models/NatsActivationClaimsTests.cs +++ b/NATS.Jwt.Tests/Models/NatsActivationClaimsTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The NATS Authors. // Licensed under the Apache License, Version 2.0. +using System; using System.Text.Json; using NATS.Jwt.Models; using Xunit; @@ -18,9 +19,9 @@ public void SerializeDeserialize_FullNatsActivationClaims_ShouldSucceed() Issuer = "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", Name = "Full Test Activation", Audience = "test_audience", - Expires = 1735689600, // 2025-01-01 - IssuedAt = 1609459200, // 2021-01-01 - NotBefore = 1609459200, // 2021-01-01 + Expires = DateTimeOffset.FromUnixTimeSeconds(1735689600), // 2025-01-01 + IssuedAt = DateTimeOffset.FromUnixTimeSeconds(1609459200), // 2021-01-01 + NotBefore = DateTimeOffset.FromUnixTimeSeconds(1609459200), // 2021-01-01 Id = "jti_test", Activation = new NatsActivation { diff --git a/NATS.Jwt.Tests/Models/NatsAuthorizationRequestClaimsTests.cs b/NATS.Jwt.Tests/Models/NatsAuthorizationRequestClaimsTests.cs index e6220e3..a503b62 100644 --- a/NATS.Jwt.Tests/Models/NatsAuthorizationRequestClaimsTests.cs +++ b/NATS.Jwt.Tests/Models/NatsAuthorizationRequestClaimsTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The NATS Authors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Text.Json; using NATS.Jwt.Models; @@ -19,9 +20,9 @@ public void SerializeDeserialize_FullNatsAuthorizationRequestClaims_ShouldSuccee Issuer = "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", Name = "Full Test Authorization Request", Audience = "test_audience", - Expires = 1735689600, - IssuedAt = 1609459200, - NotBefore = 1609459200, + Expires = DateTimeOffset.FromUnixTimeSeconds(1735689600), + IssuedAt = DateTimeOffset.FromUnixTimeSeconds(1609459200), + NotBefore = DateTimeOffset.FromUnixTimeSeconds(1609459200), Id = "jti_test", AuthorizationRequest = new NatsAuthorizationRequest { diff --git a/NATS.Jwt.Tests/Models/NatsAuthorizationResponseClaimsTests.cs b/NATS.Jwt.Tests/Models/NatsAuthorizationResponseClaimsTests.cs index 811e674..3cc14e0 100644 --- a/NATS.Jwt.Tests/Models/NatsAuthorizationResponseClaimsTests.cs +++ b/NATS.Jwt.Tests/Models/NatsAuthorizationResponseClaimsTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The NATS Authors. // Licensed under the Apache License, Version 2.0. +using System; using System.Text.Json; using NATS.Jwt.Models; using Xunit; @@ -18,9 +19,9 @@ public void SerializeDeserialize_FullNatsAuthorizationResponseClaims_ShouldSucce Issuer = "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", Name = "Full Test Authorization Response", Audience = "test_audience", - Expires = 1735689600, - IssuedAt = 1609459200, - NotBefore = 1609459200, + Expires = DateTimeOffset.FromUnixTimeSeconds(1735689600), + IssuedAt = DateTimeOffset.FromUnixTimeSeconds(1609459200), + NotBefore = DateTimeOffset.FromUnixTimeSeconds(1609459200), Id = "jti_test", AuthorizationResponse = new NatsAuthorizationResponse { diff --git a/NATS.Jwt.Tests/Models/NatsGenericFieldsClaimsTests.cs b/NATS.Jwt.Tests/Models/NatsGenericFieldsClaimsTests.cs index 7a286b0..adb6af8 100644 --- a/NATS.Jwt.Tests/Models/NatsGenericFieldsClaimsTests.cs +++ b/NATS.Jwt.Tests/Models/NatsGenericFieldsClaimsTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The NATS Authors. // Licensed under the Apache License, Version 2.0. +using System; using System.Text.Json; using NATS.Jwt.Models; using Xunit; @@ -18,9 +19,9 @@ public void SerializeDeserialize_FullNatsGenericFieldsClaims_ShouldSucceed() Issuer = "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", Name = "Full Test Generic Fields", Audience = "test_audience", - Expires = 1735689600, - IssuedAt = 1609459200, - NotBefore = 1609459200, + Expires = DateTimeOffset.FromUnixTimeSeconds(1735689600), + IssuedAt = DateTimeOffset.FromUnixTimeSeconds(1609459200), + NotBefore = DateTimeOffset.FromUnixTimeSeconds(1609459200), Id = "jti_test", GenericFields = new NatsGenericFields { diff --git a/NATS.Jwt.Tests/Models/NatsOperatorClaimsTests.cs b/NATS.Jwt.Tests/Models/NatsOperatorClaimsTests.cs index 7df3577..5c13a21 100644 --- a/NATS.Jwt.Tests/Models/NatsOperatorClaimsTests.cs +++ b/NATS.Jwt.Tests/Models/NatsOperatorClaimsTests.cs @@ -1,6 +1,7 @@ // Copyright (c) The NATS Authors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.Text.Json; using NATS.Jwt.Models; @@ -19,9 +20,9 @@ public void SerializeDeserialize_FullNatsOperatorClaims_ShouldSucceed() Issuer = "OIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", Name = "Full Test Operator", Audience = "test_audience", - Expires = 1735689600, // 2025-01-01 - IssuedAt = 1609459200, // 2021-01-01 - NotBefore = 1609459200, // 2021-01-01 + Expires = DateTimeOffset.FromUnixTimeSeconds(1735689600), // 2025-01-01 + IssuedAt = DateTimeOffset.FromUnixTimeSeconds(1609459200), // 2021-01-01 + NotBefore = DateTimeOffset.FromUnixTimeSeconds(1609459200), // 2021-01-01 Id = "jti_test", Operator = new NatsOperator { diff --git a/NATS.Jwt.Tests/Models/NatsUserClaimsTests.cs b/NATS.Jwt.Tests/Models/NatsUserClaimsTests.cs index 79f31bf..7e0d5f0 100644 --- a/NATS.Jwt.Tests/Models/NatsUserClaimsTests.cs +++ b/NATS.Jwt.Tests/Models/NatsUserClaimsTests.cs @@ -19,9 +19,9 @@ public void SerializeDeserialize_FullNatsUserClaims_ShouldSucceed() Issuer = "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII", Name = "Full Test User", Audience = "test_audience", - Expires = 1735689600, - IssuedAt = 1609459200, - NotBefore = 1609459200, + Expires = DateTimeOffset.FromUnixTimeSeconds(1735689600), + IssuedAt = DateTimeOffset.FromUnixTimeSeconds(1609459200), + NotBefore = DateTimeOffset.FromUnixTimeSeconds(1609459200), Id = "jti_test", User = new NatsUser { diff --git a/NATS.Jwt/Internal/NatsJsonDateTimeOffsetConverter.cs b/NATS.Jwt/Internal/NatsJsonDateTimeOffsetConverter.cs new file mode 100644 index 0000000..acd336e --- /dev/null +++ b/NATS.Jwt/Internal/NatsJsonDateTimeOffsetConverter.cs @@ -0,0 +1,44 @@ +// Copyright (c) The NATS Authors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using NATS.Jwt.Models; + +namespace NATS.Jwt.Internal; + +/// +internal class NatsJsonDateTimeOffsetConverter : JsonConverter +{ + /// + public override DateTimeOffset? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.Number) + { + throw new InvalidOperationException("Expected number"); + } + + var numberValue = reader.GetInt64(); + + if (typeToConvert == typeof(DateTimeOffset?)) + { + return DateTimeOffset.FromUnixTimeSeconds(numberValue); + } + + throw new InvalidOperationException($"Reading unknown date type {typeToConvert.Name} or value {numberValue}"); + } + + /// + public override void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options) + { + if (value is { } dateTimeOffset) + { + writer.WriteNumberValue(dateTimeOffset.ToUnixTimeSeconds()); + } + else + { + writer.WriteNullValue(); + } + } +} diff --git a/NATS.Jwt/Models/JwtClaimsData.cs b/NATS.Jwt/Models/JwtClaimsData.cs index 27454dd..383b46d 100644 --- a/NATS.Jwt/Models/JwtClaimsData.cs +++ b/NATS.Jwt/Models/JwtClaimsData.cs @@ -1,7 +1,9 @@ // Copyright (c) The NATS Authors. // Licensed under the Apache License, Version 2.0. +using System; using System.Text.Json.Serialization; +using NATS.Jwt.Internal; namespace NATS.Jwt.Models; @@ -33,7 +35,8 @@ public record JwtClaimsData /// [JsonPropertyName("iat")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public long IssuedAt { get; set; } + [JsonConverter(typeof(NatsJsonDateTimeOffsetConverter))] + public DateTimeOffset? IssuedAt { get; set; } /// /// Gets or sets the issuer claim in a JWT token. @@ -66,7 +69,8 @@ public record JwtClaimsData /// [JsonPropertyName("exp")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public long Expires { get; set; } + [JsonConverter(typeof(NatsJsonDateTimeOffsetConverter))] + public DateTimeOffset? Expires { get; set; } /// /// Gets or sets the "nbf" claim in a JWT token. @@ -75,5 +79,6 @@ public record JwtClaimsData /// [JsonPropertyName("nbf")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public long NotBefore { get; set; } + [JsonConverter(typeof(NatsJsonDateTimeOffsetConverter))] + public DateTimeOffset? NotBefore { get; set; } } diff --git a/NATS.Jwt/NatsJwt.cs b/NATS.Jwt/NatsJwt.cs index b63b7b9..db9779f 100644 --- a/NATS.Jwt/NatsJwt.cs +++ b/NATS.Jwt/NatsJwt.cs @@ -403,7 +403,7 @@ private string DoEncode(JwtHeader jwtHeader, KeyPair keyPair, T claim, JsonTy var c = claim; c.Issuer = issuerBytes; - c.IssuedAt = issuedAt.ToUnixTimeSeconds(); + c.IssuedAt = issuedAt; // TODO: ID generation same as Go implementation // c.Id = Hash(c, typeInfo); diff --git a/NATS.Jwt/PublicAPI.Unshipped.txt b/NATS.Jwt/PublicAPI.Unshipped.txt index 2b29e58..6b1b96a 100644 --- a/NATS.Jwt/PublicAPI.Unshipped.txt +++ b/NATS.Jwt/PublicAPI.Unshipped.txt @@ -31,17 +31,17 @@ NATS.Jwt.Models.JetStreamLimits.Streams.set -> void NATS.Jwt.Models.JwtClaimsData NATS.Jwt.Models.JwtClaimsData.Audience.get -> string! NATS.Jwt.Models.JwtClaimsData.Audience.set -> void -NATS.Jwt.Models.JwtClaimsData.Expires.get -> long +NATS.Jwt.Models.JwtClaimsData.Expires.get -> System.DateTimeOffset? NATS.Jwt.Models.JwtClaimsData.Expires.set -> void NATS.Jwt.Models.JwtClaimsData.Id.get -> string! NATS.Jwt.Models.JwtClaimsData.Id.set -> void -NATS.Jwt.Models.JwtClaimsData.IssuedAt.get -> long +NATS.Jwt.Models.JwtClaimsData.IssuedAt.get -> System.DateTimeOffset? NATS.Jwt.Models.JwtClaimsData.IssuedAt.set -> void NATS.Jwt.Models.JwtClaimsData.Issuer.get -> string! NATS.Jwt.Models.JwtClaimsData.Issuer.set -> void NATS.Jwt.Models.JwtClaimsData.Name.get -> string! NATS.Jwt.Models.JwtClaimsData.Name.set -> void -NATS.Jwt.Models.JwtClaimsData.NotBefore.get -> long +NATS.Jwt.Models.JwtClaimsData.NotBefore.get -> System.DateTimeOffset? NATS.Jwt.Models.JwtClaimsData.NotBefore.set -> void NATS.Jwt.Models.JwtClaimsData.Subject.get -> string! NATS.Jwt.Models.JwtClaimsData.Subject.set -> void From 540b8eee24748c5353686c543e5ac7fcea505670 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 23 Aug 2024 17:46:49 +0100 Subject: [PATCH 2/5] dotnet format --- NATS.Jwt.Tests/Models/NatsAccountClaimsTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NATS.Jwt.Tests/Models/NatsAccountClaimsTests.cs b/NATS.Jwt.Tests/Models/NatsAccountClaimsTests.cs index 40ff7e6..07826cb 100644 --- a/NATS.Jwt.Tests/Models/NatsAccountClaimsTests.cs +++ b/NATS.Jwt.Tests/Models/NatsAccountClaimsTests.cs @@ -102,7 +102,8 @@ public void SerializeDeserialize_FullNatsAccountClaims_ShouldSucceed() { Pub = new NatsPermission { - Allow = new List { "allowed.>", }, Deny = new List { "denied.>", }, + Allow = new List { "allowed.>", }, + Deny = new List { "denied.>", }, }, Sub = new NatsPermission From 8f34279d4fbd29e15d98618f840e3b27b70ee20f Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 23 Aug 2024 18:43:54 +0100 Subject: [PATCH 3/5] DateTimeOffset converter tests --- NATS.Jwt.Tests/Models/JwtClaimsDataTests.cs | 148 ++++++++++++++++++++ NATS.Jwt/NATS.Jwt.csproj | 4 + 2 files changed, 152 insertions(+) diff --git a/NATS.Jwt.Tests/Models/JwtClaimsDataTests.cs b/NATS.Jwt.Tests/Models/JwtClaimsDataTests.cs index 06304a9..ab90945 100644 --- a/NATS.Jwt.Tests/Models/JwtClaimsDataTests.cs +++ b/NATS.Jwt.Tests/Models/JwtClaimsDataTests.cs @@ -3,6 +3,8 @@ using System; using System.Text.Json; +using System.Text.Json.Serialization; +using NATS.Jwt.Internal; using NATS.Jwt.Models; using Xunit; @@ -111,4 +113,150 @@ public void Deserialize_InvalidJson_ShouldThrowException() Assert.Throws(() => JsonSerializer.Deserialize(invalidJson)); } + + [Fact] + public void Deserialize_WithNullValues_ShouldSetPropertiesToNull() + { + // Arrange + var json = "{}"; + + // Act + var claims = JsonSerializer.Deserialize(json); + + // Assert + Assert.Null(claims.Audience); + Assert.Null(claims.Id); + Assert.Null(claims.IssuedAt); + Assert.Null(claims.Issuer); + Assert.Null(claims.Name); + Assert.Null(claims.Subject); + Assert.Null(claims.Expires); + Assert.Null(claims.NotBefore); + } + + [Fact] + public void Serialize_WithNullValues_ShouldOmitNullProperties() + { + // Arrange + var claims = new JwtClaimsData(); + + // Act + var json = JsonSerializer.Serialize(claims); + + // Assert + Assert.Equal("{}", json); + } + + [Fact] + public void Deserialize_WithUnixTimestamps_ShouldConvertCorrectly() + { + // Arrange + var json = @"{ + ""iat"": 1609459200, + ""exp"": 1609545600, + ""nbf"": 1609372800 + }"; + + // Act + var claims = JsonSerializer.Deserialize(json); + + // Assert + Assert.Equal(new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero), claims.IssuedAt); + Assert.Equal(new DateTimeOffset(2021, 1, 2, 0, 0, 0, TimeSpan.Zero), claims.Expires); + Assert.Equal(new DateTimeOffset(2020, 12, 31, 0, 0, 0, TimeSpan.Zero), claims.NotBefore); + } + + [Fact] + public void NatsJsonDateTimeOffsetConverter_Read_ValidNumber_ShouldConvertCorrectly() + { + // Arrange + var json = @"{""Timestamp"": 1609459200}"; + var options = new JsonSerializerOptions + { + Converters = { new NatsJsonDateTimeOffsetConverter() } + }; + + // Act + var result = JsonSerializer.Deserialize(json, options); + + // Assert + Assert.Equal(new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero), result.Timestamp); + } + + [Fact] + public void NatsJsonDateTimeOffsetConverter_Read_NullValue_ShouldReturnNull() + { + // Arrange + var json = @"{""Timestamp"": null}"; + var options = new JsonSerializerOptions + { + Converters = { new NatsJsonDateTimeOffsetConverter() } + }; + + // Act + var result = JsonSerializer.Deserialize(json, options); + + // Assert + Assert.Null(result.Timestamp); + } + + [Fact] + public void NatsJsonDateTimeOffsetConverter_Read_InvalidTokenType_ShouldThrowException() + { + // Arrange + var json = @"{""Timestamp"": ""not a number""}"; + var options = new JsonSerializerOptions + { + Converters = { new NatsJsonDateTimeOffsetConverter() }, + }; + + // Act & Assert + Assert.Throws(() => JsonSerializer.Deserialize(json, options)); + } + + [Fact] + public void NatsJsonDateTimeOffsetConverter_Write_ValidValue_ShouldWriteNumber() + { + // Arrange + var testObject = new TestClass + { + Timestamp = new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero) + }; + var options = new JsonSerializerOptions + { + Converters = { new NatsJsonDateTimeOffsetConverter() } + }; + + // Act + var json = JsonSerializer.Serialize(testObject, options); + + // Assert + Assert.Equal(@"{""Timestamp"":1609459200}", json); + } + + [Fact] + public void NatsJsonDateTimeOffsetConverter_Write_NullValue_ShouldWriteNull() + { + // Arrange + var testObject = new TestClass + { + Timestamp = null + }; + var options = new JsonSerializerOptions + { + Converters = { new NatsJsonDateTimeOffsetConverter() } + }; + + // Act + var json = JsonSerializer.Serialize(testObject, options); + + // Assert + Assert.Equal(@"{""Timestamp"":null}", json); + } + + private class TestClass + { + [JsonConverter(typeof(NatsJsonDateTimeOffsetConverter))] + public DateTimeOffset? Timestamp { get; set; } + } } diff --git a/NATS.Jwt/NATS.Jwt.csproj b/NATS.Jwt/NATS.Jwt.csproj index 2eca8e4..b7f0846 100644 --- a/NATS.Jwt/NATS.Jwt.csproj +++ b/NATS.Jwt/NATS.Jwt.csproj @@ -37,4 +37,8 @@ + + + + From 755b1b65b099a6cfe802860f4ce717b48dcbfcdf Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 23 Aug 2024 18:57:59 +0100 Subject: [PATCH 4/5] Test coverage --- .../NatsJsonDateTimeOffsetConverterTests.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 NATS.Jwt.Tests/Models/NatsJsonDateTimeOffsetConverterTests.cs diff --git a/NATS.Jwt.Tests/Models/NatsJsonDateTimeOffsetConverterTests.cs b/NATS.Jwt.Tests/Models/NatsJsonDateTimeOffsetConverterTests.cs new file mode 100644 index 0000000..fab2b0e --- /dev/null +++ b/NATS.Jwt.Tests/Models/NatsJsonDateTimeOffsetConverterTests.cs @@ -0,0 +1,59 @@ +// Copyright (c) The NATS Authors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Text.Json; +using NATS.Jwt.Internal; +using Xunit; + +namespace NATS.Jwt.Tests.Models; + +public class NatsJsonDateTimeOffsetConverterTests +{ + [Fact] + public void Read_UnsupportedType_ShouldThrowException() + { + // Act & Assert + var ex = Assert.Throws(() => + { + // Arrange + var converter = new TestConverter(); + var reader = new Utf8JsonReader(JsonSerializer.SerializeToUtf8Bytes(123)); + reader.Read(); // Move to the first token + converter.Read(ref reader, typeof(DateTime), new JsonSerializerOptions()); + }); + Assert.Contains("Reading unknown date type", ex.Message); + } + + [Fact] + public void Write_ValidValue_ShouldWriteCorrectly() + { + // Arrange + var converter = new TestConverter(); + MemoryStream memoryStream = new(); + var writer = new Utf8JsonWriter(memoryStream); + var value = new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero); + + // Act + converter.Write(writer, value, new JsonSerializerOptions()); + writer.Flush(); + + // Assert + var json = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray()); + Assert.Equal("1609459200", json); + } + + private class TestConverter : NatsJsonDateTimeOffsetConverter + { + public new void Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + base.Read(ref reader, typeToConvert, options); + } + + public new void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options) + { + base.Write(writer, value, options); + } + } +} From 645c6f1becb6fd7e8425f68ea489beef4338c075 Mon Sep 17 00:00:00 2001 From: Ziya Suzen Date: Fri, 23 Aug 2024 19:12:40 +0100 Subject: [PATCH 5/5] Test coverage --- .../NatsJsonDateTimeOffsetConverterTests.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/NATS.Jwt.Tests/Models/NatsJsonDateTimeOffsetConverterTests.cs b/NATS.Jwt.Tests/Models/NatsJsonDateTimeOffsetConverterTests.cs index fab2b0e..37c2cf1 100644 --- a/NATS.Jwt.Tests/Models/NatsJsonDateTimeOffsetConverterTests.cs +++ b/NATS.Jwt.Tests/Models/NatsJsonDateTimeOffsetConverterTests.cs @@ -44,6 +44,23 @@ public void Write_ValidValue_ShouldWriteCorrectly() Assert.Equal("1609459200", json); } + [Fact] + public void Write_NullValue_ShouldWriteCorrectly() + { + // Arrange + var converter = new TestConverter(); + MemoryStream memoryStream = new(); + var writer = new Utf8JsonWriter(memoryStream); + + // Act + converter.Write(writer, null, new JsonSerializerOptions()); + writer.Flush(); + + // Assert + var json = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray()); + Assert.Equal("null", json); + } + private class TestConverter : NatsJsonDateTimeOffsetConverter { public new void Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)