feat(historian-gateway): scaffold Gateway driver project + consume client package
Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
+214
@@ -0,0 +1,214 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ZB.MOM.WW.HistorianGateway.Contracts.Grpc;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Reusable in-memory test double for <see cref="IHistorianGatewayClient"/>. Every method returns
|
||||
/// from a public settable result field and records its call arguments into public fields, so the
|
||||
/// later driver tasks (T7/T8/T11/T12/T14) can drive behaviour and assert on what the driver sent
|
||||
/// without a live gateway. Throw fields let a test simulate transport faults (e.g. an
|
||||
/// <c>RpcException</c>) per operation; reads share <see cref="ThrowOnRead"/>.
|
||||
/// </summary>
|
||||
public sealed class FakeHistorianGatewayClient : IHistorianGatewayClient
|
||||
{
|
||||
// ---- ReadRaw -------------------------------------------------------------------------------
|
||||
public IReadOnlyList<HistorianSample> RawSamples = Array.Empty<HistorianSample>();
|
||||
public string? LastReadRawTag;
|
||||
public DateTime LastReadRawStartUtc;
|
||||
public DateTime LastReadRawEndUtc;
|
||||
public int LastReadRawMaxValues;
|
||||
public int ReadRawCallCount;
|
||||
|
||||
// ---- ReadAggregate -------------------------------------------------------------------------
|
||||
public IReadOnlyList<HistorianAggregateSample> AggregateSamples = Array.Empty<HistorianAggregateSample>();
|
||||
public string? LastAggregateTag;
|
||||
public DateTime LastAggregateStartUtc;
|
||||
public DateTime LastAggregateEndUtc;
|
||||
public RetrievalMode LastAggregateMode;
|
||||
public TimeSpan LastAggregateInterval;
|
||||
public int ReadAggregateCallCount;
|
||||
|
||||
// ---- ReadAtTime ----------------------------------------------------------------------------
|
||||
public IReadOnlyList<HistorianSample> AtTimeSamples = Array.Empty<HistorianSample>();
|
||||
public string? LastReadAtTimeTag;
|
||||
public IReadOnlyList<DateTime>? LastReadAtTimeTimestamps;
|
||||
public int ReadAtTimeCallCount;
|
||||
|
||||
// ---- ReadEvents ----------------------------------------------------------------------------
|
||||
public IReadOnlyList<HistorianEvent> Events = Array.Empty<HistorianEvent>();
|
||||
public string? LastReadEventsSourceName;
|
||||
public DateTime LastReadEventsStartUtc;
|
||||
public DateTime LastReadEventsEndUtc;
|
||||
public int LastReadEventsMaxEvents;
|
||||
public int ReadEventsCallCount;
|
||||
|
||||
/// <summary>Thrown (deferred to first enumeration) by every read method when set.</summary>
|
||||
public Exception? ThrowOnRead;
|
||||
|
||||
// ---- WriteLiveValues -----------------------------------------------------------------------
|
||||
public WriteAck WriteLiveValuesResult = new() { Success = true };
|
||||
public string? LastWriteLiveTag;
|
||||
public IReadOnlyList<HistorianLiveValue>? LastWriteLiveValues;
|
||||
public int WriteLiveValuesCallCount;
|
||||
public Exception? WriteLiveValuesThrows;
|
||||
|
||||
// ---- SendEvent -----------------------------------------------------------------------------
|
||||
public WriteAck SendEventResult = new() { Success = true };
|
||||
public HistorianEvent? LastSendEvent;
|
||||
public int SendEventCallCount;
|
||||
public Exception? SendEventThrows;
|
||||
|
||||
// ---- EnsureTags ----------------------------------------------------------------------------
|
||||
public TagOperationResults EnsureTagsResult = new();
|
||||
public IReadOnlyList<HistorianTagDefinition>? LastEnsureDefinitions;
|
||||
public int EnsureTagsCallCount;
|
||||
public Exception? EnsureTagsThrows;
|
||||
|
||||
// ---- Probe ---------------------------------------------------------------------------------
|
||||
public bool ProbeResult = true;
|
||||
public int ProbeCallCount;
|
||||
public Exception? ProbeThrows;
|
||||
|
||||
// ---- GetConnectionStatus -------------------------------------------------------------------
|
||||
public ConnectionStatus ConnectionStatus = new();
|
||||
public int GetConnectionStatusCallCount;
|
||||
public Exception? GetConnectionStatusThrows;
|
||||
|
||||
// ---- Dispose -------------------------------------------------------------------------------
|
||||
public int DisposeCallCount;
|
||||
|
||||
public IAsyncEnumerable<HistorianSample> ReadRawAsync(
|
||||
string tag,
|
||||
DateTime startUtc,
|
||||
DateTime endUtc,
|
||||
int maxValues,
|
||||
CancellationToken ct)
|
||||
{
|
||||
LastReadRawTag = tag;
|
||||
LastReadRawStartUtc = startUtc;
|
||||
LastReadRawEndUtc = endUtc;
|
||||
LastReadRawMaxValues = maxValues;
|
||||
ReadRawCallCount++;
|
||||
return ToAsyncStream(RawSamples, ThrowOnRead, ct);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<HistorianAggregateSample> ReadAggregateAsync(
|
||||
string tag,
|
||||
DateTime startUtc,
|
||||
DateTime endUtc,
|
||||
RetrievalMode mode,
|
||||
TimeSpan interval,
|
||||
CancellationToken ct)
|
||||
{
|
||||
LastAggregateTag = tag;
|
||||
LastAggregateStartUtc = startUtc;
|
||||
LastAggregateEndUtc = endUtc;
|
||||
LastAggregateMode = mode;
|
||||
LastAggregateInterval = interval;
|
||||
ReadAggregateCallCount++;
|
||||
return ToAsyncStream(AggregateSamples, ThrowOnRead, ct);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<HistorianSample>> ReadAtTimeAsync(
|
||||
string tag,
|
||||
IReadOnlyList<DateTime> timestampsUtc,
|
||||
CancellationToken ct)
|
||||
{
|
||||
LastReadAtTimeTag = tag;
|
||||
LastReadAtTimeTimestamps = timestampsUtc;
|
||||
ReadAtTimeCallCount++;
|
||||
return ThrowOnRead is not null
|
||||
? Task.FromException<IReadOnlyList<HistorianSample>>(ThrowOnRead)
|
||||
: Task.FromResult(AtTimeSamples);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<HistorianEvent> ReadEventsAsync(
|
||||
string? sourceName,
|
||||
DateTime startUtc,
|
||||
DateTime endUtc,
|
||||
int maxEvents,
|
||||
CancellationToken ct)
|
||||
{
|
||||
LastReadEventsSourceName = sourceName;
|
||||
LastReadEventsStartUtc = startUtc;
|
||||
LastReadEventsEndUtc = endUtc;
|
||||
LastReadEventsMaxEvents = maxEvents;
|
||||
ReadEventsCallCount++;
|
||||
return ToAsyncStream(Events, ThrowOnRead, ct);
|
||||
}
|
||||
|
||||
public Task<WriteAck> WriteLiveValuesAsync(
|
||||
string tag,
|
||||
IReadOnlyList<HistorianLiveValue> values,
|
||||
CancellationToken ct)
|
||||
{
|
||||
LastWriteLiveTag = tag;
|
||||
LastWriteLiveValues = values;
|
||||
WriteLiveValuesCallCount++;
|
||||
return WriteLiveValuesThrows is not null
|
||||
? Task.FromException<WriteAck>(WriteLiveValuesThrows)
|
||||
: Task.FromResult(WriteLiveValuesResult);
|
||||
}
|
||||
|
||||
public Task<WriteAck> SendEventAsync(HistorianEvent evt, CancellationToken ct)
|
||||
{
|
||||
LastSendEvent = evt;
|
||||
SendEventCallCount++;
|
||||
return SendEventThrows is not null
|
||||
? Task.FromException<WriteAck>(SendEventThrows)
|
||||
: Task.FromResult(SendEventResult);
|
||||
}
|
||||
|
||||
public Task<TagOperationResults> EnsureTagsAsync(
|
||||
IReadOnlyList<HistorianTagDefinition> definitions,
|
||||
CancellationToken ct)
|
||||
{
|
||||
LastEnsureDefinitions = definitions;
|
||||
EnsureTagsCallCount++;
|
||||
return EnsureTagsThrows is not null
|
||||
? Task.FromException<TagOperationResults>(EnsureTagsThrows)
|
||||
: Task.FromResult(EnsureTagsResult);
|
||||
}
|
||||
|
||||
public Task<bool> ProbeAsync(CancellationToken ct)
|
||||
{
|
||||
ProbeCallCount++;
|
||||
return ProbeThrows is not null
|
||||
? Task.FromException<bool>(ProbeThrows)
|
||||
: Task.FromResult(ProbeResult);
|
||||
}
|
||||
|
||||
public Task<ConnectionStatus> GetConnectionStatusAsync(CancellationToken ct)
|
||||
{
|
||||
GetConnectionStatusCallCount++;
|
||||
return GetConnectionStatusThrows is not null
|
||||
? Task.FromException<ConnectionStatus>(GetConnectionStatusThrows)
|
||||
: Task.FromResult(ConnectionStatus);
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
DisposeCallCount++;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private static async IAsyncEnumerable<T> ToAsyncStream<T>(
|
||||
IReadOnlyList<T> items,
|
||||
Exception? error,
|
||||
[EnumeratorCancellation] CancellationToken ct)
|
||||
{
|
||||
if (error is not null)
|
||||
{
|
||||
throw error;
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
yield return item;
|
||||
}
|
||||
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests;
|
||||
|
||||
public sealed class ProjectSmokeTests
|
||||
{
|
||||
[Fact]
|
||||
public void GatewayClientSeam_IsReferenceable()
|
||||
{
|
||||
var t = typeof(IHistorianGatewayClient);
|
||||
Assert.Equal("IHistorianGatewayClient", t.Name);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<RootNamespace>ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit.v3"/>
|
||||
<PackageReference Include="Shouldly"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Drivers\ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway\ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user