"""Hermetic tests for the candidate_forms pure logic (plugins.py) — no DB, no FastAPI. The rated-section math, unknown-key handling, and the Annexure E combined summary are the parts a typo would silently corrupt.""" from __future__ import annotations from datetime import datetime from types import SimpleNamespace import pytest from candidate_forms.plugins import ( FORM_DEFINITIONS, FORM_READY_STATUSES, combined_summary, definitions_payload, normalize_fields, normalize_sections, ) def _sections(form_type: str, ratings_by_section: dict[str, dict[str, int | None]]): return [ {"key": key, "criteria": [{"key": ck, "rating": rv} for ck, rv in ratings.items()]} for key, ratings in ratings_by_section.items() ] class TestNormalizeSections: def test_recomputes_averages_and_overall(self): sections = _sections( "interview_analysis", { "technical": {"core_job_knowledge": 4, "relevant_experience": 3}, "behavioral": {"communication": 2}, }, ) normalized, overall = normalize_sections("interview_analysis", sections) by_key = {s["key"]: s for s in normalized} assert by_key["technical"]["average"] == 3.5 assert by_key["behavioral"]["average"] == 2 assert overall == 2.75 def test_client_sent_averages_are_discarded(self): sections = _sections("cultural_fit", {"cultural": {"company_values": 4}}) sections[0]["average"] = 1.0 # lying client normalized, overall = normalize_sections("cultural_fit", sections) assert normalized[0]["average"] == 4 assert overall == 4 def test_emits_every_definition_criterion_with_labels(self): normalized, overall = normalize_sections("interview_analysis", []) assert [s["key"] for s in normalized] == ["technical", "behavioral"] technical = normalized[0] assert len(technical["criteria"]) == 6 assert technical["criteria"][0]["label"] == "Core Job Knowledge & Domain Expertise" assert technical["average"] is None assert overall is None def test_unknown_section_rejected(self): with pytest.raises(ValueError): normalize_sections("cultural_fit", _sections("cultural_fit", {"technical": {}})) def test_unknown_criterion_rejected(self): bad = _sections("cultural_fit", {"cultural": {"made_up": 3}}) with pytest.raises(ValueError): normalize_sections("cultural_fit", bad) @pytest.mark.parametrize("rating", [0, 5, -1, "high", 3.5]) def test_out_of_range_ratings_rejected(self, rating): bad = _sections("cultural_fit", {"cultural": {"company_values": rating}}) with pytest.raises(ValueError): normalize_sections("cultural_fit", bad) def test_string_and_null_ratings_coerced(self): sections = _sections( "cultural_fit", {"cultural": {"company_values": "3", "professionalism": None}} ) normalized, _ = normalize_sections("cultural_fit", sections) ratings = {c["key"]: c["rating"] for c in normalized[0]["criteria"]} assert ratings["company_values"] == 3 assert ratings["professionalism"] is None def test_requisition_has_no_sections(self): assert normalize_sections("requisition", None) == (None, None) class TestNormalizeFields: def test_unknown_keys_dropped_and_bools_coerced(self): out = normalize_fields( "requisition", {"department": " IT ", "jd_available": "Yes", "bogus": "x", "is_replacement": False}, ) assert out == {"department": "IT", "jd_available": True, "is_replacement": False} def test_employment_type_enum_enforced(self): assert normalize_fields("requisition", {"employment_type": "Contract"}) == { "employment_type": "contract" } with pytest.raises(ValueError): normalize_fields("requisition", {"employment_type": "freelance"}) def test_evaluation_note_fields_exist(self): out = normalize_fields( "interview_analysis", {"technical_note": "solid", "behavioral_note": "calm"} ) assert out == {"technical_note": "solid", "behavioral_note": "calm"} assert normalize_fields("cultural_fit", {"cultural_note": "fits"}) == { "cultural_note": "fits" } class TestCombinedSummary: def _row(self, form_type, day, ratings_by_section): sections, _ = normalize_sections(form_type, _sections(form_type, ratings_by_section)) return SimpleNamespace( form_type=form_type, created_at=datetime(2026, 1, day), sections=sections ) def test_combined_needs_all_three_sections(self): ia = self._row( "interview_analysis", 1, {"technical": {"core_job_knowledge": 4}, "behavioral": {"communication": 2}}, ) assert combined_summary([ia])["combined_overall"] is None cf = self._row("cultural_fit", 2, {"cultural": {"company_values": 3}}) summary = combined_summary([ia, cf]) assert summary == { "technical_avg": 4, "behavioral_avg": 2, "cultural_avg": 3, "combined_overall": 3.0, } def test_latest_row_per_type_wins(self): old = self._row("cultural_fit", 1, {"cultural": {"company_values": 1}}) new = self._row("cultural_fit", 5, {"cultural": {"company_values": 4}}) assert combined_summary([old, new])["cultural_avg"] == 4 def test_no_evaluations_returns_none(self): req = SimpleNamespace(form_type="requisition", created_at=datetime(2026, 1, 1), sections=None) assert combined_summary([req]) is None assert combined_summary([]) is None class TestDefinitions: def test_paper_parity_criterion_counts(self): ia = FORM_DEFINITIONS["interview_analysis"] cf = FORM_DEFINITIONS["cultural_fit"] assert [len(s["criteria"]) for s in ia["sections"]] == [6, 5] assert [len(s["criteria"]) for s in cf["sections"]] == [13] def test_stage_gate_vocabulary(self): assert set(FORM_READY_STATUSES) == {"INTERVIEW", "OFFER", "HIRED", "APPROVED"} def test_payload_is_json_shaped(self): payload = definitions_payload() assert set(payload["form_types"]) == {"requisition", "interview_analysis", "cultural_fit"} assert payload["recommendation_labels"]["next_round"] == "Shortlist for next round" assert payload["rating_labels"]["1"] == "Below Average (1)"