// Go: gateway.go:100-150 (InterestMode enum), gateway.go:1500-1600 (switchToInterestOnlyMode) using NATS.Server.Gateways; namespace NATS.Server.Gateways.Tests.Gateways; /// /// Unit tests for GatewayInterestTracker — the per-connection interest mode state machine. /// Covers Optimistic/InterestOnly modes, threshold-based switching, and per-account isolation. /// Go reference: gateway_test.go, TestGatewaySwitchToInterestOnlyModeImmediately (line 6934), /// TestGatewayAccountInterest (line 1794), TestGatewayAccountUnsub (line 1912). /// public class GatewayInterestTrackerTests { // Go: TestGatewayBasic server/gateway_test.go:399 — initial state is Optimistic [Fact] public void StartsInOptimisticMode() { var tracker = new GatewayInterestTracker(); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.Optimistic); tracker.GetMode("ACCT_A").ShouldBe(GatewayInterestMode.Optimistic); tracker.GetMode("ANY_ACCOUNT").ShouldBe(GatewayInterestMode.Optimistic); } // Go: TestGatewayBasic server/gateway_test.go:399 — optimistic mode forwards everything [Fact] public void OptimisticForwardsEverything() { var tracker = new GatewayInterestTracker(); tracker.ShouldForward("$G", "any.subject").ShouldBeTrue(); tracker.ShouldForward("$G", "orders.created").ShouldBeTrue(); tracker.ShouldForward("$G", "deeply.nested.subject.path").ShouldBeTrue(); tracker.ShouldForward("ACCT", "foo").ShouldBeTrue(); } // Go: TestGatewayAccountUnsub server/gateway_test.go:1912 — RS- adds to no-interest [Fact] public void TrackNoInterest_AddsToNoInterestSet() { var tracker = new GatewayInterestTracker(); tracker.TrackNoInterest("$G", "orders.created"); // Should not forward that specific subject in Optimistic mode tracker.ShouldForward("$G", "orders.created").ShouldBeFalse(); // Other subjects still forwarded tracker.ShouldForward("$G", "orders.updated").ShouldBeTrue(); tracker.ShouldForward("$G", "payments.created").ShouldBeTrue(); } // Go: TestGatewaySwitchToInterestOnlyModeImmediately server/gateway_test.go:6934 — threshold switch [Fact] public void SwitchesToInterestOnlyAfterThreshold() { const int threshold = 10; var tracker = new GatewayInterestTracker(noInterestThreshold: threshold); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.Optimistic); // Add subjects up to (but not reaching) the threshold for (int i = 0; i < threshold - 1; i++) tracker.TrackNoInterest("$G", $"subject.{i}"); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.Optimistic); // One more crosses the threshold tracker.TrackNoInterest("$G", $"subject.{threshold - 1}"); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly); } // Go: TestGatewaySwitchToInterestOnlyModeImmediately server/gateway_test.go:6934 [Fact] public void InterestOnlyMode_OnlyForwardsTrackedSubjects() { const int threshold = 5; var tracker = new GatewayInterestTracker(noInterestThreshold: threshold); // Trigger mode switch for (int i = 0; i < threshold; i++) tracker.TrackNoInterest("$G", $"noise.{i}"); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly); // Nothing forwarded until interest is explicitly tracked tracker.ShouldForward("$G", "orders.created").ShouldBeFalse(); // Track a positive interest tracker.TrackInterest("$G", "orders.created"); // Now only that subject is forwarded tracker.ShouldForward("$G", "orders.created").ShouldBeTrue(); tracker.ShouldForward("$G", "orders.updated").ShouldBeFalse(); tracker.ShouldForward("$G", "payments.done").ShouldBeFalse(); } // Go: TestGatewaySubjectInterest server/gateway_test.go:1972 — wildcard interest in InterestOnly [Fact] public void InterestOnlyMode_SupportsWildcards() { const int threshold = 3; var tracker = new GatewayInterestTracker(noInterestThreshold: threshold); // Trigger InterestOnly mode for (int i = 0; i < threshold; i++) tracker.TrackNoInterest("$G", $"x.{i}"); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly); // Register a wildcard interest tracker.TrackInterest("$G", "foo.>"); // Matching subjects are forwarded tracker.ShouldForward("$G", "foo.bar").ShouldBeTrue(); tracker.ShouldForward("$G", "foo.bar.baz").ShouldBeTrue(); tracker.ShouldForward("$G", "foo.anything.deep.nested").ShouldBeTrue(); // Non-matching subjects are not forwarded tracker.ShouldForward("$G", "other.subject").ShouldBeFalse(); tracker.ShouldForward("$G", "foo").ShouldBeFalse(); // "foo.>" requires at least one token after "foo" } // Go: TestGatewayAccountInterest server/gateway_test.go:1794 — per-account mode isolation [Fact] public void ModePerAccount() { const int threshold = 5; var tracker = new GatewayInterestTracker(noInterestThreshold: threshold); // Switch ACCT_A to InterestOnly for (int i = 0; i < threshold; i++) tracker.TrackNoInterest("ACCT_A", $"noise.{i}"); tracker.GetMode("ACCT_A").ShouldBe(GatewayInterestMode.InterestOnly); // ACCT_B remains Optimistic tracker.GetMode("ACCT_B").ShouldBe(GatewayInterestMode.Optimistic); // ACCT_A blocks unknown subjects, ACCT_B forwards tracker.ShouldForward("ACCT_A", "orders.created").ShouldBeFalse(); tracker.ShouldForward("ACCT_B", "orders.created").ShouldBeTrue(); } // Go: TestGatewaySwitchToInterestOnlyModeImmediately server/gateway_test.go:6934 [Fact] public void ModePersistsAfterSwitch() { const int threshold = 3; var tracker = new GatewayInterestTracker(noInterestThreshold: threshold); // Trigger switch for (int i = 0; i < threshold; i++) tracker.TrackNoInterest("$G", $"y.{i}"); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly); // TrackInterest in InterestOnly mode — mode stays InterestOnly tracker.TrackInterest("$G", "orders.created"); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly); // TrackNoInterest in InterestOnly mode — mode stays InterestOnly tracker.TrackNoInterest("$G", "something.else"); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly); } // Go: TestGatewayAccountInterest server/gateway_test.go:1794 — explicit SwitchToInterestOnly [Fact] public void ExplicitSwitchToInterestOnly_SetsMode() { var tracker = new GatewayInterestTracker(); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.Optimistic); tracker.SwitchToInterestOnly("$G"); tracker.GetMode("$G").ShouldBe(GatewayInterestMode.InterestOnly); } // Go: TestGatewayAccountUnsub server/gateway_test.go:1912 — RS+ restores interest after RS- [Fact] public void TrackInterest_InOptimisticMode_RemovesFromNoInterestSet() { var tracker = new GatewayInterestTracker(); // Mark no interest tracker.TrackNoInterest("$G", "orders.created"); tracker.ShouldForward("$G", "orders.created").ShouldBeFalse(); // Remote re-subscribes — track interest again tracker.TrackInterest("$G", "orders.created"); tracker.ShouldForward("$G", "orders.created").ShouldBeTrue(); } // Go: TestGatewaySwitchToInterestOnlyModeImmediately server/gateway_test.go:6934 [Fact] public void InterestOnlyMode_TrackNoInterest_RemovesFromInterestSet() { const int threshold = 3; var tracker = new GatewayInterestTracker(noInterestThreshold: threshold); // Trigger InterestOnly for (int i = 0; i < threshold; i++) tracker.TrackNoInterest("$G", $"z.{i}"); tracker.TrackInterest("$G", "orders.created"); tracker.ShouldForward("$G", "orders.created").ShouldBeTrue(); // Remote unsubscribes — subject removed from interest set tracker.TrackNoInterest("$G", "orders.created"); tracker.ShouldForward("$G", "orders.created").ShouldBeFalse(); } // Go: TestGatewaySubjectInterest server/gateway_test.go:1972 — pwc wildcard in InterestOnly [Fact] public void InterestOnlyMode_SupportsPwcWildcard() { const int threshold = 3; var tracker = new GatewayInterestTracker(noInterestThreshold: threshold); for (int i = 0; i < threshold; i++) tracker.TrackNoInterest("$G", $"n.{i}"); tracker.TrackInterest("$G", "orders.*"); tracker.ShouldForward("$G", "orders.created").ShouldBeTrue(); tracker.ShouldForward("$G", "orders.deleted").ShouldBeTrue(); tracker.ShouldForward("$G", "orders.deep.nested").ShouldBeFalse(); // * is single token tracker.ShouldForward("$G", "payments.created").ShouldBeFalse(); } // Go: TestGatewayAccountInterest server/gateway_test.go:1794 — unknown account defaults optimistic [Fact] public void UnknownAccount_DefaultsToOptimisticForwarding() { var tracker = new GatewayInterestTracker(); // Account never seen — should forward everything tracker.ShouldForward("BRAND_NEW_ACCOUNT", "any.subject").ShouldBeTrue(); } }