using System.Net; using System.Net.Http.Json; using BigRegister.Api.Contracts; using BigRegister.Api.Data; using BigRegister.Domain.Applications; using BigRegister.Domain.Beoordeling; using BigRegister.Tests.Builders; namespace BigRegister.Tests.Acceptance; /// /// Behaviour-level tests for the besluit lifecycle, built through the /// type-state builder rather than the full wizard/upload dance /// uses — a fixture that's already Submitted (or already /// Decided) is a two-line Given, not fifteen. Each test persists its own Given-built /// straight into the isolated per-class SQLite file (no HTTP round trip /// needed to create it) and exercises the real write path from there. /// public class BesluitLifecycleTests(TestWebApplicationFactory factory) : IClassFixture { // Booting the client (once, here) is what makes Db.ConnectionString point at THIS class's // throwaway file and runs its migrations — see TestWebApplicationFactory's own docs. private readonly HttpClient _client = factory.CreateClient(); private static void Persist(Aanvraag aanvraag) { using var db = Db.Create(); db.Applications.Add(aanvraag.ToEntity()); db.SaveChanges(); } [Fact] public void A_terminal_besluit_is_frozen() { // Given a case already decided Goedgekeurd — terminal, per BeoordelingRules.CanDecide. var aanvraag = Given.Concept(type: "registratie").Submitted().Decided(Besluit.Goedkeuren).Build(); Persist(aanvraag); // When a behandelaar tries to record a further besluit on it... var (outcome, updated) = ApplicationStore.RecordBesluit(aanvraag.Id, Besluit.Afwijzen, "te laat", DateTimeOffset.UtcNow); // Then the write is refused, and the original decision still stands. Assert.Equal(ApplicationStore.RecordBesluitOutcome.Conflict, outcome); Assert.Null(updated); var stillGoedgekeurd = ApplicationStore.GetAny(aanvraag.Id)!.StatusAt(DateTimeOffset.UtcNow); Assert.Equal(AanvraagStatusTag.Goedgekeurd, stillGoedgekeurd.Tag); } [Fact] public void MeerInfoGevraagd_can_be_decided_again() { // Given a case a behandelaar sent back for more information — not terminal. var aanvraag = Given.Concept(type: "registratie").Submitted().Decided(Besluit.MeerInfoOpvragen, "stuur een geldig diploma").Build(); Persist(aanvraag); // When a further besluit is recorded on it... var (outcome, updated) = ApplicationStore.RecordBesluit(aanvraag.Id, Besluit.Goedkeuren, null, DateTimeOffset.UtcNow); // Then, unlike a terminal decision, it succeeds and advances the status. Assert.Equal(ApplicationStore.RecordBesluitOutcome.Ok, outcome); Assert.Equal(AanvraagStatusTag.Goedgekeurd, updated!.StatusAt(DateTimeOffset.UtcNow).Tag); } [Fact] public void A_recorded_decision_wins_over_the_auto_approve_computation() { // Given an auto-approvable submission that a behandelaar decides (Afwijzen) before the // auto-approve window would otherwise have closed it as Goedgekeurd. var aanvraag = Given.Concept(type: "registratie").Submitted(autoApprovable: true).Build(); Persist(aanvraag); var (outcome, _) = ApplicationStore.RecordBesluit(aanvraag.Id, Besluit.Afwijzen, "diploma niet erkend", DateTimeOffset.UtcNow); Assert.Equal(ApplicationStore.RecordBesluitOutcome.Ok, outcome); // When the status is read long after the auto-approve window has passed — the instant an // undecided auto-approvable case of the same shape WOULD read Goedgekeurd (see // ApplicationTests.AutoApprovable_flips_to_goedgekeurd_after_the_window)... var longAfterTheWindow = aanvraag.SubmittedAt + ApplicationStore.ProcessingWindow + TimeSpan.FromDays(1); var status = ApplicationStore.GetAny(aanvraag.Id)!.StatusAt(longAfterTheWindow); // Then the recorded decision still wins — Afgewezen, never Goedgekeurd. Assert.Equal(AanvraagStatusTag.Afgewezen, status.Tag); } private Task PostBesluit(string id, object body) { var req = new HttpRequestMessage(HttpMethod.Post, $"/api/v1/beoordeling/{id}/besluit") { Content = JsonContent.Create(body) }; req.Headers.Add("X-Medewerker", "medewerker-1"); return _client.SendAsync(req); } [Fact] public async Task Afwijzen_requires_a_toelichting() { // Given an open, decidable case (no decision recorded yet). var aanvraag = Given.Concept(type: "registratie").Submitted().Build(); Persist(aanvraag); // When a behandelaar posts Afwijzen with no toelichting... var missing = await PostBesluit(aanvraag.Id, new { besluit = "Afwijzen" }); // Then the request is rejected — the wire boundary enforces the same rule // (BeoordelingRules.RequiresToelichting) the builder enforces for a built fixture. Assert.Equal(HttpStatusCode.BadRequest, missing.StatusCode); // And the identical request WITH a toelichting succeeds. var withToelichting = await PostBesluit(aanvraag.Id, new { besluit = "Afwijzen", toelichting = "Diploma niet erkend" }); withToelichting.EnsureSuccessStatusCode(); var body = (await withToelichting.Content.ReadFromJsonAsync())!; Assert.Equal("Afgewezen", body.Status.Tag); } }