fix(central-ui): resolve CentralUI-007..014 — nav authz, UTC date filters, disposal guards, N+1 fix, async script analysis
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NSubstitute;
|
||||
using ScadaLink.CentralUI.ScriptAnalysis;
|
||||
using ScadaLink.TemplateEngine;
|
||||
|
||||
namespace ScadaLink.CentralUI.Tests.ScriptAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// Regression tests for CentralUI-013. <c>ResolveCalledShape</c> resolved shared
|
||||
/// script shapes with <c>_sharedScripts.GetShapesAsync().GetAwaiter().GetResult()</c>
|
||||
/// — a sync-over-async block on a request thread that risks thread-pool
|
||||
/// starvation and deadlock. <c>Hover</c> and <c>SignatureHelp</c> were synchronous
|
||||
/// purely to accommodate that block. The fix makes both methods async and
|
||||
/// <c>await</c>s the catalog.
|
||||
/// </summary>
|
||||
public class ScriptAnalysisAsyncResolveTests
|
||||
{
|
||||
private readonly ISharedScriptCatalog _catalog = Substitute.For<ISharedScriptCatalog>();
|
||||
private readonly IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 100 });
|
||||
private readonly IServiceProvider _services = Substitute.For<IServiceProvider>();
|
||||
private readonly ScriptAnalysisService _svc;
|
||||
|
||||
public ScriptAnalysisAsyncResolveTests()
|
||||
{
|
||||
_catalog.GetShapesAsync().Returns(Array.Empty<ScriptShape>());
|
||||
_svc = new ScriptAnalysisService(_catalog, _cache, _services);
|
||||
}
|
||||
|
||||
private static ScriptShape Shape(string name, params ParameterShape[] ps) => new(name, ps, null);
|
||||
private static ParameterShape Param(string name, string type) => new(name, type, true);
|
||||
|
||||
[Fact]
|
||||
public async Task HoverAsync_OnSharedCallName_AwaitsCatalog_AndResolvesShape()
|
||||
{
|
||||
// The catalog only completes after yielding — a truly asynchronous
|
||||
// source. The fixed Hover awaits it instead of blocking.
|
||||
_catalog.GetShapesAsync().Returns(async _ =>
|
||||
{
|
||||
await Task.Yield();
|
||||
return (IReadOnlyList<ScriptShape>)new[]
|
||||
{
|
||||
Shape("Aggregate", Param("window", "Integer")),
|
||||
};
|
||||
});
|
||||
|
||||
var resp = await _svc.Hover(new HoverRequest(
|
||||
CodeText: "var r = Scripts.CallShared(\"Aggregate\");",
|
||||
Line: 1,
|
||||
Column: 30));
|
||||
|
||||
Assert.NotNull(resp.Markdown);
|
||||
Assert.Contains("shared script", resp.Markdown);
|
||||
Assert.Contains("Aggregate", resp.Markdown);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SignatureHelpAsync_InsideSharedCall_AwaitsCatalog_AndResolvesParameters()
|
||||
{
|
||||
_catalog.GetShapesAsync().Returns(async _ =>
|
||||
{
|
||||
await Task.Yield();
|
||||
return (IReadOnlyList<ScriptShape>)new[]
|
||||
{
|
||||
Shape("Aggregate", Param("window", "Integer"), Param("mode", "String")),
|
||||
};
|
||||
});
|
||||
|
||||
var resp = await _svc.SignatureHelp(new SignatureHelpRequest(
|
||||
CodeText: "var r = Scripts.CallShared(\"Aggregate\", ",
|
||||
Line: 1,
|
||||
Column: 41));
|
||||
|
||||
Assert.NotNull(resp.Label);
|
||||
Assert.Contains("Aggregate", resp.Label!);
|
||||
Assert.Equal(2, resp.Parameters!.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HoverAndSignatureHelp_AreAsync_NotSyncOverAsync()
|
||||
{
|
||||
// Structural guard: the methods must return Task so the catalog can be
|
||||
// awaited rather than blocked with .GetAwaiter().GetResult().
|
||||
var hover = typeof(ScriptAnalysisService).GetMethod(nameof(ScriptAnalysisService.Hover));
|
||||
var sigHelp = typeof(ScriptAnalysisService).GetMethod(nameof(ScriptAnalysisService.SignatureHelp));
|
||||
|
||||
Assert.NotNull(hover);
|
||||
Assert.NotNull(sigHelp);
|
||||
Assert.Equal(typeof(Task<HoverResponse>), hover!.ReturnType);
|
||||
Assert.Equal(typeof(Task<SignatureHelpResponse>), sigHelp!.ReturnType);
|
||||
}
|
||||
}
|
||||
@@ -200,11 +200,11 @@ public class ScriptAnalysisServiceTests
|
||||
// ── Hover ─────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Hover_OnSiblingName_ReturnsSignature()
|
||||
public async Task Hover_OnSiblingName_ReturnsSignature()
|
||||
{
|
||||
var siblings = new[] { Shape("Calc", Param("x", "Integer"), Param("y", "Float")) };
|
||||
// Cursor inside the "Calc" name literal of Instance.CallScript("Calc", ...).
|
||||
var resp = _svc.Hover(new HoverRequest(
|
||||
var resp = await _svc.Hover(new HoverRequest(
|
||||
CodeText: "var r = Instance.CallScript(\"Calc\", 1, 2);",
|
||||
Line: 1,
|
||||
Column: 32,
|
||||
@@ -215,9 +215,9 @@ public class ScriptAnalysisServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Hover_OnUnrelatedToken_ReturnsNull()
|
||||
public async Task Hover_OnUnrelatedToken_ReturnsNull()
|
||||
{
|
||||
var resp = _svc.Hover(new HoverRequest(
|
||||
var resp = await _svc.Hover(new HoverRequest(
|
||||
CodeText: "var r = 1 + 2;",
|
||||
Line: 1,
|
||||
Column: 5));
|
||||
@@ -227,10 +227,10 @@ public class ScriptAnalysisServiceTests
|
||||
// ── Signature help ────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void SignatureHelp_InsideCallScript_ReturnsParameterStrip()
|
||||
public async Task SignatureHelp_InsideCallScript_ReturnsParameterStrip()
|
||||
{
|
||||
var siblings = new[] { Shape("Calc", Param("x", "Integer"), Param("y", "Float")) };
|
||||
var resp = _svc.SignatureHelp(new SignatureHelpRequest(
|
||||
var resp = await _svc.SignatureHelp(new SignatureHelpRequest(
|
||||
CodeText: "var r = Instance.CallScript(\"Calc\", 1, ",
|
||||
Line: 1,
|
||||
Column: 40,
|
||||
@@ -243,9 +243,9 @@ public class ScriptAnalysisServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SignatureHelp_OutsideCall_ReturnsNull()
|
||||
public async Task SignatureHelp_OutsideCall_ReturnsNull()
|
||||
{
|
||||
var resp = _svc.SignatureHelp(new SignatureHelpRequest(
|
||||
var resp = await _svc.SignatureHelp(new SignatureHelpRequest(
|
||||
CodeText: "var r = 1 + 2;",
|
||||
Line: 1,
|
||||
Column: 5));
|
||||
@@ -394,9 +394,9 @@ public class ScriptAnalysisServiceTests
|
||||
// ── Hover on Parameters["name"] ───────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Hover_OnParametersKey_ShowsDeclaredType()
|
||||
public async Task Hover_OnParametersKey_ShowsDeclaredType()
|
||||
{
|
||||
var resp = _svc.Hover(new HoverRequest(
|
||||
var resp = await _svc.Hover(new HoverRequest(
|
||||
CodeText: "var x = Parameters[\"name\"];",
|
||||
Line: 1,
|
||||
Column: 22,
|
||||
|
||||
Reference in New Issue
Block a user