import sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from core.validation import ( safe_parse_llm_json, ValidationError, clamp, coerce_int, coerce_float, coerce_need_types, validate_image_file, validate_report_text, ) def test_parses_clean_json(): data = safe_parse_llm_json('{"people_affected": 10, "urgency": 5, "need_types": ["medical"]}', required_keys=["people_affected", "urgency"]) assert data["people_affected"] == 10 print("PASS: clean JSON parses correctly") def test_parses_json_wrapped_in_prose_and_fences(): raw = 'Sure, here is the JSON:\n```json\n{"people_affected": 7, "urgency": 3}\n```\nLet me know if you need more.' data = safe_parse_llm_json(raw, required_keys=["people_affected", "urgency"]) assert data["people_affected"] == 7 print("PASS: JSON wrapped in markdown fences + prose is correctly extracted") def test_raises_on_missing_required_keys(): try: safe_parse_llm_json('{"people_affected": 5}', required_keys=["people_affected", "urgency"]) assert False, "should have raised" except ValidationError: print("PASS: missing required key raises ValidationError") def test_raises_on_empty_output(): try: safe_parse_llm_json("", required_keys=["x"]) assert False, "should have raised" except ValidationError: print("PASS: empty model output raises ValidationError") def test_raises_on_garbage_output(): try: safe_parse_llm_json("I cannot help with that request.", required_keys=["x"]) assert False, "should have raised" except ValidationError: print("PASS: non-JSON garbage output raises ValidationError") def test_coerce_int_clamps_and_defaults(): assert coerce_int("12", lo=0, hi=10) == 10 assert coerce_int(-5, lo=0, hi=10) == 0 assert coerce_int("not a number", default=3) == 3 print("PASS: coerce_int clamps and falls back on garbage") def test_coerce_float_clamps(): assert coerce_float(99, lo=0, hi=10) == 10 assert coerce_float(None, default=5.0) == 5.0 print("PASS: coerce_float clamps and falls back on garbage") def test_coerce_need_types_filters_unknown_values(): result = coerce_need_types(["medical", "teleportation", "rescue", "medical"]) assert result == ["medical", "rescue"], f"got {result}" print("PASS: coerce_need_types filters unknown values and dedupes") def test_validate_report_text_handles_empty_and_long(): text, warning = validate_report_text("") assert text == "" and warning print("PASS: empty report text flagged with warning") long_text = "x" * 5000 text, warning = validate_report_text(long_text, max_chars=4000) assert len(text) == 4000 and "truncated" in warning print("PASS: overlong report text truncated with warning") def test_validate_image_file_missing(): is_valid, err = validate_image_file("/nonexistent/path/image.png") assert not is_valid and err print("PASS: missing image file correctly flagged invalid") def test_clamp(): assert clamp(15, 0, 10) == 10 assert clamp(-5, 0, 10) == 0 assert clamp(5, 0, 10) == 5 print("PASS: clamp works at both bounds and in range") if __name__ == "__main__": tests = [v for k, v in list(globals().items()) if k.startswith("test_")] for t in tests: t() print(f"\n{len(tests)}/{len(tests)} validation tests passed.")