- WP-1-3: Central/site failover + dual-node recovery tests (17 tests) - WP-4: Performance testing framework for target scale (7 tests) - WP-5: Security hardening (LDAPS, JWT key length, no secrets in logs) (11 tests) - WP-6: Script sandboxing adversarial tests (28 tests, all forbidden APIs) - WP-7: Recovery drill test scaffolds (5 tests) - WP-8: Observability validation (structured logs, correlation IDs, metrics) (6 tests) - WP-9: Message contract compatibility (forward/backward compat) (18 tests) - WP-10: Deployment packaging (installation guide, production checklist, topology) - WP-11: Operational runbooks (failover, troubleshooting, maintenance) 92 new tests, all passing. Zero warnings.
120 lines
3.9 KiB
C#
120 lines
3.9 KiB
C#
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NSubstitute;
|
|
using ScadaLink.Commons.Entities.InboundApi;
|
|
using ScadaLink.Commons.Interfaces.Services;
|
|
using ScadaLink.Communication;
|
|
|
|
namespace ScadaLink.InboundAPI.Tests;
|
|
|
|
/// <summary>
|
|
/// WP-3: Tests for script execution on central — timeout, handler dispatch, error handling.
|
|
/// WP-5: Safe error messages.
|
|
/// </summary>
|
|
public class InboundScriptExecutorTests
|
|
{
|
|
private readonly InboundScriptExecutor _executor;
|
|
private readonly RouteHelper _route;
|
|
|
|
public InboundScriptExecutorTests()
|
|
{
|
|
_executor = new InboundScriptExecutor(NullLogger<InboundScriptExecutor>.Instance);
|
|
var locator = Substitute.For<IInstanceLocator>();
|
|
var commService = Substitute.For<CommunicationService>(
|
|
Microsoft.Extensions.Options.Options.Create(new CommunicationOptions()),
|
|
NullLogger<CommunicationService>.Instance);
|
|
_route = new RouteHelper(locator, commService);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RegisteredHandler_ExecutesSuccessfully()
|
|
{
|
|
var method = new ApiMethod("test", "return 42;") { Id = 1, TimeoutSeconds = 10 };
|
|
_executor.RegisterHandler("test", async ctx =>
|
|
{
|
|
await Task.CompletedTask;
|
|
return new { result = 42 };
|
|
});
|
|
|
|
var result = await _executor.ExecuteAsync(
|
|
method,
|
|
new Dictionary<string, object?>(),
|
|
_route,
|
|
TimeSpan.FromSeconds(10));
|
|
|
|
Assert.True(result.Success);
|
|
Assert.NotNull(result.ResultJson);
|
|
Assert.Contains("42", result.ResultJson);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UnregisteredHandler_ReturnsFailure()
|
|
{
|
|
var method = new ApiMethod("unknown", "return 1;") { Id = 1, TimeoutSeconds = 10 };
|
|
|
|
var result = await _executor.ExecuteAsync(
|
|
method,
|
|
new Dictionary<string, object?>(),
|
|
_route,
|
|
TimeSpan.FromSeconds(10));
|
|
|
|
Assert.False(result.Success);
|
|
Assert.Contains("not compiled", result.ErrorMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandlerThrows_ReturnsSafeErrorMessage()
|
|
{
|
|
var method = new ApiMethod("failing", "throw new Exception();") { Id = 1, TimeoutSeconds = 10 };
|
|
_executor.RegisterHandler("failing", _ => throw new InvalidOperationException("internal detail leak"));
|
|
|
|
var result = await _executor.ExecuteAsync(
|
|
method,
|
|
new Dictionary<string, object?>(),
|
|
_route,
|
|
TimeSpan.FromSeconds(10));
|
|
|
|
Assert.False(result.Success);
|
|
// WP-5: Safe error message — should NOT contain "internal detail leak"
|
|
Assert.Equal("Internal script error", result.ErrorMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandlerTimesOut_ReturnsTimeoutError()
|
|
{
|
|
var method = new ApiMethod("slow", "Thread.Sleep(60000);") { Id = 1, TimeoutSeconds = 1 };
|
|
_executor.RegisterHandler("slow", async ctx =>
|
|
{
|
|
await Task.Delay(TimeSpan.FromSeconds(60), ctx.CancellationToken);
|
|
return "never";
|
|
});
|
|
|
|
var result = await _executor.ExecuteAsync(
|
|
method,
|
|
new Dictionary<string, object?>(),
|
|
_route,
|
|
TimeSpan.FromMilliseconds(100));
|
|
|
|
Assert.False(result.Success);
|
|
Assert.Contains("timed out", result.ErrorMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HandlerAccessesParameters()
|
|
{
|
|
var method = new ApiMethod("echo", "return params;") { Id = 1, TimeoutSeconds = 10 };
|
|
_executor.RegisterHandler("echo", async ctx =>
|
|
{
|
|
await Task.CompletedTask;
|
|
return ctx.Parameters["name"];
|
|
});
|
|
|
|
var parameters = new Dictionary<string, object?> { { "name", "ScadaLink" } };
|
|
|
|
var result = await _executor.ExecuteAsync(
|
|
method, parameters, _route, TimeSpan.FromSeconds(10));
|
|
|
|
Assert.True(result.Success);
|
|
Assert.Contains("ScadaLink", result.ResultJson!);
|
|
}
|
|
}
|