Files
jobyssey/tests/test_generate_cv.py
T

185 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Contract tests for tools/generate_cv.py.
The generator turns documents/sources/ YAML plus a role config into a
LaTeX CV. What must hold:
- Source description blocks split into the right bullets, including the
leading-prose case (projects/ecosystem-devrel.yml).
- Every escape from 05-cv-templates.md's special-character table is
applied; `%` is the silent killer (truncates the rest of the line).
- Date ranges use a single ASCII hyphen, never `--` (en-dash ligature
breaks ATS date parsing - confirmed Workday import failure).
- `use: N` bullet refs resolve 1-based and fail loudly out of range.
- Skill labels absent from documents/sources/ warn unless allowlisted
via `extra-skills`; the shipped role configs generate warning-free.
- Generated documents never contain `\\item [` (F9: clipped label trap).
"""
import datetime
import re
import sys
import tempfile
import unittest
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "tools"))
import generate_cv # noqa: E402
UNBRACED_BRACKET_ITEM = re.compile(r"\\item\s*\[")
class TestParseBullets(unittest.TestCase):
def test_splits_bullets_and_joins_continuations(self):
text = "- first bullet\n continues here\n- second bullet\n"
self.assertEqual(
generate_cv.parse_bullets(text),
["first bullet continues here", "second bullet"],
)
def test_leading_prose_becomes_first_bullet(self):
text = "Intro paragraph.\n\n- real bullet\n"
self.assertEqual(
generate_cv.parse_bullets(text),
["Intro paragraph.", "real bullet"],
)
def test_empty_description(self):
self.assertEqual(generate_cv.parse_bullets(None), [])
class TestLatexEscape(unittest.TestCase):
def test_special_characters(self):
self.assertEqual(
generate_cv.latex_escape("99% uptime & $4.7M for C# devs_v1"),
"99\\% uptime \\& \\$4.7M for C\\# devs\\_v1",
)
def test_unicode_punctuation(self):
self.assertEqual(generate_cv.latex_escape("a — b – c"), "a --- b -- c")
self.assertEqual(generate_cv.latex_escape("\u201cMotr\u201d"), "``Motr''")
self.assertEqual(generate_cv.latex_escape("Yu\u0113"), "Yu\\=e")
self.assertEqual(
generate_cv.latex_escape("A \u00b7 B"), "A \\textperiodcentered{} B"
)
class TestFormatRange(unittest.TestCase):
def test_open_range_uses_ascii_hyphen_and_present(self):
start = datetime.date(2021, 3, 1)
self.assertEqual(generate_cv.format_range(start, None), "Mar 2021 - Present")
def test_year_only_range(self):
start = datetime.date(2022, 1, 1)
end = datetime.date(2023, 8, 1)
self.assertEqual(
generate_cv.format_range(start, end, month=False), "2022 - 2023"
)
def test_never_emits_double_hyphen(self):
start = datetime.date(2016, 7, 1)
end = datetime.date(2019, 5, 1)
self.assertNotIn("--", generate_cv.format_range(start, end))
class TestResolveBullets(unittest.TestCase):
SOURCE = {"description": "- alpha\n- beta\n- gamma\n"}
PATH = Path("fake.yml")
def test_use_index_is_one_based(self):
entry = {"bullets": [{"use": 2}, "literal text"]}
self.assertEqual(
generate_cv.resolve_bullets(entry, self.SOURCE, self.PATH),
["beta", "literal text"],
)
def test_default_is_all_bullets(self):
self.assertEqual(
generate_cv.resolve_bullets({}, self.SOURCE, self.PATH),
["alpha", "beta", "gamma"],
)
def test_out_of_range_index_fails(self):
with self.assertRaises(generate_cv.ConfigError):
generate_cv.resolve_bullets(
{"bullets": [{"use": 4}]}, self.SOURCE, self.PATH
)
class TestSkillValidation(unittest.TestCase):
def test_unknown_skill_warns(self):
warnings = []
generate_cv.validate_skills(
[{"group": "X", "items": ["Definitely Not A Skill"]}], [], warnings.append
)
self.assertEqual(len(warnings), 1)
def test_extra_skills_allowlist_silences(self):
warnings = []
generate_cv.validate_skills(
[{"group": "X", "items": ["Definitely Not A Skill"]}],
["Definitely Not A Skill"],
warnings.append,
)
self.assertEqual(warnings, [])
def test_parenthetical_alias_grounds_label(self):
warnings = []
# "Go (Golang)" grounds via "Go"; "DIDs" via the sources'
# "Decentralized Identifiers (DIDs)".
generate_cv.validate_skills(
[{"group": "X", "items": ["Go (Golang)", "DIDs"]}], [], warnings.append
)
self.assertEqual(warnings, [])
class TestShippedRoleConfigs(unittest.TestCase):
"""Every role config in documents/cv/roles/ must generate cleanly."""
def generate_all(self):
results = {}
with tempfile.TemporaryDirectory() as tmp:
for role in generate_cv.list_roles():
warnings = []
target = generate_cv.generate(
role, output=Path(tmp) / f"{role}.tex", warn=warnings.append
)
results[role] = (target.read_text(encoding="utf-8"), warnings)
return results
def test_expected_roles_exist(self):
self.assertLessEqual(
{
"example",
"staff-software-engineer",
"senior-software-engineer",
"founding-software-engineer",
},
set(generate_cv.list_roles()),
)
def test_configs_generate_without_grounding_warnings(self):
for role, (_, warnings) in self.generate_all().items():
self.assertEqual(warnings, [], f"{role}: ungrounded skills: {warnings}")
def test_output_is_wellformed_latex(self):
for role, (text, _) in self.generate_all().items():
body = "\n".join(
line for line in text.splitlines() if not line.startswith("%%")
)
self.assertIn("\\begin{document}", body, role)
self.assertIn("\\end{document}", body, role)
self.assertIn("prnk28@gmail.com", body, role)
# F9 guard: no bullet parsed as an optional label.
self.assertIsNone(UNBRACED_BRACKET_ITEM.search(body), role)
# No unescaped % (silent truncation) or & (compile error).
self.assertIsNone(re.search(r"(?<!\\)%", body), role)
self.assertIsNone(re.search(r"(?<!\\)&", body), role)
# No en-dash-producing "--" inside a date \textit{...}.
for match in re.finditer(r"\\textit\{([^}]*\d{4}[^}]*)\}", body):
self.assertNotIn("--", match.group(1), f"{role}: {match.group(0)}")
if __name__ == "__main__":
unittest.main()