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).
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
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("<script>", result.Html);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> TemplateIds() => AllTemplateIds.Select(id => new object[] { id });
|
||||
}
|
||||
Reference in New Issue
Block a user