feat: execute full-repo remaining parity closure plan

This commit is contained in:
Joseph Doherty
2026-02-23 13:08:52 -05:00
parent cbe1fa6121
commit 2b64d762f6
75 changed files with 2325 additions and 121 deletions

View File

@@ -0,0 +1,26 @@
using NATS.Server.JetStream.Cluster;
using NATS.Server.JetStream.Models;
namespace NATS.Server.Tests;
public class JetStreamClusterGovernanceRuntimeParityTests
{
[Fact]
public async Task Jetstream_cluster_governance_applies_consensus_backed_placement()
{
var meta = new JetStreamMetaGroup(3);
await meta.ProposeCreateStreamAsync(new StreamConfig
{
Name = "ORDERS",
Subjects = ["orders.*"],
}, default);
var planner = new AssetPlacementPlanner(3);
var placement = planner.PlanReplicas(2);
var replicas = new StreamReplicaGroup("ORDERS", 1);
await replicas.ApplyPlacementAsync(placement, default);
meta.GetState().Streams.ShouldContain("ORDERS");
replicas.Nodes.Count.ShouldBe(2);
}
}

View File

@@ -0,0 +1,33 @@
using NATS.Server.JetStream.Consumers;
using NATS.Server.JetStream.Models;
using NATS.Server.JetStream.Storage;
using NATS.Server.JetStream;
namespace NATS.Server.Tests;
public class JetStreamConsumerFlowReplayParityTests
{
[Fact]
public void Push_consumer_enqueues_flow_control_and_heartbeat_frames_when_enabled()
{
var engine = new PushConsumerEngine();
var consumer = new ConsumerHandle("ORDERS", new ConsumerConfig
{
AckPolicy = AckPolicy.Explicit,
FlowControl = true,
HeartbeatMs = 1000,
RateLimitBps = 1024,
});
engine.Enqueue(consumer, new StoredMessage
{
Sequence = 1,
Subject = "orders.created",
Payload = "payload"u8.ToArray(),
});
consumer.PushFrames.Count.ShouldBe(3);
consumer.PushFrames.Any(f => f.IsFlowControl).ShouldBeTrue();
consumer.PushFrames.Any(f => f.IsHeartbeat).ShouldBeTrue();
}
}

View File

