from __future__ import annotations import http.client import json import threading import unittest from Brain.yovision_brain.domain import Point, Zone, zone_from_payload from Brain.yovision_brain.server import DemoHTTPServer, DemoHandler, index_path, parse_bind class FakeEngine: def __init__(self) -> None: self.zone = Zone("zone-demo-01", "测试区域", 1, (Point(0.1, 0.1), Point(0.9, 0.1), Point(0.5, 0.9))) def state(self) -> dict[str, object]: return {"source": {"label": "safe", "connected": True}, "events": [], "zone": {"version": self.zone.version}} def frame_jpeg(self) -> bytes: return b"\xff\xd8safe-jpeg\xff\xd9" def update_zone(self, payload: object) -> Zone: self.zone = zone_from_payload(payload, self.zone) return self.zone class BindTests(unittest.TestCase): def test_only_explicit_loopback_is_allowed(self) -> None: self.assertEqual(parse_bind("127.0.0.1:8090"), ("127.0.0.1", 8090)) self.assertEqual(parse_bind("localhost:8090"), ("localhost", 8090)) with self.assertRaisesRegex(ValueError, "loopback"): parse_bind("0.0.0.0:8090") with self.assertRaisesRegex(ValueError, "loopback"): parse_bind("192.168.1.10:8090") class HTTPTests(unittest.TestCase): def setUp(self) -> None: html = index_path().read_text(encoding="utf-8") self.server = DemoHTTPServer(("127.0.0.1", 0), FakeEngine(), "test-token", html) self.thread = threading.Thread(target=self.server.serve_forever, daemon=True) self.thread.start() self.connection = http.client.HTTPConnection("127.0.0.1", self.server.server_port, timeout=2) def tearDown(self) -> None: self.connection.close() self.server.shutdown() self.server.server_close() self.thread.join(timeout=2) def test_index_substitutes_token_and_sets_security_headers(self) -> None: self.connection.request("GET", "/brain-demo") response = self.connection.getresponse() body = response.read().decode("utf-8") self.assertEqual(response.status, 200) self.assertIn('content="test-token"', body) self.assertNotIn("__BRAIN_DEMO_TOKEN__", body) self.assertEqual(response.getheader("X-Frame-Options"), "DENY") self.assertIn("default-src 'self'", response.getheader("Content-Security-Policy")) def test_zone_write_requires_token_and_rejects_unknown_fields(self) -> None: body = json.dumps({"name": "新区域", "points": [{"x": 0.1, "y": 0.1}, {"x": 0.9, "y": 0.1}, {"x": 0.5, "y": 0.9}]}) self.connection.request("PUT", "/api/v1/zones/active", body=body, headers={"Content-Type": "application/json"}) forbidden = self.connection.getresponse() forbidden.read() self.assertEqual(forbidden.status, 403) self.connection.request("PUT", "/api/v1/zones/active", body=body, headers={"Content-Type": "application/json", "X-Brain-Demo-Token": "test-token"}) accepted = self.connection.getresponse() payload = json.loads(accepted.read()) self.assertEqual(accepted.status, 200) self.assertEqual(payload["version"], 2) invalid = json.dumps({"name": "bad", "points": [], "tenant_id": "leak"}) self.connection.request("PUT", "/api/v1/zones/active", body=invalid, headers={"Content-Type": "application/json", "X-Brain-Demo-Token": "test-token"}) rejected = self.connection.getresponse() self.assertEqual(rejected.status, 400) self.assertEqual(json.loads(rejected.read())["code"], "invalid_zone") if __name__ == "__main__": unittest.main()