using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Conformance;
///
/// One historian backend under test. The conformance suite talks to a backend only through this contract
/// (plus Core.Abstractions), so the same hand-computed expectations run against SQL Server and QuestDB.
/// Each instance owns an isolated store (a fresh schema / table set) so tests never see each other's data.
///
public interface IConformanceBackend : IAsyncDisposable
{
/// Storage timestamp resolution in .NET ticks: 1 for SQL Server (100 ns), 10 for QuestDB (µs).
long ResolutionTicks { get; }
/// Truncates a timestamp to the backend's storage resolution (UTC kind). Every timestamp assertion goes through it.
/// The timestamp.
/// The value the backend stores/serves for .
DateTime Round(DateTime t) => new(t.Ticks - t.Ticks % ResolutionTicks, DateTimeKind.Utc);
/// A fresh data source (fresh catalog cache) over this backend's store.
/// Its "now"; null means the system clock.
IHistorianDataSource CreateDataSource(TimeProvider? clock = null);
/// A fresh live-value writer over this backend's store.
/// The clock used to stamp values that carry no timestamp; null means the system clock.
IHistorianValueWriter CreateValueWriter(Func? utcNow = null);
/// A fresh history updater over this backend's store.
IHistorianDataUpdater CreateUpdater();
///
/// Writes rows directly in the backend's schema (never through the writer or updater), status verbatim, creating the
/// tag if needed. The stored server timestamp of each row is its source time + 1 s, so tests can tell them apart.
///
/// The tag name.
/// The rows to store.
Task SeedAsync(string tag, params (DateTime T, HistorianValue V, uint Status)[] samples);
///
/// Live (non-deleted) rows for a tag, ascending by time, read directly from storage. The status is returned
/// VERBATIM as stored — there is no read-side clamp (an Empty row stored non-Bad reads back non-Bad here).
///
/// The tag name.
Task> ReadBackAsync(string tag);
/// Whether a tag with exactly this name exists in storage (no truncation of long names).
/// The tag name.
Task TagExistsAsync(string tag);
/// The data type the backend recorded for a tag, or null when the tag does not exist.
/// The tag name.
Task TagDataTypeAsync(string tag);
}
///
/// Base of every conformance class. A backend's test project subclasses each abstract suite class once,
/// supplying , which must call its fixture's skip gate so the tests report Skipped
/// (not Failed) when the backend is unavailable.
///
public abstract class ConformanceBase : IAsyncLifetime
{
/// Good.
protected const uint Good = ConformanceData.Good;
/// Bad (generic).
protected const uint Bad = ConformanceData.Bad;
/// BadNoCommunication.
protected const uint BadNoCommunication = ConformanceData.BadNoCommunication;
/// Uncertain (generic).
protected const uint Uncertain = ConformanceData.Uncertain;
/// The backend under test for the current test.
protected IConformanceBackend B { get; private set; } = null!;
/// Creates this test's isolated backend; calls the fixture's skip gate first.
protected abstract Task CreateBackendAsync();
/// T0 + seconds.
protected static DateTime At(double s) => ConformanceData.At(s);
///
public async ValueTask InitializeAsync() => B = await CreateBackendAsync();
///
public async ValueTask DisposeAsync()
{
if (B is not null) await B.DisposeAsync();
}
}