Files
scadalink-design/infra/lmxfakeproxy/tests/LmxFakeProxy.Tests/ScadaServiceTests.cs

165 lines
5.5 KiB
C#

using Grpc.Core;
using NSubstitute;
using LmxFakeProxy.Bridge;
using LmxFakeProxy.Grpc;
using LmxFakeProxy.Sessions;
using LmxFakeProxy.Services;
namespace LmxFakeProxy.Tests;
public class ScadaServiceTests
{
private readonly IOpcUaBridge _mockBridge;
private readonly SessionManager _sessionMgr;
private readonly TagMapper _tagMapper;
private readonly ScadaServiceImpl _service;
public ScadaServiceTests()
{
_mockBridge = Substitute.For<IOpcUaBridge>();
_mockBridge.IsConnected.Returns(true);
_sessionMgr = new SessionManager(null);
_tagMapper = new TagMapper("ns=3;s=");
_service = new ScadaServiceImpl(_sessionMgr, _mockBridge, _tagMapper);
}
private string ConnectClient(string clientId = "test-client")
{
var (_, _, sessionId) = _sessionMgr.Connect(clientId, "");
return sessionId;
}
private static ServerCallContext MockContext()
{
return new TestServerCallContext();
}
[Fact]
public async Task Connect_ReturnsSessionId()
{
var resp = await _service.Connect(
new ConnectRequest { ClientId = "c1", ApiKey = "" }, MockContext());
Assert.True(resp.Success);
Assert.NotEmpty(resp.SessionId);
}
[Fact]
public async Task Read_ValidSession_ReturnsVtq()
{
var sid = ConnectClient();
_mockBridge.ReadAsync("ns=3;s=Motor.Speed", Arg.Any<CancellationToken>())
.Returns(new OpcUaReadResult(42.5, DateTime.UtcNow, 0));
var resp = await _service.Read(
new ReadRequest { SessionId = sid, Tag = "Motor.Speed" }, MockContext());
Assert.True(resp.Success);
Assert.Equal("42.5", resp.Vtq.Value);
Assert.Equal("Good", resp.Vtq.Quality);
}
[Fact]
public async Task Read_InvalidSession_ReturnsFailure()
{
var resp = await _service.Read(
new ReadRequest { SessionId = "bogus", Tag = "Motor.Speed" }, MockContext());
Assert.False(resp.Success);
Assert.Contains("Invalid", resp.Message);
}
[Fact]
public async Task ReadBatch_ReturnsAllTags()
{
var sid = ConnectClient();
_mockBridge.ReadAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(new OpcUaReadResult(1.0, DateTime.UtcNow, 0));
var req = new ReadBatchRequest { SessionId = sid };
req.Tags.AddRange(new[] { "Motor.Speed", "Pump.FlowRate" });
var resp = await _service.ReadBatch(req, MockContext());
Assert.True(resp.Success);
Assert.Equal(2, resp.Vtqs.Count);
}
[Fact]
public async Task Write_ValidSession_Succeeds()
{
var sid = ConnectClient();
_mockBridge.WriteAsync("ns=3;s=Motor.Speed", Arg.Any<object?>(), Arg.Any<CancellationToken>())
.Returns(0u);
var resp = await _service.Write(
new WriteRequest { SessionId = sid, Tag = "Motor.Speed", Value = "42.5" }, MockContext());
Assert.True(resp.Success);
}
[Fact]
public async Task Write_InvalidSession_ReturnsFailure()
{
var resp = await _service.Write(
new WriteRequest { SessionId = "bogus", Tag = "Motor.Speed", Value = "42.5" }, MockContext());
Assert.False(resp.Success);
}
[Fact]
public async Task WriteBatch_ReturnsPerItemResults()
{
var sid = ConnectClient();
_mockBridge.WriteAsync(Arg.Any<string>(), Arg.Any<object?>(), Arg.Any<CancellationToken>())
.Returns(0u);
var req = new WriteBatchRequest { SessionId = sid };
req.Items.Add(new WriteItem { Tag = "Motor.Speed", Value = "42.5" });
req.Items.Add(new WriteItem { Tag = "Pump.FlowRate", Value = "10.0" });
var resp = await _service.WriteBatch(req, MockContext());
Assert.True(resp.Success);
Assert.Equal(2, resp.Results.Count);
Assert.All(resp.Results, r => Assert.True(r.Success));
}
[Fact]
public async Task CheckApiKey_Valid_ReturnsTrue()
{
var resp = await _service.CheckApiKey(
new CheckApiKeyRequest { ApiKey = "anything" }, MockContext());
Assert.True(resp.IsValid);
}
[Fact]
public async Task CheckApiKey_Invalid_ReturnsFalse()
{
var mgr = new SessionManager("secret");
var svc = new ScadaServiceImpl(mgr, _mockBridge, _tagMapper);
var resp = await svc.CheckApiKey(
new CheckApiKeyRequest { ApiKey = "wrong" }, MockContext());
Assert.False(resp.IsValid);
}
}
/// <summary>
/// Minimal ServerCallContext for unit testing gRPC services.
/// </summary>
internal class TestServerCallContext : ServerCallContext
{
protected override string MethodCore => "test";
protected override string HostCore => "localhost";
protected override string PeerCore => "test-peer";
protected override DateTime DeadlineCore => DateTime.MaxValue;
protected override Metadata RequestHeadersCore => new();
protected override CancellationToken CancellationTokenCore => CancellationToken.None;
protected override Metadata ResponseTrailersCore => new();
protected override Status StatusCore { get; set; }
protected override WriteOptions? WriteOptionsCore { get; set; }
protected override AuthContext AuthContextCore => new("test", new Dictionary<string, List<AuthProperty>>());
protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions? options) =>
throw new NotImplementedException();
protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders) => Task.CompletedTask;
}