Adds PauseUntilUtc to ConsumerHandle, a new Pause(DateTime) overload, Resume, IsPaused, and GetPauseUntil to ConsumerManager. A System.Threading.Timer fires when the deadline passes and calls AutoResume, raising OnAutoResumed so tests can synchronise via SemaphoreSlim instead of Task.Delay. ConsumerManager now implements IDisposable to clean up outstanding timers. Timer is also cancelled on explicit Resume and Delete. Go reference: consumer.go (pauseConsumer / resumeConsumer / isPaused).
104 lines
3.2 KiB
C#
104 lines
3.2 KiB
C#
using NATS.Server.JetStream;
|
|
using NATS.Server.JetStream.Models;
|
|
|
|
namespace NATS.Server.Tests.JetStream.Consumers;
|
|
|
|
/// <summary>
|
|
/// Tests for consumer pause/resume with auto-resume timer.
|
|
/// Go reference: consumer.go (pause/resume).
|
|
/// </summary>
|
|
public class ConsumerPauseResumeTests
|
|
{
|
|
private static ConsumerManager CreateManager() => new();
|
|
|
|
private static void CreateConsumer(ConsumerManager mgr, string stream, string name)
|
|
{
|
|
mgr.CreateOrUpdate(stream, new ConsumerConfig { DurableName = name });
|
|
}
|
|
|
|
[Fact]
|
|
public void Pause_with_deadline_sets_paused()
|
|
{
|
|
var mgr = CreateManager();
|
|
CreateConsumer(mgr, "test-stream", "test-consumer");
|
|
|
|
var until = DateTime.UtcNow.AddSeconds(5);
|
|
mgr.Pause("test-stream", "test-consumer", until);
|
|
|
|
mgr.IsPaused("test-stream", "test-consumer").ShouldBeTrue();
|
|
mgr.GetPauseUntil("test-stream", "test-consumer").ShouldBe(until);
|
|
}
|
|
|
|
[Fact]
|
|
public void Resume_clears_pause()
|
|
{
|
|
var mgr = CreateManager();
|
|
CreateConsumer(mgr, "test-stream", "test-consumer");
|
|
|
|
mgr.Pause("test-stream", "test-consumer", DateTime.UtcNow.AddSeconds(5));
|
|
mgr.Resume("test-stream", "test-consumer");
|
|
|
|
mgr.IsPaused("test-stream", "test-consumer").ShouldBeFalse();
|
|
mgr.GetPauseUntil("test-stream", "test-consumer").ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Pause_auto_resumes_after_deadline()
|
|
{
|
|
var mgr = CreateManager();
|
|
CreateConsumer(mgr, "test-stream", "test-consumer");
|
|
|
|
// Use a semaphore to synchronize on the actual timer callback rather than a blind delay.
|
|
using var resumed = new SemaphoreSlim(0, 1);
|
|
mgr.OnAutoResumed += (_, _) => resumed.Release();
|
|
|
|
mgr.Pause("test-stream", "test-consumer", DateTime.UtcNow.AddMilliseconds(100));
|
|
|
|
var signalled = await resumed.WaitAsync(TimeSpan.FromSeconds(5));
|
|
signalled.ShouldBeTrue("auto-resume timer did not fire within 5 seconds");
|
|
|
|
mgr.IsPaused("test-stream", "test-consumer").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void IsPaused_returns_false_for_unknown_consumer()
|
|
{
|
|
var mgr = CreateManager();
|
|
mgr.IsPaused("unknown", "unknown").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void GetPauseUntil_returns_null_for_unknown_consumer()
|
|
{
|
|
var mgr = CreateManager();
|
|
mgr.GetPauseUntil("unknown", "unknown").ShouldBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Resume_returns_false_for_unknown_consumer()
|
|
{
|
|
var mgr = CreateManager();
|
|
mgr.Resume("unknown", "unknown").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void Pause_returns_false_for_unknown_consumer()
|
|
{
|
|
var mgr = CreateManager();
|
|
mgr.Pause("unknown", "unknown", DateTime.UtcNow.AddSeconds(5)).ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void IsPaused_auto_resumes_expired_deadline()
|
|
{
|
|
var mgr = CreateManager();
|
|
CreateConsumer(mgr, "test-stream", "c1");
|
|
|
|
// Pause with a deadline in the past
|
|
mgr.Pause("test-stream", "c1", DateTime.UtcNow.AddMilliseconds(-100));
|
|
|
|
// IsPaused should detect the expired deadline and auto-resume
|
|
mgr.IsPaused("test-stream", "c1").ShouldBeFalse();
|
|
}
|
|
}
|