Adds LetterHtml.Render, a pure composer mirroring the FE letter canvas'
class vocabulary, behind two ExcludeFromDescription()'d endpoints
(GET /brief/preview, GET /admin/org-template/{subOrgId}/preview).
Auto-resolvable placeholders pull from seed/case data; unresolved
manual ones render as "[NOG IN TE VULLEN: label]". A sent brief
archives its composed HTML (BriefEntity.ArchivedHtml) so a later
org-template republish never changes it. FE gets a hand-written fetch
adapter (text/html, not JSON) and a "Voorbeeld" button that opens the
preview in a new tab.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
97 lines
4.2 KiB
C#
97 lines
4.2 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using BigRegister.Api.Contracts;
|
|
using BigRegister.Api.Data;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
|
|
namespace BigRegister.Tests;
|
|
|
|
/// <summary>
|
|
/// WP-25: the two HTML preview endpoints. Both are excluded from the OpenAPI doc
|
|
/// (see the drift check in the API-client generation step) — these tests hit them
|
|
/// as plain HTTP, the same way the hand-written FE fetch does.
|
|
/// </summary>
|
|
public class PreviewEndpointTests(TestWebApplicationFactory factory) : IClassFixture<TestWebApplicationFactory>
|
|
{
|
|
private const string Registers = OrgTemplateSeed.Registers;
|
|
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") }) }));
|
|
|
|
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 static void ResetStores()
|
|
{
|
|
BriefStore.Reset();
|
|
OrgTemplateStore.Reset();
|
|
}
|
|
|
|
private HttpRequestMessage Req(HttpMethod method, string path, string? role = null) =>
|
|
role is null ? new HttpRequestMessage(method, path) : new HttpRequestMessage(method, path) { Headers = { { "X-Role", role } } };
|
|
|
|
[Fact]
|
|
public async Task Preview_of_an_unsent_brief_renders_live_with_a_watermark()
|
|
{
|
|
ResetStores();
|
|
await _client.GetAsync("/api/v1/brief"); // GetOrCreate the demo draft
|
|
|
|
var res = await _client.GetAsync("/api/v1/brief/preview");
|
|
res.EnsureSuccessStatusCode();
|
|
Assert.Equal("text/html", res.Content.Headers.ContentType?.MediaType);
|
|
var html = await res.Content.ReadAsStringAsync();
|
|
Assert.Contains("<div class=\"preview-watermark\"", html);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Preview_of_a_sent_brief_serves_the_archive_unchanged_after_a_republish()
|
|
{
|
|
ResetStores();
|
|
var brief = (await _client.GetFromJsonAsync<BriefViewDto>("/api/v1/brief"))!.Brief;
|
|
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
|
|
await _client.SendAsync(Req(HttpMethod.Post, "/api/v1/brief/submit"));
|
|
await _client.SendAsync(Req(HttpMethod.Post, "/api/v1/brief/approve", role: "approver"));
|
|
await _client.SendAsync(Req(HttpMethod.Post, "/api/v1/brief/send"));
|
|
|
|
var sentHtml = await (await _client.GetAsync("/api/v1/brief/preview")).Content.ReadAsStringAsync();
|
|
Assert.DoesNotContain("<div class=\"preview-watermark\"", sentHtml);
|
|
Assert.Contains("BIG-register", sentHtml);
|
|
|
|
// Republish the org template under a new name.
|
|
var adminView = await _client.SendAsync(Req(HttpMethod.Get, $"/api/v1/admin/org-template/{Registers}", role: "admin"));
|
|
var draft = (await adminView.Content.ReadFromJsonAsync<OrgTemplateAdminViewDto>())!.Draft;
|
|
await _client.SendAsync(new HttpRequestMessage(HttpMethod.Put, $"/api/v1/admin/org-template/{Registers}")
|
|
{
|
|
Headers = { { "X-Role", "admin" } },
|
|
Content = JsonContent.Create(new SaveOrgTemplateRequest(draft with { OrgName = "Hertitelde organisatie" })),
|
|
});
|
|
await _client.SendAsync(Req(HttpMethod.Post, $"/api/v1/admin/org-template/{Registers}/publish", role: "admin"));
|
|
|
|
// The sent brief's preview is unchanged — still the archived rendering.
|
|
var afterRepublish = await (await _client.GetAsync("/api/v1/brief/preview")).Content.ReadAsStringAsync();
|
|
Assert.Equal(sentHtml, afterRepublish);
|
|
Assert.DoesNotContain("Hertitelde organisatie", afterRepublish);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Proefbrief_is_admin_only_and_renders_the_draft_template()
|
|
{
|
|
ResetStores();
|
|
Assert.Equal(HttpStatusCode.Forbidden,
|
|
(await _client.GetAsync($"/api/v1/admin/org-template/{Registers}/preview")).StatusCode);
|
|
|
|
var res = await _client.SendAsync(Req(HttpMethod.Get, $"/api/v1/admin/org-template/{Registers}/preview", role: "admin"));
|
|
res.EnsureSuccessStatusCode();
|
|
var html = await res.Content.ReadAsStringAsync();
|
|
Assert.Contains("BIG-register", html);
|
|
Assert.Contains("<div class=\"preview-watermark\"", html);
|
|
}
|
|
}
|