refactor: extract NATS.Server.JetStream.Tests project
Move 225 JetStream-related test files from NATS.Server.Tests into a dedicated NATS.Server.JetStream.Tests project. This includes root-level JetStream*.cs files, storage test files (FileStore, MemStore, StreamStoreContract), and the full JetStream/ subfolder tree (Api, Cluster, Consumers, MirrorSource, Snapshots, Storage, Streams). Updated all namespaces, added InternalsVisibleTo, registered in the solution file, and added the JETSTREAM_INTEGRATION_MATRIX define.
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
// Go reference: server/consumer.go — pauseConsumer / resumeConsumer / isPaused
|
||||
// Tests for the consumer pause/resume API endpoint, including pause_until (RFC3339)
|
||||
// time-bounded pauses and response body containing pause state.
|
||||
|
||||
using NATS.Server.JetStream.Api;
|
||||
using NATS.Server.TestUtilities;
|
||||
|
||||
namespace NATS.Server.JetStream.Tests.JetStream.Api;
|
||||
|
||||
public class ConsumerPauseApiTests : IAsyncLifetime
|
||||
{
|
||||
private JetStreamApiFixture _fx = null!;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_fx = await JetStreamApiFixture.StartWithStreamAsync("ORDERS", "orders.*");
|
||||
_ = await _fx.CreateConsumerAsync("ORDERS", "MON", "orders.created");
|
||||
}
|
||||
|
||||
public async Task DisposeAsync() => await _fx.DisposeAsync();
|
||||
|
||||
// Go ref: consumer.go pauseConsumer — pause=true pauses consumer.
|
||||
[Fact]
|
||||
public async Task HandlePause_with_pause_true_pauses_consumer()
|
||||
{
|
||||
var resp = await _fx.RequestLocalAsync(
|
||||
"$JS.API.CONSUMER.PAUSE.ORDERS.MON",
|
||||
"{\"pause\":true}");
|
||||
|
||||
resp.Error.ShouldBeNull();
|
||||
resp.Success.ShouldBeTrue();
|
||||
resp.Paused.ShouldBe(true);
|
||||
}
|
||||
|
||||
// Go ref: consumer.go resumeConsumer — pause=false resumes consumer.
|
||||
[Fact]
|
||||
public async Task HandlePause_with_pause_false_resumes_consumer()
|
||||
{
|
||||
// First pause
|
||||
await _fx.RequestLocalAsync("$JS.API.CONSUMER.PAUSE.ORDERS.MON", "{\"pause\":true}");
|
||||
|
||||
// Then resume
|
||||
var resp = await _fx.RequestLocalAsync(
|
||||
"$JS.API.CONSUMER.PAUSE.ORDERS.MON",
|
||||
"{\"pause\":false}");
|
||||
|
||||
resp.Error.ShouldBeNull();
|
||||
resp.Success.ShouldBeTrue();
|
||||
resp.Paused.ShouldBe(false);
|
||||
}
|
||||
|
||||
// Go ref: consumer.go pauseConsumer — pause_until sets deadline UTC datetime.
|
||||
[Fact]
|
||||
public async Task HandlePause_with_pause_until_sets_deadline()
|
||||
{
|
||||
var future = DateTime.UtcNow.AddHours(1);
|
||||
var iso = future.ToString("O"); // RFC3339 round-trip format
|
||||
|
||||
var resp = await _fx.RequestLocalAsync(
|
||||
"$JS.API.CONSUMER.PAUSE.ORDERS.MON",
|
||||
$"{{\"pause_until\":\"{iso}\"}}");
|
||||
|
||||
resp.Error.ShouldBeNull();
|
||||
resp.PauseUntil.ShouldNotBeNull();
|
||||
resp.PauseUntil!.Value.Should_Be_Close_To_Utc(future, tolerance: TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
// Go ref: consumer.go pauseConsumer — pause_until implies pause=true.
|
||||
[Fact]
|
||||
public async Task HandlePause_with_pause_until_implies_pause_true()
|
||||
{
|
||||
var future = DateTime.UtcNow.AddHours(1);
|
||||
var iso = future.ToString("O");
|
||||
|
||||
var resp = await _fx.RequestLocalAsync(
|
||||
"$JS.API.CONSUMER.PAUSE.ORDERS.MON",
|
||||
$"{{\"pause_until\":\"{iso}\"}}");
|
||||
|
||||
resp.Error.ShouldBeNull();
|
||||
resp.Paused.ShouldBe(true);
|
||||
}
|
||||
|
||||
// Go ref: consumer.go isPaused — response includes current pause state.
|
||||
[Fact]
|
||||
public async Task HandlePause_returns_pause_state_in_response()
|
||||
{
|
||||
var resp = await _fx.RequestLocalAsync(
|
||||
"$JS.API.CONSUMER.PAUSE.ORDERS.MON",
|
||||
"{\"pause\":true}");
|
||||
|
||||
resp.Paused.ShouldBe(true);
|
||||
|
||||
var resumeResp = await _fx.RequestLocalAsync(
|
||||
"$JS.API.CONSUMER.PAUSE.ORDERS.MON",
|
||||
"{\"pause\":false}");
|
||||
|
||||
resumeResp.Paused.ShouldBe(false);
|
||||
}
|
||||
|
||||
// Go ref: consumer.go pauseUntil — response includes pause_until when set.
|
||||
[Fact]
|
||||
public async Task HandlePause_returns_pause_until_in_response()
|
||||
{
|
||||
var future = DateTime.UtcNow.AddMinutes(30);
|
||||
var iso = future.ToString("O");
|
||||
|
||||
var resp = await _fx.RequestLocalAsync(
|
||||
"$JS.API.CONSUMER.PAUSE.ORDERS.MON",
|
||||
$"{{\"pause_until\":\"{iso}\"}}");
|
||||
|
||||
resp.PauseUntil.ShouldNotBeNull();
|
||||
resp.PauseUntil!.Value.Kind.ShouldBe(DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
// Go ref: consumer.go pauseConsumer — 404 when consumer not found.
|
||||
[Fact]
|
||||
public async Task HandlePause_returns_not_found_for_missing_consumer()
|
||||
{
|
||||
var resp = await _fx.RequestLocalAsync(
|
||||
"$JS.API.CONSUMER.PAUSE.ORDERS.NONEXISTENT",
|
||||
"{\"pause\":true}");
|
||||
|
||||
resp.Error.ShouldNotBeNull();
|
||||
resp.Error!.Code.ShouldBe(404);
|
||||
}
|
||||
|
||||
// Go ref: consumer.go resumeConsumer — empty payload resumes consumer.
|
||||
[Fact]
|
||||
public async Task HandlePause_with_empty_payload_resumes()
|
||||
{
|
||||
// Pause first
|
||||
await _fx.RequestLocalAsync("$JS.API.CONSUMER.PAUSE.ORDERS.MON", "{\"pause\":true}");
|
||||
|
||||
// Empty body = resume
|
||||
var resp = await _fx.RequestLocalAsync("$JS.API.CONSUMER.PAUSE.ORDERS.MON", "");
|
||||
|
||||
resp.Error.ShouldBeNull();
|
||||
resp.Success.ShouldBeTrue();
|
||||
resp.Paused.ShouldBe(false);
|
||||
}
|
||||
|
||||
// Go ref: consumer.go pauseConsumer — past pause_until auto-resumes immediately.
|
||||
[Fact]
|
||||
public async Task HandlePause_with_past_pause_until_auto_resumes()
|
||||
{
|
||||
var past = DateTime.UtcNow.AddHours(-1);
|
||||
var iso = past.ToString("O");
|
||||
|
||||
var resp = await _fx.RequestLocalAsync(
|
||||
"$JS.API.CONSUMER.PAUSE.ORDERS.MON",
|
||||
$"{{\"pause_until\":\"{iso}\"}}");
|
||||
|
||||
// Deadline already passed — consumer should auto-resume, so paused=false.
|
||||
resp.Error.ShouldBeNull();
|
||||
resp.Success.ShouldBeTrue();
|
||||
resp.Paused.ShouldBe(false);
|
||||
}
|
||||
|
||||
// Go ref: jsConsumerPauseT — bad subject (not matching stream.consumer pattern) returns 404.
|
||||
[Fact]
|
||||
public async Task HandlePause_returns_not_found_for_bad_subject()
|
||||
{
|
||||
var resp = await _fx.RequestLocalAsync(
|
||||
"$JS.API.CONSUMER.PAUSE.ONLY_ONE_TOKEN",
|
||||
"{\"pause\":true}");
|
||||
|
||||
resp.Error.ShouldNotBeNull();
|
||||
resp.Error!.Code.ShouldBe(404);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shouldly-compatible extension for DateTime proximity assertions.
|
||||
/// </summary>
|
||||
internal static class DateTimeAssertExtensions
|
||||
{
|
||||
public static void Should_Be_Close_To_Utc(this DateTime actual, DateTime expected, TimeSpan tolerance)
|
||||
{
|
||||
var diff = (actual.ToUniversalTime() - expected.ToUniversalTime()).Duration();
|
||||
diff.ShouldBeLessThanOrEqualTo(tolerance);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user