using System.Net; using System.Net.Http.Json; using BigRegister.Api.Contracts; using BigRegister.Api.Data; using Microsoft.AspNetCore.Mvc.Testing; namespace BigRegister.Tests; /// /// The brief is a single process-global demo entity, so each test resets it first /// (tests within a class run sequentially in xUnit). Role is the dev-only X-Role /// header: absent = drafter, "approver" = a different identity. /// public class BriefEndpointTests(WebApplicationFactory factory) : IClassFixture> { private readonly HttpClient _client = factory.CreateClient(); private static LetterBlockDto FreeText(string id) => new("freeText", id, new RichTextBlockDto(new[] { new ParagraphDto(new[] { new RichTextNodeDto("text", Text: "inhoud") }) })); // Fill every REQUIRED section with a block so submit is allowed. private static SaveBriefRequest FilledFrom(BriefDto brief) { var i = 0; var sections = brief.Sections .Select(s => new LetterSectionDto(s.SectionKey, s.Title, s.Required, s.Required ? new[] { FreeText($"local-{++i}") } : s.Blocks)) .ToList(); return new SaveBriefRequest(sections); } private async Task Get() { BriefStore.Reset(); var view = await _client.GetFromJsonAsync("/api/v1/brief"); return view!.Brief; } private HttpRequestMessage Post(string path, string? role = null, object? body = null) { var req = new HttpRequestMessage(HttpMethod.Post, path); if (role is not null) req.Headers.Add("X-Role", role); if (body is not null) req.Content = JsonContent.Create(body); return req; } [Fact] public async Task Get_creates_a_draft_from_the_template_with_scoped_passages() { var brief = await Get(); Assert.Equal("draft", brief.Status.Tag); Assert.Equal(new[] { "aanhef", "kern", "slot" }, brief.Sections.Select(s => s.SectionKey)); Assert.All(brief.Sections, s => Assert.Empty(s.Blocks)); var view = await _client.GetFromJsonAsync("/api/v1/brief"); // global passages + the arts-scoped one; no other-beroep passages leak in. Assert.Contains(view!.AvailablePassages, p => p.PassageId == "p-kern-arts"); Assert.All(view.AvailablePassages, p => Assert.True(p.Scope == "global" || p.Beroep == "arts")); } [Fact] public async Task Save_is_drafter_only() { var brief = await Get(); var save = FilledFrom(brief); var approver = Post("/api/v1/brief", role: "approver"); approver.Method = HttpMethod.Put; approver.Content = JsonContent.Create(save); Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(approver)).StatusCode); Assert.Equal(HttpStatusCode.OK, (await _client.PutAsJsonAsync("/api/v1/brief", save)).StatusCode); } [Fact] public async Task Submit_blocks_on_empty_required_section_then_succeeds_when_filled() { await Get(); // Nothing filled yet → required sections empty → 409. Assert.Equal(HttpStatusCode.Conflict, (await _client.SendAsync(Post("/api/v1/brief/submit"))).StatusCode); var brief = (await _client.GetFromJsonAsync("/api/v1/brief"))!.Brief; await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief)); var res = await _client.SendAsync(Post("/api/v1/brief/submit")); res.EnsureSuccessStatusCode(); var submitted = await res.Content.ReadFromJsonAsync(); Assert.Equal("submitted", submitted!.Status.Tag); } [Fact] public async Task Drafter_cannot_approve_own_letter_but_a_different_reviewer_can() { var brief = await Get(); await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief)); await _client.SendAsync(Post("/api/v1/brief/submit")); // drafter role approving own letter → 403 Assert.Equal(HttpStatusCode.Forbidden, (await _client.SendAsync(Post("/api/v1/brief/approve"))).StatusCode); var res = await _client.SendAsync(Post("/api/v1/brief/approve", role: "approver")); res.EnsureSuccessStatusCode(); Assert.Equal("approved", (await res.Content.ReadFromJsonAsync())!.Status.Tag); } [Fact] public async Task Reject_returns_comments_and_editing_reopens_to_draft() { var brief = await Get(); await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief)); await _client.SendAsync(Post("/api/v1/brief/submit")); var rejected = await (await _client.SendAsync( Post("/api/v1/brief/reject", role: "approver", body: new RejectBriefRequest("Graag aanvullen.")))).Content.ReadFromJsonAsync(); Assert.Equal("rejected", rejected!.Status.Tag); Assert.Equal("Graag aanvullen.", rejected.Status.Comments); // A drafter save on a rejected letter reopens it to draft. var reopened = await (await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief))).Content.ReadFromJsonAsync(); Assert.Equal("draft", reopened!.Status.Tag); } [Fact] public async Task Send_only_from_approved() { var brief = await Get(); await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief)); await _client.SendAsync(Post("/api/v1/brief/submit")); // submitted (not approved) → send 409 Assert.Equal(HttpStatusCode.Conflict, (await _client.SendAsync(Post("/api/v1/brief/send"))).StatusCode); await _client.SendAsync(Post("/api/v1/brief/approve", role: "approver")); var res = await _client.SendAsync(Post("/api/v1/brief/send")); res.EnsureSuccessStatusCode(); Assert.Equal("sent", (await res.Content.ReadFromJsonAsync())!.Status.Tag); } }