test(tests): Add comprehensive test cases for new validation logic and edge cases

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-28 21:13:49 -07:00
parent 82e514452e
commit 25263085c9

View file

@ -0,0 +1,178 @@
"""Integration tests for the gesture system via UDP.
Requires Chobit running (`./run start`). Tests the full stack:
BoneRegistry GestureRegistry IdleAnimator Skeleton3D
Run: python tests/test_gesture_system.py
"""
from __future__ import annotations
import json
import socket
import sys
import time
GODOT_PORT = 19700
TIMEOUT = 2.0
def send(cmd: str, **kwargs: object) -> dict | None:
msg = {"cmd": cmd, **kwargs}
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(TIMEOUT)
try:
sock.sendto(json.dumps(msg).encode(), ("127.0.0.1", GODOT_PORT))
data, _ = sock.recvfrom(8192)
return json.loads(data.decode())
except (OSError, json.JSONDecodeError) as e:
return {"error": str(e)}
finally:
sock.close()
def assert_eq(label: str, actual: object, expected: object) -> None:
if actual == expected:
print(f" PASS {label}")
else:
print(f" FAIL {label}: expected {expected!r}, got {actual!r}")
raise AssertionError(f"{label} failed")
def assert_in(label: str, item: object, collection: object) -> None:
if item in collection:
print(f" PASS {label}")
else:
print(f" FAIL {label}: {item!r} not in {collection!r}")
raise AssertionError(f"{label} failed")
def assert_not_in(label: str, item: object, collection: object) -> None:
if item not in collection:
print(f" PASS {label}")
else:
print(f" FAIL {label}: {item!r} unexpectedly found in {collection!r}")
raise AssertionError(f"{label} failed")
def assert_ok(label: str, result: dict | None) -> dict:
if result is None:
print(f" FAIL {label}: no response (is Chobit running?)")
raise AssertionError(f"{label}: no response")
if "error" in result:
print(f" FAIL {label}: {result['error']}")
raise AssertionError(f"{label}: {result['error']}")
print(f" PASS {label}")
return result
class AssertionError(Exception):
pass
def test_status() -> None:
print("\n── test_status ──")
result = send("status")
r = assert_ok("status responds", result)
assert_eq("is running", r.get("running"), True)
def test_list_animations() -> None:
print("\n── test_list_animations ──")
result = send("list_animations")
r = assert_ok("list_animations responds", result)
gestures = r.get("gestures", [])
assert_in("wave in gestures", "wave", gestures)
assert_in("stretch in gestures", "stretch", gestures)
assert_in("settle in gestures", "settle", gestures)
assert_in("curious in gestures", "curious", gestures)
assert_in("sigh in gestures", "sigh", gestures)
assert_not_in("fart_wave removed", "fart_wave", gestures)
assert_in("slow_blink in gestures", "slow_blink", gestures)
def test_list_bones() -> None:
print("\n── test_list_bones ──")
result = send("list_bones", filter="Right")
r = assert_ok("list_bones responds", result)
bones = r.get("bones", [])
bone_names = [b["name"] for b in bones]
# VRM humanoid bones should be present
for expected in ["RightUpperArm", "RightLowerArm", "RightHand"]:
found = any(expected.lower() in n.lower() for n in bone_names)
if found:
print(f" PASS bone '{expected}' found (possibly different casing)")
else:
print(f" INFO bone '{expected}' not found by name — may use Japanese names")
print(f" INFO {len(bones)} right-side bones found")
def test_play_gesture() -> None:
print("\n── test_play_gesture ──")
result = send("play_animation", name="wave")
r = assert_ok("play_animation wave", result)
assert_eq("played wave", r.get("played"), "wave")
def test_test_pose() -> None:
print("\n── test_test_pose ──")
result = send(
"test_pose",
bones={"RightUpperArm": [20, 0, -80], "RightLowerArm": [0, -100, 0]},
duration=1.0,
oscillations=[{"bone": "RightHand", "axis": [0, 1, 0], "freq": 3.0, "amp_deg": 30.0}],
)
r = assert_ok("test_pose responds", result)
assert_eq("testing has bones", "RightUpperArm" in r.get("testing", {}), True)
# Wait for gesture to finish
time.sleep(1.5)
def test_test_pose_novel_bone() -> None:
print("\n── test_test_pose_novel_bone ──")
# Register a bone not in any gesture def — dynamic registration
result = send(
"test_pose",
bones={"LeftUpperArm": [20, 0, 80]},
duration=1.0,
)
r = assert_ok("test_pose novel bone", result)
assert_eq("testing has LeftUpperArm", "LeftUpperArm" in r.get("testing", {}), True)
time.sleep(1.5)
def test_debug_bones() -> None:
print("\n── test_debug_bones ──")
result = send("debug_bones")
r = assert_ok("debug_bones responds", result)
bones = r.get("bones", {})
assert_in("RightUpperArm in debug", "RightUpperArm", bones)
def main() -> int:
tests = [
test_status,
test_list_animations,
test_list_bones,
test_debug_bones,
test_play_gesture,
test_test_pose,
test_test_pose_novel_bone,
]
passed = 0
failed = 0
for test in tests:
try:
test()
passed += 1
except (AssertionError, Exception) as e:
failed += 1
print(f" ERROR {e}")
print(f"\n{'=' * 40}")
print(f"Results: {passed} passed, {failed} failed")
return 1 if failed > 0 else 0
if __name__ == "__main__":
sys.exit(main())