feat(notif-outbox): add SourceNode to Notification entity + NotificationSubmit
This commit is contained in:
@@ -25,6 +25,15 @@ public class Notification
|
|||||||
/// <summary>Resolved delivery targets snapshotted at delivery time, for audit.</summary>
|
/// <summary>Resolved delivery targets snapshotted at delivery time, for audit.</summary>
|
||||||
public string? ResolvedTargets { get; set; }
|
public string? ResolvedTargets { get; set; }
|
||||||
public string SourceSiteId { get; set; }
|
public string SourceSiteId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cluster node on which the notification was emitted — `node-a` / `node-b`
|
||||||
|
/// for site rows (qualified by <see cref="SourceSiteId"/>), `central-a` / `central-b`
|
||||||
|
/// for central-originated rows. Carried from the site on the
|
||||||
|
/// <see cref="Commons.Messages.Notification.NotificationSubmit"/> and persisted at
|
||||||
|
/// central; nullable so rows submitted before the column existed don't block ingest.
|
||||||
|
/// </summary>
|
||||||
|
public string? SourceNode { get; set; }
|
||||||
public string? SourceInstanceId { get; set; }
|
public string? SourceInstanceId { get; set; }
|
||||||
public string? SourceScript { get; set; }
|
public string? SourceScript { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ namespace ScadaLink.Commons.Messages.Notification;
|
|||||||
/// <c>NotifyDeliver</c> audit rows. Additive trailing member — null for messages built
|
/// <c>NotifyDeliver</c> audit rows. Additive trailing member — null for messages built
|
||||||
/// before the field existed, or for non-routed runs.
|
/// before the field existed, or for non-routed runs.
|
||||||
/// </param>
|
/// </param>
|
||||||
|
/// <param name="SourceNode">
|
||||||
|
/// The cluster node on which the notification was emitted — `node-a` / `node-b` for site
|
||||||
|
/// submissions, `central-a` / `central-b` for central-originated rows. Stamped by the
|
||||||
|
/// emitting node from <c>INodeIdentityProvider</c> and carried, inside the serialized
|
||||||
|
/// payload, through the site store-and-forward buffer so the central dispatcher can
|
||||||
|
/// persist it on the <c>Notifications</c> row and echo it onto the <c>NotifyDeliver</c>
|
||||||
|
/// audit rows. Additive trailing member — null for messages built before the field
|
||||||
|
/// existed.
|
||||||
|
/// </param>
|
||||||
public record NotificationSubmit(
|
public record NotificationSubmit(
|
||||||
string NotificationId,
|
string NotificationId,
|
||||||
string ListName,
|
string ListName,
|
||||||
@@ -28,7 +37,8 @@ public record NotificationSubmit(
|
|||||||
string? SourceScript,
|
string? SourceScript,
|
||||||
DateTimeOffset SiteEnqueuedAt,
|
DateTimeOffset SiteEnqueuedAt,
|
||||||
Guid? OriginExecutionId = null,
|
Guid? OriginExecutionId = null,
|
||||||
Guid? OriginParentExecutionId = null);
|
Guid? OriginParentExecutionId = null,
|
||||||
|
string? SourceNode = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Central -> Site: ack sent after the notification row is persisted.
|
/// Central -> Site: ack sent after the notification row is persisted.
|
||||||
|
|||||||
@@ -51,6 +51,20 @@ public class NotificationEntityTests
|
|||||||
Assert.Equal(parentExecutionId, n.OriginParentExecutionId);
|
Assert.Equal(parentExecutionId, n.OriginParentExecutionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SourceNode_DefaultsToNull_AndIsSettable()
|
||||||
|
{
|
||||||
|
// SourceNode identifies the cluster node that emitted the notification
|
||||||
|
// (site node-a/node-b or central-a/central-b). Additive nullable
|
||||||
|
// property — defaults to null on rows submitted before the column
|
||||||
|
// existed, and round-trips its value when set.
|
||||||
|
var n = new Notification("id-1", NotificationType.Email, "ops-team", "subj", "body", "SiteA");
|
||||||
|
Assert.Null(n.SourceNode);
|
||||||
|
|
||||||
|
n.SourceNode = "node-a";
|
||||||
|
Assert.Equal("node-a", n.SourceNode);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Constructor_NullArguments_Throw()
|
public void Constructor_NullArguments_Throw()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -126,6 +126,32 @@ public class NotificationMessagesTests
|
|||||||
Assert.Equal(parentExecutionId, roundTripped!.OriginParentExecutionId);
|
Assert.Equal(parentExecutionId, roundTripped!.OriginParentExecutionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NotificationSubmit_carries_SourceNode()
|
||||||
|
{
|
||||||
|
// SourceNode is an additive trailing member — old call sites and old
|
||||||
|
// serialized payloads leave it null. When supplied it round-trips
|
||||||
|
// through both construction and JSON (the buffered S&F payload IS a
|
||||||
|
// serialized NotificationSubmit).
|
||||||
|
var defaulted = new NotificationSubmit(
|
||||||
|
"notif-9", "Operators", "Subject", "Body",
|
||||||
|
"site-01", "inst-1", "OnAlarm", DateTimeOffset.UtcNow);
|
||||||
|
Assert.Null(defaulted.SourceNode);
|
||||||
|
|
||||||
|
var stamped = new NotificationSubmit(
|
||||||
|
"notif-10", "Operators", "Subject", "Body",
|
||||||
|
"site-01", "inst-1", "OnAlarm", DateTimeOffset.UtcNow,
|
||||||
|
OriginExecutionId: null,
|
||||||
|
OriginParentExecutionId: null,
|
||||||
|
SourceNode: "node-a");
|
||||||
|
Assert.Equal("node-a", stamped.SourceNode);
|
||||||
|
|
||||||
|
var json = System.Text.Json.JsonSerializer.Serialize(stamped);
|
||||||
|
var roundTripped = System.Text.Json.JsonSerializer.Deserialize<NotificationSubmit>(json);
|
||||||
|
Assert.NotNull(roundTripped);
|
||||||
|
Assert.Equal("node-a", roundTripped!.SourceNode);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void NotificationSubmit_ValueEquality_EqualWhenAllFieldsMatch()
|
public void NotificationSubmit_ValueEquality_EqualWhenAllFieldsMatch()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user