Files
jobtrackingapp/JobTrackerApi.Tests/CvTemplateRendererTests.cs
T
cesnimda 66384bda60 test: add regression coverage for CV template renderer
CvTemplateRenderer (six hardcoded HTML templates, ~450 lines) had zero
test coverage. Lock in current behavior before any future theme
extraction touches it: renders without throwing and contains the
content it was given, deterministic for identical input, falls back
to ats-minimal for an unknown template id, and HTML-encodes
user-supplied content (regression guard against CV text containing
markup).
2026-07-12 15:19:02 +02:00

87 lines
3.3 KiB
C#

using JobTrackerApi.Models;
using JobTrackerApi.Services;
using Xunit;
namespace JobTrackerApi.Tests;
// Regression safety net for the renderer ahead of the theme-extraction work in
// career-workspace-implementation-roadmap.md Phase F3. The renderer had zero test coverage;
// these tests lock in "renders without throwing, is deterministic, and contains the content it
// was given" for every shipped template so a future refactor (or accidental edit) that changes
// output is caught immediately.
public sealed class CvTemplateRendererTests
{
private static readonly string[] AllTemplateIds = { "ats-minimal", "harvard", "auckland", "edinburgh", "monarch", "fjord" };
private readonly CvTemplateRenderer _renderer = new();
private static TailoredCvDocument SampleDocument(string templateId) => new()
{
TemplateId = templateId,
Headline = "Senior Backend Engineer",
Summary = { "Builds reliable distributed systems." },
SelectedSkills = { "C#", ".NET", "SQL" },
Experience =
{
new TailoredCvExperienceItem
{
Title = "Backend Engineer",
Company = "Acme Corp",
Start = "2020",
End = "Present",
IsCurrent = true,
Bullets = { "Shipped the payments service." },
},
},
Education =
{
new TailoredCvEducationItem { Qualification = "BSc Computer Science", Institution = "Example University" },
},
};
[Theory]
[MemberData(nameof(TemplateIds))]
public void Render_produces_html_containing_candidate_and_content(string templateId)
{
var result = _renderer.Render(SampleDocument(templateId), templateId, "Jamie Rivera", "Backend Engineer", "Acme Corp");
Assert.Equal(templateId, result.TemplateId);
Assert.Contains("Jamie Rivera", result.Html);
Assert.Contains("Acme Corp", result.Html);
Assert.Contains("Shipped the payments service.", result.Html);
Assert.Contains("<!DOCTYPE html>", result.Html);
}
[Theory]
[MemberData(nameof(TemplateIds))]
public void Render_is_deterministic_for_identical_input(string templateId)
{
var a = _renderer.Render(SampleDocument(templateId), templateId, "Jamie Rivera", "Backend Engineer", "Acme Corp");
var b = _renderer.Render(SampleDocument(templateId), templateId, "Jamie Rivera", "Backend Engineer", "Acme Corp");
Assert.Equal(a.Html, b.Html);
}
[Fact]
public void Unknown_template_id_falls_back_to_ats_minimal()
{
var result = _renderer.Render(SampleDocument("unknown"), "not-a-real-template", "Jamie Rivera", "Backend Engineer", null);
Assert.Equal("ats-minimal", result.TemplateId);
}
[Fact]
public void Html_encodes_user_supplied_content_to_prevent_injection()
{
var document = SampleDocument("ats-minimal");
document.Summary[0] = "<script>alert(1)</script>";
var result = _renderer.Render(document, "ats-minimal", "Jamie Rivera", "Backend Engineer", null);
Assert.DoesNotContain("<script>alert(1)</script>", result.Html);
Assert.Contains("&lt;script&gt;", result.Html);
}
public static IEnumerable<object[]> TemplateIds() => AllTemplateIds.Select(id => new object[] { id });
}