fix(notification-outbox): re-align Central UI sandbox Notify API with production

The script-analysis sandbox Notify surface was stale after the Notification
Outbox change: SandboxNotifyTarget.Send returned Task<NotificationResult> and
there was no Status method, while production NotifyTarget.Send returns
Task<string> (a NotificationId) plus NotifyHelper.Status. A script that
test-ran cleanly in the sandbox would not compile against the real site
runtime.

- Move the NotificationDeliveryStatus record from ScadaLink.SiteRuntime.Scripts
  into ScadaLink.Commons.Messages.Notification so both production and the
  CentralUI sandbox reference the exact same type (CentralUI does not, and
  should not, reference SiteRuntime). Production NotifyHelper.Status is
  otherwise untouched.
- Rewrite SandboxNotifyHelper/SandboxNotifyTarget to be a signature-faithful
  no-op fake: Send returns Task<string> (a fake NotificationId), Status returns
  Task<NotificationDeliveryStatus>. Production now enqueues into the site S&F
  engine, which has no central-side equivalent in the sandbox, so the fake no
  longer carries an INotificationDeliveryService.
- Add script-analysis tests proving a script using the new Notify shape both
  diagnoses clean and runs in the sandbox.
This commit is contained in:
Joseph Doherty
2026-05-19 03:44:34 -04:00
parent 4b61e29e27
commit c8b5871782
6 changed files with 106 additions and 42 deletions

View File

@@ -466,6 +466,42 @@ public class ScriptAnalysisServiceTests
Assert.Equal("42", result.ReturnValueJson);
}
[Fact]
public void NotifyOutboxShape_DiagnosesClean()
{
// Notification Outbox: the sandbox Notify surface must be
// signature-faithful to production NotifyHelper/NotifyTarget —
// Send returns Task<string> (a NotificationId) and Status takes that
// id. A script using the new shape must compile clean in the sandbox,
// exactly as it would against the real site runtime.
var resp = _svc.Diagnose(new DiagnoseRequest(
"var id = await Notify.To(\"ops\").Send(\"subj\", \"body\"); " +
"var st = await Notify.Status(id); " +
"return st.Status;"));
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("CS"));
Assert.DoesNotContain(resp.Markers, m => m.Code.StartsWith("SCADA"));
}
[Fact]
public async Task RunInSandbox_NotifyOutboxShape_StillRuns()
{
// The new Notify shape must also run end-to-end in the no-op sandbox:
// Send yields a fake NotificationId, Status yields a placeholder
// NotificationDeliveryStatus.
var result = await _svc.RunInSandboxAsync(
new SandboxRunRequest(
"var id = await Notify.To(\"ops\").Send(\"subj\", \"body\"); " +
"var st = await Notify.Status(id); " +
"return st.Status;",
Parameters: null,
TimeoutSeconds: null),
CancellationToken.None);
Assert.True(result.Success);
Assert.Equal("\"Unknown\"", result.ReturnValueJson);
}
[Fact]
public async Task RunInSandbox_CapturesConsoleOutput()
{