b211911f98
v2-ci / unit-tests (push) Failing after 13m22s
Akka.TestKit.Xunit2 -> Akka.TestKit.Xunit 1.5.62 (xunit.v3 TestKit; needs xunit.v3 3.2.2, set via VersionOverride on the four projects). Removes the xunit 2.9.3 pin. IAsyncLifetime returns ValueTask, SqlServerFactAttribute gains the source-info ctor, and xUnit1051 call sites pass TestContext.Current.CancellationToken. Test counts unchanged. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
100 lines
4.9 KiB
C#
100 lines
4.9 KiB
C#
using System.Text;
|
|
using System.Text.Json;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.ScriptAnalysis;
|
|
|
|
/// <summary>
|
|
/// Review D-a I-1 — the diagnostics markers cross the Blazor circuit (JS → .NET, for the Problems list) as ONE
|
|
/// hub message. A non-C# paste yields several Roslyn errors per line; uncapped, a few thousand markers exceed
|
|
/// the 1 MB hub limit and kill the circuit with every unsaved edit. Both the endpoint response and the circuit
|
|
/// report are now bounded; these tests pin the bounds on the C# side of the contract.
|
|
/// </summary>
|
|
public sealed class MarkerPayloadBoundTests
|
|
{
|
|
// ~150 KB of log-ish text: every line is several C# errors.
|
|
private static string NonCSharpPaste()
|
|
{
|
|
var sb = new StringBuilder();
|
|
for (var i = 0; sb.Length < 150 * 1024; i++)
|
|
sb.Append($"2026-10-22 12:00:{i % 60:00} INFO [worker-{i}] value={i}, state=ok; next -> {i + 1} @@ ##\n");
|
|
return sb.ToString();
|
|
}
|
|
|
|
[Fact]
|
|
public void A_non_csharp_paste_returns_a_bounded_marker_set_that_still_holds_the_errors()
|
|
{
|
|
var markers = new ScriptAnalysisService().Diagnose(new DiagnoseRequest(NonCSharpPaste()), TestContext.Current.CancellationToken).Markers;
|
|
|
|
markers.Count.ShouldBe(ScriptEditorLimits.MaxDiagnosticMarkers); // positive control: the cap was hit
|
|
markers.ShouldAllBe(m => m.Message.Length <= ScriptEditorLimits.MaxDiagnosticMessageChars);
|
|
markers[0].Severity.ShouldBe(8); // errors first — publish rejects, so must the editor
|
|
markers.Select(m => (m.Severity, m.StartLineNumber, m.StartColumn))
|
|
.ShouldBe(ScriptProblems.Order(markers).Select(m => (m.Severity, m.StartLineNumber, m.StartColumn)));
|
|
}
|
|
|
|
[Fact]
|
|
public void A_long_quoted_token_has_its_message_truncated()
|
|
{
|
|
var giant = new string('x', 20_000);
|
|
var markers = new ScriptAnalysisService().Diagnose(new DiagnoseRequest($"return {giant};"), TestContext.Current.CancellationToken).Markers;
|
|
|
|
markers.ShouldNotBeEmpty();
|
|
markers.ShouldAllBe(m => m.Message.Length <= ScriptEditorLimits.MaxDiagnosticMessageChars);
|
|
markers.ShouldContain(m => m.Message.EndsWith("…"));
|
|
}
|
|
|
|
[Fact]
|
|
public void A_small_script_is_not_truncated()
|
|
{
|
|
// Positive control for the cap: ordinary diagnostics pass through untouched.
|
|
var markers = new ScriptAnalysisService().Diagnose(new DiagnoseRequest("return ctx.Nope();"), TestContext.Current.CancellationToken).Markers;
|
|
markers.ShouldContain(m => m.Severity == 8 && !m.Message.EndsWith("…"));
|
|
}
|
|
|
|
[Fact]
|
|
public void The_worst_case_circuit_report_fits_inside_the_sync_budget()
|
|
{
|
|
// The JS sends at most MaxReportedMarkers markers, each message cut to MaxReportedMessageChars. Worst case:
|
|
// control characters (\u0001 → 6 JSON bytes, 7 when escaped again) and the largest line/column numbers.
|
|
var message = new string('\u0001', ScriptEditorLimits.MaxReportedMessageChars);
|
|
var marker = new DiagnosticMarker(8, int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue, message, "OTSCRIPT_SETVTAG_INERT");
|
|
var report = Enumerable.Repeat(marker, ScriptEditorLimits.MaxReportedMarkers).ToArray();
|
|
|
|
var once = JsonSerializer.Serialize(new object[] { report, int.MaxValue });
|
|
var twice = Encoding.UTF8.GetByteCount(JsonSerializer.Serialize(once));
|
|
|
|
twice.ShouldBeLessThan(ScriptEditorLimits.MaxEditorSyncBytes);
|
|
ScriptEditorLimits.MaxEditorSyncBytes.ShouldBeLessThan((int)ScriptEditorLimits.HubMaximumReceiveMessageSize);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_truncated_report_says_how_many_were_left_out()
|
|
{
|
|
var shown = Enumerable.Range(1, 3).Select(i => new DiagnosticMarker(8, i, 1, i, 2, "m", "CS1002")).ToArray();
|
|
|
|
var report = new ScriptProblemReport(shown, 4321);
|
|
report.Omitted.ShouldBe(4318);
|
|
ScriptProblems.Summary(report).ShouldBe("3 errors (showing 3 of 4321)");
|
|
|
|
// Positive control: an untruncated report carries no suffix.
|
|
ScriptProblems.Summary(new ScriptProblemReport(shown, 3)).ShouldBe("3 errors");
|
|
ScriptProblemReport.Empty.Omitted.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_report_from_js_is_clamped_even_if_the_js_cap_regressed()
|
|
{
|
|
var flood = Enumerable.Range(1, 5000).Select(i => new DiagnosticMarker(4, i, 1, i, 2, new string('m', 2000), "W")).ToArray();
|
|
|
|
var report = ScriptProblemReport.FromEditor(flood, total: 5000);
|
|
|
|
report.Markers.Count.ShouldBe(ScriptEditorLimits.MaxReportedMarkers);
|
|
report.Total.ShouldBe(5000);
|
|
report.Markers.ShouldAllBe(m => m.Message.Length <= ScriptEditorLimits.MaxReportedMessageChars);
|
|
ScriptProblemReport.FromEditor(null, 0).ShouldBe(ScriptProblemReport.Empty);
|
|
}
|
|
}
|