feat(notification-outbox): async Notify.Send with status handle
Notify.To(list).Send(subject,body) now generates a NotificationId GUID, enqueues a Notification-category message into the site Store-and-Forward Engine, and returns the NotificationId immediately (Task<string>). The NotificationId is the single idempotency key end-to-end: it is the S&F message Id, it is carried inside the buffered NotificationSubmit payload, and it is the id the forwarder submits to central. NotificationForwarder now deserializes the buffered payload as a NotificationSubmit and reads NotificationId from it (re-stamping only the site-owned SourceSiteId / SourceInstanceId), instead of deriving the id from StoreAndForwardMessage.Id. Adds NotifyHelper.Status(id): queries central via the site communication actor; reports the site-local Forwarding state while the notification is still buffered at the site, maps central's response when found, and Unknown otherwise. Adds a NotificationDeliveryStatus record. SiteCommunicationActor gains a NotificationStatusQuery forwarding handler mirroring NotificationSubmit. StoreAndForwardService.EnqueueAsync gains an optional messageId parameter and exposes GetMessageByIdAsync.
This commit is contained in:
@@ -18,19 +18,26 @@ public class NotificationForwarderTests : TestKit
|
||||
|
||||
/// <summary>
|
||||
/// Builds a buffered notification S&F message whose payload matches the shape
|
||||
/// produced by the site NotificationDeliveryService enqueue path.
|
||||
/// produced by the site <c>Notify.Send</c> enqueue path (Task 19): a serialized
|
||||
/// <see cref="NotificationSubmit"/> carrying a script-generated
|
||||
/// <see cref="NotificationSubmit.NotificationId"/>. The S&F message
|
||||
/// <see cref="StoreAndForwardMessage.Id"/> equals that same id.
|
||||
/// </summary>
|
||||
private static StoreAndForwardMessage BufferedNotification(
|
||||
string id = "msg-1", string listName = "Operators",
|
||||
string subject = "Pump alarm", string message = "Pump 3 tripped",
|
||||
string? originInstance = "Plant.Pump3")
|
||||
string? originInstance = "Plant.Pump3", string? sourceScript = "alarmScript")
|
||||
{
|
||||
var payload = JsonSerializer.Serialize(new
|
||||
{
|
||||
ListName = listName,
|
||||
Subject = subject,
|
||||
Message = message
|
||||
});
|
||||
var payload = JsonSerializer.Serialize(new NotificationSubmit(
|
||||
NotificationId: id,
|
||||
ListName: listName,
|
||||
Subject: subject,
|
||||
Body: message,
|
||||
// SourceSiteId is re-stamped by the forwarder; the enqueue side leaves it blank.
|
||||
SourceSiteId: string.Empty,
|
||||
SourceInstanceId: originInstance,
|
||||
SourceScript: sourceScript,
|
||||
SiteEnqueuedAt: DateTimeOffset.UtcNow));
|
||||
return new StoreAndForwardMessage
|
||||
{
|
||||
Id = id,
|
||||
@@ -57,11 +64,15 @@ public class NotificationForwarderTests : TestKit
|
||||
// The central target receives a NotificationSubmit whose fields map from the
|
||||
// buffered payload; reply Accepted so the handler completes as delivered.
|
||||
var submit = centralProbe.ExpectMsg<NotificationSubmit>();
|
||||
Assert.Equal("msg-1", submit.NotificationId);
|
||||
Assert.Equal("Operators", submit.ListName);
|
||||
Assert.Equal("Pump alarm", submit.Subject);
|
||||
Assert.Equal("Pump 3 tripped", submit.Body);
|
||||
// SourceSiteId is re-stamped by the forwarder from its own site id.
|
||||
Assert.Equal("site-7", submit.SourceSiteId);
|
||||
Assert.Equal("Plant.Pump3", submit.SourceInstanceId);
|
||||
// The originating script travels through from the buffered payload.
|
||||
Assert.Equal("alarmScript", submit.SourceScript);
|
||||
centralProbe.Reply(new NotificationSubmitAck(submit.NotificationId, Accepted: true, Error: null));
|
||||
|
||||
Assert.True(await deliverTask);
|
||||
@@ -76,12 +87,15 @@ public class NotificationForwarderTests : TestKit
|
||||
|
||||
// A buffered payload carrying an empty-string ListName: the empty value must not
|
||||
// be forwarded — the forwarder falls back to the S&F message Target instead.
|
||||
var payload = JsonSerializer.Serialize(new
|
||||
{
|
||||
ListName = "",
|
||||
Subject = "Pump alarm",
|
||||
Message = "Pump 3 tripped"
|
||||
});
|
||||
var payload = JsonSerializer.Serialize(new NotificationSubmit(
|
||||
NotificationId: "msg-empty-list",
|
||||
ListName: "",
|
||||
Subject: "Pump alarm",
|
||||
Body: "Pump 3 tripped",
|
||||
SourceSiteId: string.Empty,
|
||||
SourceInstanceId: "Plant.Pump3",
|
||||
SourceScript: null,
|
||||
SiteEnqueuedAt: DateTimeOffset.UtcNow));
|
||||
var msg = new StoreAndForwardMessage
|
||||
{
|
||||
Id = "msg-empty-list",
|
||||
|
||||
Reference in New Issue
Block a user