Files
claude-resume-kit/tests/test_resume_system.py
T
dennisthiessenandClaude Opus 5 afdef7eb94 refactor: shared agent workflows (F01) and one application registry (F02)
Checkpoint for the 2026-09-11 project review remediation. Work by a previous
agent that ran out of context; committed unchanged as a rollback point before
the remaining findings land on top of it.

F01 — the two agent directories had drifted into different processes
- Six shared workflows under `resume_builder/reference/workflows/`; both
  `.agents/skills/` and `.claude/skills/` are now thin entrypoints that route
  to the same file instead of carrying full copies.
- The `.claude` copies were the stale side: fixed bullet line variants and
  character budgets that the newer process had already dropped. Removing them
  is the point of the change, not collateral.
- Parity/link regression test added; corrected the top-level file map, which
  pointed at a directory that does not exist.

F02 — application status was stored in four places and disagreed
- New registry `job_scout/state/applications.json` (26 records) with
  `application_records.py` (schema, validation, transactional write) and
  `application_tracker.py` (add/update, no quota).
- AGENTS.md and CLAUDE.md no longer keep their own status tables; both point
  at `current_applications.md`, generated from the registry.
- Interview-reached is now independent of outcome, so a withdrawal after an
  interview can record both facts. The old tables are preserved verbatim in
  `job_scout/state/history/application_status_before_2026-09-11.json`.

Verification: `python -m unittest discover -s tests -v` — 18/18 pass.
Spot-checked that the detailed SBB and Kdo Cy notes survived the migration
into the registry rather than being flattened.

Known unresolved, deliberately not invented (see FIX_PROGRESS.md):
Aker BP's application date and its Adjacent-despite-79 classification.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qej5SBvhcbGXHwLqNWiskM
2026-09-11 19:23:13 +02:00

135 lines
5.9 KiB
Python

from __future__ import annotations
import importlib.util
import json
import re
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def load_module(name: str, relative_path: str):
spec = importlib.util.spec_from_file_location(name, ROOT / relative_path)
module = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(module)
return module
validator = load_module("validate_resume_system", "resume_builder/helpers/validate_resume_system.py")
length_helper = load_module("char_count", "resume_builder/helpers/char_count.py")
class CanonicalSystemTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.claims = json.loads(
(ROOT / "resume_builder/canonical/claims.json").read_text(encoding="utf-8")
)
cls.history = json.loads(
(ROOT / "resume_builder/canonical/historical_outputs.json").read_text(encoding="utf-8")
)
def test_canonical_and_workflow_checks_have_no_errors(self) -> None:
errors, _warnings = validator.canonical_checks(self.claims, self.history)
errors.extend(validator.workflow_checks())
self.assertEqual([], errors)
def test_claim_and_employment_ids_are_unique(self) -> None:
for collection in ("employment", "claims"):
ids = [item["id"] for item in self.claims[collection]]
self.assertEqual(len(ids), len(set(ids)), collection)
def test_swiss_work_authorization_is_verified(self) -> None:
identity = self.claims["identity"]
self.assertEqual("B residence permit", identity["swiss_permit"])
self.assertIn("no visa or employer sponsorship required", identity["swiss_work_authorization"])
self.assertFalse(identity["swiss_permit"].startswith("UNVERIFIED"))
def test_historical_outputs_are_classified_once(self) -> None:
folders = []
for category in ("unsafe_do_not_reuse", "historical_revalidate"):
folders.extend(
item["folder"] if isinstance(item, dict) else item
for item in self.history[category]
)
self.assertEqual(len(folders), len(set(folders)))
def test_templates_pass_document_claim_scan(self) -> None:
for relative in (
"resume_builder/templates/resume_template.tex",
"resume_builder/templates/coverletter_template.tex",
):
self.assertEqual([], validator.scan_document(ROOT / relative, self.claims), relative)
class WorkflowPolicyTests(unittest.TestCase):
def test_agent_entrypoints_resolve_the_same_shared_workflow(self) -> None:
for entry in sorted((ROOT / ".agents/skills").glob("*/SKILL.md")):
other = ROOT / ".claude/skills" / entry.parent.name / "SKILL.md"
bodies = []
for path in (entry, other):
body = re.sub(r"\A---\n.*?\n---\n", "", path.read_text(encoding="utf-8"), count=1, flags=re.S)
bodies.append(body)
links = re.findall(r"\]\(([^)]+\.md)\)", body)
self.assertEqual(1, len(links), path)
workflow = (path.parent / links[0]).resolve()
self.assertEqual(ROOT / "resume_builder/reference/workflows" / (entry.parent.name + ".md"), workflow)
self.assertIn("Shared workflow version:", workflow.read_text(encoding="utf-8"))
self.assertEqual(*bodies)
def test_skills_use_minimal_valid_frontmatter(self) -> None:
for skill_file in sorted((ROOT / ".agents/skills").glob("*/SKILL.md")):
text = skill_file.read_text(encoding="utf-8")
match = re.match(r"^---\n(.*?)\n---", text, re.DOTALL)
self.assertIsNotNone(match, skill_file)
fields = {}
for line in match.group(1).splitlines():
key, separator, value = line.partition(":")
self.assertEqual(":", separator, skill_file)
fields[key.strip()] = value.strip()
self.assertEqual({"name", "description"}, set(fields), skill_file)
self.assertRegex(fields["name"], r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
self.assertLessEqual(len(fields["name"]), 64)
self.assertTrue(fields["description"])
self.assertLessEqual(len(fields["description"]), 1024)
self.assertNotRegex(fields["description"], r"[<>]")
def test_top_level_instructions_expose_new_controls(self) -> None:
for filename in ("AGENTS.md", "CLAUDE.md"):
text = (ROOT / filename).read_text(encoding="utf-8")
for phrase in (
"canonical/claims.json",
"Evidence Fit",
"Document Quality",
"Channel Strength",
"no character targets",
"cover letter is conditional",
):
self.assertIn(phrase.lower(), text.lower(), f"{filename}: {phrase}")
def test_resume_template_has_conventional_hierarchy(self) -> None:
text = (ROOT / "resume_builder/templates/resume_template.tex").read_text(encoding="utf-8")
self.assertIn("Professional Experience", text)
self.assertIn("Technical Skills", text)
self.assertIn("Education", text)
self.assertNotIn("FLIPPED", text)
self.assertNotIn("Selected Publications", text)
def test_length_helper_is_diagnostic_not_target_based(self) -> None:
source = (ROOT / "resume_builder/helpers/char_count.py").read_text(encoding="utf-8")
self.assertNotIn("classify_bullet", source)
rendered, characters, words, clauses = length_helper.diagnose(
r"\item Built governed data products within the company-wide Data Mesh."
)
self.assertEqual("Built governed data products within the company-wide Data Mesh.", rendered)
self.assertGreater(characters, 0)
self.assertEqual(9, words)
self.assertEqual(1, clauses)
if __name__ == "__main__":
unittest.main()