maia3-chess-api / test_api_comprehensive.py
anandu467's picture
Fix critical bugs in tactical vision API
34951fb
Raw
History Blame Contribute Delete
8.18 kB
"""
Comprehensive API testing with known positions.
Tests tactical-vision and game-review endpoints.
"""
import requests
import json
import time
BASE_URL = "https://anandu467-maia3-chess-api.hf.space"
API_KEY = "sk-maia3-2026"
HEADERS = {
"Content-Type": "application/json",
"x-api-key": API_KEY
}
def test_position(name, fen, player_color, expected_checks=None):
"""Test a position and verify expectations"""
print(f"\n{'='*70}")
print(f"TEST: {name}")
print(f"{'='*70}")
print(f"FEN: {fen}")
print(f"Player: {player_color}")
payload = {
"fen": fen,
"elo_self": 1500,
"elo_oppo": 1500,
"top_n": 5,
"player_color": player_color,
"compute_value_delta": True
}
t0 = time.time()
r = requests.post(f"{BASE_URL}/api/tactical-vision", json=payload, headers=HEADERS, timeout=120)
elapsed = (time.time() - t0) * 1000
print(f"Status: {r.status_code} ({elapsed:.0f}ms)")
if r.status_code != 200:
print(f"ERROR: {r.text[:200]}")
return False
data = r.json()
# Display key information
threats = data.get("threats", {})
alerts = data.get("tactical_alerts", [])
top_moves = data.get("top_moves", [])
position = data.get("position", {})
print(f"\nPosition Analysis:")
print(f" Win probability: {position.get('win_probability', 0):.1%}")
print(f" Eval: {position.get('eval_cp', 0)}cp")
print(f" Confidence: {position.get('confidence', 'unknown')}")
print(f" Danger level: {position.get('danger_level', 'unknown')}")
print(f"\nTop 3 moves:")
for i, move in enumerate(top_moves[:3], 1):
tags = f" [{', '.join(move.get('tactical_tags', []))}]" if move.get('tactical_tags') else ""
delta = f" (delta: {move.get('eval_delta_cp', 0):+d}cp)" if move.get('eval_delta_cp') is not None else ""
print(f" {i}. {move['san']} - {move['probability']:.1%} - {move['classification']}{tags}{delta}")
print(f"\nThreats:")
print(f" Hanging pieces: {threats.get('hanging_pieces', [])}")
print(f" Attacked pieces: {threats.get('attacked_pieces', [])}")
print(f" Underdefended: {threats.get('underdefended_squares', [])}")
print(f" King safety: {threats.get('king_safety_concern', False)}")
print(f"\nTactical alerts: {len(alerts)}")
for alert in alerts[:5]: # Show first 5
print(f" - {alert['type']}: {alert['description']}")
if len(alerts) > 5:
print(f" ... and {len(alerts) - 5} more")
# Verify expectations
if expected_checks:
print(f"\nVerification:")
all_pass = True
for check_name, check_func in expected_checks.items():
try:
result = check_func(data)
status = "PASS" if result else "FAIL"
print(f" {check_name}: {status}")
if not result:
all_pass = False
except Exception as e:
print(f" {check_name}: ERROR - {e}")
all_pass = False
return all_pass
return True
# Test 1: Starting position
test_position(
"Starting Position",
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"white",
{
"No hanging pieces": lambda d: len(d.get("threats", {}).get("hanging_pieces", [])) == 0,
"No tactical alerts": lambda d: len(d.get("tactical_alerts", [])) == 0,
"Has top moves": lambda d: len(d.get("top_moves", [])) >= 3,
"First move is e4 or d4": lambda d: d["top_moves"][0]["san"] in ["e4", "d4", "Nf3", "c4"]
}
)
# Test 2: Scholar's mate setup (Qh5 threatening Qxf7#)
test_position(
"Scholar's Mate Setup (White to play Qxf7#)",
"r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 4 4",
"white",
{
"Has checkmate move": lambda d: any(m.get("is_mate") for m in d.get("top_moves", [])),
"Qxf7 is top move": lambda d: d["top_moves"][0]["uci"] == "f3f7",
"High win probability": lambda d: d.get("position", {}).get("win_probability", 0) > 0.9
}
)
# Test 3: Hanging piece (knight on e5 attacked by d6 pawn, no defenders)
test_position(
"Hanging Knight on e5",
"rnbqkbnr/pppp1ppp/3p4/4N3/8/8/PPPPPPPP/RNBQKB1R w KQkq - 0 1",
"black",
{
"Detects hanging piece": lambda d: "e5" in d.get("threats", {}).get("hanging_pieces", []),
"Has hanging_piece alert": lambda d: any(a["type"] == "hanging_piece" and a["square"] == "e5" for a in d.get("tactical_alerts", []))
}
)
# Test 4: Fork opportunity (knight can fork king and rook)
test_position(
"Knight Fork Opportunity (Nc7+ forking king and rook)",
"r3k2r/ppp2ppp/2n1b3/3q4/3P4/2N1B3/PPP2PPP/R2QK2R w KQkq - 0 1",
"white",
{
"Has tactical tags": lambda d: any(m.get("tactical_tags") for m in d.get("top_moves", [])),
"Fork detected": lambda d: any("fork" in m.get("tactical_tags", []) for m in d.get("top_moves", []))
}
)
# Test 5: Pin (bishop pins knight to king)
test_position(
"Absolute Pin (Bb5 pins Nc6 to king)",
"r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3",
"black",
{
"Has top moves": lambda d: len(d.get("top_moves", [])) >= 3,
"Reasonable eval": lambda d: abs(d.get("position", {}).get("eval_cp", 0)) < 200
}
)
# Test 6: Back rank mate threat
test_position(
"Back Rank Mate Threat",
"6k1/5ppp/8/8/8/8/5PPP/R5K1 w - - 0 1",
"white",
{
"Has top moves": lambda d: len(d.get("top_moves", [])) >= 3,
"Ra8 might be suggested": lambda d: any(m["uci"] == "a1a8" for m in d.get("top_moves", []))
}
)
# Test 7: Endgame position (king and pawn vs king)
test_position(
"King and Pawn Endgame",
"8/8/8/8/8/5k2/5P2/6K1 w - - 0 1",
"white",
{
"Has top moves": lambda d: len(d.get("top_moves", [])) >= 1,
"Pawn push is good": lambda d: any(m["uci"].startswith("f2") for m in d.get("top_moves", []))
}
)
# Test 8: Position with multiple tactical themes
test_position(
"Complex Tactical Position",
"r1bq1rk1/pp2ppbp/2np1np1/8/3NP3/2N1B3/PPP1BPPP/R2Q1RK1 w - - 0 9",
"white",
{
"Has top moves": lambda d: len(d.get("top_moves", [])) >= 3,
"Reasonable confidence": lambda d: d.get("position", {}).get("confidence") in ["clear", "complex", "confusing"]
}
)
# Test 9: Game review with known game (Immortal Game excerpt)
print(f"\n{'='*70}")
print("TEST: Game Review (Short Tactical Game)")
print(f"{'='*70}")
moves = ["e2e4", "e7e5", "f2f4", "e5f4", "f1c4", "d8h4", "e1f1", "b7b5"]
payload = {
"moves": moves,
"elo_white": 1800,
"elo_black": 1800
}
t0 = time.time()
r = requests.post(f"{BASE_URL}/api/game-review", json=payload, headers=HEADERS, timeout=300)
elapsed = (time.time() - t0) * 1000
print(f"Status: {r.status_code} ({elapsed:.0f}ms)")
if r.status_code == 200:
data = r.json()
summary = data.get("summary", {})
print(f"\nGame Summary:")
print(f" Consistency: {summary.get('consistency_score', 0):.2f}")
print(f" Label: {summary.get('consistency_label', 'unknown')}")
print(f" Blunders: {summary.get('blunder_count', 0)}")
print(f" Mistakes: {summary.get('mistake_count', 0)}")
print(f" Good moves: {summary.get('good_count', 0)}")
print(f" Excellent moves: {summary.get('excellent_count', 0)}")
print(f"\nPhase Breakdown:")
for phase in ["opening", "middlegame", "endgame"]:
stats = data.get("phase_breakdown", {}).get(phase, {})
if stats.get("moves", 0) > 0:
print(f" {phase}: {stats['moves']} moves, consistency {stats['consistency_score']:.2f}")
print(f"\nMove Analysis (first 4):")
for move in data.get("moves", [])[:4]:
print(f" {move['move_number']}. {move['side']} {move['san']} - {move['classification']} ({move['probability']:.1%})")
print(f"\nRecommendations:")
for rec in data.get("recommendations", [])[:3]:
print(f" - {rec}")
else:
print(f"ERROR: {r.text[:200]}")
print(f"\n{'='*70}")
print("All tests completed!")
print(f"{'='*70}")