feat(historian-gateway): scaffold Gateway driver project + consume client package
Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
@@ -120,5 +120,7 @@
|
|||||||
<PackageVersion Include="ZB.MOM.WW.Auth.AspNetCore" Version="0.1.1" />
|
<PackageVersion Include="ZB.MOM.WW.Auth.AspNetCore" Version="0.1.1" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.Audit" Version="0.1.0" />
|
<PackageVersion Include="ZB.MOM.WW.Audit" Version="0.1.0" />
|
||||||
<PackageVersion Include="ZB.MOM.WW.Theme" Version="0.3.1" />
|
<PackageVersion Include="ZB.MOM.WW.Theme" Version="0.3.1" />
|
||||||
|
<PackageVersion Include="ZB.MOM.WW.HistorianGateway.Client" Version="0.1.0" />
|
||||||
|
<PackageVersion Include="ZB.MOM.WW.HistorianGateway.Contracts" Version="0.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
<package pattern="ZB.MOM.WW.Auth.*" />
|
<package pattern="ZB.MOM.WW.Auth.*" />
|
||||||
<package pattern="ZB.MOM.WW.Audit" />
|
<package pattern="ZB.MOM.WW.Audit" />
|
||||||
<package pattern="ZB.MOM.WW.Theme" />
|
<package pattern="ZB.MOM.WW.Theme" />
|
||||||
|
<package pattern="ZB.MOM.WW.HistorianGateway.Contracts" />
|
||||||
|
<package pattern="ZB.MOM.WW.HistorianGateway.Client" />
|
||||||
</packageSource>
|
</packageSource>
|
||||||
</packageSourceMapping>
|
</packageSourceMapping>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Contracts/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Contracts.csproj" />
|
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Contracts/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Contracts.csproj" />
|
||||||
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.csproj" />
|
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.csproj" />
|
||||||
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.csproj" />
|
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.csproj" />
|
||||||
|
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.csproj" />
|
||||||
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts.csproj" />
|
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts.csproj" />
|
||||||
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus/ZB.MOM.WW.OtOpcUa.Driver.Modbus.csproj" />
|
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus/ZB.MOM.WW.OtOpcUa.Driver.Modbus.csproj" />
|
||||||
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing.csproj" />
|
<Project Path="src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing.csproj" />
|
||||||
@@ -86,6 +87,7 @@
|
|||||||
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.csproj" />
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.csproj" />
|
||||||
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser.Tests/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser.Tests.csproj" />
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser.Tests/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser.Tests.csproj" />
|
||||||
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests.csproj" />
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests.csproj" />
|
||||||
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests.csproj" />
|
||||||
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Tests.csproj" />
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Tests/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Tests.csproj" />
|
||||||
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests.csproj" />
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests.csproj" />
|
||||||
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing.Tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing.Tests.csproj" />
|
<Project Path="tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing.Tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing.Tests.csproj" />
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using ZB.MOM.WW.HistorianGateway.Contracts.Grpc;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstraction over the HistorianGateway gRPC client surface consumed by the OtOpcUa historian
|
||||||
|
/// backend driver. Proto-typed (the wire contract lives in
|
||||||
|
/// <c>ZB.MOM.WW.HistorianGateway.Contracts.Grpc</c>); the concrete adapter wrapping
|
||||||
|
/// <c>HistorianGatewayClient</c> is supplied by a later task. The seam exists so the driver and
|
||||||
|
/// its tests can depend on a fake without a live gateway.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHistorianGatewayClient : IAsyncDisposable
|
||||||
|
{
|
||||||
|
/// <summary>Streams raw historian samples for a tag over a time window.</summary>
|
||||||
|
IAsyncEnumerable<HistorianSample> ReadRawAsync(
|
||||||
|
string tag,
|
||||||
|
DateTime startUtc,
|
||||||
|
DateTime endUtc,
|
||||||
|
int maxValues,
|
||||||
|
CancellationToken ct);
|
||||||
|
|
||||||
|
/// <summary>Streams aggregate samples for a tag using the given retrieval mode and interval.</summary>
|
||||||
|
IAsyncEnumerable<HistorianAggregateSample> ReadAggregateAsync(
|
||||||
|
string tag,
|
||||||
|
DateTime startUtc,
|
||||||
|
DateTime endUtc,
|
||||||
|
RetrievalMode mode,
|
||||||
|
TimeSpan interval,
|
||||||
|
CancellationToken ct);
|
||||||
|
|
||||||
|
/// <summary>Reads the samples nearest to each of the requested timestamps (unary).</summary>
|
||||||
|
Task<IReadOnlyList<HistorianSample>> ReadAtTimeAsync(
|
||||||
|
string tag,
|
||||||
|
IReadOnlyList<DateTime> timestampsUtc,
|
||||||
|
CancellationToken ct);
|
||||||
|
|
||||||
|
/// <summary>Streams historian events over a window, optionally filtered to a single source name.</summary>
|
||||||
|
IAsyncEnumerable<HistorianEvent> ReadEventsAsync(
|
||||||
|
string? sourceName,
|
||||||
|
DateTime startUtc,
|
||||||
|
DateTime endUtc,
|
||||||
|
int maxEvents,
|
||||||
|
CancellationToken ct);
|
||||||
|
|
||||||
|
/// <summary>Writes live values for a tag through the gateway's SQL live-write path.</summary>
|
||||||
|
Task<WriteAck> WriteLiveValuesAsync(
|
||||||
|
string tag,
|
||||||
|
IReadOnlyList<HistorianLiveValue> values,
|
||||||
|
CancellationToken ct);
|
||||||
|
|
||||||
|
/// <summary>Sends a single historian event.</summary>
|
||||||
|
Task<WriteAck> SendEventAsync(HistorianEvent evt, CancellationToken ct);
|
||||||
|
|
||||||
|
/// <summary>Ensures the supplied tag definitions exist (create-or-update).</summary>
|
||||||
|
Task<TagOperationResults> EnsureTagsAsync(
|
||||||
|
IReadOnlyList<HistorianTagDefinition> definitions,
|
||||||
|
CancellationToken ct);
|
||||||
|
|
||||||
|
/// <summary>Probes gateway/historian reachability.</summary>
|
||||||
|
Task<bool> ProbeAsync(CancellationToken ct);
|
||||||
|
|
||||||
|
/// <summary>Reads the gateway's current historian connection status.</summary>
|
||||||
|
Task<ConnectionStatus> GetConnectionStatusAsync(CancellationToken ct);
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
|
<RootNamespace>ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Core\ZB.MOM.WW.OtOpcUa.Core.Abstractions\ZB.MOM.WW.OtOpcUa.Core.Abstractions.csproj"/>
|
||||||
|
<ProjectReference Include="..\..\Core\ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian\ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ZB.MOM.WW.HistorianGateway.Client" />
|
||||||
|
<PackageReference Include="ZB.MOM.WW.HistorianGateway.Contracts" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+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