test(backend): type-state Aanvraag builder, illegal fixtures unrepresentable (WP-70)
RuleTests/OpenZaakZaakSourceTests hand-built Aanvraag fixtures by initializer, keeping Submitted/Referentie/SubmittedAt/BesluitStatus consistent by hand. A type-state builder (Given.Concept().Submitted().Decided()) makes an illegal sequence a compile error instead, and delegates the toelichting-required rule to the real BeoordelingRules so it can't drift from production. Adds BesluitLifecycleTests covering the WP-68 besluit invariants end to end. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Behaviour-level tests for the besluit lifecycle (WP-65b/66/68), built through the
|
||||
/// <see cref="Given"/> type-state builder (WP-70) rather than the full wizard/upload dance
|
||||
/// <see cref="BeoordelingTests"/> uses — a fixture that's already Submitted (or already
|
||||
/// Decided) is a two-line Given, not fifteen. Each test persists its own Given-built
|
||||
/// <see cref="Aanvraag"/> straight into the isolated per-class SQLite file (no HTTP round trip
|
||||
/// needed to create it) and exercises the real write path from there.
|
||||
/// </summary>
|
||||
public class BesluitLifecycleTests(TestWebApplicationFactory factory) : IClassFixture<TestWebApplicationFactory>
|
||||
{
|
||||
// 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);
|
||||
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!.Value + 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<HttpResponseMessage> 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<RecordBesluitResponse>())!;
|
||||
Assert.Equal("Afgewezen", body.Status.Tag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using System.Threading;
|
||||
using BigRegister.Api.Data;
|
||||
using BigRegister.Domain.Applications;
|
||||
using BigRegister.Domain.Beoordeling;
|
||||
|
||||
namespace BigRegister.Tests.Builders;
|
||||
|
||||
/// <summary>Fixture identities test builders share across the suite.</summary>
|
||||
public static class TestIdentities
|
||||
{
|
||||
/// The default owner for a builder-made <see cref="Aanvraag"/> — matches
|
||||
/// <see cref="DocumentStore.DemoOwner"/> (the demo's only seeded user) so a fixture that
|
||||
/// doesn't care about identity gets a realistic, elfproef-valid BSN for free.
|
||||
public const string DemoBsn = DocumentStore.DemoOwner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type-state test-data builder for <see cref="Aanvraag"/> (WP-70). "Build test data through the
|
||||
/// same door production code uses" — a Concept can only ever become Submitted, and only a
|
||||
/// Submitted aanvraag can be Decided, so the compiler refuses a fixture built through an illegal
|
||||
/// path (e.g. deciding a still-Concept aanvraag) instead of that being a runtime assertion nobody
|
||||
/// wrote. Start at <see cref="Given.Concept"/>.
|
||||
///
|
||||
/// ponytail: <see cref="Aanvraag"/> itself stays exactly what it always was — a mutable,
|
||||
/// EF-backed bag with no invariants of its own (that's Data/ApplicationStore.cs's job in
|
||||
/// production, via its own lock + <see cref="BeoordelingRules"/> checks). This builder does not
|
||||
/// refactor it into an immutable aggregate; it's the one enforced DOOR through which TEST code
|
||||
/// builds one, so the invariants a real request path enforces don't quietly go missing from a
|
||||
/// fixture assembled by hand.
|
||||
/// </summary>
|
||||
public static class Given
|
||||
{
|
||||
/// A fresh, unsubmitted wizard draft — step 0 of 0 until <see cref="ConceptAanvraag.AtStep"/>
|
||||
/// says otherwise, exactly what <c>ApplicationStore.CreateConcept</c> hands back.
|
||||
public static ConceptAanvraag Concept(string type = "registratie", string owner = TestIdentities.DemoBsn) =>
|
||||
new(type, owner);
|
||||
}
|
||||
|
||||
/// <summary>A not-yet-submitted aanvraag. The only next step is <see cref="Submitted"/> — there
|
||||
/// is deliberately no <c>Decided</c> here, since only a submitted aanvraag can be decided.</summary>
|
||||
public sealed class ConceptAanvraag
|
||||
{
|
||||
private readonly string _type;
|
||||
private readonly string _owner;
|
||||
private int _stepIndex;
|
||||
private int _stepCount;
|
||||
|
||||
internal ConceptAanvraag(string type, string owner)
|
||||
{
|
||||
_type = type;
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
/// The wizard's current position — step <paramref name="index"/> of <paramref name="of"/>.
|
||||
public ConceptAanvraag AtStep(int index, int of)
|
||||
{
|
||||
_stepIndex = index;
|
||||
_stepCount = of;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Submits the draft — always assigns a Referentie AND SubmittedAt together (mirrors
|
||||
/// <c>ApplicationStore.Submit</c>), so <c>Aanvraag.StatusAt</c>'s <c>Referentie!</c> is honest
|
||||
/// for every fixture built this way, never a null-ref waiting to happen.
|
||||
public SubmittedAanvraag Submitted(bool autoApprovable = false) =>
|
||||
new(_type, _owner, _stepIndex, _stepCount, autoApprovable);
|
||||
|
||||
public Aanvraag Build() => new()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Type = _type,
|
||||
Owner = _owner,
|
||||
StepIndex = _stepIndex,
|
||||
StepCount = _stepCount,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>A submitted aanvraag, open for a behandelaar's decision. The only next step is
|
||||
/// <see cref="Decided"/> — there is no way back to <c>ConceptAanvraag</c>.</summary>
|
||||
public sealed class SubmittedAanvraag
|
||||
{
|
||||
private static int _referentieSeq;
|
||||
|
||||
private readonly string _type;
|
||||
private readonly string _owner;
|
||||
private readonly int _stepIndex;
|
||||
private readonly int _stepCount;
|
||||
private readonly bool _autoApprovable;
|
||||
private readonly string _referentie;
|
||||
private readonly DateTimeOffset _submittedAt;
|
||||
|
||||
internal SubmittedAanvraag(string type, string owner, int stepIndex, int stepCount, bool autoApprovable)
|
||||
{
|
||||
_type = type;
|
||||
_owner = owner;
|
||||
_stepIndex = stepIndex;
|
||||
_stepCount = stepCount;
|
||||
_autoApprovable = autoApprovable;
|
||||
// A plausible reference in SubmissionRules.NewReference's shape ("BIG-2026-" + a number) —
|
||||
// sequential (not random) so a fixture's value is reproducible across a test run.
|
||||
_referentie = $"BIG-2026-{Interlocked.Increment(ref _referentieSeq)}";
|
||||
_submittedAt = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>Records a behandelaar's decision — reusing <see cref="BeoordelingRules.RequiresToelichting"/>,
|
||||
/// the SAME rule production's besluit endpoint runs, rather than restating it here where it
|
||||
/// could quietly drift. Throws <see cref="ArgumentException"/> for an Afwijzen/MeerInfoOpvragen
|
||||
/// with a null/blank <paramref name="toelichting"/> — exactly what that endpoint rejects with
|
||||
/// a 400, just caught here at fixture-build time instead.</summary>
|
||||
public DecidedAanvraag Decided(Besluit besluit, string? toelichting = null)
|
||||
{
|
||||
if (BeoordelingRules.RequiresToelichting(besluit) && string.IsNullOrWhiteSpace(toelichting))
|
||||
throw new ArgumentException($"{besluit} requires a toelichting.", nameof(toelichting));
|
||||
return new DecidedAanvraag(this, besluit, toelichting);
|
||||
}
|
||||
|
||||
public Aanvraag Build() => new()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Type = _type,
|
||||
Owner = _owner,
|
||||
StepIndex = _stepIndex,
|
||||
StepCount = _stepCount,
|
||||
Submitted = true,
|
||||
Referentie = _referentie,
|
||||
AutoApprovable = _autoApprovable,
|
||||
SubmittedAt = _submittedAt,
|
||||
CreatedAt = _submittedAt,
|
||||
UpdatedAt = _submittedAt,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>A submitted aanvraag with a behandelaar's decision already recorded. Terminal in the
|
||||
/// builder too — there's nothing past <see cref="Build"/>, matching Goedgekeurd/Afgewezen being
|
||||
/// terminal in the domain (<see cref="BeoordelingRules.CanDecide"/>); a fixture that needs a
|
||||
/// SECOND besluit (the MeerInfoGevraagd "still decidable" case) builds fresh from
|
||||
/// <see cref="Given.Concept"/> again, exactly as a real second request would.</summary>
|
||||
public sealed class DecidedAanvraag
|
||||
{
|
||||
private readonly SubmittedAanvraag _submitted;
|
||||
private readonly Besluit _besluit;
|
||||
private readonly string? _toelichting;
|
||||
|
||||
internal DecidedAanvraag(SubmittedAanvraag submitted, Besluit besluit, string? toelichting)
|
||||
{
|
||||
_submitted = submitted;
|
||||
_besluit = besluit;
|
||||
_toelichting = toelichting;
|
||||
}
|
||||
|
||||
public Aanvraag Build()
|
||||
{
|
||||
var aanvraag = _submitted.Build();
|
||||
aanvraag.BesluitStatus = _besluit;
|
||||
aanvraag.BesluitToelichting = _toelichting;
|
||||
return aanvraag;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using BigRegister.Api.Data;
|
||||
using BigRegister.Api.Zgw;
|
||||
using BigRegister.Domain.Applications;
|
||||
using BigRegister.Domain.Authorization;
|
||||
using BigRegister.Tests.Builders;
|
||||
|
||||
namespace BigRegister.Tests;
|
||||
|
||||
@@ -140,17 +141,12 @@ public class OpenZaakZaakSourceTests
|
||||
ZaaktypeUrls = new() { ["registratie"] = zaaktypeUrl },
|
||||
};
|
||||
var source = new OpenZaakZaakSource(new HttpClient(handler), new ZgwTokenProvider(options), options);
|
||||
var aanvraag = new Aanvraag
|
||||
{
|
||||
Id = "a1",
|
||||
Type = "registratie",
|
||||
Owner = "111222333",
|
||||
Referentie = "BIG-2026-000123",
|
||||
};
|
||||
var aanvraag = Given.Concept(type: "registratie", owner: "111222333").Submitted().Build();
|
||||
|
||||
var caller = new ZorgverlenerCaller(aanvraag.Owner, "Dr. Test", PrincipalRole.Drafter);
|
||||
var (referentie, status, zaakUrl) = source.CreateZaak(aanvraag, new DateTimeOffset(2026, 7, 28, 12, 0, 0, TimeSpan.Zero), caller);
|
||||
|
||||
// The stub echoes back its own (hardcoded) identificatie, same as a real OpenZaak response.
|
||||
Assert.Equal("BIG-2026-000123", referentie);
|
||||
Assert.Equal("InBehandeling", status.Tag);
|
||||
Assert.Equal("BIG-2026-000123", status.Referentie);
|
||||
@@ -160,7 +156,7 @@ public class OpenZaakZaakSourceTests
|
||||
var zaakBody = handler.BodyOf($"{ZrcBase}/zaken");
|
||||
Assert.Contains(zaaktypeUrl, zaakBody);
|
||||
Assert.Contains("123443210", zaakBody);
|
||||
Assert.Contains("BIG-2026-000123", zaakBody);
|
||||
Assert.Contains(aanvraag.Referentie!, zaakBody);
|
||||
|
||||
// Status: points at the created zaak's URL and the resolved statustype.
|
||||
var statusBody = handler.BodyOf($"{ZrcBase}/statussen");
|
||||
@@ -180,7 +176,7 @@ public class OpenZaakZaakSourceTests
|
||||
var options = new ZgwOptions { ZrcBaseUrl = ZrcBase, ZtcBaseUrl = ZtBase, ClientId = "c", Secret = "s" };
|
||||
var handler = new ZgwStubHandler(url => throw new InvalidOperationException($"no HTTP call expected, got {url}"));
|
||||
var source = new OpenZaakZaakSource(new HttpClient(handler), new ZgwTokenProvider(options), options);
|
||||
var aanvraag = new Aanvraag { Id = "a1", Type = "unknown-type", Owner = "111222333", Referentie = "BIG-2026-000123" };
|
||||
var aanvraag = Given.Concept(type: "unknown-type", owner: "111222333").Submitted().Build();
|
||||
var caller = new ZorgverlenerCaller(aanvraag.Owner, "Dr. Test", PrincipalRole.Drafter);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => source.CreateZaak(aanvraag, DateTimeOffset.UtcNow, caller));
|
||||
@@ -217,14 +213,8 @@ public class OpenZaakZaakSourceTests
|
||||
ZaaktypeUrls = new() { ["registratie"] = zaaktypeUrl },
|
||||
};
|
||||
var source = new OpenZaakZaakSource(new HttpClient(handler), new ZgwTokenProvider(options), options);
|
||||
var aanvraag = new Aanvraag
|
||||
{
|
||||
Id = "a1",
|
||||
Type = "registratie",
|
||||
Owner = "111222333",
|
||||
Referentie = "BIG-2026-000123",
|
||||
ZaakUrl = $"{ZrcBase}/zaken/uuid-existing",
|
||||
};
|
||||
var aanvraag = Given.Concept(type: "registratie", owner: "111222333").Submitted().Build();
|
||||
aanvraag.ZaakUrl = $"{ZrcBase}/zaken/uuid-existing";
|
||||
var caller = new MedewerkerCaller("m1", new[] { MedewerkerRol.Behandelaar }, "Medewerker Test", PrincipalRole.Drafter);
|
||||
|
||||
source.RecordBesluit(aanvraag, Besluit.Afwijzen, "onvolledig", DateTimeOffset.UtcNow, caller);
|
||||
@@ -268,14 +258,8 @@ public class OpenZaakZaakSourceTests
|
||||
ZaaktypeUrls = new() { ["registratie"] = zaaktypeUrl },
|
||||
};
|
||||
var source = new OpenZaakZaakSource(new HttpClient(handler), new ZgwTokenProvider(options), options);
|
||||
var aanvraag = new Aanvraag
|
||||
{
|
||||
Id = "a1",
|
||||
Type = "registratie",
|
||||
Owner = "111222333",
|
||||
Referentie = "BIG-2026-000123",
|
||||
ZaakUrl = $"{ZrcBase}/zaken/uuid-existing",
|
||||
};
|
||||
var aanvraag = Given.Concept(type: "registratie", owner: "111222333").Submitted().Build();
|
||||
aanvraag.ZaakUrl = $"{ZrcBase}/zaken/uuid-existing";
|
||||
var caller = new MedewerkerCaller("m1", new[] { MedewerkerRol.Behandelaar }, "Medewerker Test", PrincipalRole.Drafter);
|
||||
|
||||
source.RecordBesluit(aanvraag, Besluit.Goedkeuren, null, DateTimeOffset.UtcNow, caller);
|
||||
@@ -296,7 +280,8 @@ public class OpenZaakZaakSourceTests
|
||||
var options = new ZgwOptions { ZrcBaseUrl = ZrcBase, ZtcBaseUrl = ZtBase, ClientId = "c", Secret = "s" };
|
||||
var handler = new ZgwStubHandler(url => throw new InvalidOperationException($"no HTTP call expected, got {url}"));
|
||||
var source = new OpenZaakZaakSource(new HttpClient(handler), new ZgwTokenProvider(options), options);
|
||||
var aanvraag = new Aanvraag { Id = "a1", Type = "registratie", Owner = "111222333", ZaakUrl = null };
|
||||
// ZaakUrl deliberately left unset — no builder call touches it, so it stays null.
|
||||
var aanvraag = Given.Concept(type: "registratie", owner: "111222333").Submitted().Build();
|
||||
var caller = new MedewerkerCaller("m1", new[] { MedewerkerRol.Behandelaar }, "Medewerker Test", PrincipalRole.Drafter);
|
||||
|
||||
source.RecordBesluit(aanvraag, Besluit.Goedkeuren, null, DateTimeOffset.UtcNow, caller);
|
||||
@@ -310,7 +295,8 @@ public class OpenZaakZaakSourceTests
|
||||
var options = new ZgwOptions { ZrcBaseUrl = ZrcBase, ZtcBaseUrl = ZtBase, ClientId = "c", Secret = "s" };
|
||||
var handler = new ZgwStubHandler(url => throw new InvalidOperationException($"no HTTP call expected, got {url}"));
|
||||
var source = new OpenZaakZaakSource(new HttpClient(handler), new ZgwTokenProvider(options), options);
|
||||
var aanvraag = new Aanvraag { Id = "a1", Type = "unknown-type", Owner = "111222333", ZaakUrl = $"{ZrcBase}/zaken/uuid-existing" };
|
||||
var aanvraag = Given.Concept(type: "unknown-type", owner: "111222333").Submitted().Build();
|
||||
aanvraag.ZaakUrl = $"{ZrcBase}/zaken/uuid-existing";
|
||||
var caller = new MedewerkerCaller("m1", new[] { MedewerkerRol.Behandelaar }, "Medewerker Test", PrincipalRole.Drafter);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => source.RecordBesluit(aanvraag, Besluit.Goedkeuren, null, DateTimeOffset.UtcNow, caller));
|
||||
@@ -331,7 +317,7 @@ public class OpenZaakZaakSourceTests
|
||||
VerantwoordelijkeOrganisatie = "123443210",
|
||||
ZaaktypeUrls = new() { ["registratie"] = zaaktypeUrl },
|
||||
};
|
||||
var aanvraag = new Aanvraag { Id = "a1", Type = "registratie", Owner = "111222333", Referentie = "BIG-2026-000123" };
|
||||
var aanvraag = Given.Concept(type: "registratie", owner: "111222333").Submitted().Build();
|
||||
var caller = new ZorgverlenerCaller(aanvraag.Owner, "Dr. Test", PrincipalRole.Drafter);
|
||||
return (options, aanvraag, caller);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using BigRegister.Api.Data;
|
||||
using BigRegister.Domain.Applications;
|
||||
using BigRegister.Domain.Beoordeling;
|
||||
using BigRegister.Domain.Diplomas;
|
||||
using BigRegister.Domain.Documents;
|
||||
using BigRegister.Domain.Registrations;
|
||||
using BigRegister.Domain.Submissions;
|
||||
using BigRegister.Tests.Builders;
|
||||
|
||||
namespace BigRegister.Tests;
|
||||
|
||||
@@ -207,35 +207,24 @@ public class BeoordelingRuleTests
|
||||
// StatusAt, and CanDecide refuses a further besluit regardless of which one. Pins the
|
||||
// domain statement "Afgewezen/Goedgekeurd → no further besluit" independent of the
|
||||
// endpoint's own (integration-level) Already_decided_case_rejects_a_further_besluit.
|
||||
private static Aanvraag Decided(Besluit besluit) => new()
|
||||
{
|
||||
Id = "x",
|
||||
Type = "registratie",
|
||||
Owner = "test",
|
||||
Submitted = true,
|
||||
Referentie = "BIG-2026-1",
|
||||
SubmittedAt = DateTimeOffset.UtcNow,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UpdatedAt = DateTimeOffset.UtcNow,
|
||||
BesluitStatus = besluit,
|
||||
BesluitToelichting = besluit == Besluit.Goedkeuren ? null : "toelichting",
|
||||
};
|
||||
|
||||
// WP-70: built via Given, not a hand-rolled Aanvraag literal — Decided(Besluit.Afwijzen) with
|
||||
// no toelichting simply couldn't compile as a fixture here.
|
||||
[Theory]
|
||||
[InlineData(Besluit.Goedkeuren)]
|
||||
[InlineData(Besluit.Afwijzen)]
|
||||
public void A_terminal_decision_refuses_any_further_besluit(Besluit recorded)
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var tag = Decided(recorded).StatusAt(now).Tag!.Value;
|
||||
Assert.False(BeoordelingRules.CanDecide(tag));
|
||||
var toelichting = recorded == Besluit.Goedkeuren ? null : "toelichting";
|
||||
var aanvraag = Given.Concept(owner: "test").Submitted().Decided(recorded, toelichting).Build();
|
||||
Assert.False(BeoordelingRules.CanDecide(aanvraag.StatusAt(now).Tag!.Value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeerInfoOpvragen_is_not_terminal_a_further_besluit_is_still_legal()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var tag = Decided(Besluit.MeerInfoOpvragen).StatusAt(now).Tag!.Value;
|
||||
Assert.True(BeoordelingRules.CanDecide(tag));
|
||||
var aanvraag = Given.Concept(owner: "test").Submitted().Decided(Besluit.MeerInfoOpvragen, "toelichting").Build();
|
||||
Assert.True(BeoordelingRules.CanDecide(aanvraag.StatusAt(now).Tag!.Value));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user