134 lines
5.8 KiB
C#
134 lines
5.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using FluentAssertions;
|
|
using Newtonsoft.Json.Linq;
|
|
using Xunit;
|
|
using ZB.MOM.WW.LmxProxy.Host.Domain;
|
|
using ZB.MOM.WW.LmxProxy.Host.Health;
|
|
using HealthCheckService = ZB.MOM.WW.LmxProxy.Host.Health.HealthCheckService;
|
|
using ZB.MOM.WW.LmxProxy.Host.Metrics;
|
|
using ZB.MOM.WW.LmxProxy.Host.Status;
|
|
using ZB.MOM.WW.LmxProxy.Host.Subscriptions;
|
|
|
|
namespace ZB.MOM.WW.LmxProxy.Host.Tests.Status
|
|
{
|
|
public class StatusReportServiceTests
|
|
{
|
|
private class FakeScadaClient : IScadaClient
|
|
{
|
|
public bool IsConnected { get; set; } = true;
|
|
public ConnectionState ConnectionState { get; set; } = ConnectionState.Connected;
|
|
public event EventHandler<ConnectionStateChangedEventArgs>? ConnectionStateChanged;
|
|
public Task ConnectAsync(CancellationToken ct = default) => Task.CompletedTask;
|
|
public Task DisconnectAsync(CancellationToken ct = default) => Task.CompletedTask;
|
|
public Task<Vtq> ReadAsync(string address, CancellationToken ct = default) =>
|
|
Task.FromResult(Vtq.Good(42.0));
|
|
public Task<IReadOnlyDictionary<string, Vtq>> ReadBatchAsync(IEnumerable<string> addresses, CancellationToken ct = default) =>
|
|
Task.FromResult<IReadOnlyDictionary<string, Vtq>>(new Dictionary<string, Vtq>());
|
|
public Task WriteAsync(string address, object value, CancellationToken ct = default) => Task.CompletedTask;
|
|
public Task WriteBatchAsync(IReadOnlyDictionary<string, object> values, CancellationToken ct = default) => Task.CompletedTask;
|
|
public Task<(bool flagReached, int elapsedMs)> WriteBatchAndWaitAsync(
|
|
IReadOnlyDictionary<string, object> values, string flagTag, object flagValue,
|
|
int timeoutMs, int pollIntervalMs, CancellationToken ct = default) =>
|
|
Task.FromResult((false, 0));
|
|
public Task<ProbeResult> ProbeConnectionAsync(string testTagAddress, int timeoutMs, CancellationToken ct = default) =>
|
|
Task.FromResult(ProbeResult.Healthy(Quality.Good, DateTime.UtcNow));
|
|
public Task UnsubscribeByAddressAsync(IEnumerable<string> addresses) => Task.CompletedTask;
|
|
public Task<IAsyncDisposable> SubscribeAsync(IEnumerable<string> addresses, Action<string, Vtq> callback, CancellationToken ct = default) =>
|
|
Task.FromResult<IAsyncDisposable>(new FakeHandle());
|
|
public ValueTask DisposeAsync() => default;
|
|
internal void FireEvent() => ConnectionStateChanged?.Invoke(this, null!);
|
|
private class FakeHandle : IAsyncDisposable { public ValueTask DisposeAsync() => default; }
|
|
}
|
|
|
|
private (StatusReportService svc, PerformanceMetrics pm, SubscriptionManager sm) CreateService(
|
|
bool connected = true)
|
|
{
|
|
var client = new FakeScadaClient
|
|
{
|
|
IsConnected = connected,
|
|
ConnectionState = connected ? ConnectionState.Connected : ConnectionState.Disconnected
|
|
};
|
|
var sm = new SubscriptionManager(client);
|
|
var pm = new PerformanceMetrics();
|
|
var health = new HealthCheckService(client, sm, pm);
|
|
var detailed = new DetailedHealthCheckService(client);
|
|
var svc = new StatusReportService(client, sm, pm, health, detailed);
|
|
return (svc, pm, sm);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GenerateJsonReportAsync_ReturnsCamelCaseJson()
|
|
{
|
|
var (svc, pm, sm) = CreateService();
|
|
using (pm) using (sm)
|
|
{
|
|
var json = await svc.GenerateJsonReportAsync();
|
|
|
|
json.Should().Contain("\"serviceName\"");
|
|
json.Should().Contain("\"connection\"");
|
|
json.Should().Contain("\"isConnected\"");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GenerateHtmlReportAsync_ContainsAutoRefresh()
|
|
{
|
|
var (svc, pm, sm) = CreateService();
|
|
using (pm) using (sm)
|
|
{
|
|
var html = await svc.GenerateHtmlReportAsync();
|
|
|
|
html.Should().Contain("<meta http-equiv=\"refresh\" content=\"30\">");
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task IsHealthyAsync_ReturnsTrueWhenHealthy()
|
|
{
|
|
var (svc, pm, sm) = CreateService(connected: true);
|
|
using (pm) using (sm)
|
|
{
|
|
var result = await svc.IsHealthyAsync();
|
|
|
|
result.Should().BeTrue();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task IsHealthyAsync_ReturnsFalseWhenUnhealthy()
|
|
{
|
|
var (svc, pm, sm) = CreateService(connected: false);
|
|
using (pm) using (sm)
|
|
{
|
|
var result = await svc.IsHealthyAsync();
|
|
|
|
result.Should().BeFalse();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GenerateJsonReportAsync_IncludesPerformanceMetrics()
|
|
{
|
|
var (svc, pm, sm) = CreateService();
|
|
using (pm) using (sm)
|
|
{
|
|
pm.RecordOperation("Read", TimeSpan.FromMilliseconds(15), true);
|
|
pm.RecordOperation("Write", TimeSpan.FromMilliseconds(25), true);
|
|
|
|
var json = await svc.GenerateJsonReportAsync();
|
|
var parsed = JObject.Parse(json);
|
|
|
|
var operations = parsed["performance"]?["operations"];
|
|
operations.Should().NotBeNull();
|
|
// Newtonsoft CamelCasePropertyNamesContractResolver camelCases dictionary keys
|
|
operations!["read"].Should().NotBeNull();
|
|
operations!["write"].Should().NotBeNull();
|
|
((long)operations!["read"]!["totalCount"]!).Should().Be(1);
|
|
}
|
|
}
|
|
}
|
|
}
|