// Copyright 2018-2025 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using Shouldly; using ZB.MOM.NatsNet.Server.Auth; namespace ZB.MOM.NatsNet.Server.Tests.Auth; /// /// Tests for JwtProcessor functions. /// Mirrors Go jwt.go functionality for standalone testable functions. /// public class JwtProcessorTests { // ========================================================================= // JwtPrefix constant // ========================================================================= [Fact] public void JwtPrefix_IsCorrect() { JwtProcessor.JwtPrefix.ShouldBe("eyJ"); } // ========================================================================= // WipeSlice // ========================================================================= [Fact] public void WipeSlice_FillsWithX() { var buf = new byte[] { 0x01, 0x02, 0x03 }; JwtProcessor.WipeSlice(buf); buf.ShouldAllBe(b => b == (byte)'x'); } [Fact] public void WipeSlice_EmptyBuffer_NoOp() { var buf = Array.Empty(); JwtProcessor.WipeSlice(buf); } // ========================================================================= // ValidateSrc // ========================================================================= [Fact] public void ValidateSrc_NullCidrs_ReturnsFalse() { JwtProcessor.ValidateSrc(null, "192.168.1.1").ShouldBeFalse(); } [Fact] public void ValidateSrc_EmptyCidrs_ReturnsTrue() { JwtProcessor.ValidateSrc([], "192.168.1.1").ShouldBeTrue(); } [Fact] public void ValidateSrc_EmptyHost_ReturnsFalse() { JwtProcessor.ValidateSrc(["192.168.0.0/16"], "").ShouldBeFalse(); } [Fact] public void ValidateSrc_InvalidHost_ReturnsFalse() { JwtProcessor.ValidateSrc(["192.168.0.0/16"], "not-an-ip").ShouldBeFalse(); } [Fact] public void ValidateSrc_MatchingCidr_ReturnsTrue() { JwtProcessor.ValidateSrc(["192.168.0.0/16"], "192.168.1.100").ShouldBeTrue(); } [Fact] public void ValidateSrc_NonMatchingCidr_ReturnsFalse() { JwtProcessor.ValidateSrc(["10.0.0.0/8"], "192.168.1.100").ShouldBeFalse(); } [Fact] public void ValidateSrc_MultipleCidrs_MatchesAny() { var cidrs = new[] { "10.0.0.0/8", "192.168.0.0/16" }; JwtProcessor.ValidateSrc(cidrs, "192.168.1.100").ShouldBeTrue(); JwtProcessor.ValidateSrc(cidrs, "10.1.2.3").ShouldBeTrue(); JwtProcessor.ValidateSrc(cidrs, "172.16.0.1").ShouldBeFalse(); } [Fact] public void ValidateSrc_ExactMatch_SingleHost() { JwtProcessor.ValidateSrc(["192.168.1.100/32"], "192.168.1.100").ShouldBeTrue(); JwtProcessor.ValidateSrc(["192.168.1.100/32"], "192.168.1.101").ShouldBeFalse(); } [Fact] public void ValidateSrc_InvalidCidr_ReturnsFalse() { JwtProcessor.ValidateSrc(["not-a-cidr"], "192.168.1.1").ShouldBeFalse(); } [Fact] public void ValidateSrc_Ipv6_MatchingCidr() { JwtProcessor.ValidateSrc(["::1/128"], "::1").ShouldBeTrue(); JwtProcessor.ValidateSrc(["::1/128"], "::2").ShouldBeFalse(); } [Fact] public void ValidateSrc_MismatchedIpFamilies_ReturnsFalse() { // IPv6 CIDR with IPv4 address should not match. JwtProcessor.ValidateSrc(["::1/128"], "127.0.0.1").ShouldBeFalse(); } // ========================================================================= // ValidateTimes // ========================================================================= [Fact] public void ValidateTimes_NullRanges_ReturnsFalse() { var (allowed, remaining) = JwtProcessor.ValidateTimes(null); allowed.ShouldBeFalse(); remaining.ShouldBe(TimeSpan.Zero); } [Fact] public void ValidateTimes_EmptyRanges_ReturnsTrue() { var (allowed, remaining) = JwtProcessor.ValidateTimes([]); allowed.ShouldBeTrue(); remaining.ShouldBe(TimeSpan.Zero); } [Fact] public void ValidateTimes_CurrentTimeInRange_ReturnsTrue() { var now = DateTimeOffset.Now; var start = now.AddMinutes(-30).ToString("HH:mm:ss"); var end = now.AddMinutes(30).ToString("HH:mm:ss"); var ranges = new[] { new TimeRange { Start = start, End = end } }; var (allowed, remaining) = JwtProcessor.ValidateTimes(ranges); allowed.ShouldBeTrue(); remaining.TotalMinutes.ShouldBeGreaterThan(0); remaining.TotalMinutes.ShouldBeLessThanOrEqualTo(30); } [Fact] public void ValidateTimes_CurrentTimeOutOfRange_ReturnsFalse() { var now = DateTimeOffset.Now; // Set a range entirely in the past today. var start = now.AddHours(-3).ToString("HH:mm:ss"); var end = now.AddHours(-2).ToString("HH:mm:ss"); var ranges = new[] { new TimeRange { Start = start, End = end } }; var (allowed, _) = JwtProcessor.ValidateTimes(ranges); allowed.ShouldBeFalse(); } [Fact] public void ValidateTimes_InvalidFormat_ReturnsFalse() { var ranges = new[] { new TimeRange { Start = "bad", End = "data" } }; var (allowed, _) = JwtProcessor.ValidateTimes(ranges); allowed.ShouldBeFalse(); } }