Developer 02 3b0428130a feat(AuthClient): implementiert IHostedService.
- Aktualisiert um AuthClient zu den Diensten als Hosted Service hinzuzufügen.
 - Der zugehörige Unit-Test wurde aktualisiert, um IHost anstelle von IServiceProvider zu verwenden, um gehostete Dienste testen zu können.
2025-03-11 15:53:37 +01:00

278 lines
8.3 KiB
C#

using DigitalData.Auth.Abstractions;
using DigitalData.Auth.API.Hubs;
using DigitalData.Auth.Client;
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.RSAKey;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace DigitalData.Auth.Tests.Client;
// TODO: The test checks if the services are working. Performance measurement is ignored. Update it to measure performance as well.
[TestFixture]
public class AuthClientTests
{
private string _hubUrl;
private Func<Action<ClientParams>, IHost> Build;
private WebApplication? _app;
private int _port;
private readonly Queue<IAsyncDisposable> _disposableAsync = new();
private static int AvailablePort
{
get
{
using var listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Loopback, 0);
listener.Start();
int port = ((System.Net.IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
}
private static IEnumerable<RSATokenDescriptor> CreatetokenDescriptors()
{
return [
new()
{
Issuer = "Foo",
Audience = "Bar",
Lifetime = new TimeSpan(1, 0, 0),
Content = Instance.RSAFactory.CreatePrivateKeyPem()
}
];
}
private WebApplication CreateWebApplication(int port)
{
// Create builder and add SignalR service
var builder = WebApplication.CreateBuilder();
builder.Services.AddSignalR();
builder.Services.AddCryptoFactory(new CryptoFactoryParams()
{
PemDirectory = "/",
Decryptors = [new RSADecryptor()],
TokenDescriptors = CreatetokenDescriptors()
});
builder.Services.AddMemoryCache();
// Listen AvailablePort and map hub
var app = builder.Build();
var url = $"http://localhost:{port}";
var hubRoute = "/auth-hub";
_hubUrl = url + hubRoute;
app.Urls.Add(url);
app.MapHub<AuthHub>(hubRoute);
app.Start();
_disposableAsync.Enqueue(app);
return app;
}
private static CryptoFactoryParams GetCryptoFactoryParamsOf(WebApplication application) => application
.Services.GetRequiredService<IOptions<CryptoFactoryParams>>().Value;
[SetUp]
public void Setup()
{
Build = options =>
{
var host = Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddAuthHubClient(options: options)
.BuildServiceProvider();
})
.Build();
if(host is IAsyncDisposable disposable)
_disposableAsync.Enqueue(disposable);
return host;
};
// Create and run test server
_port = AvailablePort;
_app = CreateWebApplication(_port);
}
[TearDown]
public async Task TearDown()
{
// Stop test server
if (_app is not null)
{
await _app.StopAsync();
await _app.DisposeAsync();
Console.WriteLine("Test server stopped.");
}
while (_disposableAsync.Count > 0)
{
var disposable = _disposableAsync.Dequeue();
if (disposable is IHost host)
await host.StopAsync();
await disposable.DisposeAsync();
}
}
[Test]
[TestCase(true, false, true, TestName = "ShouldStart_WhenHostStartsEvenIfClientDoesNot")]
[TestCase(false, true, true, TestName = "ShouldStart_WhenClientStartsEvenIfHostDoesNot")]
public async Task StartAsync_ShouldConnectSuccessfully(bool startHost, bool startClient, bool expectedIsConnected)
{
// Arrange
var host = Build(opt => opt.Url = _hubUrl);
var client = host.Services.GetRequiredService<IAuthClient>();
// Act
if (startHost)
await host.StartAsync();
if (startClient)
await client.StartAsync();
// Assert
Assert.That(client.IsConnected, Is.EqualTo(expectedIsConnected));
}
[Test]
public async Task ReceiveMessage_ShouldCallOnMessageReceived()
{
// Arrange
string rcv_issuer = string.Empty;
string rcv_audience = string.Empty;
string rcv_key = string.Empty;
// Sender client
var sender_host = Build(opt => opt.Url = _hubUrl);
var sender_client = sender_host.Services.GetRequiredService<IAuthClient>();
await sender_client.StartAsync();
// Receiver client
var receiver_host = Build(opt =>
{
opt.Url = _hubUrl;
opt.OnPublicKeyReceived += (client, issuer, audience, key, logger) =>
{
rcv_issuer = issuer;
rcv_audience = audience;
rcv_key = key;
};
});
var client_receiver = receiver_host.Services.GetRequiredService<IAuthClient>();
await client_receiver.StartAsync();
string issuer = "issuer";
string audience = "audience";
string key = "key";
// Act
await sender_client.SendPublicKeyAsync(issuer, audience, key);
// delay fort getting answer
await Task.Delay(2000);
// Assert
Assert.Multiple(() =>
{
Assert.That(rcv_issuer, Is.EqualTo(issuer));
Assert.That(rcv_audience, Is.EqualTo(audience));
Assert.That(rcv_key, Is.EqualTo(key));
});
}
[Test]
public async Task GetPublicKey_ShouldReturnExpectedPublicKey()
{
// Arrange
string? publicKey = null;
var host = Build(opt =>
{
opt.Url = _hubUrl;
opt.OnPublicKeyReceived += (client, issuer, audience, key, logger) => publicKey = key;
});
var client = host.Services.GetRequiredService<IAuthClient>();
await client.StartAsync();
var expectedPublicKey = GetCryptoFactoryParamsOf(_app).TokenDescriptors.Get("Foo", "Bar").PublicKey.Content;
// Act
await client.GetPublicKeyAsync("Foo", "Bar");
// wait for network
await Task.Delay(2000);
// Assert
Assert.Multiple(() =>
{
Assert.That(publicKey, Is.Not.Null);
Assert.That(publicKey, Is.EqualTo(expectedPublicKey));
});
}
[Test]
public async Task StartAsync_ShouldUpdateAllPublicKey()
{
// Arrange
var publicKey = new ClientPublicKey() { Issuer = "Foo", Audience = "Bar" };
var host = Build(opt =>
{
opt.Url = _hubUrl;
opt.PublicKeys.Add(publicKey);
});
var client = host.Services.GetRequiredService<IAuthClient>();
await client.StartAsync();
// Act
var expectedPublicKey = GetCryptoFactoryParamsOf(_app).TokenDescriptors.Get("Foo", "Bar").PublicKey;
// wait for network
await Task.Delay(2000);
// Assert
Assert.That(publicKey.Content, Is.EqualTo(expectedPublicKey.Content));
}
[Test]
public async Task Reconnected_ShouldUpdateAllPublicKey()
{
// Arrange
var publicKey = new ClientPublicKey() { Issuer = "Foo", Audience = "Bar" };
var host = Build(opt =>
{
opt.Url = _hubUrl;
opt.PublicKeys.Add(publicKey);
opt.RetryDelay = new TimeSpan(0, 0, 1);
});
var client = host.Services.GetRequiredService<IAuthClient>();
await client.StartAsync();
// Act
CancellationToken cancellationToken = default;
await _app!.StopAsync(cancellationToken);
_app = null;
var newApp = CreateWebApplication(_port);
var expectedPublicKey = GetCryptoFactoryParamsOf(newApp).TokenDescriptors.Get("Foo", "Bar").PublicKey;
// wait for network
await Task.Delay(5000);
// Assert
Assert.Multiple(() =>
{
Assert.That(cancellationToken.IsCancellationRequested, Is.False);
Assert.That(publicKey.Content, Is.EqualTo(expectedPublicKey.Content));
});
}
}