64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
135 lines
6.3 KiB
C#
135 lines
6.3 KiB
C#
using System.Runtime.Serialization;
|
|
using ArchestrA;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests.Backend;
|
|
|
|
/// <summary>
|
|
/// Driver.Historian.Wonderware-012 coverage — pins the two static helpers on
|
|
/// <see cref="HistorianDataSource"/> that previously had no direct tests:
|
|
/// <see cref="HistorianDataSource.SelectValueFromPair"/> (the string-vs-numeric heuristic
|
|
/// for the raw + at-time read paths) and <see cref="HistorianDataSource.ExtractAggregateValue"/>
|
|
/// (the aggregate-column dispatch). The SDK <c>HistoryQueryResult</c> initialises internal
|
|
/// state lazily on first property access, which makes it impractical to fake via
|
|
/// <see cref="FormatterServices.GetUninitializedObject"/>; the heuristic was therefore
|
|
/// refactored into an SDK-independent overload that the tests drive directly.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class HistorianDataSourceValueAndAggregateTests
|
|
{
|
|
// ── SelectValueFromPair ───────────────────────────────────────────────
|
|
|
|
/// <summary>Verifies that numeric value is returned when StringValue is empty.</summary>
|
|
[Fact]
|
|
public void SelectValueFromPair_returns_numeric_value_when_StringValue_is_empty()
|
|
{
|
|
HistorianDataSource.SelectValueFromPair(42.5, string.Empty).ShouldBe(42.5);
|
|
}
|
|
|
|
/// <summary>Verifies that numeric value is returned when Value is non-zero even if StringValue is populated.</summary>
|
|
[Fact]
|
|
public void SelectValueFromPair_returns_numeric_value_when_Value_is_non_zero_even_with_StringValue_populated()
|
|
{
|
|
// Tag is numeric and sampled non-zero; the SDK may still populate a formatted
|
|
// StringValue but the value path wins.
|
|
HistorianDataSource.SelectValueFromPair(3.14, "3.14").ShouldBe(3.14);
|
|
}
|
|
|
|
/// <summary>Verifies that StringValue is returned when Value is zero and StringValue is non-empty.</summary>
|
|
[Fact]
|
|
public void SelectValueFromPair_returns_StringValue_when_Value_is_zero_and_StringValue_non_empty()
|
|
{
|
|
// String tags in the SDK always project Value=0 — that's the documented heuristic.
|
|
HistorianDataSource.SelectValueFromPair(0.0, "Ready").ShouldBe("Ready");
|
|
}
|
|
|
|
/// <summary>Verifies that numeric zero is returned when Value is zero and StringValue is empty.</summary>
|
|
[Fact]
|
|
public void SelectValueFromPair_returns_numeric_zero_when_Value_is_zero_and_StringValue_empty()
|
|
{
|
|
// Numeric tag legitimately samples zero, no formatted text — must remain numeric.
|
|
HistorianDataSource.SelectValueFromPair(0.0, string.Empty).ShouldBe(0.0);
|
|
}
|
|
|
|
/// <summary>Verifies that null StringValue falls back to numeric value.</summary>
|
|
[Fact]
|
|
public void SelectValueFromPair_null_StringValue_falls_back_to_numeric()
|
|
{
|
|
HistorianDataSource.SelectValueFromPair(7.7, null).ShouldBe(7.7);
|
|
}
|
|
|
|
/// <summary>Verifies the documented edge case where numeric zero with a formatted string returns the string.</summary>
|
|
[Fact]
|
|
public void SelectValueFromPair_documented_edge_case_numeric_zero_with_formatted_string_returns_string()
|
|
{
|
|
// The doc comment on SelectValue calls this out as a known SDK-binding edge case:
|
|
// "A numeric tag at exactly zero with a non-empty formatted StringValue (e.g. '0.00')
|
|
// would be mis-reported as a string". This test pins that documented behaviour so
|
|
// a future SDK upgrade that surfaces a real data-type field can replace the
|
|
// heuristic deliberately rather than by accident.
|
|
HistorianDataSource.SelectValueFromPair(0.0, "0.00").ShouldBe("0.00");
|
|
}
|
|
|
|
// ── ExtractAggregateValue ─────────────────────────────────────────────
|
|
|
|
/// <summary>Verifies that aggregate value extraction dispatches correctly for known columns.</summary>
|
|
/// <param name="column">The aggregate result column name to extract.</param>
|
|
/// <param name="expected">The expected aggregate double value.</param>
|
|
[Theory]
|
|
[InlineData("Average", 10.0)]
|
|
[InlineData("Minimum", 1.0)]
|
|
[InlineData("Maximum", 20.0)]
|
|
[InlineData("First", 2.0)]
|
|
[InlineData("Last", 8.0)]
|
|
[InlineData("StdDev", 1.5)]
|
|
public void ExtractAggregateValue_dispatches_known_columns(string column, double expected)
|
|
{
|
|
var result = NewAggregateResult();
|
|
result.Average = 10.0;
|
|
result.Minimum = 1.0;
|
|
result.Maximum = 20.0;
|
|
result.ValueCount = 5;
|
|
result.First = 2.0;
|
|
result.Last = 8.0;
|
|
result.StdDev = 1.5;
|
|
|
|
HistorianDataSource.ExtractAggregateValue(result, column).ShouldBe(expected);
|
|
}
|
|
|
|
/// <summary>Verifies that ValueCount is dispatched to the uint field.</summary>
|
|
[Fact]
|
|
public void ExtractAggregateValue_ValueCount_dispatches_to_uint_field()
|
|
{
|
|
var result = NewAggregateResult();
|
|
result.ValueCount = 42;
|
|
HistorianDataSource.ExtractAggregateValue(result, "ValueCount").ShouldBe(42.0);
|
|
}
|
|
|
|
/// <summary>Verifies that an unknown column returns null.</summary>
|
|
[Fact]
|
|
public void ExtractAggregateValue_unknown_column_returns_null()
|
|
{
|
|
// Unknown column → null → IPC sample carries no value → client maps to BadNoData.
|
|
HistorianDataSource.ExtractAggregateValue(NewAggregateResult(), "NotAColumn").ShouldBeNull();
|
|
}
|
|
|
|
/// <summary>Verifies that aggregate value dispatch is case-sensitive.</summary>
|
|
[Fact]
|
|
public void ExtractAggregateValue_case_sensitive_dispatch()
|
|
{
|
|
// The switch is case-sensitive — "average" (lowercase) does NOT dispatch. Pinned so
|
|
// the canonical column-name casing is preserved across refactors.
|
|
var result = NewAggregateResult();
|
|
result.Average = 99.0;
|
|
HistorianDataSource.ExtractAggregateValue(result, "average").ShouldBeNull();
|
|
HistorianDataSource.ExtractAggregateValue(result, "Average").ShouldBe(99.0);
|
|
}
|
|
|
|
private static AnalogSummaryQueryResult NewAggregateResult()
|
|
{
|
|
return (AnalogSummaryQueryResult)FormatterServices.GetUninitializedObject(typeof(AnalogSummaryQueryResult));
|
|
}
|
|
}
|