2
0
mirror of https://github.com/esiur/esiur-dotnet.git synced 2026-06-13 22:48:42 +00:00
Files
esiur-dotnet/Tests/Unit/Integration/IntegrationHarness.cs
T
2026-06-03 13:02:56 +03:00

105 lines
3.8 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using Esiur.Core;
using Esiur.Protocol;
using Esiur.Resource;
using Esiur.Security.Authority;
using Esiur.Security.Authority.Providers;
using Esiur.Stores;
namespace Esiur.Tests.Unit.Integration;
// ---- hash auth providers (self-consistent: client password {1..5} || server salt {6..10}
// == {1..10}, which is what the server stores the hash of) ------------------------------
internal class TestServerAuthProvider : PasswordAuthenticationProvider
{
public override PasswordHash GetHostedAccountCredential(string identity, string domain)
=> identity == "tester" && domain == "test"
? new PasswordHash(
PasswordAuthenticationHandler.ComputeSha3(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }),
new byte[] { 6, 7, 8, 9, 10 })
: new PasswordHash(null, null);
}
internal class TestClientAuthProvider : PasswordAuthenticationProvider
{
public override byte[] GetSelfCredential(string identity, string domain, string hostname)
=> identity == "tester" && domain == "test" ? new byte[] { 1, 2, 3, 4, 5 } : null;
public override IdentityPassword GetSelfIdentityAndCredential(string domain, string hostname)
=> domain == "test"
? new IdentityPassword { Identity = "tester", Password = new byte[] { 1, 2, 3, 4, 5 } }
: new IdentityPassword { Identity = null, Password = null };
}
/// <summary>
/// Spins up an in-process Esiur server and an authenticated client connection over loopback TCP,
/// so the real socket + protocol + FetchResource stack is exercised end to end. Each instance
/// uses a distinct port. Dispose closes the connection and tears down the server.
/// </summary>
internal sealed class IntegrationCluster : IAsyncDisposable
{
static int _portCounter = 14400;
public Warehouse ServerWarehouse { get; }
public Warehouse ClientWarehouse { get; }
public EpServer Server { get; }
public EpConnection Connection { get; private set; }
public int Port { get; }
IntegrationCluster(Warehouse serverWh, EpServer server, int port)
{
ServerWarehouse = serverWh;
Server = server;
Port = port;
ClientWarehouse = new Warehouse();
}
/// <summary>
/// Builds a server hosting resources under "sys/&lt;rootPath&gt;" populated by
/// <paramref name="populate"/>, opens it, then connects an authenticated client.
/// </summary>
public static async Task<IntegrationCluster> StartAsync(Func<Warehouse, Task> populate)
{
var port = Interlocked.Increment(ref _portCounter);
var serverWh = new Warehouse();
serverWh.RegisterAuthenticationProvider(new TestServerAuthProvider());
await serverWh.Put("sys", new MemoryStore());
var server = await serverWh.Put("sys/server", new EpServer
{
Port = (ushort)port,
AllowedAuthenticationProviders = new[] { "hash" },
});
await populate(serverWh);
await serverWh.Open();
var cluster = new IntegrationCluster(serverWh, server, port);
cluster.ClientWarehouse.RegisterAuthenticationProvider(new TestClientAuthProvider());
cluster.Connection = await cluster.ClientWarehouse.Get<EpConnection>(
$"ep://localhost:{port}",
new EpConnectionContext
{
AuthenticationMode = AuthenticationMode.InitializerIdentity,
Identity = "tester",
AuthenticationProtocol = "hash",
Domain = "test",
});
return cluster;
}
public async ValueTask DisposeAsync()
{
try { Connection?.Destroy(); } catch { }
try { Server?.Destroy(); } catch { }
await Task.Delay(50); // let the listener socket release the port
}
}