165 lines
5.5 KiB
C#
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;
|
|
}
|