Versiehistorie in een tabel, voor site en app
De versiehistorie stond als tekst in zowel de app als de website. Nu staat hij in tabel version_history, met een beheerpagina (Beheer > Versiehistorie) op de site. De site leest de tabel rechtstreeks, de app via de nieuwe actie app.versiehistorie. Startrijen: db/handmatig/22b_versiehistorie_startrijen.sql (als root). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014MGLqt1uKfNARQHut9ifcx
This commit is contained in:
@@ -27,6 +27,7 @@ public class Store
|
||||
public List<CampagneUur> CampagneUren = new();
|
||||
public List<VaultCategoryDefinition> Soorten = VaultCategorySaatgoed.Maak();
|
||||
public List<VaultCategoryField> SoortVelden = new();
|
||||
public List<VersieRegel> VersieRegels = new();
|
||||
|
||||
private long _seq;
|
||||
public long NextId() => ++_seq;
|
||||
@@ -533,6 +534,27 @@ public class TestPush : IPushSender
|
||||
|
||||
|
||||
/// <summary>De startrijen uit db/21_kluissoorten.sql, zodat de tests met dezelfde soorten beginnen als de echte database.</summary>
|
||||
public class VersiehistorieRepo : IVersiehistorieRepository
|
||||
{
|
||||
private readonly Store _s; public VersiehistorieRepo(Store s) => _s = s;
|
||||
private int _volgende = 1;
|
||||
|
||||
public Task<IReadOnlyList<VersieRegel>> ListAsync(bool alleenZichtbaar, CancellationToken ct = default) =>
|
||||
Task.FromResult<IReadOnlyList<VersieRegel>>(
|
||||
_s.VersieRegels.Where(r => !alleenZichtbaar || r.Zichtbaar)
|
||||
.OrderByDescending(r => r.Datum).ThenBy(r => r.Id).ToList());
|
||||
|
||||
public Task<VersieRegel?> FindAsync(int id, CancellationToken ct = default) =>
|
||||
Task.FromResult(_s.VersieRegels.FirstOrDefault(r => r.Id == id));
|
||||
|
||||
// Hoofdletterongevoelig, zoals de database (utf8mb4_unicode_ci) ook doet.
|
||||
public Task<bool> BestaatAsync(DateOnly datum, string kop, int? behalveId, CancellationToken ct = default) =>
|
||||
Task.FromResult(_s.VersieRegels.Any(r => r.Datum == datum && string.Equals(r.Kop, kop, StringComparison.OrdinalIgnoreCase) && (behalveId == null || r.Id != behalveId)));
|
||||
|
||||
public void Add(VersieRegel regel) { if (regel.Id == 0) regel.Id = _volgende++; _s.VersieRegels.Add(regel); }
|
||||
public void Remove(VersieRegel regel) => _s.VersieRegels.Remove(regel);
|
||||
}
|
||||
|
||||
public static class VaultCategorySaatgoed
|
||||
{
|
||||
public static List<VaultCategoryDefinition> Maak()
|
||||
|
||||
@@ -473,7 +473,7 @@ runner.Test("Gedeelde onderdelen zijn in app en website letterlijk gelijk", () =
|
||||
// Privacytekst: de privacyverklaring, op de site een pagina en in de app een popup. Twee
|
||||
// versies daarvan is het ergste geval van allemaal - het is het document waar iemand zich op
|
||||
// beroept, en waarvan Google Play een adres wil.
|
||||
foreach (var naam in new[] { "Penning.razor", "Productuitleg.razor", "Privacytekst.razor", "Versiehistorie.razor" })
|
||||
foreach (var naam in new[] { "Penning.razor", "Productuitleg.razor", "Privacytekst.razor" })
|
||||
{
|
||||
var web = Path.Combine(map.FullName, "src", "MijnBesluit.Web", "Components", "Onderdelen", naam);
|
||||
var app = Path.Combine(map.FullName, "src", "MijnBesluit.AppUi", "Onderdelen", naam);
|
||||
@@ -1943,5 +1943,6 @@ ServiceChecks.Register(runner);
|
||||
AdvertentieChecks.Register(runner);
|
||||
UitrolChecks.Register(runner);
|
||||
SoortenChecks.Register(runner);
|
||||
VersiehistorieChecks.Register(runner);
|
||||
|
||||
return runner.Summary();
|
||||
|
||||
@@ -26,6 +26,7 @@ public class World
|
||||
public AdvertentieService Adverteren { get; }
|
||||
public AccountOpheffenService Opheffen { get; }
|
||||
public VaultCategoryService Soorten { get; }
|
||||
public VersiehistorieService Versiehistorie { get; }
|
||||
|
||||
/// <summary>Maakt in één stap een account aan; scheelt in de scenario's het hele codegedoe.</summary>
|
||||
public async Task<MijnBesluit.Domain.Entities.User> MaakGebruikerAsync(string email, string voornaam, string achternaam = "")
|
||||
@@ -108,6 +109,7 @@ public class World
|
||||
Families = new FamilyService(families, people, users, invitations, messages, legacy,
|
||||
Email, audit, uow, Clock);
|
||||
Soorten = new VaultCategoryService(new VaultCategoryRepo(Store), users, audit, uow, Clock);
|
||||
Versiehistorie = new VersiehistorieService(new VersiehistorieRepo(Store), users, audit, uow, Clock);
|
||||
Vault = new VaultService(vault, people, messages, users, keys, files, audit, uow, Clock, Verkleiner, families, legacy, Soorten);
|
||||
Meldingen = new MeldingService(new ToestelRepo(Store), users, Push, uow, Clock);
|
||||
Messages = new MessageService(messages, people, users, vault, keys, files, Email, audit, uow, Clock, Meldingen);
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
using MijnBesluit.Application.Models;
|
||||
using MijnBesluit.Application.Services;
|
||||
using MijnBesluit.Domain.Entities;
|
||||
using MijnBesluit.Domain.Enums;
|
||||
|
||||
namespace MijnBesluit.SelfCheck;
|
||||
|
||||
/// <summary>
|
||||
/// De versiehistorie uit de database: wie hem mag beheren en wat een gebruiker te zien krijgt.
|
||||
///
|
||||
/// De afspraak is eenvoudig: één bron voor site en app. Een gebruiker leest alleen de zichtbare
|
||||
/// regels, nieuwste dag bovenaan; alleen een beheerder maakt, wijzigt, verbergt of verwijdert.
|
||||
/// </summary>
|
||||
public static class VersiehistorieChecks
|
||||
{
|
||||
private static async Task<User> BeheerderAsync(World w, string email = "beheer@example.nl")
|
||||
{
|
||||
var u = await w.MaakGebruikerAsync(email, "Beheer", "Er");
|
||||
w.Store.Users.Single(x => x.Id == u.Id).IsBeheerder = true;
|
||||
return u;
|
||||
}
|
||||
|
||||
private static async Task<int> MaakAsync(World w, User beheerder, DateOnly datum, string kop, string? versie = null,
|
||||
string tekst = "Uitleg.", bool zichtbaar = true)
|
||||
{
|
||||
var r = await w.Versiehistorie.MaakAsync(beheerder.Id, datum, versie, kop, tekst, zichtbaar);
|
||||
if (!r.Succeeded) throw new AssertionException($"regel maken mislukte: {r.Error}");
|
||||
return r.Value;
|
||||
}
|
||||
|
||||
public static void Register(Runner runner)
|
||||
{
|
||||
runner.TestAsync("Versiehistorie: zonder regels is de lijst leeg", async () =>
|
||||
{
|
||||
var w = new World();
|
||||
Assert.True((await w.Versiehistorie.ZichtbaarAsync()).Count == 0, "een lege tabel geeft een lege lijst");
|
||||
});
|
||||
|
||||
runner.TestAsync("Versiehistorie: alleen een beheerder maakt, wijzigt of verwijdert", async () =>
|
||||
{
|
||||
var w = new World();
|
||||
var jan = await w.MaakGebruikerAsync("jan@example.nl", "Jan");
|
||||
var beheer = await BeheerderAsync(w);
|
||||
var dag = new DateOnly(2026, 3, 1);
|
||||
var id = await MaakAsync(w, beheer, dag, "Eerste");
|
||||
|
||||
Assert.True(!(await w.Versiehistorie.MaakAsync(jan.Id, dag, null, "Tweede", "Uitleg")).Succeeded,
|
||||
"een gewone gebruiker maakt geen regel");
|
||||
Assert.True(!(await w.Versiehistorie.OverzichtAsync(jan.Id)).Succeeded, "en ziet het beheeroverzicht niet");
|
||||
Assert.True(!(await w.Versiehistorie.WijzigAsync(jan.Id, id, dag, null, "Anders", "Uitleg", true)).Succeeded,
|
||||
"en wijzigt niets");
|
||||
Assert.True(!(await w.Versiehistorie.VerwijderAsync(jan.Id, id)).Succeeded, "en verwijdert niets");
|
||||
Assert.True((await w.Versiehistorie.ZichtbaarAsync()).Single().Kop == "Eerste", "de regel is onaangetast");
|
||||
});
|
||||
|
||||
runner.TestAsync("Versiehistorie: nieuwste dag bovenaan, binnen een dag in invoervolgorde", async () =>
|
||||
{
|
||||
var w = new World();
|
||||
var beheer = await BeheerderAsync(w);
|
||||
await MaakAsync(w, beheer, new DateOnly(2026, 2, 1), "Oud, eerste");
|
||||
await MaakAsync(w, beheer, new DateOnly(2026, 4, 1), "Nieuw, eerste", "1.1");
|
||||
await MaakAsync(w, beheer, new DateOnly(2026, 2, 1), "Oud, tweede");
|
||||
await MaakAsync(w, beheer, new DateOnly(2026, 4, 1), "Nieuw, tweede", "1.1");
|
||||
|
||||
var lijst = await w.Versiehistorie.ZichtbaarAsync();
|
||||
Assert.True(lijst.Select(r => r.Kop).SequenceEqual(
|
||||
new[] { "Nieuw, eerste", "Nieuw, tweede", "Oud, eerste", "Oud, tweede" }),
|
||||
"de nieuwste dag staat bovenaan en binnen een dag de volgorde van invoer");
|
||||
Assert.True(lijst[0].Datum == "2026-04-01" && lijst[0].Versie == "1.1", "datum als jjjj-mm-dd, met versie");
|
||||
Assert.True(lijst[2].Versie is null, "een regel zonder versie heeft er geen");
|
||||
});
|
||||
|
||||
runner.TestAsync("Versiehistorie: een verborgen regel ziet alleen de beheerder", async () =>
|
||||
{
|
||||
var w = new World();
|
||||
var beheer = await BeheerderAsync(w);
|
||||
var dag = new DateOnly(2026, 3, 1);
|
||||
await MaakAsync(w, beheer, dag, "Zichtbaar");
|
||||
var verborgen = await MaakAsync(w, beheer, dag, "Nog niet", zichtbaar: false);
|
||||
|
||||
Assert.True((await w.Versiehistorie.ZichtbaarAsync()).Select(r => r.Kop).SequenceEqual(new[] { "Zichtbaar" }),
|
||||
"gebruikers zien de verborgen regel niet");
|
||||
var overzicht = (await w.Versiehistorie.OverzichtAsync(beheer.Id)).Value!;
|
||||
Assert.True(overzicht.Count == 2, "de beheerder ziet beide");
|
||||
|
||||
var w2 = await w.Versiehistorie.WijzigAsync(beheer.Id, verborgen, dag, null, "Nog niet", "Uitleg.", true);
|
||||
Assert.True(w2.Succeeded, "tonen lukt");
|
||||
Assert.True((await w.Versiehistorie.ZichtbaarAsync()).Count == 2, "daarna staat hij er ook voor gebruikers");
|
||||
});
|
||||
|
||||
runner.TestAsync("Versiehistorie: wat niet klopt wordt geweigerd", async () =>
|
||||
{
|
||||
var w = new World();
|
||||
var beheer = await BeheerderAsync(w);
|
||||
var dag = new DateOnly(2026, 3, 1);
|
||||
|
||||
Assert.True(!(await w.Versiehistorie.MaakAsync(beheer.Id, dag, null, " ", "Uitleg")).Succeeded, "een lege kop mag niet");
|
||||
Assert.True(!(await w.Versiehistorie.MaakAsync(beheer.Id, dag, null, "Kop", "")).Succeeded, "een lege uitleg mag niet");
|
||||
Assert.True(!(await w.Versiehistorie.MaakAsync(beheer.Id, dag, null,
|
||||
new string('k', VersiehistorieService.MaxKop + 1), "Uitleg")).Succeeded, "een te lange kop mag niet");
|
||||
Assert.True(!(await w.Versiehistorie.MaakAsync(beheer.Id, dag, null, "Kop",
|
||||
new string('t', VersiehistorieService.MaxTekst + 1))).Succeeded, "een te lange uitleg mag niet");
|
||||
Assert.True(!(await w.Versiehistorie.MaakAsync(beheer.Id, dag, new string('1', VersiehistorieService.MaxVersie + 1),
|
||||
"Kop", "Uitleg")).Succeeded, "een te lang versienummer mag niet");
|
||||
Assert.True(!(await w.Versiehistorie.MaakAsync(beheer.Id, new DateOnly(2090, 1, 1), null, "Kop", "Uitleg")).Succeeded,
|
||||
"een datum ver in de toekomst is een typefout");
|
||||
Assert.True(!(await w.Versiehistorie.MaakAsync(beheer.Id, new DateOnly(1999, 1, 1), null, "Kop", "Uitleg")).Succeeded,
|
||||
"een datum van lang geleden ook");
|
||||
|
||||
await MaakAsync(w, beheer, dag, "Dubbel");
|
||||
Assert.True(!(await w.Versiehistorie.MaakAsync(beheer.Id, dag, null, "dubbel", "Uitleg")).Succeeded,
|
||||
"dezelfde kop op dezelfde dag staat er al");
|
||||
Assert.True((await w.Versiehistorie.MaakAsync(beheer.Id, dag.AddDays(1), null, "Dubbel", "Uitleg")).Succeeded,
|
||||
"op een andere dag mag dezelfde kop wel");
|
||||
});
|
||||
|
||||
runner.TestAsync("Versiehistorie: wijzigen en verwijderen, en het blijft vastgelegd", async () =>
|
||||
{
|
||||
var w = new World();
|
||||
var beheer = await BeheerderAsync(w);
|
||||
var dag = new DateOnly(2026, 3, 1);
|
||||
var id = await MaakAsync(w, beheer, dag, "Eerste", tekst: "Oude uitleg");
|
||||
|
||||
Assert.True((await w.Versiehistorie.WijzigAsync(beheer.Id, id, dag, " 1.2 ", " Nieuwe kop ", " Nieuwe uitleg ", true)).Succeeded,
|
||||
"wijzigen lukt");
|
||||
var regel = (await w.Versiehistorie.ZichtbaarAsync()).Single();
|
||||
Assert.True(regel.Kop == "Nieuwe kop" && regel.Tekst == "Nieuwe uitleg" && regel.Versie == "1.2",
|
||||
"spaties eromheen verdwijnen");
|
||||
|
||||
Assert.True(!(await w.Versiehistorie.WijzigAsync(beheer.Id, 9999, dag, null, "Kop", "Uitleg", true)).Succeeded,
|
||||
"een regel die er niet is, is niet te wijzigen");
|
||||
Assert.True((await w.Versiehistorie.VerwijderAsync(beheer.Id, id)).Succeeded, "verwijderen lukt");
|
||||
Assert.True((await w.Versiehistorie.ZichtbaarAsync()).Count == 0, "en dan is hij weg");
|
||||
|
||||
var acties = w.Store.Audit.Count(a => a.Action == AuditAction.VersiehistorieBeheerd);
|
||||
Assert.True(acties == 3, "maken, wijzigen en verwijderen staan alle drie in het logboek");
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user