fix(auditlog): capture request/response payloads on outbound API audit rows

The outbound ApiCall emitter hard-coded RequestSummary/ResponseSummary to null,
so audited API calls carried no inputs/outputs — contrary to the Audit Log
payload-capture spec. Thread the call arguments into the sync ApiCall emitter
and the cached immediate-completion path (CachedSubmit / ApiCallCached /
CachedResolve), and stamp the response body from ExternalCallResult.ResponseJson.
The writer's payload filter still applies the size cap + redaction downstream.

The S&F retry-loop cached rows are unchanged — request data is not threaded
through the store-and-forward buffer (same boundary as SourceScript).
This commit is contained in:
Joseph Doherty
2026-05-21 10:17:42 -04:00
parent 405de525ca
commit 849a011400
3 changed files with 106 additions and 8 deletions

View File

@@ -81,6 +81,29 @@ public class ExternalSystemCallAuditEmissionTests
Assert.Equal(DateTimeKind.Utc, evt.OccurredAtUtc.Kind);
Assert.NotEqual(Guid.Empty, evt.EventId);
Assert.False(evt.PayloadTruncated);
// No call arguments → null request summary; the response body is captured.
Assert.Null(evt.RequestSummary);
Assert.Equal("{}", evt.ResponseSummary);
}
[Fact]
public async Task Call_CapturesRequestArgs_AndResponseBody_OnTheAuditRow()
{
var client = new Mock<IExternalSystemClient>();
client
.Setup(c => c.CallAsync("Weather", "GetCurrent", It.IsAny<IReadOnlyDictionary<string, object?>?>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ExternalCallResult(true, "{\"tempC\":11.4}", null));
var writer = new CapturingAuditWriter();
var helper = CreateHelper(client.Object, writer);
var args = new Dictionary<string, object?> { ["city"] = "Dublin" };
await helper.Call("Weather", "GetCurrent", args);
var evt = Assert.Single(writer.Events);
// RequestSummary is the serialized argument dictionary; ResponseSummary
// is the verbatim response body. (Cap + redaction are the writer's job.)
Assert.Equal("{\"city\":\"Dublin\"}", evt.RequestSummary);
Assert.Equal("{\"tempC\":11.4}", evt.ResponseSummary);
}
[Fact]