host.controller_web_editor.tests.test_hitboxes_route
Round-trip + safety tests for the hitbox layout route.
Generated by Claude
Verifies the layout file loads cleanly, the layout allowlist rejects unknown names, and save_layout writes a recoverable .bak.
1"""Round-trip + safety tests for the hitbox layout route. 2 3# Generated by Claude 4 5Verifies the layout file loads cleanly, the layout allowlist rejects 6unknown names, and save_layout writes a recoverable .bak. 7""" 8 9import json 10import os 11import sys 12 13import pytest 14 15 16_REPO_ROOT = os.path.abspath( 17 os.path.join(os.path.dirname(__file__), "..", "..", "..")) 18if _REPO_ROOT not in sys.path: 19 sys.path.insert(0, _REPO_ROOT) 20 21 22from host.controller_web_editor.routes.hitboxes import ( # noqa: E402 23 HITBOXES_DIR, 24 LayoutNotAllowedError, 25 list_layouts, 26 load_layout, 27 save_layout, 28) 29 30 31class TestLoad: 32 def test_xbox_layout_loads(self): 33 data = load_layout("xbox") 34 assert data["viewBox"] == "0 0 744 500" 35 assert isinstance(data["regions"], dict) 36 # Spot-check that the canonical xbox inputs are present. 37 assert "a_button" in data["regions"] 38 assert "left_trigger" in data["regions"] 39 assert "pov_up" in data["regions"] 40 41 def test_rejects_unknown_layout(self): 42 with pytest.raises(LayoutNotAllowedError): 43 load_layout("ps5") 44 45 def test_rejects_traversal(self): 46 with pytest.raises(LayoutNotAllowedError): 47 load_layout("../paths") 48 49 def test_list_layouts_contains_xbox(self): 50 assert "xbox" in list_layouts() 51 52 53class TestSave: 54 @pytest.fixture 55 def restore_xbox(self): 56 # Snapshot the file so the test can restore it regardless of outcome. 57 src = HITBOXES_DIR / "xbox.json" 58 snapshot = src.read_bytes() 59 yield 60 src.write_bytes(snapshot) 61 bak = src.with_suffix(src.suffix + ".bak") 62 if bak.exists(): 63 bak.unlink() 64 65 def test_save_writes_file_and_bak(self, restore_xbox): 66 original = load_layout("xbox") 67 # Tweak one field so we can confirm the write took. 68 modified = json.loads(json.dumps(original)) 69 modified["regions"]["a_button"] = { 70 "shape": "circle", "cx": 999, "cy": 999, "r": 17, 71 } 72 save_layout("xbox", modified) 73 74 reloaded = load_layout("xbox") 75 assert reloaded["regions"]["a_button"]["cx"] == 999 76 77 bak = HITBOXES_DIR / "xbox.json.bak" 78 assert bak.is_file() 79 assert json.loads(bak.read_text())["regions"]["a_button"]["cx"] == \ 80 original["regions"]["a_button"]["cx"] 81 82 def test_save_rejects_unknown_layout(self): 83 with pytest.raises(LayoutNotAllowedError): 84 save_layout("ps5", {"viewBox": "0 0 1 1", "regions": {}}) 85 86 def test_save_rejects_missing_regions(self, restore_xbox): 87 with pytest.raises(ValueError): 88 save_layout("xbox", {"viewBox": "0 0 1 1"}) 89 90 def test_save_rejects_missing_viewbox(self, restore_xbox): 91 with pytest.raises(ValueError): 92 save_layout("xbox", {"regions": {}}) 93 94 def test_save_rejects_non_dict(self, restore_xbox): 95 with pytest.raises(ValueError): 96 save_layout("xbox", ["not", "a", "dict"]) # type: ignore[arg-type] 97 98 def test_save_accepts_optional_labels_and_fonts(self, restore_xbox): 99 original = load_layout("xbox") 100 payload = json.loads(json.dumps(original)) 101 payload["labels"] = {"a_button": {"dx": 4.5, "dy": -22.0}} 102 payload["fonts"] = {"input_name": 8, "action_label": 12} 103 save_layout("xbox", payload) 104 reloaded = load_layout("xbox") 105 assert reloaded["labels"]["a_button"]["dy"] == -22.0 106 assert reloaded["fonts"]["action_label"] == 12 107 108 def test_save_rejects_non_dict_labels(self, restore_xbox): 109 original = load_layout("xbox") 110 payload = json.loads(json.dumps(original)) 111 payload["labels"] = "not a dict" 112 with pytest.raises(ValueError): 113 save_layout("xbox", payload) 114 115 def test_save_rejects_non_dict_fonts(self, restore_xbox): 116 original = load_layout("xbox") 117 payload = json.loads(json.dumps(original)) 118 payload["fonts"] = 12 119 with pytest.raises(ValueError): 120 save_layout("xbox", payload)
class
TestLoad:
32class TestLoad: 33 def test_xbox_layout_loads(self): 34 data = load_layout("xbox") 35 assert data["viewBox"] == "0 0 744 500" 36 assert isinstance(data["regions"], dict) 37 # Spot-check that the canonical xbox inputs are present. 38 assert "a_button" in data["regions"] 39 assert "left_trigger" in data["regions"] 40 assert "pov_up" in data["regions"] 41 42 def test_rejects_unknown_layout(self): 43 with pytest.raises(LayoutNotAllowedError): 44 load_layout("ps5") 45 46 def test_rejects_traversal(self): 47 with pytest.raises(LayoutNotAllowedError): 48 load_layout("../paths") 49 50 def test_list_layouts_contains_xbox(self): 51 assert "xbox" in list_layouts()
def
test_xbox_layout_loads(self):
33 def test_xbox_layout_loads(self): 34 data = load_layout("xbox") 35 assert data["viewBox"] == "0 0 744 500" 36 assert isinstance(data["regions"], dict) 37 # Spot-check that the canonical xbox inputs are present. 38 assert "a_button" in data["regions"] 39 assert "left_trigger" in data["regions"] 40 assert "pov_up" in data["regions"]
class
TestSave:
54class TestSave: 55 @pytest.fixture 56 def restore_xbox(self): 57 # Snapshot the file so the test can restore it regardless of outcome. 58 src = HITBOXES_DIR / "xbox.json" 59 snapshot = src.read_bytes() 60 yield 61 src.write_bytes(snapshot) 62 bak = src.with_suffix(src.suffix + ".bak") 63 if bak.exists(): 64 bak.unlink() 65 66 def test_save_writes_file_and_bak(self, restore_xbox): 67 original = load_layout("xbox") 68 # Tweak one field so we can confirm the write took. 69 modified = json.loads(json.dumps(original)) 70 modified["regions"]["a_button"] = { 71 "shape": "circle", "cx": 999, "cy": 999, "r": 17, 72 } 73 save_layout("xbox", modified) 74 75 reloaded = load_layout("xbox") 76 assert reloaded["regions"]["a_button"]["cx"] == 999 77 78 bak = HITBOXES_DIR / "xbox.json.bak" 79 assert bak.is_file() 80 assert json.loads(bak.read_text())["regions"]["a_button"]["cx"] == \ 81 original["regions"]["a_button"]["cx"] 82 83 def test_save_rejects_unknown_layout(self): 84 with pytest.raises(LayoutNotAllowedError): 85 save_layout("ps5", {"viewBox": "0 0 1 1", "regions": {}}) 86 87 def test_save_rejects_missing_regions(self, restore_xbox): 88 with pytest.raises(ValueError): 89 save_layout("xbox", {"viewBox": "0 0 1 1"}) 90 91 def test_save_rejects_missing_viewbox(self, restore_xbox): 92 with pytest.raises(ValueError): 93 save_layout("xbox", {"regions": {}}) 94 95 def test_save_rejects_non_dict(self, restore_xbox): 96 with pytest.raises(ValueError): 97 save_layout("xbox", ["not", "a", "dict"]) # type: ignore[arg-type] 98 99 def test_save_accepts_optional_labels_and_fonts(self, restore_xbox): 100 original = load_layout("xbox") 101 payload = json.loads(json.dumps(original)) 102 payload["labels"] = {"a_button": {"dx": 4.5, "dy": -22.0}} 103 payload["fonts"] = {"input_name": 8, "action_label": 12} 104 save_layout("xbox", payload) 105 reloaded = load_layout("xbox") 106 assert reloaded["labels"]["a_button"]["dy"] == -22.0 107 assert reloaded["fonts"]["action_label"] == 12 108 109 def test_save_rejects_non_dict_labels(self, restore_xbox): 110 original = load_layout("xbox") 111 payload = json.loads(json.dumps(original)) 112 payload["labels"] = "not a dict" 113 with pytest.raises(ValueError): 114 save_layout("xbox", payload) 115 116 def test_save_rejects_non_dict_fonts(self, restore_xbox): 117 original = load_layout("xbox") 118 payload = json.loads(json.dumps(original)) 119 payload["fonts"] = 12 120 with pytest.raises(ValueError): 121 save_layout("xbox", payload)
@pytest.fixture
def
restore_xbox(self):
55 @pytest.fixture 56 def restore_xbox(self): 57 # Snapshot the file so the test can restore it regardless of outcome. 58 src = HITBOXES_DIR / "xbox.json" 59 snapshot = src.read_bytes() 60 yield 61 src.write_bytes(snapshot) 62 bak = src.with_suffix(src.suffix + ".bak") 63 if bak.exists(): 64 bak.unlink()
def
test_save_writes_file_and_bak(self, restore_xbox):
66 def test_save_writes_file_and_bak(self, restore_xbox): 67 original = load_layout("xbox") 68 # Tweak one field so we can confirm the write took. 69 modified = json.loads(json.dumps(original)) 70 modified["regions"]["a_button"] = { 71 "shape": "circle", "cx": 999, "cy": 999, "r": 17, 72 } 73 save_layout("xbox", modified) 74 75 reloaded = load_layout("xbox") 76 assert reloaded["regions"]["a_button"]["cx"] == 999 77 78 bak = HITBOXES_DIR / "xbox.json.bak" 79 assert bak.is_file() 80 assert json.loads(bak.read_text())["regions"]["a_button"]["cx"] == \ 81 original["regions"]["a_button"]["cx"]
def
test_save_accepts_optional_labels_and_fonts(self, restore_xbox):
99 def test_save_accepts_optional_labels_and_fonts(self, restore_xbox): 100 original = load_layout("xbox") 101 payload = json.loads(json.dumps(original)) 102 payload["labels"] = {"a_button": {"dx": 4.5, "dy": -22.0}} 103 payload["fonts"] = {"input_name": 8, "action_label": 12} 104 save_layout("xbox", payload) 105 reloaded = load_layout("xbox") 106 assert reloaded["labels"]["a_button"]["dy"] == -22.0 107 assert reloaded["fonts"]["action_label"] == 12