99 lines
5.0 KiB
C#
99 lines
5.0 KiB
C#
using JobTrackerApi.Models;
|
|
using JobTrackerApi.Services;
|
|
using System.Text.Json;
|
|
using Xunit;
|
|
|
|
namespace JobTrackerApi.Tests;
|
|
|
|
public sealed class CoverLetterDocumentContractTests
|
|
{
|
|
[Theory]
|
|
[InlineData("en", "Cover letter", "Cover letter text")]
|
|
[InlineData("nb-NO", "Søknadsbrev", "Brevtekst")]
|
|
public void Empty_optional_details_do_not_create_empty_headings_or_an_unnamed_main_landmark(string language, string title, string bodyLabel)
|
|
{
|
|
var html = new CoverLetterDocumentRenderer().Render("Reviewed body", new() { Language = language }).Html;
|
|
Assert.DoesNotContain("<h1></h1>", html);
|
|
Assert.DoesNotContain("<h2></h2>", html);
|
|
Assert.Contains($"<title>{System.Net.WebUtility.HtmlEncode(title)}</title>", html);
|
|
Assert.Contains($"<main aria-label=\"{bodyLabel}\">", html);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null, "en")]
|
|
[InlineData("{}", "en")]
|
|
[InlineData("{\"senderName\":\"Legacy sender\"}", "en")]
|
|
[InlineData("{\"language\":\"en\"}", "en")]
|
|
[InlineData("{\"language\":\"nb\"}", "nb-NO")]
|
|
[InlineData("{\"language\":\"nb-NO\"}", "nb-NO")]
|
|
[InlineData("{\"language\":\" NB-no \"}", "nb-NO")]
|
|
[InlineData("{\"language\":null}", "en")]
|
|
[InlineData("{\"language\":\"fr\"}", "en")]
|
|
[InlineData("{\"language\":\"en\\\" onload=alert(1)\"}", "en")]
|
|
public void Document_language_defaults_normalizes_and_roundtrips_only_supported_values(string? json, string expected)
|
|
{
|
|
var settings = CoverLetterDocumentSettingsJson.Deserialize(json);
|
|
var normalized = CoverLetterDocumentSettingsJson.Serialize(settings);
|
|
Assert.Equal(expected, JsonDocument.Parse(normalized).RootElement.GetProperty("language").GetString());
|
|
Assert.Equal(normalized, CoverLetterDocumentSettingsJson.Serialize(CoverLetterDocumentSettingsJson.Deserialize(normalized)));
|
|
var html = new CoverLetterDocumentRenderer().Render("Reviewed letter — æøå", settings).Html;
|
|
Assert.Contains($"<html lang=\"{expected}\">", html);
|
|
Assert.DoesNotContain("onload=", html);
|
|
}
|
|
|
|
[Fact]
|
|
public void Document_style_reuses_cv_allowlists_clamps_non_finite_values_and_escapes_all_metadata()
|
|
{
|
|
var settings = CoverLetterDocumentSettingsJson.Normalize(new()
|
|
{
|
|
ThemeId = "unknown", BodyFont = "Arial; background:url(https://invalid.test)", AccentColor = "#fff;bad",
|
|
BaseFontSizePt = 100, LineHeight = double.PositiveInfinity, PageMarginMm = -10, PageSize = "<style>", SourceCvId = -1,
|
|
});
|
|
Assert.Equal("modern", settings.ThemeId);
|
|
Assert.Equal("#2563eb", settings.AccentColor);
|
|
Assert.Equal("'Segoe UI', Roboto, Arial, sans-serif", settings.BodyFont);
|
|
Assert.Equal(13, settings.BaseFontSizePt);
|
|
Assert.Equal(1.42, settings.LineHeight);
|
|
Assert.Equal(8, settings.PageMarginMm);
|
|
Assert.Equal("a4", settings.PageSize);
|
|
Assert.Null(settings.SourceCvId);
|
|
var html = new CoverLetterDocumentRenderer().Render("**bold**\n- bullet\n*italic*\n[x](javascript:alert(1))", new()
|
|
{
|
|
SenderTitle = "<iframe src=x>", SenderEmail = "<img src=x>", SenderPhone = "<script>x</script>",
|
|
Recipient = "<object data=x>", Subject = "</title><script>x</script>", Date = "<svg onload=x>", PageSize = "letter",
|
|
}).Html;
|
|
Assert.Contains("<strong>bold</strong>", html);
|
|
Assert.Contains("<li>bullet</li>", html);
|
|
Assert.Contains("<em>italic</em>", html);
|
|
Assert.Contains("@page{size:215.9mm 279.4mm", html);
|
|
Assert.DoesNotContain("<script>", html);
|
|
Assert.DoesNotContain("<img src", html);
|
|
Assert.DoesNotContain("<iframe", html);
|
|
Assert.DoesNotContain("href=\"javascript:", html);
|
|
Assert.DoesNotContain("<svg", html);
|
|
Assert.Contains("<object", html);
|
|
}
|
|
|
|
[Fact]
|
|
public void Renderer_escapes_untrusted_content_and_exposes_shared_physical_page_contract()
|
|
{
|
|
var result = new CoverLetterDocumentRenderer().Render("<script>alert(1)</script>", new()
|
|
{ SenderName = "<img src=x onerror=alert(1)>", AccentColor = "red; background:url(https://invalid.test)", PageMarginMm = 16 });
|
|
Assert.DoesNotContain("<script>alert", result.Html);
|
|
Assert.DoesNotContain("<img src=x", result.Html);
|
|
Assert.DoesNotContain("https://invalid.test", result.Html);
|
|
Assert.Contains("class=\"page letter-paper\"", result.Html);
|
|
Assert.Contains("--cv-page-margin:16mm", result.Html);
|
|
Assert.Contains("@page{size:210mm 297mm", result.Html);
|
|
}
|
|
|
|
[Fact]
|
|
public void Letter_design_and_frozen_render_are_additive_nullable_persistence_fields()
|
|
{
|
|
Assert.NotNull(typeof(JobApplication).GetProperty("CoverLetterDocumentSettingsJson"));
|
|
Assert.NotNull(typeof(CoverLetterVersion).GetProperty("DocumentSettingsJson"));
|
|
Assert.NotNull(typeof(SubmittedApplicationPackage).GetProperty("CoverLetterDocumentSettingsJson"));
|
|
Assert.NotNull(typeof(SubmittedApplicationPackage).GetProperty("RenderedCoverLetterHtml"));
|
|
}
|
|
}
|