Eerste commit

This commit is contained in:
jf
2026-09-27 14:21:31 +00:00
commit de4ff128cb
295 changed files with 47374 additions and 0 deletions
+266
View File
@@ -0,0 +1,266 @@
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");
});
}
}