Add XML documentation across gateway, worker, and .NET client
This commit is contained in:
@@ -4,6 +4,7 @@ namespace MxGateway.IntegrationTests.Galaxy;
|
||||
|
||||
public sealed class GalaxyRepositoryLiveTests
|
||||
{
|
||||
/// <summary>Verifies that the Galaxy Repository can establish a live connection to the ZB database.</summary>
|
||||
[LiveGalaxyRepositoryFact]
|
||||
[Trait("Category", "LiveGalaxy")]
|
||||
public async Task TestConnection_AgainstZb_Succeeds()
|
||||
@@ -15,6 +16,7 @@ public sealed class GalaxyRepositoryLiveTests
|
||||
Assert.True(ok, "TestConnectionAsync should return true against the ZB database.");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the last deploy time can be retrieved from the ZB database.</summary>
|
||||
[LiveGalaxyRepositoryFact]
|
||||
[Trait("Category", "LiveGalaxy")]
|
||||
public async Task GetLastDeployTime_AgainstZb_ReturnsTimestamp()
|
||||
@@ -26,6 +28,7 @@ public sealed class GalaxyRepositoryLiveTests
|
||||
Assert.NotNull(lastDeploy);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the hierarchy can be retrieved from the ZB database.</summary>
|
||||
[LiveGalaxyRepositoryFact]
|
||||
[Trait("Category", "LiveGalaxy")]
|
||||
public async Task GetHierarchy_AgainstZb_ReturnsObjects()
|
||||
@@ -43,6 +46,7 @@ public sealed class GalaxyRepositoryLiveTests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Verifies that object attributes can be retrieved from the ZB database.</summary>
|
||||
[LiveGalaxyRepositoryFact]
|
||||
[Trait("Category", "LiveGalaxy")]
|
||||
public async Task GetAttributes_AgainstZb_ReturnsAtLeastOneAttribute()
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
namespace MxGateway.IntegrationTests.Galaxy;
|
||||
|
||||
/// <summary>Fact attribute that skips tests unless live Galaxy Repository tests are explicitly enabled.</summary>
|
||||
public sealed class LiveGalaxyRepositoryFactAttribute : FactAttribute
|
||||
{
|
||||
/// <summary>Environment variable name to enable live Galaxy Repository tests.</summary>
|
||||
public const string EnableVariableName = "MXGATEWAY_RUN_LIVE_GALAXY_TESTS";
|
||||
/// <summary>Environment variable name for the Galaxy Repository connection string.</summary>
|
||||
public const string ConnectionStringVariableName = "MXGATEWAY_LIVE_GALAXY_CONN";
|
||||
|
||||
/// <summary>Initializes a new instance of the LiveGalaxyRepositoryFactAttribute class.</summary>
|
||||
public LiveGalaxyRepositoryFactAttribute()
|
||||
{
|
||||
if (!Enabled)
|
||||
@@ -13,12 +17,14 @@ public sealed class LiveGalaxyRepositoryFactAttribute : FactAttribute
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets a value indicating whether live Galaxy Repository tests are enabled.</summary>
|
||||
public static bool Enabled =>
|
||||
string.Equals(
|
||||
Environment.GetEnvironmentVariable(EnableVariableName),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
|
||||
/// <summary>Gets the Galaxy Repository connection string from environment or default.</summary>
|
||||
public static string ConnectionString =>
|
||||
Environment.GetEnvironmentVariable(ConnectionStringVariableName)
|
||||
?? "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;";
|
||||
|
||||
@@ -8,27 +8,33 @@ public static class IntegrationTestEnvironment
|
||||
public const string LiveMxAccessClientNameVariableName = "MXGATEWAY_LIVE_MXACCESS_CLIENT_NAME";
|
||||
public const string LiveMxAccessEventTimeoutSecondsVariableName = "MXGATEWAY_LIVE_MXACCESS_EVENT_TIMEOUT_SECONDS";
|
||||
|
||||
/// <summary>Gets whether live MXAccess tests are enabled.</summary>
|
||||
public static bool LiveMxAccessTestsEnabled =>
|
||||
string.Equals(
|
||||
Environment.GetEnvironmentVariable(LiveMxAccessVariableName),
|
||||
"1",
|
||||
StringComparison.Ordinal);
|
||||
|
||||
/// <summary>Gets the MXAccess item name for live tests.</summary>
|
||||
public static string LiveMxAccessItem =>
|
||||
GetOptionalEnvironmentVariable(
|
||||
LiveMxAccessItemVariableName,
|
||||
"TestChildObject.TestInt");
|
||||
|
||||
/// <summary>Gets the client name for live tests.</summary>
|
||||
public static string LiveMxAccessClientName =>
|
||||
GetOptionalEnvironmentVariable(
|
||||
LiveMxAccessClientNameVariableName,
|
||||
"MxGateway.IntegrationTests");
|
||||
|
||||
/// <summary>Gets the timeout for waiting on events in live tests.</summary>
|
||||
public static TimeSpan LiveMxAccessEventTimeout =>
|
||||
TimeSpan.FromSeconds(GetPositiveIntegerEnvironmentVariable(
|
||||
LiveMxAccessEventTimeoutSecondsVariableName,
|
||||
defaultValue: 15));
|
||||
|
||||
/// <summary>Resolves the path to the worker executable for live tests.</summary>
|
||||
/// <returns>Path to MxGateway.Worker.exe.</returns>
|
||||
public static string ResolveLiveMxAccessWorkerExecutablePath()
|
||||
{
|
||||
string? configuredPath = Environment.GetEnvironmentVariable(LiveMxAccessWorkerExecutableVariableName);
|
||||
@@ -74,6 +80,9 @@ public static class IntegrationTestEnvironment
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>Resolves the root directory of the repository by searching for .git and src directories.</summary>
|
||||
/// <param name="startDirectory">Starting directory to search from.</param>
|
||||
/// <returns>The repository root path, or the start directory if not found.</returns>
|
||||
internal static string ResolveRepositoryRoot(string startDirectory)
|
||||
{
|
||||
DirectoryInfo? directory = new(startDirectory);
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace MxGateway.IntegrationTests;
|
||||
|
||||
public sealed class IntegrationTestEnvironmentTests
|
||||
{
|
||||
/// <summary>Verifies that live MXAccess tests use correct environment variable name.</summary>
|
||||
[Fact]
|
||||
public void LiveMxAccessTests_AreOptInByEnvironmentVariable()
|
||||
{
|
||||
@@ -10,6 +11,7 @@ public sealed class IntegrationTestEnvironmentTests
|
||||
IntegrationTestEnvironment.LiveMxAccessVariableName);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that worker executable uses correct environment variable name.</summary>
|
||||
[Fact]
|
||||
public void LiveMxAccessWorkerExecutable_UsesDocumentedEnvironmentVariable()
|
||||
{
|
||||
@@ -18,6 +20,7 @@ public sealed class IntegrationTestEnvironmentTests
|
||||
IntegrationTestEnvironment.LiveMxAccessWorkerExecutableVariableName);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that repository root resolution accepts git worktree files.</summary>
|
||||
[Fact]
|
||||
public void ResolveRepositoryRoot_AcceptsGitWorktreeFile()
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
namespace MxGateway.IntegrationTests;
|
||||
|
||||
/// <summary>Marks an xUnit test as requiring installed MXAccess COM and live provider state.</summary>
|
||||
public sealed class LiveMxAccessFactAttribute : FactAttribute
|
||||
{
|
||||
/// <summary>Initializes the attribute, skipping the test unless the integration test environment variable is set.</summary>
|
||||
public LiveMxAccessFactAttribute()
|
||||
{
|
||||
if (!IntegrationTestEnvironment.LiveMxAccessTestsEnabled)
|
||||
|
||||
@@ -21,6 +21,9 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
private static readonly TimeSpan CommandTimeout = TimeSpan.FromSeconds(15);
|
||||
private static readonly TimeSpan StreamShutdownTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a gateway session can register, add item, advise, and stream events from live MXAccess.
|
||||
/// </summary>
|
||||
[LiveMxAccessFact]
|
||||
[Trait("Category", "LiveMxAccess")]
|
||||
public async Task GatewaySession_WithLiveWorker_RegistersAdvisesStreamsDataAndCloses()
|
||||
@@ -208,12 +211,21 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
$"Event value_type={dataChange.Value?.DataType} raw_status={dataChange.RawStatus}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test fixture that assembles the gateway service with a worker process factory for live MXAccess testing.
|
||||
/// </summary>
|
||||
private sealed class GatewayServiceFixture : IAsyncDisposable
|
||||
{
|
||||
private readonly GatewayMetrics _metrics = new();
|
||||
private readonly SessionRegistry _registry = new();
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the fixture with worker executable path, factory, and test output helper.
|
||||
/// </summary>
|
||||
/// <param name="workerExecutablePath">Path to the worker process executable.</param>
|
||||
/// <param name="processFactory">Factory for creating worker processes.</param>
|
||||
/// <param name="output">Test output helper for logging.</param>
|
||||
public GatewayServiceFixture(
|
||||
string workerExecutablePath,
|
||||
IWorkerProcessFactory processFactory,
|
||||
@@ -255,8 +267,14 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
_loggerFactory.CreateLogger<MxAccessGatewayService>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The assembled gateway service instance.
|
||||
/// </summary>
|
||||
public MxAccessGatewayService Service { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the fixture resources and closes all sessions.
|
||||
/// </summary>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
foreach (GatewaySession session in _registry.Snapshot())
|
||||
@@ -295,12 +313,18 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gathers messages written to a server stream for test inspection.
|
||||
/// </summary>
|
||||
private sealed class RecordingServerStreamWriter<T> : IServerStreamWriter<T>
|
||||
{
|
||||
private readonly object syncRoot = new();
|
||||
private readonly TaskCompletionSource<T> firstMessage = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly List<T> messages = [];
|
||||
|
||||
/// <summary>
|
||||
/// All messages that have been written to the stream.
|
||||
/// </summary>
|
||||
public IReadOnlyList<T> Messages
|
||||
{
|
||||
get
|
||||
@@ -312,8 +336,15 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inherited write options.
|
||||
/// </summary>
|
||||
public WriteOptions? WriteOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Records the message and completes the first-message task.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to write.</param>
|
||||
public Task WriteAsync(T message)
|
||||
{
|
||||
lock (syncRoot)
|
||||
@@ -325,12 +356,20 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the first message up to the specified timeout.
|
||||
/// </summary>
|
||||
/// <param name="timeout">The maximum time to wait.</param>
|
||||
/// <returns>The first message written to the stream.</returns>
|
||||
public async Task<T> WaitForFirstMessageAsync(TimeSpan timeout)
|
||||
{
|
||||
return await firstMessage.Task.WaitAsync(timeout).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mock server call context for testing gRPC calls.
|
||||
/// </summary>
|
||||
private sealed class TestServerCallContext(CancellationToken cancellationToken = default) : ServerCallContext
|
||||
{
|
||||
private readonly Metadata requestHeaders = [];
|
||||
@@ -339,43 +378,56 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
private Status status;
|
||||
private WriteOptions? writeOptions;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string MethodCore => "/mxaccess_gateway.v1.MxAccessGateway/Test";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string HostCore => "localhost";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string PeerCore => "ipv4:127.0.0.1:5000";
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override DateTime DeadlineCore => DateTime.UtcNow.AddMinutes(1);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Metadata RequestHeadersCore => requestHeaders;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override CancellationToken CancellationTokenCore => cancellationToken;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Metadata ResponseTrailersCore => responseTrailers;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Status StatusCore
|
||||
{
|
||||
get => status;
|
||||
set => status = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override WriteOptions? WriteOptionsCore
|
||||
{
|
||||
get => writeOptions;
|
||||
set => writeOptions = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override AuthContext AuthContextCore { get; } = new(
|
||||
string.Empty,
|
||||
new Dictionary<string, List<AuthProperty>>(StringComparer.Ordinal));
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IDictionary<object, object> UserStateCore => userState;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ContextPropagationToken CreatePropagationTokenCore(
|
||||
ContextPropagationOptions? options)
|
||||
{
|
||||
@@ -383,10 +435,14 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory that launches worker processes and records their outputs for testing.
|
||||
/// </summary>
|
||||
private sealed class TestWorkerProcessFactory(ITestOutputHelper output) : IWorkerProcessFactory
|
||||
{
|
||||
private readonly ConcurrentBag<TestWorkerProcess> processes = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
public IWorkerProcess Start(ProcessStartInfo startInfo)
|
||||
{
|
||||
startInfo.RedirectStandardError = true;
|
||||
@@ -418,6 +474,7 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
return workerProcess;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task WaitForProcessesAsync(TimeSpan timeout)
|
||||
{
|
||||
foreach (TestWorkerProcess process in processes)
|
||||
@@ -445,57 +502,77 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adapter wrapping a System.Diagnostics.Process as IWorkerProcess for testing.
|
||||
/// </summary>
|
||||
private sealed class TestWorkerProcess(Process process) : IWorkerProcess
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int Id => process.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasExited => process.HasExited;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int? ExitCode => process.HasExited ? process.ExitCode : null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask WaitForExitAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Kill(bool entireProcessTree)
|
||||
{
|
||||
process.Kill(entireProcessTree);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
process.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logger provider that writes all output to the test output helper.
|
||||
/// </summary>
|
||||
private sealed class TestOutputLoggerProvider(ITestOutputHelper output) : ILoggerProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new TestOutputLogger(output, categoryName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logger that writes messages to the test output helper.
|
||||
/// </summary>
|
||||
private sealed class TestOutputLogger(
|
||||
ITestOutputHelper output,
|
||||
string categoryName) : ILogger
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IDisposable? BeginScope<TState>(TState state)
|
||||
where TState : notnull
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return logLevel >= LogLevel.Information;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Log<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
|
||||
Reference in New Issue
Block a user