feat(batch17): merge client-core-second-half
This commit is contained in:
@@ -6,7 +6,9 @@ using System.Text;
|
||||
using System.Linq;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
using ZB.MOM.NatsNet.Server.Auth;
|
||||
using ZB.MOM.NatsNet.Server.Internal;
|
||||
using ZB.MOM.NatsNet.Server.Internal.DataStructures;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests;
|
||||
|
||||
@@ -152,4 +154,246 @@ public sealed class ClientConnectionStubFeaturesTests
|
||||
result.sub.ShouldNotBeNull();
|
||||
c.Subs.Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSubscribe_WithAllowAndDenyQueues_ShouldMatchExpected()
|
||||
{
|
||||
var c = new ClientConnection(ClientKind.Client)
|
||||
{
|
||||
Perms = new ClientPermissions(),
|
||||
};
|
||||
c.Perms.Sub.Allow = SubscriptionIndex.NewSublistWithCache();
|
||||
c.Perms.Sub.Deny = SubscriptionIndex.NewSublistWithCache();
|
||||
c.Perms.Sub.Allow.Insert(new Subscription
|
||||
{
|
||||
Subject = Encoding.ASCII.GetBytes("foo.*"),
|
||||
Queue = Encoding.ASCII.GetBytes("q"),
|
||||
});
|
||||
c.Perms.Sub.Deny.Insert(new Subscription
|
||||
{
|
||||
Subject = Encoding.ASCII.GetBytes("foo.blocked"),
|
||||
});
|
||||
|
||||
c.CanSubscribe("foo.bar", "q").ShouldBeTrue();
|
||||
c.CanSubscribe("foo.bar", "other").ShouldBeFalse();
|
||||
c.CanSubscribe("foo.blocked").ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessUnsub_WithKnownSid_ShouldRemoveSubscription()
|
||||
{
|
||||
var c = new ClientConnection(ClientKind.Client);
|
||||
c.ParseSub(Encoding.ASCII.GetBytes("foo sid1"), noForward: false).ShouldBeNull();
|
||||
c.Subs.Count.ShouldBe(1);
|
||||
|
||||
c.ProcessUnsub(Encoding.ASCII.GetBytes("sid1")).ShouldBeNull();
|
||||
c.Subs.ShouldNotContainKey("sid1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MsgHeaderAndRouteHeader_ShouldIncludeSubjectsAndSizes()
|
||||
{
|
||||
var c = new ClientConnection(ClientKind.Client);
|
||||
c.ParseCtx.Pa.HeaderSize = 10;
|
||||
c.ParseCtx.Pa.Size = 30;
|
||||
c.ParseCtx.Pa.HeaderBytes = Encoding.ASCII.GetBytes("10");
|
||||
c.ParseCtx.Pa.SizeBytes = Encoding.ASCII.GetBytes("30");
|
||||
|
||||
var sub = new Subscription { Sid = Encoding.ASCII.GetBytes("22") };
|
||||
var mh = c.MsgHeader(Encoding.ASCII.GetBytes("foo.bar"), Encoding.ASCII.GetBytes("_R_.x"), sub);
|
||||
Encoding.ASCII.GetString(mh).ShouldContain("foo.bar 22 _R_.x");
|
||||
Encoding.ASCII.GetString(mh).ShouldContain("30");
|
||||
|
||||
var routeTarget = new RouteTarget { Sub = sub, Qs = Encoding.ASCII.GetBytes("q1 q2") };
|
||||
var rmh = c.MsgHeaderForRouteOrLeaf(
|
||||
Encoding.ASCII.GetBytes("foo.bar"),
|
||||
Encoding.ASCII.GetBytes("_R_.x"),
|
||||
routeTarget,
|
||||
null);
|
||||
Encoding.ASCII.GetString(rmh).ShouldContain("foo.bar");
|
||||
Encoding.ASCII.GetString(rmh).ShouldContain("q1 q2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PubAllowedFullCheck_ShouldHonorResponseReplyCache()
|
||||
{
|
||||
var c = new ClientConnection(ClientKind.Client)
|
||||
{
|
||||
Perms = new ClientPermissions
|
||||
{
|
||||
Resp = new ResponsePermission
|
||||
{
|
||||
MaxMsgs = 2,
|
||||
Expires = TimeSpan.FromMinutes(1),
|
||||
},
|
||||
},
|
||||
Replies = new Dictionary<string, RespEntry>(StringComparer.Ordinal)
|
||||
{
|
||||
["_R_.x"] = new RespEntry { Time = DateTime.UtcNow, N = 0 },
|
||||
},
|
||||
};
|
||||
c.Perms.Pub.Deny = SubscriptionIndex.NewSublistWithCache();
|
||||
c.Perms.Pub.Deny.Insert(new Subscription { Subject = Encoding.ASCII.GetBytes(">") });
|
||||
|
||||
c.PubAllowed("_R_.x").ShouldBeTrue();
|
||||
c.PubAllowedFullCheck("_R_.x", fullCheck: true, hasLock: true).ShouldBeTrue();
|
||||
c.PubAllowedFullCheck("_R_.x", fullCheck: true, hasLock: true).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InboundAndHeaderHelpers_GroupB_ShouldBehave()
|
||||
{
|
||||
ClientConnection.IsReservedReply(Encoding.ASCII.GetBytes("_R_.A.B")).ShouldBeTrue();
|
||||
ClientConnection.IsReservedReply(Encoding.ASCII.GetBytes("$JS.ACK.A.B")).ShouldBeTrue();
|
||||
ClientConnection.IsReservedReply(Encoding.ASCII.GetBytes("$GNR.A.B")).ShouldBeTrue();
|
||||
ClientConnection.IsReservedReply(Encoding.ASCII.GetBytes("foo.bar")).ShouldBeFalse();
|
||||
|
||||
var c = new ClientConnection(ClientKind.Client)
|
||||
{
|
||||
ParseCtx = { Pa = { HeaderSize = 0 } },
|
||||
};
|
||||
|
||||
var before = DateTime.UtcNow;
|
||||
c.ProcessInboundMsg(Encoding.ASCII.GetBytes("data"));
|
||||
c.LastIn.ShouldBeGreaterThan(before - TimeSpan.FromMilliseconds(1));
|
||||
|
||||
c.Subs["sid"] = new Subscription { Sid = Encoding.ASCII.GetBytes("sid"), Subject = Encoding.ASCII.GetBytes("foo") };
|
||||
c.SubForReply(Encoding.ASCII.GetBytes("inbox")).ShouldNotBeNull();
|
||||
|
||||
var header = ClientConnection.GenHeader(null, "X-Test", "one");
|
||||
Encoding.ASCII.GetString(ClientConnection.GetHeader("X-Test", header)!).ShouldBe("one");
|
||||
ClientConnection.GetHeaderKeyIndex("X-Test", header).ShouldBeGreaterThan(0);
|
||||
ClientConnection.SliceHeader("X-Test", header).ShouldNotBeNull();
|
||||
|
||||
var replaced = ClientConnection.SetHeaderStatic("X-Test", "two", header);
|
||||
Encoding.ASCII.GetString(ClientConnection.GetHeader("X-Test", replaced)!).ShouldBe("two");
|
||||
ClientConnection.RemoveHeaderIfPresent(replaced, "X-Test").ShouldBeNull();
|
||||
|
||||
var prefixed = ClientConnection.GenHeader(header, "Nats-Expected-Last-Sequence", "10");
|
||||
ClientConnection.RemoveHeaderIfPrefixPresent(prefixed!, "Nats-Expected-").ShouldNotBeNull();
|
||||
|
||||
c.ParseCtx.Pa.HeaderSize = header.Length;
|
||||
var merged = new byte[header.Length + 5];
|
||||
Buffer.BlockCopy(header, 0, merged, 0, header.Length);
|
||||
Buffer.BlockCopy("hello"u8.ToArray(), 0, merged, header.Length, 5);
|
||||
var next = c.SetHeaderInternal("X-Test", "three", merged);
|
||||
Encoding.ASCII.GetString(next).ShouldContain("X-Test: three");
|
||||
|
||||
var result = new SubscriptionIndexResult();
|
||||
result.PSubs.Add(new Subscription { Subject = Encoding.ASCII.GetBytes("foo"), Sid = Encoding.ASCII.GetBytes("1") });
|
||||
c.ProcessMsgResults(null, result, "hello\r\n"u8.ToArray(), null, Encoding.ASCII.GetBytes("foo"), null, PmrFlags.None).didDeliver.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LifecycleAndTlsHelpers_GroupC_ShouldBehave()
|
||||
{
|
||||
var logger = new CaptureLogger();
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions
|
||||
{
|
||||
PingInterval = TimeSpan.FromMilliseconds(120),
|
||||
});
|
||||
err.ShouldBeNull();
|
||||
server.SetLogger(logger, debugFlag: true, traceFlag: true);
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
var c = new ClientConnection(ClientKind.Client, server, ms)
|
||||
{
|
||||
Cid = 42,
|
||||
Host = "127.0.0.1",
|
||||
Start = DateTime.UtcNow.AddSeconds(-2),
|
||||
Rtt = TimeSpan.FromMilliseconds(5),
|
||||
};
|
||||
|
||||
c.SetFirstPingTimer();
|
||||
GetTimer(c, "_pingTimer").ShouldNotBeNull();
|
||||
|
||||
c.WatchForStaleConnection(TimeSpan.FromMilliseconds(20), pingMax: 0);
|
||||
Thread.Sleep(60);
|
||||
c.IsClosed().ShouldBeTrue();
|
||||
|
||||
var temp = Account.NewAccount("A");
|
||||
temp.Sublist = SubscriptionIndex.NewSublistWithCache();
|
||||
c.SetAccount(temp);
|
||||
|
||||
var registered = server.LookupOrRegisterAccount("A").Account;
|
||||
registered.Sublist = SubscriptionIndex.NewSublistWithCache();
|
||||
var inserted = new Subscription
|
||||
{
|
||||
Subject = Encoding.ASCII.GetBytes("foo.bar"),
|
||||
Sid = Encoding.ASCII.GetBytes("11"),
|
||||
};
|
||||
registered.Sublist.Insert(inserted).ShouldBeNull();
|
||||
|
||||
c.SwapAccountAfterReload();
|
||||
c.GetAccount().ShouldBe(registered);
|
||||
|
||||
c.Perms = new ClientPermissions();
|
||||
c.Perms.Sub.Deny = SubscriptionIndex.NewSublistWithCache();
|
||||
c.Perms.Sub.Deny.Insert(new Subscription { Subject = Encoding.ASCII.GetBytes(">") }).ShouldBeNull();
|
||||
c.Subs["22"] = new Subscription
|
||||
{
|
||||
Subject = Encoding.ASCII.GetBytes("foo.bar"),
|
||||
Sid = Encoding.ASCII.GetBytes("22"),
|
||||
};
|
||||
c.ProcessSubsOnConfigReload(new HashSet<string>(StringComparer.Ordinal) { registered.Name });
|
||||
c.Subs.ContainsKey("22").ShouldBeFalse();
|
||||
|
||||
c.ParseCtx.Pa.Account = Encoding.ASCII.GetBytes("A");
|
||||
c.ParseCtx.Pa.Subject = Encoding.ASCII.GetBytes("foo.bar");
|
||||
c.ParseCtx.Pa.PaCache = Encoding.ASCII.GetBytes("A:foo.bar");
|
||||
var cached = c.GetAccAndResultFromCache();
|
||||
cached.Account.ShouldBe(registered);
|
||||
cached.Result.ShouldNotBeNull();
|
||||
cached.Result.PSubs.Count.ShouldBeGreaterThan(0);
|
||||
|
||||
var closedSub = new Subscription { Subject = Encoding.ASCII.GetBytes("foo.closed") };
|
||||
closedSub.Close();
|
||||
var inField = typeof(ClientConnection).GetField("_in", BindingFlags.Instance | BindingFlags.NonPublic)!;
|
||||
var state = (ReadCacheState)inField.GetValue(c)!;
|
||||
state.PaCache = new Dictionary<string, PerAccountCache>(StringComparer.Ordinal)
|
||||
{
|
||||
["closed"] = new PerAccountCache
|
||||
{
|
||||
Acc = registered,
|
||||
Results = new SubscriptionIndexResult
|
||||
{
|
||||
PSubs = { closedSub },
|
||||
},
|
||||
GenId = 1,
|
||||
},
|
||||
};
|
||||
inField.SetValue(c, state);
|
||||
c.PruneClosedSubFromPerAccountCache();
|
||||
state = (ReadCacheState)inField.GetValue(c)!;
|
||||
state.PaCache.ShouldNotBeNull();
|
||||
state.PaCache.Count.ShouldBe(0);
|
||||
|
||||
var info = c.GetClientInfo(detailed: true);
|
||||
info.ShouldNotBeNull();
|
||||
info!.Account.ShouldBe("A");
|
||||
info.Server.ShouldNotBeNullOrWhiteSpace();
|
||||
info.ServiceAccount().ShouldBe("A");
|
||||
|
||||
var (allowed, convertErr) = ClientConnection.ConvertAllowedConnectionTypes(
|
||||
["standard", "mqtt", "bad"]);
|
||||
allowed.ShouldContain(AuthHandler.ConnectionTypes.Standard);
|
||||
allowed.ShouldContain(AuthHandler.ConnectionTypes.Mqtt);
|
||||
convertErr.ShouldNotBeNull();
|
||||
|
||||
c.RateLimitWarnf("warn {0}", 1);
|
||||
c.RateLimitWarnf("warn {0}", 1);
|
||||
logger.Warnings.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
private sealed class CaptureLogger : INatsLogger
|
||||
{
|
||||
public List<string> Warnings { get; } = [];
|
||||
|
||||
public void Noticef(string format, params object[] args) { }
|
||||
public void Warnf(string format, params object[] args) => Warnings.Add(string.Format(format, args));
|
||||
public void Fatalf(string format, params object[] args) { }
|
||||
public void Errorf(string format, params object[] args) { }
|
||||
public void Debugf(string format, params object[] args) { }
|
||||
public void Tracef(string format, params object[] args) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,13 @@ public sealed class ClientTests
|
||||
c2.SetExpiration(DateTimeOffset.UtcNow.AddSeconds(-1).ToUnixTimeSeconds(), TimeSpan.Zero);
|
||||
SpinWait.SpinUntil(c2.IsClosed, TimeSpan.FromSeconds(2)).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplyHelpers_ServiceAndReserved_ShouldClassifyPrefixes()
|
||||
{
|
||||
ClientConnection.IsServiceReply(Encoding.ASCII.GetBytes("_R_.A.B")).ShouldBeTrue();
|
||||
ClientConnection.IsServiceReply(Encoding.ASCII.GetBytes("foo.bar")).ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -63,6 +63,41 @@ public sealed class NatsServerTests
|
||||
tlsConfig.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RateLimitedClientLogging_ShouldSuppressDuplicates()
|
||||
{
|
||||
var logger = new NatsServerCaptureLogger();
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.SetLogger(logger, debugFlag: true, traceFlag: true);
|
||||
|
||||
var c = new ClientConnection(ClientKind.Client, server, new MemoryStream());
|
||||
c.RateLimitWarnf("duplicate warning {0}", "A");
|
||||
c.RateLimitWarnf("duplicate warning {0}", "A");
|
||||
c.RateLimitFormatWarnf("format warning {0}", "B");
|
||||
c.RateLimitFormatWarnf("format warning {0}", "C");
|
||||
c.RateLimitErrorf("duplicate error {0}", "X");
|
||||
c.RateLimitErrorf("duplicate error {0}", "X");
|
||||
|
||||
logger.Warnings.Count.ShouldBe(2);
|
||||
logger.Errors.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerRateLimitLogging_ShouldSucceed()
|
||||
{
|
||||
var logger = new NatsServerCaptureLogger();
|
||||
var (server, err) = NatsServer.NewServer(new ServerOptions());
|
||||
err.ShouldBeNull();
|
||||
server.SetLogger(logger, debugFlag: false, traceFlag: false);
|
||||
|
||||
server.RateLimitWarnf("batch17 warning");
|
||||
server.RateLimitWarnf("batch17 warning");
|
||||
|
||||
logger.Warnings.Count.ShouldBe(1);
|
||||
logger.Errors.Count.ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact] // T:2886
|
||||
public void CustomRouterAuthentication_ShouldSucceed()
|
||||
{
|
||||
@@ -575,10 +610,24 @@ public sealed class NatsServerTests
|
||||
"TestServerShutdownDuringStart".ShouldNotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
private sealed class NatsServerCaptureLogger : INatsLogger
|
||||
{
|
||||
public List<string> Warnings { get; } = [];
|
||||
public List<string> Errors { get; } = [];
|
||||
|
||||
public void Noticef(string format, params object[] args) { }
|
||||
public void Warnf(string format, params object[] args) => Warnings.Add(string.Format(format, args));
|
||||
public void Fatalf(string format, params object[] args) { }
|
||||
public void Errorf(string format, params object[] args) => Errors.Add(string.Format(format, args));
|
||||
public void Debugf(string format, params object[] args) { }
|
||||
public void Tracef(string format, params object[] args) { }
|
||||
}
|
||||
|
||||
private static void SetField(object target, string name, object? value)
|
||||
{
|
||||
target.GetType()
|
||||
.GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.SetValue(target, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,107 @@ namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
public sealed class JetStreamFileStoreTests
|
||||
{
|
||||
[Fact]
|
||||
public void FileStoreSubjectDeleteMarkers_ShouldSucceed()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), $"fs-sdm-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(root);
|
||||
try
|
||||
{
|
||||
var fs = new JetStreamFileStore(
|
||||
new FileStoreConfig { StoreDir = root },
|
||||
new FileStreamInfo
|
||||
{
|
||||
Created = DateTime.UtcNow,
|
||||
Config = new StreamConfig
|
||||
{
|
||||
Name = "SDM",
|
||||
Storage = StorageType.FileStorage,
|
||||
Subjects = ["test"],
|
||||
MaxAge = TimeSpan.FromSeconds(1),
|
||||
AllowMsgTTL = true,
|
||||
SubjectDeleteMarkerTTL = TimeSpan.FromSeconds(1),
|
||||
},
|
||||
});
|
||||
|
||||
var (seq, _) = fs.StoreMsg("test", null, [1], 0);
|
||||
seq.ShouldBe(1UL);
|
||||
|
||||
var (removed, err) = fs.RemoveMsg(seq);
|
||||
removed.ShouldBeTrue();
|
||||
err.ShouldBeNull();
|
||||
fs.State().Msgs.ShouldBe(0UL);
|
||||
|
||||
fs.Stop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FileStoreNoPanicOnRecoverTTLWithCorruptBlocks_ShouldSucceed()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), $"fs-ttl-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(root);
|
||||
try
|
||||
{
|
||||
var hdr = NatsMessageHeaders.GenHeader(null, NatsHeaderConstants.JsMessageTtl, "1");
|
||||
var fs = NewStore(root, cfg =>
|
||||
{
|
||||
cfg.AllowMsgTTL = true;
|
||||
cfg.Subjects = ["foo"];
|
||||
});
|
||||
|
||||
fs.StoreMsg("foo", hdr, [1], 1).Seq.ShouldBe(1UL);
|
||||
fs.Stop();
|
||||
|
||||
var reopened = NewStore(root, cfg =>
|
||||
{
|
||||
cfg.AllowMsgTTL = true;
|
||||
cfg.Subjects = ["foo"];
|
||||
});
|
||||
reopened.State().Msgs.ShouldBeGreaterThanOrEqualTo(0UL);
|
||||
reopened.Stop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FileStorePurgeMsgBlockRemovesSchedules_ShouldSucceed()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), $"fs-purge-sched-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(root);
|
||||
try
|
||||
{
|
||||
var fs = NewStore(root, cfg =>
|
||||
{
|
||||
cfg.AllowMsgSchedules = true;
|
||||
cfg.Subjects = ["foo.*"];
|
||||
});
|
||||
|
||||
var hdr = NatsMessageHeaders.GenHeader(null, NatsHeaderConstants.JsSchedulePattern, "@every 10s");
|
||||
hdr = NatsMessageHeaders.GenHeader(hdr, NatsHeaderConstants.JsScheduleTarget, "foo.target");
|
||||
for (var i = 0; i < 10; i++)
|
||||
fs.StoreMsg($"foo.schedule.{i}", hdr, [1], 0);
|
||||
|
||||
var (purged, err) = fs.Purge();
|
||||
err.ShouldBeNull();
|
||||
purged.ShouldBe(10UL);
|
||||
fs.State().Msgs.ShouldBe(0UL);
|
||||
|
||||
fs.Stop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StoreMsg_LoadAndPurge_ShouldRoundTrip()
|
||||
{
|
||||
@@ -58,19 +159,22 @@ public sealed class JetStreamFileStoreTests
|
||||
}
|
||||
}
|
||||
|
||||
private static JetStreamFileStore NewStore(string root)
|
||||
private static JetStreamFileStore NewStore(string root, Action<StreamConfig>? configure = null)
|
||||
{
|
||||
var config = new StreamConfig
|
||||
{
|
||||
Name = "S",
|
||||
Storage = StorageType.FileStorage,
|
||||
Subjects = ["foo", "bar"],
|
||||
};
|
||||
configure?.Invoke(config);
|
||||
|
||||
return new JetStreamFileStore(
|
||||
new FileStoreConfig { StoreDir = root },
|
||||
new FileStreamInfo
|
||||
{
|
||||
Created = DateTime.UtcNow,
|
||||
Config = new StreamConfig
|
||||
{
|
||||
Name = "S",
|
||||
Storage = StorageType.FileStorage,
|
||||
Subjects = ["foo", "bar"],
|
||||
},
|
||||
Config = config,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,30 @@ public class JetStreamMemoryStoreTests
|
||||
ms.Stop();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MemStoreSubjectDeleteMarkers_ShouldSucceed()
|
||||
{
|
||||
var fs = NewMemStore(new StreamConfig
|
||||
{
|
||||
Name = "zzz",
|
||||
Subjects = ["test"],
|
||||
Storage = StorageType.MemoryStorage,
|
||||
MaxAge = TimeSpan.FromSeconds(1),
|
||||
AllowMsgTTL = true,
|
||||
SubjectDeleteMarkerTTL = TimeSpan.FromSeconds(1),
|
||||
});
|
||||
|
||||
var (seq, _) = fs.StoreMsg("test", null, Bytes("x"), 0);
|
||||
seq.ShouldBe(1UL);
|
||||
|
||||
var (removed, err) = fs.RemoveMsg(seq);
|
||||
removed.ShouldBeTrue();
|
||||
err.ShouldBeNull();
|
||||
fs.State().Msgs.ShouldBe(0UL);
|
||||
|
||||
fs.Stop();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllLastSeqsLocked_MatchesPublicAllLastSeqsOrdering()
|
||||
{
|
||||
|
||||
@@ -181,6 +181,20 @@ public class ProtocolParserTests
|
||||
Encoding.ASCII.GetString(c.ArgBuf!).ShouldBe("foo 1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClientConnection_InboundDispatchAndPingIntervalHelpers_ShouldBehave()
|
||||
{
|
||||
var c = new ClientConnection(ClientKind.Client);
|
||||
var before = DateTime.UtcNow;
|
||||
c.ProcessInboundMsg(Encoding.ASCII.GetBytes("hello"));
|
||||
c.LastIn.ShouldBeGreaterThan(before - TimeSpan.FromMilliseconds(1));
|
||||
|
||||
ClientConnection.AdjustPingInterval(ClientKind.Router, TimeSpan.FromHours(1))
|
||||
.ShouldBeLessThan(TimeSpan.FromHours(1));
|
||||
ClientConnection.AdjustPingInterval(ClientKind.Gateway, TimeSpan.FromHours(1))
|
||||
.ShouldBeLessThan(TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// TestParsePub — Go test ID 2602
|
||||
// =====================================================================
|
||||
|
||||
Reference in New Issue
Block a user