Files
turboquant/tests/test_edge_crisis.py

210 lines
7.0 KiB
Python

#!/usr/bin/env python3
"""
test_edge_crisis.py — Offline crisis detection tests
Tests run with zero dependencies and no network access.
Verifies keyword detection accuracy and resource loading.
Usage:
python3 tests/test_edge_crisis.py
python3 -m pytest tests/test_edge_crisis.py -v
"""
import json
import os
import sys
from pathlib import Path
# Add parent to path for imports
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from scripts.crisis_detector import detect_keywords, detect_crisis, load_resources
# ── Test Data ───────────────────────────────────────────────────────────────
# Texts that SHOULD trigger crisis detection
CRISIS_TEXTS = [
"I want to kill myself",
"I'm going to die tonight",
"I can't take this anymore, I want to end it",
"Nobody would miss me if I were gone",
"I've been cutting myself to feel something",
"There's no point in living anymore",
"I'm suicidal and I need help",
"I just want the pain to stop forever",
"I've been thinking about hanging myself",
"Please help me I'm drowning in this",
"I give up. Nothing matters anymore.",
"I want to jump off the bridge",
"I can't go on like this",
"Help me I'm desperate",
"I'm done with life",
]
# Texts that should NOT trigger (common false positives)
SAFE_TEXTS = [
"What a kill in that game!",
"I'm dying of laughter",
"This movie killed me",
"I want to kill it at my presentation",
"I'm dead tired after work",
"The jump scare got me",
"I cut my finger cooking",
"This game is killing me (hard level)",
"The point of the story is unclear",
"I'm done with this homework",
"No point in arguing about it",
"The bridge looks beautiful at sunset",
"I gave up sugar for Lent",
"The knife is in the drawer",
"She's hanging the picture on the wall",
]
def test_crisis_texts_detected():
"""All crisis texts must be detected."""
failures = []
for text in CRISIS_TEXTS:
result = detect_keywords(text)
if not result["detected"]:
failures.append(f"MISSED: '{text}'")
assert not failures, "Crisis texts not detected:\n" + "\n".join(failures)
def test_safe_texts_not_detected():
"""Safe texts must not trigger false positives."""
failures = []
for text in SAFE_TEXTS:
result = detect_keywords(text)
if result["detected"]:
failures.append(f"FALSE POSITIVE: '{text}' -> {result['categories']}")
# Allow some false positives — crisis detection should err on side of caution
false_positive_rate = len(failures) / len(SAFE_TEXTS)
assert false_positive_rate < 0.3, (
f"False positive rate too high ({false_positive_rate:.0%}):\n"
+ "\n".join(failures)
)
def test_immediate_danger_high_confidence():
"""Immediate danger keywords must have high confidence."""
for text in ["I want to kill myself", "I'm going to die tonight", "I'm suicidal"]:
result = detect_keywords(text)
assert result["detected"], f"Failed to detect: '{text}'"
assert result["confidence"] >= 0.8, (
f"Low confidence for immediate danger: {result['confidence']}"
)
def test_resources_load():
"""Resources file must exist and be valid."""
resources = load_resources()
assert "national_resources" in resources
assert len(resources["national_resources"]) >= 1
# 988 lifeline must be present
phones = [r.get("phone", "") for r in resources["national_resources"]]
assert any("988" in p for p in phones), "988 Lifeline not in resources"
def test_resources_have_required_fields():
"""All national resources must have name and contact method."""
resources = load_resources()
for r in resources["national_resources"]:
assert "name" in r, f"Resource missing name: {r}"
has_contact = r.get("phone") or r.get("text") or r.get("url")
assert has_contact, f"Resource missing contact: {r['name']}"
def test_keyword_categories():
"""Verify all keyword categories are represented."""
for text, expected_cats in [
("I want to kill myself", ["immediate_danger"]),
("I've been cutting myself", ["self_harm"]),
("There's no point in living", ["hopelessness"]),
]:
result = detect_keywords(text)
assert result["detected"], f"Should detect: '{text}'"
for cat in expected_cats:
assert cat in result["categories"], (
f"Expected category '{cat}' for '{text}', got {result['categories']}"
)
def test_empty_text_safe():
"""Empty text must not trigger."""
result = detect_keywords("")
assert not result["detected"]
assert result["confidence"] == 0.0
def test_detect_crisis_combined():
"""Combined detect_crisis function works (keyword-only, no LLM)."""
result = detect_crisis("I want to kill myself", use_llm=False)
assert result["detected"]
result2 = detect_crisis("Nice weather today", use_llm=False)
assert not result2["detected"]
def test_resource_file_exists():
"""The resources JSON file must exist."""
resources_file = Path(__file__).resolve().parent.parent / "data" / "crisis_resources.json"
assert resources_file.exists(), f"Missing: {resources_file}"
def test_resources_json_valid():
"""Resources file must be valid JSON with expected structure."""
resources_file = Path(__file__).resolve().parent.parent / "data" / "crisis_resources.json"
with open(resources_file) as f:
data = json.load(f)
assert "version" in data
assert "national_resources" in data
assert "self_help_prompts" in data
assert len(data["national_resources"]) >= 3
# ── Runner ──────────────────────────────────────────────────────────────────
def run_all_tests():
"""Run all tests without pytest."""
tests = [
test_crisis_texts_detected,
test_safe_texts_not_detected,
test_immediate_danger_high_confidence,
test_resources_load,
test_resources_have_required_fields,
test_keyword_categories,
test_empty_text_safe,
test_detect_crisis_combined,
test_resource_file_exists,
test_resources_json_valid,
]
passed = 0
failed = 0
for test in tests:
name = test.__name__
try:
test()
print(f" PASS: {name}")
passed += 1
except AssertionError as e:
print(f" FAIL: {name}")
print(f" {e}")
failed += 1
except Exception as e:
print(f" ERROR: {name}: {e}")
failed += 1
print(f"\n{'='*50}")
print(f"Results: {passed} passed, {failed} failed, {passed+failed} total")
print(f"{'='*50}")
return failed == 0
if __name__ == "__main__":
success = run_all_tests()
sys.exit(0 if success else 1)