feat(auditlog): NotifyDeliver rows carry the originating ParentExecutionId
This commit is contained in:
@@ -95,7 +95,8 @@ public class NotificationOutboxActorAttemptEmissionTests : TestKit
|
||||
Guid? notificationId = null,
|
||||
string sourceSite = "site-1",
|
||||
int retryCount = 0,
|
||||
Guid? originExecutionId = null)
|
||||
Guid? originExecutionId = null,
|
||||
Guid? originParentExecutionId = null)
|
||||
{
|
||||
return new Notification(
|
||||
(notificationId ?? Guid.NewGuid()).ToString("D"),
|
||||
@@ -110,6 +111,7 @@ public class NotificationOutboxActorAttemptEmissionTests : TestKit
|
||||
SourceInstanceId = "instance-42",
|
||||
SourceScript = "AlarmScript",
|
||||
OriginExecutionId = originExecutionId,
|
||||
OriginParentExecutionId = originParentExecutionId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -207,6 +209,50 @@ public class NotificationOutboxActorAttemptEmissionTests : TestKit
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Attempt_CarriesOriginParentExecutionId_AsParentExecutionId()
|
||||
{
|
||||
// Audit Log ParentExecutionId: the Attempted NotifyDeliver row must echo
|
||||
// the notification's OriginParentExecutionId so the central dispatcher's
|
||||
// rows carry the routed run's parent id.
|
||||
SetupSmtpRetryPolicy(maxRetries: 5, retryDelay: TimeSpan.FromMinutes(1));
|
||||
var parentExecutionId = Guid.NewGuid();
|
||||
var notification = MakeNotification(originParentExecutionId: parentExecutionId);
|
||||
_outboxRepository.GetDueAsync(Arg.Any<DateTimeOffset>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new[] { notification });
|
||||
var adapter = new StubAdapter(() => DeliveryOutcome.Success("ops@example.com"));
|
||||
var actor = CreateActor([adapter]);
|
||||
|
||||
actor.Tell(InternalMessages.DispatchTick.Instance);
|
||||
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
var attempted = EventsByStatus(AuditStatus.Attempted);
|
||||
Assert.Single(attempted);
|
||||
Assert.Equal(parentExecutionId, attempted[0].ParentExecutionId);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Attempt_NullOriginParentExecutionId_HasNullParentExecutionId()
|
||||
{
|
||||
SetupSmtpRetryPolicy(maxRetries: 5, retryDelay: TimeSpan.FromMinutes(1));
|
||||
var notification = MakeNotification(originParentExecutionId: null);
|
||||
_outboxRepository.GetDueAsync(Arg.Any<DateTimeOffset>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new[] { notification });
|
||||
var adapter = new StubAdapter(() => DeliveryOutcome.Success("ops@example.com"));
|
||||
var actor = CreateActor([adapter]);
|
||||
|
||||
actor.Tell(InternalMessages.DispatchTick.Instance);
|
||||
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
var attempted = EventsByStatus(AuditStatus.Attempted);
|
||||
Assert.Single(attempted);
|
||||
Assert.Null(attempted[0].ParentExecutionId);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Attempt_TransientFailure_EmitsEvent_StatusAttempted_ErrorMessageSet()
|
||||
{
|
||||
|
||||
@@ -40,7 +40,9 @@ public class NotificationOutboxActorIngestTests : TestKit
|
||||
}
|
||||
|
||||
private static NotificationSubmit MakeSubmit(
|
||||
string? notificationId = null, Guid? originExecutionId = null)
|
||||
string? notificationId = null,
|
||||
Guid? originExecutionId = null,
|
||||
Guid? originParentExecutionId = null)
|
||||
{
|
||||
return new NotificationSubmit(
|
||||
NotificationId: notificationId ?? Guid.NewGuid().ToString(),
|
||||
@@ -51,7 +53,8 @@ public class NotificationOutboxActorIngestTests : TestKit
|
||||
SourceInstanceId: "instance-42",
|
||||
SourceScript: "AlarmScript",
|
||||
SiteEnqueuedAt: new DateTimeOffset(2026, 5, 19, 8, 30, 0, TimeSpan.Zero),
|
||||
OriginExecutionId: originExecutionId);
|
||||
OriginExecutionId: originExecutionId,
|
||||
OriginParentExecutionId: originParentExecutionId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -121,6 +124,42 @@ public class NotificationOutboxActorIngestTests : TestKit
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationSubmit_CopiesOriginParentExecutionId_OntoPersistedNotification()
|
||||
{
|
||||
// Audit Log ParentExecutionId: the routed run's parent ExecutionId rides
|
||||
// on the NotificationSubmit and must be persisted on the Notification row
|
||||
// so the dispatcher can later echo it onto NotifyDeliver audit rows.
|
||||
_repository.InsertIfNotExistsAsync(Arg.Any<Notification>(), Arg.Any<CancellationToken>())
|
||||
.Returns(true);
|
||||
var parentExecutionId = Guid.NewGuid();
|
||||
var submit = MakeSubmit(originParentExecutionId: parentExecutionId);
|
||||
var actor = CreateActor();
|
||||
|
||||
actor.Tell(submit, TestActor);
|
||||
|
||||
ExpectMsg<NotificationSubmitAck>();
|
||||
_repository.Received(1).InsertIfNotExistsAsync(
|
||||
Arg.Is<Notification>(n => n.OriginParentExecutionId == parentExecutionId),
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NotificationSubmit_NullOriginParentExecutionId_PersistsNull()
|
||||
{
|
||||
_repository.InsertIfNotExistsAsync(Arg.Any<Notification>(), Arg.Any<CancellationToken>())
|
||||
.Returns(true);
|
||||
var submit = MakeSubmit(originParentExecutionId: null);
|
||||
var actor = CreateActor();
|
||||
|
||||
actor.Tell(submit, TestActor);
|
||||
|
||||
ExpectMsg<NotificationSubmitAck>();
|
||||
_repository.Received(1).InsertIfNotExistsAsync(
|
||||
Arg.Is<Notification>(n => n.OriginParentExecutionId == null),
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DuplicateSubmit_RepositoryReturnsFalse_StillAcksAccepted()
|
||||
{
|
||||
|
||||
@@ -88,7 +88,8 @@ public class NotificationOutboxActorTerminalEmissionTests : TestKit
|
||||
NotificationStatus status = NotificationStatus.Pending,
|
||||
int retryCount = 0,
|
||||
Guid? notificationId = null,
|
||||
Guid? originExecutionId = null)
|
||||
Guid? originExecutionId = null,
|
||||
Guid? originParentExecutionId = null)
|
||||
{
|
||||
return new Notification(
|
||||
(notificationId ?? Guid.NewGuid()).ToString("D"),
|
||||
@@ -102,6 +103,7 @@ public class NotificationOutboxActorTerminalEmissionTests : TestKit
|
||||
RetryCount = retryCount,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
OriginExecutionId = originExecutionId,
|
||||
OriginParentExecutionId = originParentExecutionId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -191,6 +193,50 @@ public class NotificationOutboxActorTerminalEmissionTests : TestKit
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Terminal_Delivered_CarriesOriginParentExecutionId_AsParentExecutionId()
|
||||
{
|
||||
// Audit Log ParentExecutionId: the terminal NotifyDeliver row must echo
|
||||
// the notification's OriginParentExecutionId so the central dispatcher's
|
||||
// rows carry the routed run's parent id.
|
||||
SetupSmtpRetryPolicy(maxRetries: 5, retryDelay: TimeSpan.FromMinutes(1));
|
||||
var parentExecutionId = Guid.NewGuid();
|
||||
var notification = MakeNotification(originParentExecutionId: parentExecutionId);
|
||||
_outboxRepository.GetDueAsync(Arg.Any<DateTimeOffset>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new[] { notification });
|
||||
var adapter = new StubAdapter(() => DeliveryOutcome.Success("ops@example.com"));
|
||||
var actor = CreateActor([adapter]);
|
||||
|
||||
actor.Tell(InternalMessages.DispatchTick.Instance);
|
||||
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
var delivered = EventsByStatus(AuditStatus.Delivered);
|
||||
Assert.Single(delivered);
|
||||
Assert.Equal(parentExecutionId, delivered[0].ParentExecutionId);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Terminal_Delivered_NullOriginParentExecutionId_HasNullParentExecutionId()
|
||||
{
|
||||
SetupSmtpRetryPolicy(maxRetries: 5, retryDelay: TimeSpan.FromMinutes(1));
|
||||
var notification = MakeNotification(originParentExecutionId: null);
|
||||
_outboxRepository.GetDueAsync(Arg.Any<DateTimeOffset>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns(new[] { notification });
|
||||
var adapter = new StubAdapter(() => DeliveryOutcome.Success("ops@example.com"));
|
||||
var actor = CreateActor([adapter]);
|
||||
|
||||
actor.Tell(InternalMessages.DispatchTick.Instance);
|
||||
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
var delivered = EventsByStatus(AuditStatus.Delivered);
|
||||
Assert.Single(delivered);
|
||||
Assert.Null(delivered[0].ParentExecutionId);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Terminal_Parked_OnPermanentFailure_EmitsEvent_StatusParked()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user