feat(batch29): implement jetstream batching group-a lifecycle/store methods

This commit is contained in:
Joseph Doherty
2026-03-01 01:45:35 -05:00
parent 6cf904cc7d
commit 02d3b610a1
3 changed files with 356 additions and 22 deletions

View File

@@ -0,0 +1,146 @@
using NSubstitute;
using Shouldly;
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
public sealed class JetStreamBatchingCoreTests
{
[Fact]
public void GetBatchStoreDir_ValidInputs_ReturnsHashedBatchPath()
{
var storeDir = Path.Combine(Path.GetTempPath(), $"jsa-{Guid.NewGuid():N}");
var (batchName, batchPath) = JetStreamBatching.GetBatchStoreDir(storeDir, "ORDERS", "batch-A");
batchName.ShouldBe(NatsServer.GetHash("batch-A"));
batchPath.ShouldBe(Path.Combine(storeDir, "_streams", "ORDERS", "batches", batchName));
}
[Fact]
public void NewBatchStore_FileSingleReplica_CreatesFileStore()
{
var tempRoot = Path.Combine(Path.GetTempPath(), $"batch-store-{Guid.NewGuid():N}");
var stream = new StreamConfig
{
Name = "ORDERS",
Replicas = 1,
Storage = StorageType.FileStorage,
};
var store = JetStreamBatching.NewBatchStore(tempRoot, stream, "batch-A");
try
{
store.ShouldBeOfType<JetStreamFileStore>();
}
finally
{
store.Stop();
}
}
[Fact]
public void NewBatchStore_MemoryOrReplicated_CreatesMemStore()
{
var tempRoot = Path.Combine(Path.GetTempPath(), $"batch-store-{Guid.NewGuid():N}");
var stream = new StreamConfig
{
Name = "ORDERS",
Replicas = 3,
Storage = StorageType.FileStorage,
};
var store = JetStreamBatching.NewBatchStore(tempRoot, stream, "batch-A");
try
{
store.ShouldBeOfType<JetStreamMemStore>();
}
finally
{
store.Stop();
}
}
[Fact]
public void ReadyForCommit_TimerStopped_FlushesAndReturnsTrue()
{
var store = Substitute.For<IStreamStore>();
var timer = Substitute.For<IBatchTimer>();
timer.Stop().Returns(true);
var group = new BatchGroup
{
Store = store,
BatchTimer = timer,
};
group.ReadyForCommit().ShouldBeTrue();
store.Received(1).FlushAllPending();
}
[Fact]
public void ReadyForCommit_TimerAlreadyExpired_DoesNotFlushAndReturnsFalse()
{
var store = Substitute.For<IStreamStore>();
var timer = Substitute.For<IBatchTimer>();
timer.Stop().Returns(false);
var group = new BatchGroup
{
Store = store,
BatchTimer = timer,
};
group.ReadyForCommit().ShouldBeFalse();
store.DidNotReceive().FlushAllPending();
}
[Fact]
public void CleanupLocked_BatchPresent_StopsTimerDeletesStoreRemovesGroupAndDecrementsGlobalInflight()
{
JetStreamBatching.ResetGlobalInflightBatchesForTest();
JetStreamBatching.IncrementGlobalInflightBatchesForTest();
var store = Substitute.For<IStreamStore>();
var timer = Substitute.For<IBatchTimer>();
timer.Stop().Returns(true);
var group = new BatchGroup
{
Store = store,
BatchTimer = timer,
};
var batching = new Batching();
batching.Group["batch-A"] = group;
group.CleanupLocked("batch-A", batching);
timer.Received(1).Stop();
store.Received(1).Delete(true);
batching.Group.ContainsKey("batch-A").ShouldBeFalse();
JetStreamBatching.GlobalInflightBatches.ShouldBe(0);
}
[Fact]
public void StopLocked_BatchPresent_StopsTimerStopsStoreAndDecrementsGlobalInflight()
{
JetStreamBatching.ResetGlobalInflightBatchesForTest();
JetStreamBatching.IncrementGlobalInflightBatchesForTest();
var store = Substitute.For<IStreamStore>();
var timer = Substitute.For<IBatchTimer>();
var group = new BatchGroup
{
Store = store,
BatchTimer = timer,
};
group.StopLocked();
timer.Received(1).Stop();
store.Received(1).Stop();
JetStreamBatching.GlobalInflightBatches.ShouldBe(0);
}
}