66384bda60
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).
87 lines
3.3 KiB
C#
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("<script>", result.Html);
|
|
}
|
|
|
|
public static IEnumerable<object[]> TemplateIds() => AllTemplateIds.Select(id => new object[] { id });
|
|
}
|