267 lines
13 KiB
C#
267 lines
13 KiB
C#
using System.Net;
|
|
using MijnBesluit.Protocol;
|
|
|
|
namespace MijnBesluit.SelfCheck;
|
|
|
|
/// <summary>Controleert de SocketServer met een echte verbinding over localhost.</summary>
|
|
public static class SocketChecks
|
|
{
|
|
public static void Register(Runner runner)
|
|
{
|
|
runner.TestAsync("SocketServer: verzoek en antwoord over een echte verbinding", async () =>
|
|
{
|
|
await using var server = new SocketListener(
|
|
new SocketListenerOptions { Address = IPAddress.Loopback, Port = 0 },
|
|
(req, conn, ct) => Task.FromResult(SocketResponse.Ok(req.RequestId, new { echo = req.Action })));
|
|
server.Start();
|
|
|
|
await using var client = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false);
|
|
var response = await client.SendAsync(SocketActions.Ping);
|
|
|
|
Assert.True(response.Status == "Ok", "de server antwoordt met Ok");
|
|
});
|
|
|
|
runner.TestAsync("SocketServer: meerdere verzoeken tegelijk komen bij het juiste antwoord terecht", async () =>
|
|
{
|
|
await using var server = new SocketListener(
|
|
new SocketListenerOptions { Address = IPAddress.Loopback, Port = 0 },
|
|
async (req, conn, ct) =>
|
|
{
|
|
// Antwoorden bewust door elkaar laten lopen.
|
|
var nummer = req.PayloadAs<Dictionary<string, int>>()!["n"];
|
|
await Task.Delay(50 - nummer * 5, ct);
|
|
return SocketResponse.Ok(req.RequestId, new { n = nummer });
|
|
});
|
|
server.Start();
|
|
|
|
await using var client = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false);
|
|
await client.ConnectAsync();
|
|
|
|
var taken = Enumerable.Range(1, 8)
|
|
.Select(n => client.SendAsync(SocketActions.Ping, new { n }))
|
|
.ToArray();
|
|
var antwoorden = await Task.WhenAll(taken);
|
|
|
|
for (var i = 0; i < antwoorden.Length; i++)
|
|
{
|
|
var terug = System.Text.Json.JsonSerializer
|
|
.Deserialize<Dictionary<string, int>>(
|
|
System.Text.Json.JsonSerializer.Serialize(antwoorden[i].Payload, SocketFraming.Json),
|
|
SocketFraming.Json)!["n"];
|
|
Assert.True(terug == i + 1, $"antwoord {i + 1} hoort bij het juiste verzoek");
|
|
}
|
|
});
|
|
|
|
runner.TestAsync("SocketServer: inhoud met '**BYE**' erin komt ongeschonden aan", async () =>
|
|
{
|
|
// Precies het randgeval waar een markering-gebaseerd protocol op stukloopt.
|
|
var lastig = new string('A', 200_000) + "**BYE**" + new string('B', 200_000) + "BYE";
|
|
|
|
await using var server = new SocketListener(
|
|
new SocketListenerOptions { Address = IPAddress.Loopback, Port = 0 },
|
|
(req, conn, ct) => Task.FromResult(SocketResponse.Ok(req.RequestId,
|
|
new { tekst = req.PayloadAs<Dictionary<string, string>>()!["tekst"] })));
|
|
server.Start();
|
|
|
|
await using var client = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false);
|
|
var response = await client.SendAsync(SocketActions.Ping, new { tekst = lastig });
|
|
|
|
var terug = System.Text.Json.JsonSerializer
|
|
.Deserialize<Dictionary<string, string>>(
|
|
System.Text.Json.JsonSerializer.Serialize(response.Payload, SocketFraming.Json),
|
|
SocketFraming.Json)!["tekst"];
|
|
|
|
Assert.True(response.Status == "Ok", "de server antwoordt");
|
|
Assert.True(terug.Length == lastig.Length, "de lengte klopt nog");
|
|
Assert.True(terug == lastig, "de inhoud is niet afgekapt op de markering");
|
|
});
|
|
|
|
runner.TestAsync("SocketServer: een fout in de afhandeling lekt geen interne details", async () =>
|
|
{
|
|
await using var server = new SocketListener(
|
|
new SocketListenerOptions { Address = IPAddress.Loopback, Port = 0 },
|
|
(req, conn, ct) => throw new InvalidOperationException("verbinding met database mislukt op server01"));
|
|
server.Start();
|
|
|
|
await using var client = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false);
|
|
var response = await client.SendAsync(SocketActions.Ping);
|
|
|
|
Assert.True(response.Status == "Error", "de app krijgt een foutantwoord");
|
|
Assert.True(!response.Message!.Contains("server01"), "zonder interne details erin");
|
|
});
|
|
|
|
runner.TestAsync("SocketServer: de server kan uit zichzelf een melding sturen", async () =>
|
|
{
|
|
SocketConnection? verbinding = null;
|
|
await using var server = new SocketListener(
|
|
new SocketListenerOptions { Address = IPAddress.Loopback, Port = 0 },
|
|
(req, conn, ct) => { verbinding = conn; conn.UserId = 42; return Task.FromResult(SocketResponse.Ok(req.RequestId)); });
|
|
server.Start();
|
|
|
|
await using var client = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false);
|
|
var ontvangen = new TaskCompletionSource<SocketResponse>();
|
|
client.PushReceived += r => ontvangen.TrySetResult(r);
|
|
|
|
await client.SendAsync(SocketActions.Ping);
|
|
Assert.True(verbinding is not null, "de server kent de verbinding");
|
|
|
|
await server.PushToUserAsync(42, new SocketResponse { Status = "Ok", Message = "Er staat een bericht voor je klaar." });
|
|
|
|
var melding = await ontvangen.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
|
Assert.True(melding.Message!.Contains("bericht voor je klaar"), "de app ontvangt de melding");
|
|
});
|
|
|
|
runner.TestAsync("SocketServer: elke verbinding krijgt een eigen thread", async () =>
|
|
{
|
|
var threadsPerVerbinding = new System.Collections.Concurrent.ConcurrentDictionary<string, string>();
|
|
|
|
await using var server = new SocketListener(
|
|
new SocketListenerOptions { Address = IPAddress.Loopback, Port = 0 },
|
|
(req, conn, ct) =>
|
|
{
|
|
threadsPerVerbinding[conn.Id] = conn.ThreadName ?? "?";
|
|
return Task.FromResult(SocketResponse.Ok(req.RequestId, new { thread = conn.ThreadName }));
|
|
});
|
|
server.Start();
|
|
|
|
await using var app1 = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false);
|
|
await using var app2 = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false);
|
|
|
|
await app1.SendAsync(SocketActions.Ping);
|
|
await app2.SendAsync(SocketActions.Ping);
|
|
|
|
Assert.True(threadsPerVerbinding.Count == 2, "de server ziet twee aparte verbindingen");
|
|
Assert.True(threadsPerVerbinding.Values.Distinct().Count() == 2,
|
|
"elke verbinding wordt door een eigen thread bediend");
|
|
Assert.True(threadsPerVerbinding.Values.All(n => n.StartsWith("MijnBesluit-verbinding-")),
|
|
"de thread draagt de naam van de verbinding, handig bij foutzoeken");
|
|
});
|
|
|
|
runner.TestAsync("SocketServer: een traag verzoek houdt de volgende niet op", async () =>
|
|
{
|
|
// Drie verzoeken van dezelfde verbinding die elk een halve seconde blijven hangen.
|
|
// Op een gedeelde thread zou dat anderhalve seconde duren; elk op een eigen thread
|
|
// hooguit iets meer dan een halve.
|
|
var gebruikteThreads = new System.Collections.Concurrent.ConcurrentBag<int>();
|
|
|
|
await using var server = new SocketListener(
|
|
new SocketListenerOptions { Address = IPAddress.Loopback, Port = 0 },
|
|
(req, conn, ct) =>
|
|
{
|
|
gebruikteThreads.Add(Environment.CurrentManagedThreadId);
|
|
Thread.Sleep(500);
|
|
return Task.FromResult(SocketResponse.Ok(req.RequestId));
|
|
});
|
|
server.Start();
|
|
|
|
await using var app = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false);
|
|
await app.ConnectAsync();
|
|
|
|
var klok = System.Diagnostics.Stopwatch.StartNew();
|
|
var antwoorden = await Task.WhenAll(
|
|
app.SendAsync(SocketActions.Ping),
|
|
app.SendAsync(SocketActions.Ping),
|
|
app.SendAsync(SocketActions.Ping));
|
|
klok.Stop();
|
|
|
|
Assert.True(antwoorden.All(a => a.Status == "Ok"), "alle drie de verzoeken worden beantwoord");
|
|
Assert.True(gebruikteThreads.Distinct().Count() == 3, "elk verzoek liep op zijn eigen thread");
|
|
Assert.True(klok.ElapsedMilliseconds < 1200,
|
|
$"ze liepen naast elkaar (duurde {klok.ElapsedMilliseconds} ms, na elkaar zou 1500 ms zijn)");
|
|
});
|
|
|
|
runner.TestAsync("SocketServer: boven de grens wachten verzoeken netjes op hun beurt", async () =>
|
|
{
|
|
var tegelijk = 0;
|
|
var hoogste = 0;
|
|
var slot = new object();
|
|
|
|
await using var server = new SocketListener(
|
|
new SocketListenerOptions
|
|
{
|
|
Address = IPAddress.Loopback,
|
|
Port = 0,
|
|
MaxRequestsPerConnection = 2
|
|
},
|
|
(req, conn, ct) =>
|
|
{
|
|
lock (slot) { tegelijk++; hoogste = Math.Max(hoogste, tegelijk); }
|
|
Thread.Sleep(150);
|
|
lock (slot) { tegelijk--; }
|
|
return Task.FromResult(SocketResponse.Ok(req.RequestId));
|
|
});
|
|
server.Start();
|
|
|
|
await using var app = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false);
|
|
await app.ConnectAsync();
|
|
|
|
var antwoorden = await Task.WhenAll(Enumerable.Range(0, 6)
|
|
.Select(_ => app.SendAsync(SocketActions.Ping, timeout: TimeSpan.FromSeconds(20))));
|
|
|
|
Assert.True(antwoorden.All(a => a.Status == "Ok"), "alle zes verzoeken worden alsnog beantwoord");
|
|
Assert.True(hoogste <= 2, $"er liepen er nooit meer dan twee tegelijk (gemeten: {hoogste})");
|
|
});
|
|
|
|
runner.Test("Bestandsoverdracht: stukken moeten op volgorde en binnen de maat blijven", () =>
|
|
{
|
|
var klok = new TestClock();
|
|
var overdracht = new MijnBesluit.Server.Bestandsoverdracht(klok);
|
|
|
|
var id = overdracht.Begin(gebruikerId: 1, "foto.jpg", "image/jpeg", null, totaalBytes: 10);
|
|
overdracht.Stuk(id, 1, 0, new byte[4]);
|
|
|
|
Assert.Throws<InvalidOperationException>(() => overdracht.Stuk(id, 1, 0, new byte[4]),
|
|
"hetzelfde stuk twee keer wordt geweigerd");
|
|
Assert.Throws<InvalidOperationException>(() => overdracht.Stuk(id, 1, 5, new byte[4]),
|
|
"een stuk dat overslaat wordt geweigerd");
|
|
Assert.Throws<InvalidOperationException>(() => overdracht.Stuk(id, 1, 1, new byte[99]),
|
|
"meer dan aangekondigd wordt geweigerd");
|
|
Assert.Throws<InvalidOperationException>(() => overdracht.Klaar(id, 1),
|
|
"afsluiten met te weinig bytes wordt geweigerd");
|
|
|
|
overdracht.Stuk(id, 1, 1, new byte[6]);
|
|
var (naam, soort, item, inhoud) = overdracht.Klaar(id, 1);
|
|
Assert.True(naam == "foto.jpg" && soort == "image/jpeg" && item is null, "de gegevens komen terug");
|
|
Assert.True(inhoud.Length == 10, "en de volledige inhoud");
|
|
Assert.True(overdracht.Lopend == 0, "de overdracht is opgeruimd");
|
|
});
|
|
|
|
runner.Test("Bestandsoverdracht: van een ander, of te lang blijven liggen", () =>
|
|
{
|
|
var klok = new TestClock();
|
|
var overdracht = new MijnBesluit.Server.Bestandsoverdracht(klok);
|
|
|
|
var id = overdracht.Begin(gebruikerId: 1, "polis.pdf", "application/pdf", null, totaalBytes: 100);
|
|
|
|
// Gebruiker 2 mag er niet bij, ook niet als hij het kenmerk op de een of andere manier kent.
|
|
Assert.Throws<InvalidOperationException>(() => overdracht.Stuk(id, 2, 0, new byte[10]),
|
|
"de upload van iemand anders bestaat voor jou niet");
|
|
overdracht.Afbreken(id, 2);
|
|
Assert.True(overdracht.Lopend == 1, "en is door hem ook niet af te breken");
|
|
|
|
// Blijft hij te lang liggen, dan is hij weg.
|
|
klok.Advance(MijnBesluit.Server.Bestandsoverdracht.Verlooptijd + TimeSpan.FromMinutes(1));
|
|
Assert.Throws<InvalidOperationException>(() => overdracht.Stuk(id, 1, 0, new byte[10]),
|
|
"een upload die te lang stillag wordt geweigerd");
|
|
});
|
|
|
|
runner.Test("Bestandsoverdracht: niet eindeloos veel tegelijk", () =>
|
|
{
|
|
var overdracht = new MijnBesluit.Server.Bestandsoverdracht(new TestClock());
|
|
|
|
for (var i = 0; i < MijnBesluit.Server.Bestandsoverdracht.MaxPerGebruiker; i++)
|
|
overdracht.Begin(1, $"bestand{i}.bin", "application/octet-stream", null, 100);
|
|
|
|
Assert.Throws<InvalidOperationException>(
|
|
() => overdracht.Begin(1, "nog een.bin", "application/octet-stream", null, 100),
|
|
"boven de grens wordt een nieuwe upload geweigerd");
|
|
|
|
// Iemand anders heeft daar geen last van.
|
|
overdracht.Begin(2, "van piet.bin", "application/octet-stream", null, 100);
|
|
Assert.True(overdracht.Lopend == MijnBesluit.Server.Bestandsoverdracht.MaxPerGebruiker + 1,
|
|
"de grens geldt per gebruiker");
|
|
});
|
|
}
|
|
}
|
|
|