docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
This commit is contained in:
@@ -17,14 +17,15 @@ using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
namespace ZB.MOM.WW.ScadaBridge.InboundAPI.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// WP-3: Tests for script execution on central — timeout, handler dispatch, error handling.
|
||||
/// WP-5: Safe error messages.
|
||||
/// Tests for script execution on central — timeout, handler dispatch, error handling.
|
||||
/// Also covers safe error messages.
|
||||
/// </summary>
|
||||
public class InboundScriptExecutorTests
|
||||
{
|
||||
private readonly InboundScriptExecutor _executor;
|
||||
private readonly RouteHelper _route;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="InboundScriptExecutorTests"/> class.</summary>
|
||||
public InboundScriptExecutorTests()
|
||||
{
|
||||
_executor = new InboundScriptExecutor(NullLogger<InboundScriptExecutor>.Instance, Substitute.For<IServiceProvider>());
|
||||
@@ -33,6 +34,8 @@ public class InboundScriptExecutorTests
|
||||
_route = new RouteHelper(locator, router);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the trace flag reaches the routed call request.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_TraceFlag_ReachesTheRoutedCallRequest()
|
||||
{
|
||||
@@ -62,6 +65,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.False(captured[1].Trace);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a method with SkipBodyCapture set routes with body capture turned off.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Executor_MethodWithSkipBodyCapture_RoutesWithBodyCaptureOff()
|
||||
{
|
||||
@@ -99,6 +104,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Null(row.ResponseSummary);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a method without SkipBodyCapture routes with body capture turned on.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Executor_MethodWithoutSkipBodyCapture_RoutesWithBodyCaptureOn()
|
||||
{
|
||||
@@ -133,6 +140,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Equal("1", row.ResponseSummary);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a registered handler executes successfully.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RegisteredHandler_ExecutesSuccessfully()
|
||||
{
|
||||
@@ -154,6 +163,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Contains("42", result.ResultJson);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unregistered handler with an invalid script returns a compilation failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UnregisteredHandler_InvalidScript_ReturnsCompilationFailure()
|
||||
{
|
||||
@@ -170,6 +181,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Contains("Script compilation failed", result.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unregistered handler with a valid script is compiled lazily.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task UnregisteredHandler_ValidScript_LazyCompiles()
|
||||
{
|
||||
@@ -185,6 +198,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.True(result.Success);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a throwing handler returns a safe error message.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task HandlerThrows_ReturnsSafeErrorMessage()
|
||||
{
|
||||
@@ -198,10 +213,12 @@ public class InboundScriptExecutorTests
|
||||
TimeSpan.FromSeconds(10));
|
||||
|
||||
Assert.False(result.Success);
|
||||
// WP-5: Safe error message — should NOT contain "internal detail leak"
|
||||
// Safe error message — should NOT contain "internal detail leak"
|
||||
Assert.Equal("Internal script error", result.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the error detail carries the exception while the message stays safe.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task HandlerThrows_ErrorDetailCarriesTheException_WhileTheMessageStaysSafe()
|
||||
{
|
||||
@@ -229,6 +246,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.DoesNotContain("real reason", result.ErrorMessage!);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a handler that times out carries no error detail.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task HandlerTimesOut_CarriesNoErrorDetail()
|
||||
{
|
||||
@@ -252,6 +271,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Null(result.ErrorDetail);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a timed-out routed ask returns a 500 with the site-unreachable code.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RoutedAskTimeout_Returns500WithSiteUnreachableCode()
|
||||
{
|
||||
@@ -276,6 +297,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Equal("SITE_UNREACHABLE", result.ErrorCode);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a site failure whose message mentions unreachable is reported as a script error rather than site unreachable.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SiteRespondedFailure_MessageMentioningUnreachable_IsScriptErrorNotSiteUnreachable()
|
||||
{
|
||||
@@ -303,6 +326,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Equal("SCRIPT_ERROR", result.ErrorCode);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a handler that times out returns a timeout error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task HandlerTimesOut_ReturnsTimeoutError()
|
||||
{
|
||||
@@ -323,6 +348,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Contains("timed out", result.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a handler can access the request parameters.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task HandlerAccessesParameters()
|
||||
{
|
||||
@@ -342,8 +369,10 @@ public class InboundScriptExecutorTests
|
||||
Assert.Contains("ScadaBridge", result.ResultJson!);
|
||||
}
|
||||
|
||||
// --- InboundAPI-001: concurrent lazy-compile must not corrupt the handler cache ---
|
||||
// --- Concurrent lazy-compile must not corrupt the handler cache ---
|
||||
|
||||
/// <summary>Verifies that concurrent lazy compilation of the same method does not corrupt the handler cache.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ConcurrentLazyCompile_SameMethod_DoesNotCorruptCache()
|
||||
{
|
||||
@@ -368,6 +397,8 @@ public class InboundScriptExecutorTests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Verifies that concurrent registration and execution do not throw.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ConcurrentRegisterAndExecute_DoesNotThrow()
|
||||
{
|
||||
@@ -397,8 +428,10 @@ public class InboundScriptExecutorTests
|
||||
await Task.WhenAll(writers.Concat(readers));
|
||||
}
|
||||
|
||||
// --- InboundAPI-005: compiled scripts must not bypass the script trust model ---
|
||||
// --- Compiled scripts must not bypass the script trust model ---
|
||||
|
||||
/// <summary>Verifies that compiling and registering a script that uses a forbidden API rejects the script.</summary>
|
||||
/// <param name="script">The script source that uses a forbidden API.</param>
|
||||
[Theory]
|
||||
[InlineData("System.IO.File.Delete(\"/tmp/x\"); return null;")]
|
||||
[InlineData("System.Diagnostics.Process.Start(\"/bin/sh\"); return null;")]
|
||||
@@ -414,6 +447,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.False(registered);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that executing a script that uses a forbidden API does not run it and returns a failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_ForbiddenApiScript_DoesNotRunAndReturnsFailure()
|
||||
{
|
||||
@@ -436,6 +471,7 @@ public class InboundScriptExecutorTests
|
||||
Assert.False(System.IO.File.Exists(marker));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a permitted script still registers.</summary>
|
||||
[Fact]
|
||||
public void CompileAndRegister_PermittedScript_StillRegisters()
|
||||
{
|
||||
@@ -451,6 +487,7 @@ public class InboundScriptExecutorTests
|
||||
|
||||
// --- Compile-diagnostics overload: surface errors so a non-compiling save is not a silent no-op ---
|
||||
|
||||
/// <summary>Verifies that a script that does not compile reports errors.</summary>
|
||||
[Fact]
|
||||
public void CompileAndRegister_NonCompilingScript_ReportsErrors()
|
||||
{
|
||||
@@ -465,6 +502,7 @@ public class InboundScriptExecutorTests
|
||||
Assert.NotEmpty(errors);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a valid script reports no errors.</summary>
|
||||
[Fact]
|
||||
public void CompileAndRegister_ValidScript_ReportsNoErrors()
|
||||
{
|
||||
@@ -476,6 +514,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Empty(errors);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that executing after a non-compiling update surfaces the compile failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CompileAndRegister_NonCompilingUpdate_ExecuteSurfacesCompileFailure()
|
||||
{
|
||||
@@ -509,8 +549,10 @@ public class InboundScriptExecutorTests
|
||||
Assert.Contains("111", stillGood.ResultJson);
|
||||
}
|
||||
|
||||
// --- InboundAPI-002: lazy compile-and-fetch must be atomic, never KeyNotFoundException ---
|
||||
// --- Lazy compile-and-fetch must be atomic, never KeyNotFoundException ---
|
||||
|
||||
/// <summary>Verifies that a lazy compile racing a handler removal never throws a <see cref="KeyNotFoundException"/>.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task LazyCompile_RacingRemoveHandler_NeverThrowsKeyNotFound()
|
||||
{
|
||||
@@ -540,8 +582,10 @@ public class InboundScriptExecutorTests
|
||||
await Task.WhenAll(removers.Concat(callers));
|
||||
}
|
||||
|
||||
// --- InboundAPI-004: a client disconnect must NOT be reported as a script timeout ---
|
||||
// --- A client disconnect must NOT be reported as a script timeout ---
|
||||
|
||||
/// <summary>Verifies that a client disconnect is not reported as a timeout.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ClientDisconnect_IsNotReportedAsTimeout()
|
||||
{
|
||||
@@ -570,11 +614,13 @@ public class InboundScriptExecutorTests
|
||||
Assert.DoesNotContain("timed out", result.ErrorMessage ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a genuine timeout is still reported as a timeout.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GenuineTimeout_StillReportedAsTimeout()
|
||||
{
|
||||
// A method that exceeds its timeout with no client abort must still be
|
||||
// reported as "timed out" (regression guard for the InboundAPI-004 fix).
|
||||
// reported as "timed out" (regression guard for the client-disconnect fix).
|
||||
var method = new ApiMethod("genuine", "return 1;") { Id = 1, TimeoutSeconds = 1 };
|
||||
_executor.RegisterHandler("genuine", async ctx =>
|
||||
{
|
||||
@@ -593,9 +639,11 @@ public class InboundScriptExecutorTests
|
||||
Assert.Contains("timed out", result.ErrorMessage);
|
||||
}
|
||||
|
||||
// --- InboundAPI-009: a script that fails to compile must be compiled at most
|
||||
// --- A script that fails to compile must be compiled at most
|
||||
// once — repeated calls must not re-run the expensive Roslyn compilation. ---
|
||||
|
||||
/// <summary>Verifies that a failed compilation is not retried on every request.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task FailedCompilation_IsNotRetriedOnEveryRequest()
|
||||
{
|
||||
@@ -620,6 +668,7 @@ public class InboundScriptExecutorTests
|
||||
Assert.Equal(1, counter.CompilationFailures);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a failed compilation is recompiled after compile-and-register is called again.</summary>
|
||||
[Fact]
|
||||
public void FailedCompilation_RecompilesAfterCompileAndRegisterCalledAgain()
|
||||
{
|
||||
@@ -632,9 +681,10 @@ public class InboundScriptExecutorTests
|
||||
Assert.True(_executor.CompileAndRegister(good));
|
||||
}
|
||||
|
||||
// --- InboundAPI-024: _knownBadMethods must be bounded so a spam attack of
|
||||
// --- _knownBadMethods must be bounded so a spam attack of
|
||||
// unique method names cannot grow the cache without bound. ---
|
||||
|
||||
/// <summary>Verifies that the known-bad-methods cache never exceeds its cap under a flood of unique method names.</summary>
|
||||
[Fact]
|
||||
public void KnownBadMethodsCache_SizeNeverExceedsCap_UnderUniqueNameFlood()
|
||||
{
|
||||
@@ -656,6 +706,8 @@ public class InboundScriptExecutorTests
|
||||
$"known-bad cache size {_executor.KnownBadMethodCount} must not exceed cap {cap}");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the lazy-compile path also keeps the known-bad-methods cache capped under a flood of unique method names.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task KnownBadMethodsCache_LazyCompilePath_AlsoCappedUnderUniqueNameFlood()
|
||||
{
|
||||
@@ -678,8 +730,10 @@ public class InboundScriptExecutorTests
|
||||
$"known-bad cache size {_executor.KnownBadMethodCount} must not exceed cap {cap}");
|
||||
}
|
||||
|
||||
// --- InboundAPI-014: the script return value is validated against ReturnDefinition ---
|
||||
// --- The script return value is validated against ReturnDefinition ---
|
||||
|
||||
/// <summary>Verifies that a return value matching the return definition succeeds.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReturnValue_MatchingReturnDefinition_Succeeds()
|
||||
{
|
||||
@@ -701,6 +755,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.True(result.Success, result.ErrorMessage);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a return value not matching the return definition returns a failure instead of a malformed 200.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReturnValue_NotMatchingReturnDefinition_ReturnsFailureNotMalformed200()
|
||||
{
|
||||
@@ -725,6 +781,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Null(result.ResultJson);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a method with no return definition leaves the return value unconstrained.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReturnValue_NoReturnDefinition_IsUnconstrained()
|
||||
{
|
||||
@@ -744,9 +802,11 @@ public class InboundScriptExecutorTests
|
||||
Assert.Contains("whatever", result.ResultJson!);
|
||||
}
|
||||
|
||||
// --- Audit Log #23 (ParentExecutionId, T3): the inbound request's
|
||||
// --- ParentExecutionId: the inbound request's
|
||||
// ExecutionId is threaded through ExecuteAsync onto routed calls ---
|
||||
|
||||
/// <summary>Verifies that executing with a parent execution id makes the routed call carry it as its parent execution id.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithParentExecutionId_RoutedCallCarriesItAsParentExecutionId()
|
||||
{
|
||||
@@ -780,6 +840,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Equal(inboundExecutionId, captured!.ParentExecutionId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that executing without a parent execution id gives the routed call a null parent execution id.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_WithoutParentExecutionId_RoutedCallHasNullParentExecutionId()
|
||||
{
|
||||
@@ -812,6 +874,8 @@ public class InboundScriptExecutorTests
|
||||
// --- IpsenMES MoveIn: the read-only Database helper is exposed on the script
|
||||
// context and resolves IDatabaseGateway from a DI scope per execution. ---
|
||||
|
||||
/// <summary>Verifies that a script that uses Database queries through the gateway.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Script_UsingDatabase_QueriesViaGateway()
|
||||
{
|
||||
@@ -844,6 +908,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Contains("Z28061A", result.ResultJson!);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a script that does not use Database runs with a service provider that lacks the gateway.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Script_NotUsingDatabase_RunsWithProviderLackingGateway()
|
||||
{
|
||||
@@ -865,6 +931,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Contains("99", result.ResultJson!);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a throwing script still disposes its DI scope.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ScriptThrows_DisposesDiScope()
|
||||
{
|
||||
@@ -915,9 +983,13 @@ public class InboundScriptExecutorTests
|
||||
private sealed class SqliteGateway : IDatabaseGateway
|
||||
{
|
||||
private readonly string _cs;
|
||||
/// <summary>Initializes a new instance of the <see cref="SqliteGateway"/> class.</summary>
|
||||
/// <param name="cs">The SQLite connection string.</param>
|
||||
public SqliteGateway(string cs) => _cs = cs;
|
||||
/// <inheritdoc />
|
||||
public async Task<DbConnection> GetConnectionAsync(string name, CancellationToken ct = default)
|
||||
{ var c = new SqliteConnection(_cs); await c.OpenAsync(ct); return c; }
|
||||
/// <inheritdoc />
|
||||
public Task<ExternalCallResult> CachedWriteAsync(string c, string s,
|
||||
IReadOnlyDictionary<string, object?>? p = null, string? o = null, CancellationToken ct = default,
|
||||
TrackedOperationId? t = null, Guid? e = null, string? src = null, Guid? pe = null)
|
||||
@@ -934,6 +1006,8 @@ public class InboundScriptExecutorTests
|
||||
return new SqliteGateway("DataSource=file:movein-exec?mode=memory&cache=shared");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a timed-out script has its scope disposal deferred so no scope is orphaned.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task TimedOutScript_ScopeDisposalDeferred_NoOrphanLeak()
|
||||
{
|
||||
@@ -956,6 +1030,8 @@ public class InboundScriptExecutorTests
|
||||
|
||||
// --- Flow Debugger step logs (design 2026-09-09 D4/D5/D7): the Log global ---
|
||||
|
||||
/// <summary>Verifies that a traced run emits one ScriptLog row on the trace sink.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Log_TracedRun_EmitsOneScriptLogRowOnTheTraceSink()
|
||||
{
|
||||
@@ -987,6 +1063,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Null(extra.StepEventId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a traced run writes the ScriptLog row to the central audit store.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Log_TracedRun_WritesTheScriptLogRowToTheCentralAuditStore()
|
||||
{
|
||||
@@ -1018,6 +1096,8 @@ public class InboundScriptExecutorTests
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an untraced run writes only to the node logger.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Log_UntracedRun_WritesOnlyToTheNodeLogger()
|
||||
{
|
||||
@@ -1045,6 +1125,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Contains(recorded, m => m.Contains("unwatched", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a traced log call with an exception carries both the message and the detail.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Log_TracedWithException_CarriesMessageAndDetail()
|
||||
{
|
||||
@@ -1068,6 +1150,8 @@ public class InboundScriptExecutorTests
|
||||
Assert.Equal("warn", ScriptTraceExtra.TryParse(details.Extra)!.LogLevel);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that log lines past the per-run maximum are dropped and a single closing runtime warning is emitted.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Log_PastMaxLogLinesPerRun_DropsAndEmitsOneClosingRuntimeWarning()
|
||||
{
|
||||
@@ -1110,9 +1194,23 @@ public class InboundScriptExecutorTests
|
||||
// logger saw a line even when nothing was audited.
|
||||
private sealed class RecordingLogger<T>(List<string> messages) : ILogger<T>
|
||||
{
|
||||
/// <summary>Returns no scope; the fake logger does not track scopes.</summary>
|
||||
/// <typeparam name="TState">The scope state type.</typeparam>
|
||||
/// <param name="state">The scope state.</param>
|
||||
/// <returns>Always null.</returns>
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
/// <summary>Reports every log level as enabled.</summary>
|
||||
/// <param name="logLevel">The log level being queried.</param>
|
||||
/// <returns>Always true.</returns>
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
/// <summary>Records the log entry for later assertions.</summary>
|
||||
/// <typeparam name="TState">The log state type.</typeparam>
|
||||
/// <param name="logLevel">The entry's log level.</param>
|
||||
/// <param name="eventId">The entry's event id.</param>
|
||||
/// <param name="state">The entry's state.</param>
|
||||
/// <param name="exception">The entry's exception, if any.</param>
|
||||
/// <param name="formatter">Formats the state and exception into the message.</param>
|
||||
public void Log<TState>(
|
||||
LogLevel logLevel, EventId eventId, TState state, Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
@@ -1128,9 +1226,24 @@ public class InboundScriptExecutorTests
|
||||
|
||||
private sealed class CountingLogger<T>(CompileLogCounter counter) : ILogger<T>
|
||||
{
|
||||
/// <summary>Returns no scope; the fake does not track scopes.</summary>
|
||||
/// <typeparam name="TState">The type of the scope state.</typeparam>
|
||||
/// <param name="state">The scope state (ignored).</param>
|
||||
/// <returns>Always null.</returns>
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
|
||||
/// <summary>Reports every log level as enabled.</summary>
|
||||
/// <param name="logLevel">The log level to check.</param>
|
||||
/// <returns>Always true.</returns>
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
/// <summary>Counts entries whose message reports a script compilation failure.</summary>
|
||||
/// <typeparam name="TState">The type of the log state.</typeparam>
|
||||
/// <param name="logLevel">The severity of the entry.</param>
|
||||
/// <param name="eventId">The event identifier.</param>
|
||||
/// <param name="state">The state object to format.</param>
|
||||
/// <param name="exception">The exception associated with the entry, if any.</param>
|
||||
/// <param name="formatter">Formats the state and exception into the message.</param>
|
||||
public void Log<TState>(
|
||||
LogLevel logLevel, EventId eventId, TState state, Exception? exception,
|
||||
Func<TState, Exception?, string> formatter)
|
||||
|
||||
Reference in New Issue
Block a user