test(integration): edge removal stops flow; node removal preserves stream

This commit is contained in:
VolcanicCore
2026-08-27 12:17:47 +00:00
parent 10eeb44621
commit 2fc3a5dd2c
2 changed files with 112 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
using System.Text;
using NATS.Client.Core;
using NATS.Client.JetStream;
using NATS.Client.JetStream.Models;
using VolcanicCore.Integration.Tests.Fixtures;
using Xunit;
namespace VolcanicCore.Integration.Tests.Scenarios;
public class EdgeRemovalTests : IAsyncLifetime
{
private readonly RealNatsFixture _fx = new();
public Task InitializeAsync() => _fx.InitializeAsync();
public Task DisposeAsync() => _fx.DisposeAsync();
[Fact]
public async Task Removing_source_stops_flow_but_preserves_past_messages()
{
await _fx.Js.CreateStreamAsync(new StreamConfig
{
Name = "A",
Subjects = new List<string> { "a.>" },
Storage = StreamConfigStorage.File,
});
await _fx.Js.CreateStreamAsync(new StreamConfig
{
Name = "B",
Subjects = new List<string> { "b.>" },
Storage = StreamConfigStorage.File,
Sources = new List<StreamSource>
{
new()
{
Name = "A",
SubjectTransforms = new List<SubjectTransform>
{
new() { Src = "a.>", Dest = "b.>" }
}
}
}
});
for (var i = 0; i < 10; i++)
await _fx.Js.PublishAsync($"a.{i}", Encoding.UTF8.GetBytes($"m{i}"));
// Wait for the first batch to propagate
var streamB = await _fx.Js.GetStreamAsync("B");
for (var i = 0; i < 30; i++)
{
await streamB.RefreshAsync();
if (streamB.Info.State.Messages >= 10) break;
await Task.Delay(200);
}
await streamB.RefreshAsync();
Assert.Equal(10u, streamB.Info.State.Messages);
// Now update B to remove its source
await _fx.Js.UpdateStreamAsync(new StreamConfig
{
Name = "B",
Subjects = new List<string> { "b.>" },
Storage = StreamConfigStorage.File,
Sources = new List<StreamSource>(),
});
// Publish 10 more
for (var i = 10; i < 20; i++)
await _fx.Js.PublishAsync($"a.{i}", Encoding.UTF8.GetBytes($"m{i}"));
await Task.Delay(1500);
await streamB.RefreshAsync();
Assert.Equal(10u, streamB.Info.State.Messages); // still just the first 10
}
}

View File

@@ -0,0 +1,38 @@
using System.Text;
using NATS.Client.JetStream;
using NATS.Client.JetStream.Models;
using VolcanicCore.Integration.Tests.Fixtures;
using Xunit;
namespace VolcanicCore.Integration.Tests.Scenarios;
public class NodeRemovalPreservesStreamTests : IAsyncLifetime
{
private readonly RealNatsFixture _fx = new();
public Task InitializeAsync() => _fx.InitializeAsync();
public Task DisposeAsync() => _fx.DisposeAsync();
[Fact]
public async Task Stream_survives_when_the_graph_forgets_about_it()
{
await _fx.Js.CreateStreamAsync(new StreamConfig
{
Name = "SURVIVOR",
Subjects = new List<string> { "survivor.>" },
Storage = StreamConfigStorage.File,
});
for (var i = 0; i < 5; i++)
await _fx.Js.PublishAsync($"survivor.msg.{i}", Encoding.UTF8.GetBytes($"m{i}"));
// The reconciler "forgets" about the node (SQLite deletion has no effect on NATS).
// Simulate a re-adoption on a fresh backend by opening a new context — the stream
// remains discoverable.
var names = new List<string>();
await foreach (var s in _fx.Js.ListStreamsAsync()) names.Add(s.Info.Config.Name);
Assert.Contains("SURVIVOR", names);
var stream = await _fx.Js.GetStreamAsync("SURVIVOR");
await stream.RefreshAsync();
Assert.Equal(5u, stream.Info.State.Messages);
}
}