using System.Net; using System.Text.Json; using MijnBesluit.Protocol; using MijnBesluit.Server; namespace MijnBesluit.SelfCheck; /// /// Loopt de hele keten door: app -> socket -> dispatcher -> Application-laag -> opslag en terug. /// Alleen de database is hier vervangen door opslag in het geheugen. /// public static class EndToEndChecks { public static void Register(Runner runner) { runner.TestAsync("Van app tot kluis: registreren, familie, uitnodigen, bericht", async () => { var wereld = new World(); var sessies = new SessionStore(wereld.Clock); var dispatcher = new SocketDispatcher(sessies, work => work(new RequestScope { Accounts = wereld.Accounts, Families = wereld.Families, Vault = wereld.Vault, Messages = wereld.Messages, Legacy = wereld.Legacy, Overzicht = wereld.Overzicht, Samenvoegen = wereld.Samenvoegen })); await using var server = new SocketListener( new SocketListenerOptions { Address = IPAddress.Loopback, Port = 0 }, dispatcher.HandleAsync); server.Start(); await using var app = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false); // Zonder token mag er niets. var zonderToken = await app.SendAsync(SocketActions.VaultList); Assert.True(zonderToken.Status == "Error" && zonderToken.Message!.Contains("Log opnieuw in"), "zonder geldige sessie wordt een verzoek geweigerd"); // Registreren gaat in twee stappen: code aanvragen, code invullen. var codeAanvraag = await app.SendAsync(SocketActions.RequestRegisterCode, new { email = "jan@example.nl", firstName = "Jan", lastName = "Jansen" }); Assert.True(codeAanvraag.Status == "Ok", "de registratiecode wordt verstuurd"); Assert.True(!JsonSerializer.Serialize(codeAanvraag.Payload, SocketFraming.Json).Contains("jan@example.nl"), "het antwoord toont het adres alleen afgeschermd"); var registratie = await app.SendAsync(SocketActions.Register, new { email = "jan@example.nl", code = wereld.LaatsteCodeUitMail("jan@example.nl") }); Assert.True(registratie.Status == "Ok", "registreren lukt met de code uit de e-mail"); app.Token = Field(registratie, "token"); Assert.True(!string.IsNullOrEmpty(app.Token), "er komt een token terug"); // Kluisitem opslaan en weer openen, helemaal via de socket. var opslaan = await app.SendAsync(SocketActions.VaultSave, new { category = 3, // Wachtwoord title = "Bank", payload = new { website = "bank.nl", username = "jan", secret = "zeergeheim" }, releasePolicy = 1, // Na overlijden releaseAt = (DateTime?)null }); Assert.True(opslaan.Status == "Ok", "kluisitem opslaan lukt"); var lijst = await app.SendAsync(SocketActions.VaultList); Assert.True(lijst.Status == "Ok", "de kluislijst komt terug"); Assert.True(!JsonSerializer.Serialize(lijst.Payload, SocketFraming.Json).Contains("zeergeheim"), "de lijst bevat geen geheimen, alleen titels"); var itemId = long.Parse(Field(opslaan, "itemId")!); var openen = await app.SendAsync(SocketActions.VaultOpen, new { id = itemId }); Assert.True(JsonSerializer.Serialize(openen.Payload, SocketFraming.Json).Contains("zeergeheim"), "openen geeft de inhoud terug aan de eigenaar"); // Familie uitbreiden en uitnodigen. var familie = wereld.Store.Families.Single(); var toevoegen = await app.SendAsync(SocketActions.AddMember, new { familyId = familie.Id, firstName = "Eva", lastName = "Jansen", email = "eva@example.nl", relation = 2, // Kind relationLabel = (string?)null }); Assert.True(toevoegen.Status == "Ok", "familielid toevoegen lukt"); var evaId = long.Parse(Field(toevoegen, "persoonId")!); var uitnodigen = await app.SendAsync(SocketActions.Invite, new { familyId = familie.Id, personId = evaId }); Assert.True(uitnodigen.Status == "Ok", "uitnodigen lukt"); Assert.True(!JsonSerializer.Serialize(uitnodigen.Payload, SocketFraming.Json) .Contains(wereld.Store.Invitations.Single().Email), "het antwoord bevat geen uitnodigingstoken"); // Eva neemt de uitnodiging aan vanaf haar eigen toestel. var mail = wereld.Email.Sent.Last(m => m.To == "eva@example.nl"); var token = mail.Body.Split("/uitnodiging/")[1].Split('"')[0]; await using var appVanEva = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false); var accepteren = await appVanEva.SendAsync(SocketActions.AcceptInvitation, new { token }); Assert.True(accepteren.Status == "Ok", "Eva kan de uitnodiging aannemen via de app"); appVanEva.Token = Field(accepteren, "token"); // Jan ziet nu dat het profiel geclaimd is. var leden = await app.SendAsync(SocketActions.ListMembers, new { familyId = familie.Id }); var ledenJson = JsonSerializer.Serialize(leden.Payload, SocketFraming.Json); Assert.True(ledenJson.Contains("\"state\":2"), "Jan ziet het profiel van Eva als geclaimd"); // Jan zet een bericht klaar voor Eva, te bezorgen na zijn overlijden. var bericht = await app.SendAsync(SocketActions.MessageCompose, new { toPersonId = evaId, subject = "Voor later", text = "Ik ben trots op je.", deliverAt = (DateTime?)null, afterDeath = true, kind = 0, vaultFileId = (long?)null }); Assert.True(bericht.Status == "Ok", "bericht klaarzetten lukt"); var inboxVanEva = await appVanEva.SendAsync(SocketActions.Inbox); Assert.True(JsonSerializer.Serialize(inboxVanEva.Payload, SocketFraming.Json) == "[]", "Eva ziet het bericht nog niet"); // Uitloggen maakt het token onbruikbaar. await app.SendAsync(SocketActions.SignOut); var naUitloggen = await app.SendAsync(SocketActions.VaultList); Assert.True(naUitloggen.Status == "Error", "na uitloggen werkt het token niet meer"); }); runner.TestAsync("Verlopen sessie wordt geweigerd", async () => { var wereld = new World(); var sessies = new SessionStore(wereld.Clock, TimeSpan.FromHours(1)); var dispatcher = new SocketDispatcher(sessies, work => work(new RequestScope { Accounts = wereld.Accounts, Families = wereld.Families, Vault = wereld.Vault, Messages = wereld.Messages, Legacy = wereld.Legacy, Overzicht = wereld.Overzicht })); await using var server = new SocketListener( new SocketListenerOptions { Address = IPAddress.Loopback, Port = 0 }, dispatcher.HandleAsync); server.Start(); await using var app = new MijnBesluitSocketClient("127.0.0.1", server.Port, useTls: false); await app.SendAsync(SocketActions.RequestRegisterCode, new { email = "a@example.nl", firstName = "A", lastName = "A" }); var reg = await app.SendAsync(SocketActions.Register, new { email = "a@example.nl", code = wereld.LaatsteCodeUitMail("a@example.nl") }); app.Token = Field(reg, "token"); Assert.True((await app.SendAsync(SocketActions.VaultList)).Status == "Ok", "meteen na inloggen werkt het"); wereld.Clock.Advance(TimeSpan.FromHours(2)); Assert.True((await app.SendAsync(SocketActions.VaultList)).Status == "Error", "na twee uur is de sessie verlopen"); }); } /// Haalt één veld uit de payload van een antwoord. private static string? Field(SocketResponse response, string name) { var json = JsonSerializer.Serialize(response.Payload, SocketFraming.Json); using var doc = JsonDocument.Parse(json); return doc.RootElement.TryGetProperty(name, out var value) ? (value.ValueKind == JsonValueKind.String ? value.GetString() : value.ToString()) : null; } }