task2: implement batch38 group A consumer lifecycle features
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
using System.Text.Json;
|
||||
using Shouldly;
|
||||
using ZB.MOM.NatsNet.Server;
|
||||
|
||||
namespace ZB.MOM.NatsNet.Server.Tests.JetStream;
|
||||
|
||||
public sealed class ConsumerPoliciesTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConsumerAction_StringAndJsonParity_ShouldMatchGo()
|
||||
{
|
||||
ConsumerAction.CreateOrUpdate.String().ShouldBe("\"create_or_update\"");
|
||||
ConsumerAction.Create.String().ShouldBe("\"create\"");
|
||||
ConsumerAction.Update.String().ShouldBe("\"update\"");
|
||||
|
||||
JsonSerializer.Serialize(ConsumerAction.Create).ShouldBe("\"create\"");
|
||||
JsonSerializer.Deserialize<ConsumerAction>("\"update\"").ShouldBe(ConsumerAction.Update);
|
||||
Should.Throw<JsonException>(() => JsonSerializer.Deserialize<ConsumerAction>("\"bogus\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PriorityPolicy_StringAndJsonParity_ShouldMatchGo()
|
||||
{
|
||||
PriorityPolicy.PriorityNone.String().ShouldBe("\"none\"");
|
||||
PriorityPolicy.PriorityOverflow.String().ShouldBe("\"overflow\"");
|
||||
PriorityPolicy.PriorityPinnedClient.String().ShouldBe("\"pinned_client\"");
|
||||
PriorityPolicy.PriorityPrioritized.String().ShouldBe("\"prioritized\"");
|
||||
|
||||
JsonSerializer.Serialize(PriorityPolicy.PriorityPinnedClient).ShouldBe("\"pinned_client\"");
|
||||
JsonSerializer.Deserialize<PriorityPolicy>("\"prioritized\"").ShouldBe(PriorityPolicy.PriorityPrioritized);
|
||||
Should.Throw<JsonException>(() => JsonSerializer.Deserialize<PriorityPolicy>("\"none-ish\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConsumerPolicies_StringParity_ShouldMatchGo()
|
||||
{
|
||||
DeliverPolicy.DeliverByStartSequence.String().ShouldBe("by_start_sequence");
|
||||
AckPolicy.AckExplicit.String().ShouldBe("explicit");
|
||||
ReplayPolicy.ReplayInstant.String().ShouldBe("instant");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubjectTokens_Subjects_RemovesEmptyValues()
|
||||
{
|
||||
var subjects = SubjectTokens.Subjects(new[] { "foo.*", string.Empty, " ", "bar.>" });
|
||||
subjects.ShouldBe(["foo.*", "bar.>"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetConsumerConfigDefaults_InvalidNegativesInPedanticMode_ReturnsError()
|
||||
{
|
||||
var cfg = new ConsumerConfig { MaxDeliver = -2 };
|
||||
var streamCfg = new StreamConfig { Name = "ORDERS", Replicas = 3 };
|
||||
|
||||
var err = NatsConsumer.SetConsumerConfigDefaults(cfg, streamCfg, null, pedantic: true);
|
||||
|
||||
err.ShouldNotBeNull();
|
||||
cfg.MaxDeliver.ShouldBe(-2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetConsumerConfigDefaults_AppliesGoDefaults_ShouldPopulateExpectedValues()
|
||||
{
|
||||
var cfg = new ConsumerConfig
|
||||
{
|
||||
Durable = "D",
|
||||
MaxDeliver = 0,
|
||||
AckPolicy = AckPolicy.AckExplicit,
|
||||
Replicas = 0,
|
||||
};
|
||||
var streamCfg = new StreamConfig { Name = "ORDERS", Replicas = 3 };
|
||||
var limits = new JetStreamAccountLimits { MaxAckPending = 2500 };
|
||||
|
||||
var err = NatsConsumer.SetConsumerConfigDefaults(cfg, streamCfg, limits, pedantic: false);
|
||||
|
||||
err.ShouldBeNull();
|
||||
cfg.MaxDeliver.ShouldBe(-1);
|
||||
cfg.AckWait.ShouldBe(TimeSpan.FromSeconds(30));
|
||||
cfg.MaxAckPending.ShouldBe(2500);
|
||||
cfg.Replicas.ShouldBe(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckConsumerCfg_DurableNameMismatch_ReturnsError()
|
||||
{
|
||||
var cfg = new ConsumerConfig { Name = "A", Durable = "B", AckPolicy = AckPolicy.AckExplicit };
|
||||
var streamCfg = new StreamConfig { Name = "ORDERS", Replicas = 1 };
|
||||
|
||||
var err = NatsConsumer.CheckConsumerCfg(cfg, streamCfg, null, isRecovering: false);
|
||||
|
||||
err.ShouldNotBeNull();
|
||||
err.ErrCode.ShouldBe(JsApiErrors.ConsumerCreateDurableAndNameMismatch.ErrCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckConsumerCfg_OverlappingFilterSubjects_ReturnsError()
|
||||
{
|
||||
var cfg = new ConsumerConfig
|
||||
{
|
||||
Durable = "D",
|
||||
AckPolicy = AckPolicy.AckExplicit,
|
||||
FilterSubjects = ["orders.*", "orders.created"],
|
||||
};
|
||||
var streamCfg = new StreamConfig { Name = "ORDERS", Replicas = 1 };
|
||||
|
||||
var err = NatsConsumer.CheckConsumerCfg(cfg, streamCfg, null, isRecovering: false);
|
||||
|
||||
err.ShouldNotBeNull();
|
||||
err.ErrCode.ShouldBe(JsApiErrors.ConsumerOverlappingSubjectFilters.ErrCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckConsumerCfg_WithValidPullConfig_ReturnsNull()
|
||||
{
|
||||
var cfg = new ConsumerConfig
|
||||
{
|
||||
Durable = "D",
|
||||
AckPolicy = AckPolicy.AckExplicit,
|
||||
FilterSubject = "orders.created",
|
||||
};
|
||||
var streamCfg = new StreamConfig
|
||||
{
|
||||
Name = "ORDERS",
|
||||
Replicas = 1,
|
||||
Retention = RetentionPolicy.LimitsPolicy,
|
||||
Subjects = ["orders.>"],
|
||||
};
|
||||
|
||||
var err = NatsConsumer.CheckConsumerCfg(cfg, streamCfg, null, isRecovering: false);
|
||||
|
||||
err.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
@@ -127,4 +127,89 @@ public sealed class NatsConsumerTests
|
||||
q.Peek()!.Reply.ShouldBe("2a");
|
||||
q.Peek()!.N.ShouldBe(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddConsumerWithAction_CreateThenUpdate_ShouldRespectActions()
|
||||
{
|
||||
var account = new Account { Name = "A" };
|
||||
var streamCfg = new StreamConfig { Name = "S", Subjects = ["foo"], Retention = RetentionPolicy.LimitsPolicy };
|
||||
var stream = NatsStream.Create(account, streamCfg, null, null, null, null);
|
||||
stream.ShouldNotBeNull();
|
||||
|
||||
var cfg = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckExplicit };
|
||||
var (created, createErr) = stream!.AddConsumerWithAction(cfg, "D", ConsumerAction.Create, pedantic: false);
|
||||
createErr.ShouldBeNull();
|
||||
created.ShouldNotBeNull();
|
||||
|
||||
var updateCfg = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckAll };
|
||||
var (updated, updateErr) = stream.AddConsumerWithAction(updateCfg, "D", ConsumerAction.Update, pedantic: false);
|
||||
updateErr.ShouldBeNull();
|
||||
updated.ShouldNotBeNull();
|
||||
updated!.GetConfig().AckPolicy.ShouldBe(AckPolicy.AckAll);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddConsumer_WithAssignment_ShouldAttachAssignment()
|
||||
{
|
||||
var account = new Account { Name = "A" };
|
||||
var streamCfg = new StreamConfig { Name = "S", Subjects = ["foo"], Retention = RetentionPolicy.LimitsPolicy };
|
||||
var stream = NatsStream.Create(account, streamCfg, null, null, null, null);
|
||||
stream.ShouldNotBeNull();
|
||||
|
||||
var assignment = new ConsumerAssignment
|
||||
{
|
||||
Name = "D",
|
||||
Stream = "S",
|
||||
Group = new RaftGroup { Name = "RG", Peers = ["N1"] },
|
||||
};
|
||||
var cfg = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckExplicit };
|
||||
|
||||
var (consumer, err) = stream!.AddConsumerWithAssignment(cfg, "D", assignment, isRecovering: false, ConsumerAction.Create, pedantic: false);
|
||||
err.ShouldBeNull();
|
||||
consumer.ShouldNotBeNull();
|
||||
consumer!.ConsumerAssignment().ShouldBeSameAs(assignment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateInactiveThreshold_AndPauseState_ShouldTrackConfigValues()
|
||||
{
|
||||
var account = new Account { Name = "A" };
|
||||
var streamCfg = new StreamConfig { Name = "S", Subjects = ["foo"], Retention = RetentionPolicy.LimitsPolicy };
|
||||
var stream = NatsStream.Create(account, streamCfg, null, null, null, null);
|
||||
stream.ShouldNotBeNull();
|
||||
|
||||
var cfg = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckExplicit };
|
||||
var consumer = NatsConsumer.Create(stream!, cfg, ConsumerAction.Create, null);
|
||||
consumer.ShouldNotBeNull();
|
||||
|
||||
consumer!.UpdateInactiveThreshold(new ConsumerConfig { InactiveThreshold = TimeSpan.FromSeconds(30) });
|
||||
consumer.GetConfig().InactiveThreshold.ShouldBe(TimeSpan.FromSeconds(30));
|
||||
|
||||
var pauseUntil = DateTime.UtcNow.AddMinutes(1);
|
||||
consumer.UpdatePauseState(new ConsumerConfig { PauseUntil = pauseUntil });
|
||||
consumer.GetConfig().PauseUntil.ShouldBe(pauseUntil);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConsumerAssignment_GetSet_ShouldRoundTrip()
|
||||
{
|
||||
var account = new Account { Name = "A" };
|
||||
var streamCfg = new StreamConfig { Name = "S", Subjects = ["foo"], Retention = RetentionPolicy.LimitsPolicy };
|
||||
var stream = NatsStream.Create(account, streamCfg, null, null, null, null);
|
||||
stream.ShouldNotBeNull();
|
||||
|
||||
var cfg = new ConsumerConfig { Durable = "D", AckPolicy = AckPolicy.AckExplicit };
|
||||
var consumer = NatsConsumer.Create(stream!, cfg, ConsumerAction.Create, null);
|
||||
consumer.ShouldNotBeNull();
|
||||
|
||||
var assignment = new ConsumerAssignment
|
||||
{
|
||||
Name = "D",
|
||||
Stream = "S",
|
||||
Group = new RaftGroup { Name = "RG", Peers = ["N1"] },
|
||||
};
|
||||
|
||||
consumer!.SetConsumerAssignment(assignment);
|
||||
consumer.ConsumerAssignment().ShouldBeSameAs(assignment);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user