Eliminate PortTracker stub backlog by implementing Raft/file-store/stream/server/client/OCSP stubs and adding coverage. This makes all tracked stub features/tests executable and verified in the current porting phase.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
// Copyright 2012-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
public sealed class CompressionInfoTests
|
||||
{
|
||||
[Fact]
|
||||
public void MarshalMetadata_UnmarshalMetadata_ShouldRoundTrip()
|
||||
{
|
||||
var ci = new CompressionInfo
|
||||
{
|
||||
Type = StoreCompression.S2Compression,
|
||||
Original = 12345,
|
||||
Compressed = 6789,
|
||||
};
|
||||
|
||||
var payload = ci.MarshalMetadata();
|
||||
payload.Length.ShouldBeGreaterThan(4);
|
||||
|
||||
var copy = new CompressionInfo();
|
||||
var consumed = copy.UnmarshalMetadata(payload);
|
||||
|
||||
consumed.ShouldBe(payload.Length);
|
||||
copy.Type.ShouldBe(StoreCompression.S2Compression);
|
||||
copy.Original.ShouldBe(12345UL);
|
||||
copy.Compressed.ShouldBe(6789UL);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnmarshalMetadata_InvalidPrefix_ShouldReturnZero()
|
||||
{
|
||||
var ci = new CompressionInfo();
|
||||
ci.UnmarshalMetadata([1, 2, 3, 4]).ShouldBe(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2012-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
public sealed class ConsumerFileStoreTests
|
||||
{
|
||||
[Fact]
|
||||
public void UpdateDelivered_UpdateAcks_AndReload_ShouldPersistState()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), $"cfs-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(root);
|
||||
try
|
||||
{
|
||||
var fs = NewStore(root);
|
||||
var cfg = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckExplicit };
|
||||
var cs = (ConsumerFileStore)fs.ConsumerStore("D", DateTime.UtcNow, cfg);
|
||||
|
||||
cs.SetStarting(0);
|
||||
cs.UpdateDelivered(1, 1, 1, 123);
|
||||
cs.UpdateDelivered(2, 2, 1, 456);
|
||||
cs.UpdateAcks(1, 1);
|
||||
|
||||
var (state, err) = cs.State();
|
||||
err.ShouldBeNull();
|
||||
state.ShouldNotBeNull();
|
||||
state!.Delivered.Consumer.ShouldBe(2UL);
|
||||
state.AckFloor.Consumer.ShouldBe(1UL);
|
||||
|
||||
cs.Stop();
|
||||
|
||||
var odir = Path.Combine(root, FileStoreDefaults.ConsumerDir, "D");
|
||||
var loaded = new ConsumerFileStore(
|
||||
fs,
|
||||
new FileConsumerInfo { Name = "D", Created = DateTime.UtcNow, Config = cfg },
|
||||
"D",
|
||||
odir);
|
||||
|
||||
var (loadedState, loadedErr) = loaded.State();
|
||||
loadedErr.ShouldBeNull();
|
||||
loadedState.ShouldNotBeNull();
|
||||
loadedState!.Delivered.Consumer.ShouldBe(2UL);
|
||||
loadedState.AckFloor.Consumer.ShouldBe(1UL);
|
||||
|
||||
loaded.Delete();
|
||||
Directory.Exists(odir).ShouldBeFalse();
|
||||
fs.Stop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(root))
|
||||
Directory.Delete(root, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static JetStreamFileStore NewStore(string root)
|
||||
{
|
||||
return new JetStreamFileStore(
|
||||
new FileStoreConfig { StoreDir = root },
|
||||
new FileStreamInfo
|
||||
{
|
||||
Created = DateTime.UtcNow,
|
||||
Config = new StreamConfig
|
||||
{
|
||||
Name = "S",
|
||||
Storage = StorageType.FileStorage,
|
||||
Subjects = ["foo"],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,100 @@
|
||||
// Copyright 2020-2025 The NATS Authors
|
||||
// Copyright 2020-2026 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.
|
||||
//
|
||||
// Mirrors server/jetstream_errors_test.go in the NATS server Go source.
|
||||
//
|
||||
// All 4 tests are deferred:
|
||||
// T:1381 — TestIsNatsErr: uses IsNatsErr(error, ...) where the Go version accepts
|
||||
// arbitrary error interface values (including plain errors.New("x") which
|
||||
// evaluates to false). The .NET JsApiErrors.IsNatsError only accepts JsApiError?
|
||||
// and the "NewJS*" factory constructors (NewJSRestoreSubscribeFailedError etc.)
|
||||
// that populate Description templates from tags have not been ported yet.
|
||||
// T:1382 — TestApiError_Error: uses ApiErrors[JSClusterNotActiveErr].Error() — the Go
|
||||
// ApiErrors map and per-error .Error() method (returns "description (errCode)")
|
||||
// differs from the .NET JsApiErrors.ClusterNotActive.ToString() convention.
|
||||
// T:1383 — TestApiError_NewWithTags: uses NewJSRestoreSubscribeFailedError with tag
|
||||
// substitution — factory constructors not yet ported.
|
||||
// T:1384 — TestApiError_NewWithUnless: uses NewJSStreamRestoreError, Unless() helper,
|
||||
// NewJSPeerRemapError — not yet ported.
|
||||
|
||||
using Shouldly;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for JetStream API error types and IsNatsErr helper.
|
||||
/// Tests for JetStream API error helpers.
|
||||
/// Mirrors server/jetstream_errors_test.go.
|
||||
/// All tests deferred pending port of Go factory constructors and tag-substitution system.
|
||||
/// </summary>
|
||||
public sealed class JetStreamErrorsTests
|
||||
{
|
||||
[Fact(Skip = "deferred: NewJS* factory constructors and IsNatsErr(error) not yet ported")] // T:1381
|
||||
public void IsNatsErr_ShouldSucceed() { }
|
||||
[Fact] // T:1381
|
||||
public void IsNatsErr_ShouldSucceed()
|
||||
{
|
||||
JsApiErrors.IsNatsErr(
|
||||
JsApiErrors.NotEnabledForAccount,
|
||||
JsApiErrors.NotEnabledForAccount.ErrCode).ShouldBeTrue();
|
||||
|
||||
[Fact(Skip = "deferred: ApiErrors map and .Error() method not yet ported")] // T:1382
|
||||
public void ApiError_Error_ShouldSucceed() { }
|
||||
JsApiErrors.IsNatsErr(
|
||||
JsApiErrors.NotEnabledForAccount,
|
||||
JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();
|
||||
|
||||
[Fact(Skip = "deferred: NewJSRestoreSubscribeFailedError with tag substitution not yet ported")] // T:1383
|
||||
public void ApiError_NewWithTags_ShouldSucceed() { }
|
||||
JsApiErrors.IsNatsErr(
|
||||
JsApiErrors.NotEnabledForAccount,
|
||||
JsApiErrors.ClusterNotActive.ErrCode,
|
||||
JsApiErrors.ClusterNotAvail.ErrCode).ShouldBeFalse();
|
||||
|
||||
[Fact(Skip = "deferred: NewJSStreamRestoreError / Unless() helper not yet ported")] // T:1384
|
||||
public void ApiError_NewWithUnless_ShouldSucceed() { }
|
||||
JsApiErrors.IsNatsErr(
|
||||
JsApiErrors.NotEnabledForAccount,
|
||||
JsApiErrors.ClusterNotActive.ErrCode,
|
||||
JsApiErrors.NotEnabledForAccount.ErrCode).ShouldBeTrue();
|
||||
|
||||
JsApiErrors.IsNatsErr(
|
||||
new JsApiError { ErrCode = JsApiErrors.NotEnabledForAccount.ErrCode },
|
||||
1,
|
||||
JsApiErrors.ClusterNotActive.ErrCode,
|
||||
JsApiErrors.NotEnabledForAccount.ErrCode).ShouldBeTrue();
|
||||
|
||||
JsApiErrors.IsNatsErr(
|
||||
new JsApiError { ErrCode = JsApiErrors.NotEnabledForAccount.ErrCode },
|
||||
1,
|
||||
2,
|
||||
JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();
|
||||
|
||||
JsApiErrors.IsNatsErr(null, JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();
|
||||
JsApiErrors.IsNatsErr(new InvalidOperationException("x"), JsApiErrors.ClusterNotActive.ErrCode).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact] // T:1382
|
||||
public void ApiError_Error_ShouldSucceed()
|
||||
{
|
||||
JsApiErrors.Error(JsApiErrors.ClusterNotActive).ShouldBe("JetStream not in clustered mode (10006)");
|
||||
}
|
||||
|
||||
[Fact] // T:1383
|
||||
public void ApiError_NewWithTags_ShouldSucceed()
|
||||
{
|
||||
var ne = JsApiErrors.NewJSRestoreSubscribeFailedError(new Exception("failed error"), "the.subject");
|
||||
ne.Description.ShouldBe("JetStream unable to subscribe to restore snapshot the.subject: failed error");
|
||||
ReferenceEquals(ne, JsApiErrors.RestoreSubscribeFailed).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact] // T:1384
|
||||
public void ApiError_NewWithUnless_ShouldSucceed()
|
||||
{
|
||||
var notEnabled = JsApiErrors.NotEnabledForAccount.ErrCode;
|
||||
var streamRestore = JsApiErrors.StreamRestore.ErrCode;
|
||||
var peerRemap = JsApiErrors.PeerRemap.ErrCode;
|
||||
|
||||
JsApiErrors.IsNatsErr(
|
||||
JsApiErrors.NewJSStreamRestoreError(
|
||||
new Exception("failed error"),
|
||||
JsApiErrors.Unless(JsApiErrors.NotEnabledForAccount)),
|
||||
notEnabled).ShouldBeTrue();
|
||||
|
||||
JsApiErrors.IsNatsErr(
|
||||
JsApiErrors.NewJSStreamRestoreError(new Exception("failed error")),
|
||||
streamRestore).ShouldBeTrue();
|
||||
|
||||
JsApiErrors.IsNatsErr(
|
||||
JsApiErrors.NewJSStreamRestoreError(
|
||||
new Exception("failed error"),
|
||||
JsApiErrors.Unless(new Exception("other error"))),
|
||||
streamRestore).ShouldBeTrue();
|
||||
|
||||
JsApiErrors.IsNatsErr(
|
||||
JsApiErrors.NewJSPeerRemapError(JsApiErrors.Unless(JsApiErrors.NotEnabledForAccount)),
|
||||
notEnabled).ShouldBeTrue();
|
||||
|
||||
JsApiErrors.IsNatsErr(
|
||||
JsApiErrors.NewJSPeerRemapError(JsApiErrors.Unless(null)),
|
||||
peerRemap).ShouldBeTrue();
|
||||
|
||||
JsApiErrors.IsNatsErr(
|
||||
JsApiErrors.NewJSPeerRemapError(JsApiErrors.Unless(new Exception("other error"))),
|
||||
peerRemap).ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright 2012-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
public sealed class JetStreamFileStoreTests
|
||||
{
|
||||
[Fact]
|
||||
public void StoreMsg_LoadAndPurge_ShouldRoundTrip()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), $"fs-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(root);
|
||||
try
|
||||
{
|
||||
var fs = NewStore(root);
|
||||
|
||||
var (seq1, _) = fs.StoreMsg("foo", [1], [2, 3], 0);
|
||||
var (seq2, _) = fs.StoreMsg("bar", null, [4, 5], 0);
|
||||
|
||||
seq1.ShouldBe(1UL);
|
||||
seq2.ShouldBe(2UL);
|
||||
fs.State().Msgs.ShouldBe(2UL);
|
||||
|
||||
var msg = fs.LoadMsg(1, null);
|
||||
msg.ShouldNotBeNull();
|
||||
msg!.Subject.ShouldBe("foo");
|
||||
|
||||
fs.SubjectForSeq(2).Subject.ShouldBe("bar");
|
||||
fs.SubjectsTotals(string.Empty).Count.ShouldBe(2);
|
||||
|
||||
var (removed, remErr) = fs.RemoveMsg(1);
|
||||
removed.ShouldBeTrue();
|
||||
remErr.ShouldBeNull();
|
||||
fs.State().Msgs.ShouldBe(1UL);
|
||||
|
||||
var (purged, purgeErr) = fs.Purge();
|
||||
purgeErr.ShouldBeNull();
|
||||
purged.ShouldBe(1UL);
|
||||
fs.State().Msgs.ShouldBe(0UL);
|
||||
|
||||
var (snapshot, snapErr) = fs.Snapshot(TimeSpan.FromSeconds(1), includeConsumers: false, checkMsgs: false);
|
||||
snapErr.ShouldBeNull();
|
||||
snapshot.ShouldNotBeNull();
|
||||
snapshot!.Reader.ShouldNotBeNull();
|
||||
|
||||
var (total, reported, utilErr) = fs.Utilization();
|
||||
utilErr.ShouldBeNull();
|
||||
total.ShouldBe(reported);
|
||||
|
||||
fs.Stop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static JetStreamFileStore NewStore(string root)
|
||||
{
|
||||
return new JetStreamFileStore(
|
||||
new FileStoreConfig { StoreDir = root },
|
||||
new FileStreamInfo
|
||||
{
|
||||
Created = DateTime.UtcNow,
|
||||
Config = new StreamConfig
|
||||
{
|
||||
Name = "S",
|
||||
Storage = StorageType.FileStorage,
|
||||
Subjects = ["foo", "bar"],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2012-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
public sealed class NatsConsumerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Create_SetLeader_UpdateConfig_AndStop_ShouldBehave()
|
||||
{
|
||||
var account = new Account { Name = "A" };
|
||||
var streamCfg = new StreamConfig { Name = "S", Subjects = ["foo"], Storage = StorageType.FileStorage };
|
||||
var stream = NatsStream.Create(account, streamCfg, null, null, null, null);
|
||||
stream.ShouldNotBeNull();
|
||||
|
||||
var cfg = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckExplicit };
|
||||
var consumer = NatsConsumer.Create(stream!, cfg, ConsumerAction.CreateOrUpdate, null);
|
||||
consumer.ShouldNotBeNull();
|
||||
|
||||
consumer!.IsLeader().ShouldBeFalse();
|
||||
consumer.SetLeader(true, 3);
|
||||
consumer.IsLeader().ShouldBeTrue();
|
||||
|
||||
var updated = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckAll };
|
||||
consumer.UpdateConfig(updated);
|
||||
consumer.GetConfig().AckPolicy.ShouldBe(AckPolicy.AckAll);
|
||||
|
||||
var info = consumer.GetInfo();
|
||||
info.Stream.ShouldBe("S");
|
||||
info.Name.ShouldBe("D");
|
||||
|
||||
consumer.Stop();
|
||||
consumer.IsLeader().ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2012-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
public sealed class NatsStreamTests
|
||||
{
|
||||
[Fact]
|
||||
public void Create_SetLeader_Purge_AndSeal_ShouldBehave()
|
||||
{
|
||||
var account = new Account { Name = "A" };
|
||||
var streamCfg = new StreamConfig { Name = "ORDERS", Subjects = ["orders.*"], Storage = StorageType.FileStorage };
|
||||
|
||||
var memCfg = streamCfg.Clone();
|
||||
memCfg.Storage = StorageType.MemoryStorage;
|
||||
var store = new JetStreamMemStore(memCfg);
|
||||
store.StoreMsg("orders.new", null, [1, 2], 0);
|
||||
|
||||
var stream = NatsStream.Create(account, streamCfg, null, store, null, null);
|
||||
stream.ShouldNotBeNull();
|
||||
|
||||
stream!.IsLeader().ShouldBeFalse();
|
||||
stream.SetLeader(true, 7);
|
||||
stream.IsLeader().ShouldBeTrue();
|
||||
|
||||
stream.State().Msgs.ShouldBe(1UL);
|
||||
stream.Purge();
|
||||
stream.State().Msgs.ShouldBe(0UL);
|
||||
|
||||
stream.IsSealed().ShouldBeFalse();
|
||||
stream.Seal();
|
||||
stream.IsSealed().ShouldBeTrue();
|
||||
|
||||
stream.GetAccount().Name.ShouldBe("A");
|
||||
stream.GetInfo().Config.Name.ShouldBe("ORDERS");
|
||||
|
||||
stream.Delete();
|
||||
stream.IsLeader().ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2012-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
using System.Threading.Channels;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server.Internal;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
public sealed class RaftTypesTests
|
||||
{
|
||||
[Fact]
|
||||
public void Raft_Methods_ShouldProvideNonStubBehavior()
|
||||
{
|
||||
var raft = new Raft
|
||||
{
|
||||
Id = "N1",
|
||||
GroupName = "RG",
|
||||
AccName = "ACC",
|
||||
StateValue = (int)RaftState.Leader,
|
||||
LeaderId = "N1",
|
||||
Csz = 3,
|
||||
Qn = 2,
|
||||
PIndex = 10,
|
||||
Commit = 8,
|
||||
Applied_ = 6,
|
||||
Processed_ = 7,
|
||||
PApplied = 9,
|
||||
WalBytes = 128,
|
||||
Peers_ = new Dictionary<string, Lps>
|
||||
{
|
||||
["N2"] = new() { Ts = DateTime.UtcNow, Kp = true, Li = 9 },
|
||||
},
|
||||
ApplyQ_ = new IpQueue<CommittedEntry>("apply-q"),
|
||||
LeadC = Channel.CreateUnbounded<bool>(),
|
||||
Quit = Channel.CreateUnbounded<bool>(),
|
||||
};
|
||||
|
||||
raft.Propose([1, 2, 3]);
|
||||
raft.ForwardProposal([4, 5]);
|
||||
raft.ProposeMulti([new Entry { Data = [6] }]);
|
||||
|
||||
raft.PropQ.ShouldNotBeNull();
|
||||
raft.PropQ!.Len().ShouldBe(3);
|
||||
|
||||
raft.InstallSnapshot([9, 9], force: false);
|
||||
raft.SendSnapshot([8, 8, 8]);
|
||||
raft.CreateSnapshotCheckpoint(force: false).ShouldBeOfType<Checkpoint>();
|
||||
raft.NeedSnapshot().ShouldBeTrue();
|
||||
|
||||
raft.Applied(5).ShouldBe((1UL, 128UL));
|
||||
raft.Processed(11, 10).ShouldBe((11UL, 128UL));
|
||||
raft.Size().ShouldBe((11UL, 128UL));
|
||||
raft.Progress().ShouldBe((10UL, 8UL, 10UL));
|
||||
raft.Leader().ShouldBeTrue();
|
||||
raft.LeaderSince().ShouldNotBeNull();
|
||||
raft.Quorum().ShouldBeTrue();
|
||||
raft.Current().ShouldBeTrue();
|
||||
raft.Healthy().ShouldBeTrue();
|
||||
raft.Term().ShouldBe(raft.Term_);
|
||||
raft.Leaderless().ShouldBeFalse();
|
||||
raft.GroupLeader().ShouldBe("N1");
|
||||
|
||||
raft.SetObserver(true);
|
||||
raft.IsObserver().ShouldBeTrue();
|
||||
raft.Campaign();
|
||||
raft.State().ShouldBe(RaftState.Candidate);
|
||||
raft.CampaignImmediately();
|
||||
raft.StepDown("N2");
|
||||
raft.State().ShouldBe(RaftState.Follower);
|
||||
|
||||
raft.ProposeKnownPeers(["P1", "P2"]);
|
||||
raft.Peers().Count.ShouldBe(3);
|
||||
raft.ProposeAddPeer("P3");
|
||||
raft.ClusterSize().ShouldBeGreaterThan(1);
|
||||
raft.ProposeRemovePeer("P2");
|
||||
raft.Peers().Count.ShouldBe(3);
|
||||
raft.MembershipChangeInProgress().ShouldBeTrue();
|
||||
raft.AdjustClusterSize(5);
|
||||
raft.ClusterSize().ShouldBe(5);
|
||||
raft.AdjustBootClusterSize(4);
|
||||
raft.ClusterSize().ShouldBe(4);
|
||||
|
||||
raft.ApplyQ().ShouldNotBeNull();
|
||||
raft.PauseApply();
|
||||
raft.Paused.ShouldBeTrue();
|
||||
raft.ResumeApply();
|
||||
raft.Paused.ShouldBeFalse();
|
||||
raft.DrainAndReplaySnapshot().ShouldBeTrue();
|
||||
raft.LeadChangeC().ShouldNotBeNull();
|
||||
raft.QuitC().ShouldNotBeNull();
|
||||
raft.Created().ShouldBe(raft.Created_);
|
||||
raft.ID().ShouldBe("N1");
|
||||
raft.Group().ShouldBe("RG");
|
||||
raft.GetTrafficAccountName().ShouldBe("ACC");
|
||||
|
||||
raft.RecreateInternalSubs();
|
||||
raft.Stop();
|
||||
raft.WaitForStop();
|
||||
raft.Delete();
|
||||
raft.IsDeleted().ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Checkpoint_Methods_ShouldRoundTripSnapshotData()
|
||||
{
|
||||
var node = new Raft
|
||||
{
|
||||
Id = "NODE",
|
||||
PTerm = 3,
|
||||
AReply = "_R_",
|
||||
};
|
||||
|
||||
var checkpoint = new Checkpoint
|
||||
{
|
||||
Node = node,
|
||||
Term = 5,
|
||||
Applied = 11,
|
||||
PApplied = 7,
|
||||
SnapFile = Path.Combine(Path.GetTempPath(), $"checkpoint-{Guid.NewGuid():N}.bin"),
|
||||
};
|
||||
|
||||
var written = checkpoint.InstallSnapshot([1, 2, 3, 4]);
|
||||
written.ShouldBe(4UL);
|
||||
|
||||
var loaded = checkpoint.LoadLastSnapshot();
|
||||
loaded.ShouldBe([1, 2, 3, 4]);
|
||||
|
||||
var seq = checkpoint.AppendEntriesSeq().ToList();
|
||||
seq.Count.ShouldBe(1);
|
||||
seq[0].Error.ShouldBeNull();
|
||||
seq[0].Entry.Leader.ShouldBe("NODE");
|
||||
seq[0].Entry.TermV.ShouldBe(5UL);
|
||||
seq[0].Entry.Commit.ShouldBe(11UL);
|
||||
seq[0].Entry.PIndex.ShouldBe(7UL);
|
||||
|
||||
checkpoint.Abort();
|
||||
File.Exists(checkpoint.SnapFile).ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2012-2026 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
public sealed class WaitQueueTests
|
||||
{
|
||||
[Fact]
|
||||
public void Add_Peek_Pop_IsFull_ShouldBehaveAsFifo()
|
||||
{
|
||||
var q = new WaitQueue();
|
||||
|
||||
q.Peek().ShouldBeNull();
|
||||
q.Pop().ShouldBeNull();
|
||||
|
||||
q.Add(new WaitingRequest { Subject = "A", N = 1 });
|
||||
q.Add(new WaitingRequest { Subject = "B", N = 2 });
|
||||
|
||||
q.Len.ShouldBe(2);
|
||||
q.IsFull(2).ShouldBeTrue();
|
||||
q.Peek()!.Subject.ShouldBe("A");
|
||||
|
||||
q.Pop()!.Subject.ShouldBe("A");
|
||||
q.Pop()!.Subject.ShouldBe("B");
|
||||
q.Len.ShouldBe(0);
|
||||
q.IsFull(1).ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user