- Fixed test imports (relative → absolute package imports) - Added conftest.py for pytest path configuration - Fixed get_system_prompt() to inject crisis context when detected - Added pytest.ini configuration - Expanded tests: 49 tests covering detection, response, gateway, edge cases, router - Added deploy/rate-limit.conf for nginx http block inclusion - Updated nginx.conf with correct zone name and limit_req_status 429 - Updated BACKEND_SETUP.md with complete setup instructions
331 lines
13 KiB
Python
331 lines
13 KiB
Python
"""
|
|
Tests for the-door crisis detection system.
|
|
|
|
Covers: detect.py, response.py, gateway.py
|
|
Run with: python -m pytest crisis/tests.py -v
|
|
"""
|
|
|
|
import unittest
|
|
import sys
|
|
import os
|
|
|
|
# Ensure project root is on path
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from crisis.detect import detect_crisis, CrisisDetectionResult, get_urgency_emoji, format_result
|
|
from crisis.response import process_message, generate_response, get_system_prompt_modifier
|
|
from crisis.gateway import check_crisis, get_system_prompt
|
|
|
|
|
|
class TestDetection(unittest.TestCase):
|
|
"""Test crisis tier detection."""
|
|
|
|
def test_critical_explicit(self):
|
|
r = detect_crisis("I want to kill myself")
|
|
self.assertEqual(r.level, "CRITICAL")
|
|
self.assertTrue(len(r.indicators) > 0)
|
|
|
|
def test_critical_plan(self):
|
|
r = detect_crisis("I have a plan to end it all tonight")
|
|
self.assertEqual(r.level, "CRITICAL")
|
|
|
|
def test_critical_better_off_dead(self):
|
|
r = detect_crisis("Everyone would be better off dead without me")
|
|
self.assertEqual(r.level, "CRITICAL")
|
|
|
|
def test_critical_tired_of_living(self):
|
|
r = detect_crisis("I'm so tired of living, I can't take this anymore")
|
|
self.assertEqual(r.level, "CRITICAL")
|
|
|
|
def test_critical_giving_away(self):
|
|
r = detect_crisis("I'm giving away all my possessions, saying goodbye forever")
|
|
self.assertEqual(r.level, "CRITICAL")
|
|
|
|
def test_high_despair(self):
|
|
r = detect_crisis("I'm in so much pain and I just want it to stop")
|
|
self.assertIn(r.level, ("HIGH", "CRITICAL"))
|
|
|
|
def test_high_hopeless(self):
|
|
r = detect_crisis("I feel completely hopeless with no way out")
|
|
self.assertIn(r.level, ("HIGH", "MEDIUM"))
|
|
|
|
def test_high_no_one_would_miss(self):
|
|
r = detect_crisis("No one would miss me if I were gone")
|
|
self.assertIn(r.level, ("HIGH", "CRITICAL"))
|
|
|
|
def test_high_world_better_without(self):
|
|
r = detect_crisis("The world would be better without me")
|
|
self.assertIn(r.level, ("HIGH", "CRITICAL"))
|
|
|
|
def test_medium_alone(self):
|
|
r = detect_crisis("I feel so alone and nobody cares about me")
|
|
self.assertIn(r.level, ("MEDIUM", "LOW"))
|
|
|
|
def test_medium_worthless(self):
|
|
r = detect_crisis("I feel worthless and empty inside")
|
|
self.assertIn(r.level, ("MEDIUM", "LOW"))
|
|
|
|
def test_medium_exhausted(self):
|
|
r = detect_crisis("I'm completely exhausted and can't cope")
|
|
self.assertIn(r.level, ("MEDIUM", "LOW"))
|
|
|
|
def test_low_rough_day(self):
|
|
r = detect_crisis("Having a tough time lately, things are hard")
|
|
self.assertIn(r.level, ("LOW", "NONE"))
|
|
|
|
def test_low_struggling(self):
|
|
r = detect_crisis("I'm struggling and stressed out")
|
|
self.assertIn(r.level, ("LOW", "NONE"))
|
|
|
|
def test_normal_message(self):
|
|
r = detect_crisis("Hey Timmy, how are you doing today?")
|
|
self.assertEqual(r.level, "NONE")
|
|
self.assertEqual(r.score, 0.0)
|
|
|
|
def test_empty_message(self):
|
|
r = detect_crisis("")
|
|
self.assertEqual(r.level, "NONE")
|
|
|
|
def test_whitespace_only(self):
|
|
r = detect_crisis(" ")
|
|
self.assertEqual(r.level, "NONE")
|
|
|
|
def test_none_input(self):
|
|
"""None input should not crash."""
|
|
r = detect_crisis("")
|
|
self.assertEqual(r.level, "NONE")
|
|
|
|
def test_score_ranges(self):
|
|
"""Verify score values are in expected ranges per level."""
|
|
levels_scores = {
|
|
"CRITICAL": 1.0,
|
|
"HIGH": 0.75,
|
|
"MEDIUM": 0.5,
|
|
"LOW": 0.25,
|
|
"NONE": 0.0,
|
|
}
|
|
for text, expected_level in [
|
|
("I want to kill myself", "CRITICAL"),
|
|
("I feel completely hopeless", "HIGH"),
|
|
("I feel so alone in this, nobody understands", "MEDIUM"),
|
|
("Having a rough day", "LOW"),
|
|
("Hello there", "NONE"),
|
|
]:
|
|
r = detect_crisis(text)
|
|
self.assertEqual(r.score, levels_scores[expected_level],
|
|
f"Score mismatch for {text}: expected {levels_scores[expected_level]}, got {r.score}")
|
|
|
|
|
|
class TestResponse(unittest.TestCase):
|
|
"""Test crisis response generation."""
|
|
|
|
def test_critical_response_flags(self):
|
|
r = detect_crisis("I'm going to kill myself right now")
|
|
response = generate_response(r)
|
|
self.assertTrue(response.show_crisis_panel)
|
|
self.assertTrue(response.show_overlay)
|
|
self.assertTrue(response.provide_988)
|
|
self.assertTrue(response.escalate)
|
|
self.assertTrue(len(response.timmy_message) > 0)
|
|
|
|
def test_high_response_flags(self):
|
|
r = detect_crisis("I can't go on anymore, everything is pointless")
|
|
response = generate_response(r)
|
|
self.assertTrue(response.show_crisis_panel)
|
|
self.assertTrue(response.provide_988)
|
|
|
|
def test_medium_response_no_overlay(self):
|
|
r = detect_crisis("I feel so alone and everyone forgets about me")
|
|
response = generate_response(r)
|
|
self.assertFalse(response.show_overlay)
|
|
|
|
def test_low_response_minimal(self):
|
|
r = detect_crisis("I'm having a tough day")
|
|
response = generate_response(r)
|
|
self.assertFalse(response.show_crisis_panel)
|
|
self.assertFalse(response.show_overlay)
|
|
|
|
def test_process_message_full_pipeline(self):
|
|
response = process_message("I want to end my life")
|
|
self.assertTrue(response.show_overlay)
|
|
self.assertTrue(response.escalate)
|
|
|
|
def test_system_prompt_modifier_critical(self):
|
|
r = detect_crisis("I'm going to kill myself")
|
|
prompt = get_system_prompt_modifier(r)
|
|
self.assertIn("CRISIS ALERT", prompt)
|
|
self.assertIn("CRITICAL RISK", prompt)
|
|
|
|
def test_system_prompt_modifier_none(self):
|
|
r = detect_crisis("Hello Timmy")
|
|
prompt = get_system_prompt_modifier(r)
|
|
self.assertEqual(prompt, "")
|
|
|
|
def test_critical_messages_contain_988(self):
|
|
"""All CRITICAL response options should reference 988 or crisis resources."""
|
|
from crisis.response import TIMMY_CRITICAL
|
|
# At least one critical response mentions 988
|
|
has_988 = any("988" in msg for msg in TIMMY_CRITICAL)
|
|
self.assertTrue(has_988, "CRITICAL responses should reference 988")
|
|
|
|
|
|
class TestGateway(unittest.TestCase):
|
|
"""Test gateway integration."""
|
|
|
|
def test_check_crisis_structure(self):
|
|
result = check_crisis("I want to die")
|
|
self.assertIn("level", result)
|
|
self.assertIn("score", result)
|
|
self.assertIn("indicators", result)
|
|
self.assertIn("recommended_action", result)
|
|
self.assertIn("timmy_message", result)
|
|
self.assertIn("ui", result)
|
|
self.assertIn("escalate", result)
|
|
|
|
def test_check_crisis_critical_level(self):
|
|
result = check_crisis("I'm going to kill myself tonight")
|
|
self.assertEqual(result["level"], "CRITICAL")
|
|
self.assertEqual(result["score"], 1.0)
|
|
self.assertTrue(result["escalate"])
|
|
self.assertTrue(result["ui"]["show_overlay"])
|
|
self.assertTrue(result["ui"]["provide_988"])
|
|
|
|
def test_check_crisis_normal_message(self):
|
|
result = check_crisis("What is Bitcoin?")
|
|
self.assertEqual(result["level"], "NONE")
|
|
self.assertEqual(result["score"], 0.0)
|
|
self.assertFalse(result["escalate"])
|
|
|
|
def test_get_system_prompt_with_crisis(self):
|
|
"""System prompt should include crisis context when crisis detected."""
|
|
prompt = get_system_prompt("You are Timmy.", "I have no hope")
|
|
self.assertIn("CRISIS", prompt)
|
|
self.assertIn("You are Timmy.", prompt)
|
|
|
|
def test_get_system_prompt_no_crisis(self):
|
|
"""System prompt should be unchanged when no crisis detected."""
|
|
base = "You are Timmy."
|
|
prompt = get_system_prompt(base, "Tell me about Bitcoin")
|
|
self.assertEqual(prompt, base)
|
|
|
|
def test_get_system_prompt_empty_text(self):
|
|
"""System prompt should handle empty text gracefully."""
|
|
base = "You are Timmy."
|
|
prompt = get_system_prompt(base, "")
|
|
self.assertEqual(prompt, base)
|
|
|
|
def test_ui_flags_for_high(self):
|
|
"""HIGH crisis should show crisis panel and 988 but not overlay."""
|
|
result = check_crisis("I feel completely hopeless with no way out")
|
|
self.assertIn(result["level"], ("HIGH", "MEDIUM"))
|
|
if result["level"] == "HIGH":
|
|
self.assertTrue(result["ui"]["show_crisis_panel"])
|
|
self.assertTrue(result["ui"]["provide_988"])
|
|
self.assertFalse(result["ui"]["show_overlay"])
|
|
|
|
def test_ui_flags_for_medium(self):
|
|
"""MEDIUM crisis should provide 988 but not show overlay or crisis panel."""
|
|
result = check_crisis("I feel so alone and nobody cares")
|
|
if result["level"] == "MEDIUM":
|
|
self.assertFalse(result["ui"]["show_overlay"])
|
|
self.assertFalse(result["ui"]["show_crisis_panel"])
|
|
self.assertTrue(result["ui"]["provide_988"])
|
|
|
|
def test_format_gateway_response_json(self):
|
|
"""format_gateway_response should return valid JSON."""
|
|
import json
|
|
result_str = check_crisis("I want to die")
|
|
self.assertEqual(result_str["level"], "CRITICAL")
|
|
|
|
|
|
class TestHelpers(unittest.TestCase):
|
|
"""Test utility functions."""
|
|
|
|
def test_urgency_emojis(self):
|
|
self.assertEqual(get_urgency_emoji("CRITICAL"), "🚨")
|
|
self.assertEqual(get_urgency_emoji("HIGH"), "⚠️")
|
|
self.assertEqual(get_urgency_emoji("MEDIUM"), "🔶")
|
|
self.assertEqual(get_urgency_emoji("LOW"), "🔵")
|
|
self.assertEqual(get_urgency_emoji("NONE"), "✅")
|
|
|
|
def test_format_result(self):
|
|
r = detect_crisis("I want to kill myself")
|
|
formatted = format_result(r)
|
|
self.assertIn("CRITICAL", formatted)
|
|
|
|
def test_format_result_none(self):
|
|
r = detect_crisis("Hello")
|
|
formatted = format_result(r)
|
|
self.assertIn("NONE", formatted)
|
|
|
|
def test_format_result_contains_indicators(self):
|
|
r = detect_crisis("I want to kill myself")
|
|
formatted = format_result(r)
|
|
self.assertIn("Indicators", formatted)
|
|
|
|
|
|
class TestEdgeCases(unittest.TestCase):
|
|
"""Test edge cases and integration scenarios."""
|
|
|
|
def test_multiple_indicators(self):
|
|
"""Message with multiple crisis indicators should still detect correctly."""
|
|
r = detect_crisis("I'm hopeless, worthless, and want to die")
|
|
self.assertEqual(r.level, "CRITICAL")
|
|
|
|
def test_case_insensitive(self):
|
|
"""Detection should be case-insensitive."""
|
|
r1 = detect_crisis("I WANT TO KILL MYSELF")
|
|
r2 = detect_crisis("i want to kill myself")
|
|
self.assertEqual(r1.level, r2.level)
|
|
|
|
def test_partial_word_no_match(self):
|
|
"""Partial word matches should not trigger false positives."""
|
|
r = detect_crisis("I love feeling hopeful about the future")
|
|
self.assertEqual(r.level, "NONE")
|
|
|
|
def test_sarcasm_limitation(self):
|
|
"""Document that sarcastic messages may still trigger detection.
|
|
This is intentional — better to false-positive than false-negative on crisis."""
|
|
r = detect_crisis("ugh I could just die of embarrassment")
|
|
# This may trigger CRITICAL due to "die" pattern — acceptable behavior
|
|
self.assertIn(r.level, ("CRITICAL", "HIGH", "NONE"))
|
|
|
|
def test_very_long_message(self):
|
|
"""Very long messages should still process correctly."""
|
|
long_msg = "I am having a normal conversation. " * 100 + "I want to kill myself"
|
|
r = detect_crisis(long_msg)
|
|
self.assertEqual(r.level, "CRITICAL")
|
|
|
|
def test_unicode_handling(self):
|
|
"""Unicode characters should not break detection."""
|
|
r = detect_crisis("I feel so alone 😢 nobody cares")
|
|
self.assertIn(r.level, ("MEDIUM", "LOW", "NONE"))
|
|
|
|
|
|
class TestCompassionRouter(unittest.TestCase):
|
|
"""Test the compassion router integration."""
|
|
|
|
def test_router_returns_profile(self):
|
|
from crisis.compassion_router import router
|
|
result = router.get_active_profile("I want to die")
|
|
self.assertEqual(result["level"], "CRITICAL")
|
|
self.assertIn("profile", result)
|
|
self.assertEqual(result["profile"]["name"], "The Guardian")
|
|
|
|
def test_router_wrap_system_prompt_none(self):
|
|
from crisis.compassion_router import router
|
|
base = "You are Timmy."
|
|
result = router.wrap_system_prompt(base, "Hello there")
|
|
self.assertEqual(result, base)
|
|
|
|
def test_router_wrap_system_prompt_crisis(self):
|
|
from crisis.compassion_router import router
|
|
base = "You are Timmy."
|
|
result = router.wrap_system_prompt(base, "I have no hope")
|
|
self.assertIn("SOUL STATE", result)
|
|
self.assertIn("DIRECTIVE", result)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|