513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
172 lines
6.6 KiB
C#
172 lines
6.6 KiB
C#
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.PerformanceTests;
|
|
|
|
/// <summary>
|
|
/// Performance test framework for health reporting aggregation.
|
|
/// Verifies health reporting from 10 sites can be aggregated correctly.
|
|
/// </summary>
|
|
public class HealthAggregationTests
|
|
{
|
|
private readonly CentralHealthAggregator _aggregator;
|
|
|
|
/// <summary>Initializes the test class with a central health aggregator configured for 30-second reports and a 60-second offline timeout.</summary>
|
|
public HealthAggregationTests()
|
|
{
|
|
var options = Options.Create(new HealthMonitoringOptions
|
|
{
|
|
ReportInterval = TimeSpan.FromSeconds(30),
|
|
OfflineTimeout = TimeSpan.FromSeconds(60)
|
|
});
|
|
_aggregator = new CentralHealthAggregator(
|
|
options,
|
|
NullLogger<CentralHealthAggregator>.Instance);
|
|
}
|
|
|
|
/// <summary>Verifies that health reports from ten sites are all tracked by the aggregator.</summary>
|
|
[Trait("Category", "Performance")]
|
|
[Fact]
|
|
public void AggregateHealthReports_10Sites_AllTracked()
|
|
{
|
|
const int siteCount = 10;
|
|
|
|
for (var i = 0; i < siteCount; i++)
|
|
{
|
|
var siteId = $"site-{i + 1:D2}";
|
|
var report = new SiteHealthReport(
|
|
SiteId: siteId,
|
|
SequenceNumber: 1,
|
|
ReportTimestamp: DateTimeOffset.UtcNow,
|
|
DataConnectionStatuses: new Dictionary<string, ConnectionHealth>
|
|
{
|
|
[$"opc-{siteId}"] = ConnectionHealth.Connected
|
|
},
|
|
TagResolutionCounts: new Dictionary<string, TagResolutionStatus>
|
|
{
|
|
[$"opc-{siteId}"] = new(75, 72)
|
|
},
|
|
ScriptErrorCount: 0,
|
|
AlarmEvaluationErrorCount: 0,
|
|
StoreAndForwardBufferDepths: new Dictionary<string, int>
|
|
{
|
|
["ext-system"] = i * 2
|
|
},
|
|
DeadLetterCount: 0,
|
|
DeployedInstanceCount: 0,
|
|
EnabledInstanceCount: 0,
|
|
DisabledInstanceCount: 0);
|
|
|
|
_aggregator.ProcessReport(report);
|
|
}
|
|
|
|
var states = _aggregator.GetAllSiteStates();
|
|
Assert.Equal(siteCount, states.Count);
|
|
Assert.All(states.Values, s => Assert.True(s.IsOnline));
|
|
}
|
|
|
|
/// <summary>Verifies that the aggregator handles a high volume of rapid report updates.</summary>
|
|
[Trait("Category", "Performance")]
|
|
[Fact]
|
|
public void AggregateHealthReports_RapidUpdates_HandlesVolume()
|
|
{
|
|
const int siteCount = 10;
|
|
const int updatesPerSite = 100;
|
|
|
|
for (var seq = 1; seq <= updatesPerSite; seq++)
|
|
{
|
|
for (var s = 0; s < siteCount; s++)
|
|
{
|
|
var report = new SiteHealthReport(
|
|
SiteId: $"site-{s + 1:D2}",
|
|
SequenceNumber: seq,
|
|
ReportTimestamp: DateTimeOffset.UtcNow,
|
|
DataConnectionStatuses: new Dictionary<string, ConnectionHealth>(),
|
|
TagResolutionCounts: new Dictionary<string, TagResolutionStatus>(),
|
|
ScriptErrorCount: seq % 5 == 0 ? 1 : 0,
|
|
AlarmEvaluationErrorCount: 0,
|
|
StoreAndForwardBufferDepths: new Dictionary<string, int>(),
|
|
DeadLetterCount: 0,
|
|
DeployedInstanceCount: 0,
|
|
EnabledInstanceCount: 0,
|
|
DisabledInstanceCount: 0);
|
|
|
|
_aggregator.ProcessReport(report);
|
|
}
|
|
}
|
|
|
|
var states = _aggregator.GetAllSiteStates();
|
|
Assert.Equal(siteCount, states.Count);
|
|
|
|
// Verify all sites have the latest sequence number
|
|
Assert.All(states.Values, s =>
|
|
{
|
|
Assert.Equal(updatesPerSite, s.LastSequenceNumber);
|
|
Assert.True(s.IsOnline);
|
|
});
|
|
}
|
|
|
|
/// <summary>Verifies that stale health reports are rejected.</summary>
|
|
[Trait("Category", "Performance")]
|
|
[Fact]
|
|
public void AggregateHealthReports_StaleReportsRejected()
|
|
{
|
|
var siteId = "site-01";
|
|
|
|
// Send report with seq 10
|
|
_aggregator.ProcessReport(new SiteHealthReport(
|
|
siteId, 10, DateTimeOffset.UtcNow,
|
|
new Dictionary<string, ConnectionHealth>(),
|
|
new Dictionary<string, TagResolutionStatus>(),
|
|
5, 0, new Dictionary<string, int>(), 0, 0, 0, 0));
|
|
|
|
// Send stale report with seq 5 — should be rejected
|
|
_aggregator.ProcessReport(new SiteHealthReport(
|
|
siteId, 5, DateTimeOffset.UtcNow,
|
|
new Dictionary<string, ConnectionHealth>(),
|
|
new Dictionary<string, TagResolutionStatus>(),
|
|
99, 0, new Dictionary<string, int>(), 0, 0, 0, 0));
|
|
|
|
var state = _aggregator.GetSiteState(siteId);
|
|
Assert.NotNull(state);
|
|
Assert.Equal(10, state!.LastSequenceNumber);
|
|
// The script error count from report 10 (5) should be kept, not replaced by 99
|
|
Assert.Equal(5, state.LatestReport!.ScriptErrorCount);
|
|
}
|
|
|
|
/// <summary>Verifies that collecting a report resets the interval counters.</summary>
|
|
[Trait("Category", "Performance")]
|
|
[Fact]
|
|
public void HealthCollector_CollectReport_ResetsIntervalCounters()
|
|
{
|
|
var collector = new SiteHealthCollector();
|
|
|
|
// Simulate errors during an interval
|
|
for (var i = 0; i < 10; i++) collector.IncrementScriptError();
|
|
for (var i = 0; i < 3; i++) collector.IncrementAlarmError();
|
|
for (var i = 0; i < 7; i++) collector.IncrementDeadLetter();
|
|
|
|
collector.UpdateConnectionHealth("opc-1", ConnectionHealth.Connected);
|
|
collector.UpdateTagResolution("opc-1", 75, 72);
|
|
|
|
var report = collector.CollectReport("site-01");
|
|
|
|
Assert.Equal("site-01", report.SiteId);
|
|
Assert.Equal(10, report.ScriptErrorCount);
|
|
Assert.Equal(3, report.AlarmEvaluationErrorCount);
|
|
Assert.Equal(7, report.DeadLetterCount);
|
|
Assert.Single(report.DataConnectionStatuses);
|
|
|
|
// Second collect should have reset interval counters
|
|
var report2 = collector.CollectReport("site-01");
|
|
Assert.Equal(0, report2.ScriptErrorCount);
|
|
Assert.Equal(0, report2.AlarmEvaluationErrorCount);
|
|
Assert.Equal(0, report2.DeadLetterCount);
|
|
// Connection status persists (not interval-based)
|
|
Assert.Single(report2.DataConnectionStatuses);
|
|
}
|
|
}
|