cq-test / test_repair.py
NANI-Nithin's picture
Phase 2: ship the core happy path
e9fc2fc
Raw
History Blame
14.6 kB
"""
Test the repair pass logic for Phase 2, Task 8.
Tests that repair_game() can:
1. Fix missing fields and structure
2. Add safety constraints
3. Repair task content issues
4. Handle multiple failures simultaneously
5. Preserve valid content when fixing
"""
from app.services.validator import validate_game, repair_game
def print_result(label: str, valid: bool, failures: list[str]):
status = "βœ“ PASS" if valid else "βœ— FAIL"
print(f" {status}: {label}")
if failures:
for f in failures[:3]:
print(f" β†’ {f}")
if len(failures) > 3:
print(f" β†’ ... and {len(failures) - 3} more")
def test_repair_minimal_game():
"""Repair a game with almost nothing filled in."""
print("\n" + "=" * 80)
print("TEST 1: Repair minimal (empty) game")
print("=" * 80)
bad_game = {
"game_id": "",
"title": "",
"theme": "",
"setup": {},
"rules": [],
"tasks": [],
"global_hints": [],
"score_rules": [],
"tie_breaker": "",
"safety": {},
"story_seed": {}
}
config = {
"game_type": "scavenger_hunt",
"city": "Paris",
"area": "Le Marais",
"duration_minutes": 60,
"num_players": 4,
"difficulty": "medium",
"age_group": "adults"
}
# Show validation before repair
is_valid, failures = validate_game(bad_game, config)
print_result("Before repair", is_valid, failures)
# Apply repair
repaired = repair_game(bad_game, failures, config)
# Validate repaired game
is_valid, failures = validate_game(repaired, config)
print_result("After repair", is_valid, failures)
# Show what was fixed
print(f"\n Repaired game ID: {repaired['game_id']}")
print(f" Title: {repaired['title']}")
print(f" Theme: {repaired['theme']}")
print(f" Tasks: {len(repaired['tasks'])}")
print(f" Rules: {len(repaired['rules'])}")
print(f" Score rules: {len(repaired['score_rules'])}")
print(f" Safety zone: {repaired['safety']['allowed_zone'][:50]}...")
print(f" Supervision: {repaired['safety']['adult_supervision']}")
print(f" Forbidden behaviors: {len(repaired['safety']['forbidden_behaviors'])}")
print(f" Stop conditions: {len(repaired['safety']['stop_conditions'])}")
return is_valid
def test_repair_missing_task_fields():
"""Repair tasks with missing critical fields."""
print("\n" + "=" * 80)
print("TEST 2: Repair tasks with missing fields")
print("=" * 80)
bad_game = {
"game_id": "game-001",
"title": "Test Game",
"theme": "discovery",
"setup": {
"city": "Paris",
"area": "Montmartre",
"meeting_point": "SacrΓ©-CΕ“ur steps",
"duration_minutes": 45,
"num_players": 4
},
"rules": ["Rule 1", "Rule 2"],
"tasks": [
{
"task_id": "t1",
"title": "Find the vineyard",
"description": "Locate the vineyard sign",
"location_hint": "North slope near Lapin Agile",
"points": 20,
"time_limit_minutes": 15,
# missing proof_type
# missing hint
# missing safety_note
}
],
"global_hints": [],
"score_rules": [],
"tie_breaker": "",
"safety": {
"allowed_zone": "Montmartre public areas",
"forbidden_behaviors": ["No entering buildings"],
# missing adult_supervision
# missing stop_conditions
},
"story_seed": {
"tone": "playful",
"motifs": [],
"recap_style": "episode_recap"
}
}
config = {
"game_type": "scavenger_hunt",
"city": "Paris",
"area": "Montmartre",
"duration_minutes": 45,
"num_players": 4,
"difficulty": "hard",
"age_group": "adults"
}
# Before repair
is_valid, failures = validate_game(bad_game, config)
print_result("Before repair", is_valid, failures)
# Repair
repaired = repair_game(bad_game, failures, config)
# After repair
is_valid, failures = validate_game(repaired, config)
print_result("After repair", is_valid, failures)
# Show fixed task
task = repaired['tasks'][0]
print(f"\n Fixed task fields:")
print(f" proof_type: {task.get('proof_type')}")
print(f" hint: {task.get('hint')[:40]}...")
print(f" safety_note: {task.get('safety_note')[:40]}...")
print(f" Fixed safety:")
print(f" adult_supervision: {repaired['safety'].get('adult_supervision')}")
print(f" stop_conditions: {len(repaired['safety'].get('stop_conditions', []))}")
return is_valid
def test_repair_kids_game_no_supervision():
"""Repair a kids game missing adult supervision."""
print("\n" + "=" * 80)
print("TEST 3: Repair kids game missing adult supervision")
print("=" * 80)
bad_game = {
"game_id": "game-kids",
"title": "Kids Hunt",
"theme": "play",
"setup": {
"city": "Paris",
"area": "Parc des Buttes-Chaumont",
"meeting_point": "Main entrance",
"duration_minutes": 30,
"num_players": 6
},
"rules": ["Stay in park"],
"tasks": [
{
"task_id": "t1",
"title": "Find the temple",
"description": "Find the temple viewpoint",
"location_hint": "At the top of the hill",
"points": 15,
"time_limit_minutes": 10,
"proof_type": "observation",
"hint": "Look up",
"safety_note": "Stay safe"
}
],
"global_hints": [],
"score_rules": [],
"tie_breaker": "",
"safety": {
"allowed_zone": "Park",
"forbidden_behaviors": ["No climbing"],
"adult_supervision": False, # Should be True for kids!
"stop_conditions": []
},
"story_seed": {
"tone": "playful",
"motifs": [],
"recap_style": "episode_recap"
}
}
config = {
"game_type": "hide_and_seek",
"city": "Paris",
"area": "Parc des Buttes-Chaumont",
"duration_minutes": 30,
"num_players": 6,
"difficulty": "easy",
"age_group": "kids"
}
# Before repair
is_valid, failures = validate_game(bad_game, config)
print_result("Before repair", is_valid, failures)
# Repair
repaired = repair_game(bad_game, failures, config)
# After repair
is_valid, failures = validate_game(repaired, config)
print_result("After repair", is_valid, failures)
print(f"\n Adult supervision: {repaired['safety']['adult_supervision']} (was False)")
print(f" Rules: {len(repaired['rules'])} (was 1)")
print(f" Safety note: {repaired['tasks'][0]['safety_note'][:50]}...")
return is_valid
def test_repair_content_with_forbidden_keywords():
"""Repair tasks mentioning forbidden locations."""
print("\n" + "=" * 80)
print("TEST 4: Repair content with forbidden keyword mentions")
print("=" * 80)
bad_game = {
"game_id": "game-004",
"title": "Building Hunt",
"theme": "urban",
"setup": {
"city": "Paris",
"area": "Centre",
"meeting_point": "Plaza",
"duration_minutes": 45,
"num_players": 3,
"time_limit_minutes": 40
},
"rules": ["Rule 1", "Rule 2", "Rule 3"],
"tasks": [
{
"task_id": "t1",
"title": "Enter the shop",
"description": "Go inside the shop on Rue de Rivoli and ask staff for directions",
"location_hint": "Inside the building entrance",
"points": 20,
"time_limit_minutes": 10,
"proof_type": "photo",
"hint": "Check behind the counter",
"safety_note": "Be careful"
}
],
"global_hints": [],
"score_rules": ["Complete tasks"],
"tie_breaker": "First to finish",
"safety": {
"allowed_zone": "City center",
"forbidden_behaviors": [],
"adult_supervision": False,
"stop_conditions": []
},
"story_seed": {
"tone": "playful",
"motifs": [],
"recap_style": "episode_recap"
}
}
config = {
"game_type": "scavenger_hunt",
"city": "Paris",
"area": "Centre",
"duration_minutes": 45,
"num_players": 3,
"difficulty": "medium",
"age_group": "adults"
}
# Before repair
is_valid, failures = validate_game(bad_game, config)
print_result("Before repair", is_valid, failures)
# Repair
repaired = repair_game(bad_game, failures, config)
# After repair
is_valid, failures = validate_game(repaired, config)
print_result("After repair", is_valid, failures)
print(f"\n Task safety note: {repaired['tasks'][0]['safety_note'][:70]}...")
print(f" Forbidden behaviors: {len(repaired['safety']['forbidden_behaviors'])} items")
return is_valid
def test_repair_wrong_task_count():
"""Repair games with too few or too many tasks for duration."""
print("\n" + "=" * 80)
print("TEST 5: Repair unrealistic task count for duration")
print("=" * 80)
# 90 min game with only 1 task
bad_game = {
"game_id": "game-005",
"title": "Long Game",
"theme": "epic",
"setup": {
"city": "Paris",
"area": "Le Marais",
"meeting_point": "Centre",
"duration_minutes": 90,
"num_players": 4
},
"rules": ["Rule 1", "Rule 2", "Rule 3", "Rule 4", "Rule 5"],
"tasks": [
{
"task_id": "t1",
"title": "One task",
"description": "Walk around",
"location_hint": "Around",
"points": 30,
"time_limit_minutes": None,
"proof_type": "photo",
"hint": "Look around",
"safety_note": "Stay in public areas"
}
],
"global_hints": [],
"score_rules": ["Complete all"],
"tie_breaker": "First",
"safety": {
"allowed_zone": "Le Marais",
"forbidden_behaviors": ["No buildings", "No roads"],
"adult_supervision": False,
"stop_conditions": ["Injury", "Weather", "Emergency"]
},
"story_seed": {"tone": "playful", "motifs": [], "recap_style": "episode_recap"}
}
config = {
"game_type": "scavenger_hunt",
"city": "Paris",
"area": "Le Marais",
"duration_minutes": 90,
"num_players": 4,
"difficulty": "medium",
"age_group": "adults"
}
# Before repair
is_valid, failures = validate_game(bad_game, config)
print_result("Before repair", is_valid, failures)
# Repair
repaired = repair_game(bad_game, failures, config)
# After repair
is_valid, failures = validate_game(repaired, config)
print_result("After repair", is_valid, failures)
print(f"\n Tasks: {len(repaired['tasks'])} (was 1)")
print(f" Task IDs: {', '.join(t['task_id'] for t in repaired['tasks'])}")
print(f" Task points: {[t['points'] for t in repaired['tasks']]}")
return is_valid
def test_full_pipeline_repair():
"""Test full pipeline: generate β†’ validate β†’ repair β†’ re-validate."""
print("\n" + "=" * 80)
print("TEST 6: Full pipeline - generate, validate, repair, re-validate")
print("=" * 80)
from app.services.generator import generate_game
from app.services.retrieval import load_games_dataset, normalize_game_record, retrieve_examples
# Load dataset
raw = load_games_dataset("app/data/games_dataset.json")
normalized = [normalize_game_record(r) for r in raw]
config = {
"game_type": "scavenger_hunt",
"city": "Paris",
"area": "Le Marais",
"location_type": "mixed",
"duration_minutes": 60,
"num_players": 4,
"difficulty": "medium",
"age_group": "adults",
"photo_enabled": True
}
# Generate
retrieved = retrieve_examples(config, normalized, k=3)
game = generate_game(config, retrieved)
print(f" Generated: {game['game_id']} ({len(game['tasks'])} tasks)")
# Validate
is_valid, failures = validate_game(game, config)
print_result("Initial validation", is_valid, failures)
# Repair if needed
if not is_valid:
repaired = repair_game(game, failures, config)
is_valid, failures = validate_game(repaired, config)
print_result("After repair", is_valid, failures)
print(f"\n Originally: {len(game['tasks'])} tasks, {len(game['rules'])} rules")
print(f" Repaired: {len(repaired['tasks'])} tasks, {len(repaired['rules'])} rules")
else:
print(" No repair needed (game already valid)")
return is_valid
def main():
print("\n" + "=" * 80)
print("PHASE 2, TASK 8: REPAIR PASS LOGIC TESTS")
print("=" * 80)
results = []
# Run all tests
results.append(("Test 1: Minimal game repair", test_repair_minimal_game()))
results.append(("Test 2: Missing task fields", test_repair_missing_task_fields()))
results.append(("Test 3: Kids game supervision", test_repair_kids_game_no_supervision()))
results.append(("Test 4: Forbidden content", test_repair_content_with_forbidden_keywords()))
results.append(("Test 5: Task count realism", test_repair_wrong_task_count()))
results.append(("Test 6: Full pipeline", test_full_pipeline_repair()))
# Summary
print("\n" + "=" * 80)
print("REPAIR PASS TEST SUMMARY")
print("=" * 80)
passed = sum(1 for _, ok in results if ok)
for name, ok in results:
status = "βœ“ PASS" if ok else "βœ— FAIL"
print(f" {status}: {name}")
print(f"\n Total: {passed}/{len(results)} tests passed")
print("\n" + "=" * 80)
if __name__ == "__main__":
main()