@@ -0,0 +1,25 @@
using NATS.Server.JetStream.Consumers;
namespace NATS.Server.Tests;
public class JetStreamConsumerRuntimeParityTests
{
[Fact]
public async Task Consumer_runtime_honors_ack_all_redelivery_and_max_deliver_limits()
{
var ack = new AckProcessor();
ack.Register(1, ackWaitMs: 1);
await Task.Delay(5);
ack.TryGetExpired(out var seq, out var deliveries).ShouldBeTrue();
seq.ShouldBe((ulong)1);
deliveries.ShouldBe(1);
ack.ScheduleRedelivery(seq, delayMs: 1);
await Task.Delay(5);
ack.TryGetExpired(out _, out deliveries).ShouldBeTrue();
deliveries.ShouldBe(2);
ack.AckAll(1);
ack.HasPending.ShouldBeFalse();
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.Gateways;
namespace NATS.Server.Tests;
public class JetStreamCrossClusterRuntimeParityTests
{
[Fact]
public async Task Jetstream_cross_cluster_messages_are_forward_counted()
{
var manager = new GatewayManager(
new GatewayOptions { Host = "127.0.0.1", Port = 0, Name = "A" },
new ServerStats(),
"S1",
_ => { },
_ => { },
NullLogger<GatewayManager>.Instance);
await manager.ForwardJetStreamClusterMessageAsync(
new GatewayMessage("$JS.CLUSTER.REPL", null, "x"u8.ToArray()),
default);
manager.ForwardedJetStreamClusterMessages.ShouldBe(1);
}
}

View File

@@ -0,0 +1,33 @@
using NATS.Server.JetStream.Storage;
namespace NATS.Server.Tests;
public class JetStreamFileStoreCryptoCompressionTests
{
[Fact]
public async Task File_store_compression_and_encryption_roundtrip_preserves_payload()
{
var dir = Path.Combine(Path.GetTempPath(), $"natsdotnet-filestore-crypto-{Guid.NewGuid():N}");
try
{
await using var store = new FileStore(new FileStoreOptions
{
Directory = dir,
EnableCompression = true,
EnableEncryption = true,
EncryptionKey = [1, 2, 3, 4],
});
var payload = Enumerable.Repeat((byte)'a', 512).ToArray();
var seq = await store.AppendAsync("orders.created", payload, default);
var loaded = await store.LoadAsync(seq, default);
loaded.ShouldNotBeNull();
loaded.Payload.ToArray().ShouldBe(payload);
}
finally
{
if (Directory.Exists(dir))
Directory.Delete(dir, recursive: true);
}
}
}

View File

@@ -0,0 +1,33 @@
using NATS.Server.JetStream.Storage;
namespace NATS.Server.Tests;
public class JetStreamFileStoreLayoutParityTests
{
[Fact]
public async Task File_store_uses_block_index_layout_with_ttl_prune_invariants()
{
var dir = Path.Combine(Path.GetTempPath(), $"natsdotnet-filestore-{Guid.NewGuid():N}");
try
{
await using var store = new FileStore(new FileStoreOptions
{
Directory = dir,
BlockSizeBytes = 128,
MaxAgeMs = 60_000,
});
for (var i = 0; i < 100; i++)
await store.AppendAsync($"orders.{i}", "x"u8.ToArray(), default);
var state = await store.GetStateAsync(default);
state.Messages.ShouldBe((ulong)100);
store.BlockCount.ShouldBeGreaterThan(1);
}
finally
{
if (Directory.Exists(dir))
Directory.Delete(dir, recursive: true);
}
}
}

View File

@@ -0,0 +1,40 @@
using NATS.Server.JetStream.MirrorSource;
using NATS.Server.JetStream.Models;
using NATS.Server.JetStream.Storage;
namespace NATS.Server.Tests;
public class JetStreamMirrorSourceRuntimeParityTests
{
[Fact]
public async Task Mirror_source_runtime_tracks_sync_state_and_subject_mapping()
{
var mirrorTarget = new MemStore();
var sourceTarget = new MemStore();
var mirror = new MirrorCoordinator(mirrorTarget);
var source = new SourceCoordinator(sourceTarget, new StreamSourceConfig
{
Name = "SRC",
SubjectTransformPrefix = "agg.",
SourceAccount = "A",
});
var message = new StoredMessage
{
Sequence = 10,
Subject = "orders.created",
Payload = "ok"u8.ToArray(),
TimestampUtc = DateTime.UtcNow,
};
await mirror.OnOriginAppendAsync(message, default);
await source.OnOriginAppendAsync(message, default);
mirror.LastOriginSequence.ShouldBe((ulong)10);
source.LastOriginSequence.ShouldBe((ulong)10);
var sourced = await sourceTarget.LoadAsync(1, default);
sourced.ShouldNotBeNull();
sourced.Subject.ShouldBe("agg.orders.created");
}
}

View File

@@ -0,0 +1,30 @@
using NATS.Server.JetStream.Models;
using NATS.Server.JetStream.Validation;
namespace NATS.Server.Tests;
public class JetStreamStreamFeatureToggleParityTests
{
[Fact]
public void Stream_feature_toggles_are_preserved_in_config_model_and_validation()
{
var config = new StreamConfig
{
Name = "ORDERS",
Subjects = ["orders.*"],
Sealed = true,
DenyDelete = true,
DenyPurge = true,
AllowDirect = true,
MaxMsgSize = 1024,
MaxMsgsPer = 10,
MaxAgeMs = 5000,
};
JetStreamConfigValidator.Validate(config).IsValid.ShouldBeTrue();
config.Sealed.ShouldBeTrue();
config.DenyDelete.ShouldBeTrue();
config.DenyPurge.ShouldBeTrue();
config.AllowDirect.ShouldBeTrue();
}
}

View File

@@ -0,0 +1,29 @@
using NATS.Server.JetStream.Models;
using NATS.Server.JetStream.Publish;
using NATS.Server.JetStream.Validation;
namespace NATS.Server.Tests;
public class JetStreamStreamRuntimeParityTests
{
[Fact]
public void Stream_runtime_enforces_retention_and_size_preconditions()
{
var invalid = new StreamConfig
{
Name = "ORDERS",
Subjects = ["orders.*"],
Retention = RetentionPolicy.WorkQueue,
MaxConsumers = 0,
MaxMsgSize = -1,
};
var result = JetStreamConfigValidator.Validate(invalid);
result.IsValid.ShouldBeFalse();
var preconditions = new PublishPreconditions();
preconditions.Record("m1", 1);
preconditions.IsDuplicate("m1", duplicateWindowMs: 10_000, out var existing).ShouldBeTrue();
existing.ShouldBe((ulong)1);
}
}