Add unit tests for Router configuration and transport layers
- Implemented tests for RouterConfig, RoutingOptions, StaticInstanceConfig, and RouterConfigOptions to ensure default values are set correctly. - Added tests for RouterConfigProvider to validate configurations and ensure defaults are returned when no file is specified. - Created tests for ConfigValidationResult to check success and error scenarios. - Developed tests for ServiceCollectionExtensions to verify service registration for RouterConfig. - Introduced UdpTransportTests to validate serialization, connection, request-response, and error handling in UDP transport. - Added scripts for signing authority gaps and hashing DevPortal SDK snippets.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Transport.Tcp\StellaOps.Router.Transport.Tcp.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Common\StellaOps.Router.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,199 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Router.Common.Enums;
|
||||
using StellaOps.Router.Common.Models;
|
||||
using StellaOps.Router.Transport.Tcp;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Router.Transport.Tcp.Tests;
|
||||
|
||||
public class TcpTransportOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultOptions_HaveCorrectValues()
|
||||
{
|
||||
var options = new TcpTransportOptions();
|
||||
|
||||
Assert.Equal(5100, options.Port);
|
||||
Assert.Equal(64 * 1024, options.ReceiveBufferSize);
|
||||
Assert.Equal(64 * 1024, options.SendBufferSize);
|
||||
Assert.Equal(TimeSpan.FromSeconds(30), options.KeepAliveInterval);
|
||||
Assert.Equal(TimeSpan.FromSeconds(10), options.ConnectTimeout);
|
||||
Assert.Equal(10, options.MaxReconnectAttempts);
|
||||
Assert.Equal(TimeSpan.FromMinutes(1), options.MaxReconnectBackoff);
|
||||
Assert.Equal(16 * 1024 * 1024, options.MaxFrameSize);
|
||||
}
|
||||
}
|
||||
|
||||
public class FrameProtocolTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task WriteAndReadFrame_RoundTrip()
|
||||
{
|
||||
// Arrange
|
||||
using var stream = new MemoryStream();
|
||||
var originalFrame = new Frame
|
||||
{
|
||||
Type = FrameType.Request,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = new byte[] { 1, 2, 3, 4, 5 }
|
||||
};
|
||||
|
||||
// Act - Write
|
||||
await FrameProtocol.WriteFrameAsync(stream, originalFrame, CancellationToken.None);
|
||||
|
||||
// Act - Read
|
||||
stream.Position = 0;
|
||||
var readFrame = await FrameProtocol.ReadFrameAsync(stream, 1024 * 1024, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(readFrame);
|
||||
Assert.Equal(originalFrame.Type, readFrame.Type);
|
||||
Assert.Equal(originalFrame.CorrelationId, readFrame.CorrelationId);
|
||||
Assert.Equal(originalFrame.Payload.ToArray(), readFrame.Payload.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteAndReadFrame_EmptyPayload()
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
var originalFrame = new Frame
|
||||
{
|
||||
Type = FrameType.Cancel,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = ReadOnlyMemory<byte>.Empty
|
||||
};
|
||||
|
||||
await FrameProtocol.WriteFrameAsync(stream, originalFrame, CancellationToken.None);
|
||||
|
||||
stream.Position = 0;
|
||||
var readFrame = await FrameProtocol.ReadFrameAsync(stream, 1024 * 1024, CancellationToken.None);
|
||||
|
||||
Assert.NotNull(readFrame);
|
||||
Assert.Equal(FrameType.Cancel, readFrame.Type);
|
||||
Assert.Empty(readFrame.Payload.ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadFrame_ReturnsNullOnEmptyStream()
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
|
||||
var result = await FrameProtocol.ReadFrameAsync(stream, 1024 * 1024, CancellationToken.None);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadFrame_ThrowsOnOversizedFrame()
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
var largeFrame = new Frame
|
||||
{
|
||||
Type = FrameType.Request,
|
||||
CorrelationId = Guid.NewGuid().ToString("N"),
|
||||
Payload = new byte[1000]
|
||||
};
|
||||
|
||||
await FrameProtocol.WriteFrameAsync(stream, largeFrame, CancellationToken.None);
|
||||
|
||||
stream.Position = 0;
|
||||
|
||||
// Max frame size is smaller than the written frame
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => FrameProtocol.ReadFrameAsync(stream, 100, CancellationToken.None));
|
||||
}
|
||||
}
|
||||
|
||||
public class PendingRequestTrackerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task TrackRequest_CompletesWithResponse()
|
||||
{
|
||||
using var tracker = new PendingRequestTracker();
|
||||
var correlationId = Guid.NewGuid();
|
||||
var expectedResponse = new Frame
|
||||
{
|
||||
Type = FrameType.Response,
|
||||
CorrelationId = correlationId.ToString("N"),
|
||||
Payload = ReadOnlyMemory<byte>.Empty
|
||||
};
|
||||
|
||||
var responseTask = tracker.TrackRequest(correlationId, CancellationToken.None);
|
||||
Assert.False(responseTask.IsCompleted);
|
||||
|
||||
tracker.CompleteRequest(correlationId, expectedResponse);
|
||||
|
||||
var response = await responseTask;
|
||||
Assert.Equal(expectedResponse.Type, response.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TrackRequest_CancelsOnTokenCancellation()
|
||||
{
|
||||
using var tracker = new PendingRequestTracker();
|
||||
using var cts = new CancellationTokenSource();
|
||||
var correlationId = Guid.NewGuid();
|
||||
|
||||
var responseTask = tracker.TrackRequest(correlationId, cts.Token);
|
||||
|
||||
cts.Cancel();
|
||||
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(() => responseTask);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Count_ReturnsCorrectValue()
|
||||
{
|
||||
using var tracker = new PendingRequestTracker();
|
||||
|
||||
Assert.Equal(0, tracker.Count);
|
||||
|
||||
_ = tracker.TrackRequest(Guid.NewGuid(), CancellationToken.None);
|
||||
_ = tracker.TrackRequest(Guid.NewGuid(), CancellationToken.None);
|
||||
|
||||
Assert.Equal(2, tracker.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelAll_CancelsAllPendingRequests()
|
||||
{
|
||||
using var tracker = new PendingRequestTracker();
|
||||
var task1 = tracker.TrackRequest(Guid.NewGuid(), CancellationToken.None);
|
||||
var task2 = tracker.TrackRequest(Guid.NewGuid(), CancellationToken.None);
|
||||
|
||||
tracker.CancelAll();
|
||||
|
||||
Assert.True(task1.IsCanceled || task1.IsFaulted);
|
||||
Assert.True(task2.IsCanceled || task2.IsFaulted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FailRequest_SetsException()
|
||||
{
|
||||
using var tracker = new PendingRequestTracker();
|
||||
var correlationId = Guid.NewGuid();
|
||||
var task = tracker.TrackRequest(correlationId, CancellationToken.None);
|
||||
|
||||
tracker.FailRequest(correlationId, new InvalidOperationException("Test error"));
|
||||
|
||||
Assert.True(task.IsFaulted);
|
||||
Assert.IsType<InvalidOperationException>(task.Exception?.InnerException);
|
||||
}
|
||||
}
|
||||
|
||||
public class TcpTransportServerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task StartAsync_StartsListening()
|
||||
{
|
||||
var options = Options.Create(new TcpTransportOptions { Port = 0 }); // Port 0 = auto-assign
|
||||
await using var server = new TcpTransportServer(options, NullLogger<TcpTransportServer>.Instance);
|
||||
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(0, server.ConnectionCount);
|
||||
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Transport.Tls\StellaOps.Router.Transport.Tls.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Common\StellaOps.Router.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,302 @@
|
||||
using System.Net;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Router.Transport.Tls;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Router.Transport.Tls.Tests;
|
||||
|
||||
public class TlsTransportOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultOptions_HaveCorrectValues()
|
||||
{
|
||||
var options = new TlsTransportOptions();
|
||||
|
||||
Assert.Equal(5101, options.Port);
|
||||
Assert.Equal(64 * 1024, options.ReceiveBufferSize);
|
||||
Assert.Equal(64 * 1024, options.SendBufferSize);
|
||||
Assert.Equal(TimeSpan.FromSeconds(30), options.KeepAliveInterval);
|
||||
Assert.Equal(TimeSpan.FromSeconds(10), options.ConnectTimeout);
|
||||
Assert.Equal(10, options.MaxReconnectAttempts);
|
||||
Assert.Equal(TimeSpan.FromMinutes(1), options.MaxReconnectBackoff);
|
||||
Assert.Equal(16 * 1024 * 1024, options.MaxFrameSize);
|
||||
Assert.False(options.RequireClientCertificate);
|
||||
Assert.False(options.AllowSelfSigned);
|
||||
Assert.False(options.CheckCertificateRevocation);
|
||||
Assert.Equal(SslProtocols.Tls12 | SslProtocols.Tls13, options.EnabledProtocols);
|
||||
}
|
||||
}
|
||||
|
||||
public class CertificateLoaderTests
|
||||
{
|
||||
[Fact]
|
||||
public void LoadServerCertificate_WithDirectCertificate_ReturnsCertificate()
|
||||
{
|
||||
var cert = CreateSelfSignedCertificate("TestServer");
|
||||
var options = new TlsTransportOptions
|
||||
{
|
||||
ServerCertificate = cert
|
||||
};
|
||||
|
||||
var loaded = CertificateLoader.LoadServerCertificate(options);
|
||||
|
||||
Assert.Same(cert, loaded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadServerCertificate_WithNoCertificate_ThrowsException()
|
||||
{
|
||||
var options = new TlsTransportOptions();
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => CertificateLoader.LoadServerCertificate(options));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadClientCertificate_WithNoCertificate_ReturnsNull()
|
||||
{
|
||||
var options = new TlsTransportOptions();
|
||||
|
||||
var result = CertificateLoader.LoadClientCertificate(options);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadClientCertificate_WithDirectCertificate_ReturnsCertificate()
|
||||
{
|
||||
var cert = CreateSelfSignedCertificate("TestClient");
|
||||
var options = new TlsTransportOptions
|
||||
{
|
||||
ClientCertificate = cert
|
||||
};
|
||||
|
||||
var loaded = CertificateLoader.LoadClientCertificate(options);
|
||||
|
||||
Assert.Same(cert, loaded);
|
||||
}
|
||||
|
||||
private static X509Certificate2 CreateSelfSignedCertificate(string subject)
|
||||
{
|
||||
using var rsa = RSA.Create(2048);
|
||||
var request = new CertificateRequest(
|
||||
$"CN={subject}",
|
||||
rsa,
|
||||
HashAlgorithmName.SHA256,
|
||||
RSASignaturePadding.Pkcs1);
|
||||
|
||||
request.CertificateExtensions.Add(
|
||||
new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, critical: true));
|
||||
|
||||
var certificate = request.CreateSelfSigned(
|
||||
DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
DateTimeOffset.UtcNow.AddYears(1));
|
||||
|
||||
// Export and re-import to get the private key
|
||||
var pfxBytes = certificate.Export(X509ContentType.Pfx);
|
||||
return X509CertificateLoader.LoadPkcs12(
|
||||
pfxBytes,
|
||||
null,
|
||||
X509KeyStorageFlags.MachineKeySet);
|
||||
}
|
||||
}
|
||||
|
||||
public class TlsTransportServerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task StartAsync_WithValidCertificate_StartsListening()
|
||||
{
|
||||
var cert = CreateSelfSignedCertificate("TestServer");
|
||||
var options = Options.Create(new TlsTransportOptions
|
||||
{
|
||||
Port = 0,
|
||||
ServerCertificate = cert
|
||||
});
|
||||
|
||||
await using var server = new TlsTransportServer(options, NullLogger<TlsTransportServer>.Instance);
|
||||
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(0, server.ConnectionCount);
|
||||
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartAsync_WithNoCertificate_ThrowsException()
|
||||
{
|
||||
var options = Options.Create(new TlsTransportOptions { Port = 0 });
|
||||
await using var server = new TlsTransportServer(options, NullLogger<TlsTransportServer>.Instance);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
server.StartAsync(CancellationToken.None));
|
||||
}
|
||||
|
||||
private static X509Certificate2 CreateSelfSignedCertificate(string subject)
|
||||
{
|
||||
using var rsa = RSA.Create(2048);
|
||||
var request = new CertificateRequest(
|
||||
$"CN={subject}",
|
||||
rsa,
|
||||
HashAlgorithmName.SHA256,
|
||||
RSASignaturePadding.Pkcs1);
|
||||
|
||||
request.CertificateExtensions.Add(
|
||||
new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, critical: true));
|
||||
|
||||
request.CertificateExtensions.Add(
|
||||
new X509EnhancedKeyUsageExtension(
|
||||
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") },
|
||||
critical: true));
|
||||
|
||||
var certificate = request.CreateSelfSigned(
|
||||
DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
DateTimeOffset.UtcNow.AddYears(1));
|
||||
|
||||
var pfxBytes = certificate.Export(X509ContentType.Pfx);
|
||||
return X509CertificateLoader.LoadPkcs12(
|
||||
pfxBytes,
|
||||
null,
|
||||
X509KeyStorageFlags.MachineKeySet);
|
||||
}
|
||||
}
|
||||
|
||||
public class TlsConnectionTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConnectionId_IsSet()
|
||||
{
|
||||
// This is more of a documentation test since TlsConnection
|
||||
// requires actual TcpClient and SslStream instances
|
||||
var options = new TlsTransportOptions();
|
||||
|
||||
Assert.NotNull(options);
|
||||
}
|
||||
}
|
||||
|
||||
public class TlsIntegrationTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ServerAndClient_CanEstablishConnection()
|
||||
{
|
||||
// Create self-signed server certificate
|
||||
var serverCert = CreateSelfSignedServerCertificate("localhost");
|
||||
|
||||
var serverOptions = Options.Create(new TlsTransportOptions
|
||||
{
|
||||
Port = 0, // Auto-assign
|
||||
ServerCertificate = serverCert,
|
||||
RequireClientCertificate = false
|
||||
});
|
||||
|
||||
await using var server = new TlsTransportServer(serverOptions, NullLogger<TlsTransportServer>.Instance);
|
||||
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
Assert.Equal(0, server.ConnectionCount);
|
||||
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ServerWithMtls_RequiresClientCertificate()
|
||||
{
|
||||
var serverCert = CreateSelfSignedServerCertificate("localhost");
|
||||
|
||||
var serverOptions = Options.Create(new TlsTransportOptions
|
||||
{
|
||||
Port = 0,
|
||||
ServerCertificate = serverCert,
|
||||
RequireClientCertificate = true,
|
||||
AllowSelfSigned = true
|
||||
});
|
||||
|
||||
await using var server = new TlsTransportServer(serverOptions, NullLogger<TlsTransportServer>.Instance);
|
||||
|
||||
await server.StartAsync(CancellationToken.None);
|
||||
|
||||
Assert.True(serverOptions.Value.RequireClientCertificate);
|
||||
|
||||
await server.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
private static X509Certificate2 CreateSelfSignedServerCertificate(string hostname)
|
||||
{
|
||||
using var rsa = RSA.Create(2048);
|
||||
var request = new CertificateRequest(
|
||||
$"CN={hostname}",
|
||||
rsa,
|
||||
HashAlgorithmName.SHA256,
|
||||
RSASignaturePadding.Pkcs1);
|
||||
|
||||
// Key usage for server auth
|
||||
request.CertificateExtensions.Add(
|
||||
new X509KeyUsageExtension(
|
||||
X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment,
|
||||
critical: true));
|
||||
|
||||
// Server authentication EKU
|
||||
request.CertificateExtensions.Add(
|
||||
new X509EnhancedKeyUsageExtension(
|
||||
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") },
|
||||
critical: true));
|
||||
|
||||
// Subject Alternative Name
|
||||
var sanBuilder = new SubjectAlternativeNameBuilder();
|
||||
sanBuilder.AddDnsName(hostname);
|
||||
sanBuilder.AddIpAddress(IPAddress.Loopback);
|
||||
request.CertificateExtensions.Add(sanBuilder.Build());
|
||||
|
||||
var certificate = request.CreateSelfSigned(
|
||||
DateTimeOffset.UtcNow.AddMinutes(-5),
|
||||
DateTimeOffset.UtcNow.AddYears(1));
|
||||
|
||||
var pfxBytes = certificate.Export(X509ContentType.Pfx);
|
||||
return X509CertificateLoader.LoadPkcs12(
|
||||
pfxBytes,
|
||||
null,
|
||||
X509KeyStorageFlags.MachineKeySet);
|
||||
}
|
||||
}
|
||||
|
||||
public class ServiceCollectionExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddTlsTransportServer_RegistersServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
|
||||
services.AddTlsTransportServer(options =>
|
||||
{
|
||||
options.Port = 5101;
|
||||
});
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
var server = provider.GetService<TlsTransportServer>();
|
||||
|
||||
Assert.NotNull(server);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTlsTransportClient_RegistersServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
|
||||
services.AddTlsTransportClient(options =>
|
||||
{
|
||||
options.Host = "localhost";
|
||||
options.Port = 5101;
|
||||
});
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
var client = provider.GetService<TlsTransportClient>();
|
||||
|
||||
Assert.NotNull(client);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user