"""The settings page's server and probes, offline: routes against the fake Companion, the LAN binding, the editing-code gate, the probe parsers on captured output, and the teaching pages.""" import json import os import socket import sys import tempfile import threading import time import unittest import urllib.error import urllib.request from http.server import ThreadingHTTPServer ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, ROOT) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import status_panel as sp # noqa: E402 from settings import keyguide, probes, server # noqa: E402 from test_status_panel import CONFIG, FakeCompanion, healthy_snapshot # noqa: E402 def get(port, path, json_body=None, origin=None): data = json.dumps(json_body).encode() if json_body is not None else None req = urllib.request.Request("http://127.0.0.1:%d%s" % (port, path), data=data, headers={"Content-Type": "application/json", **({"Origin": origin} if origin else {})}) try: with urllib.request.urlopen(req, timeout=10) as r: body = r.read() return r.status, body except urllib.error.HTTPError as e: return e.code, e.read() class TestServer(unittest.TestCase): @classmethod def setUpClass(cls): snap = healthy_snapshot(time.time()) FakeCompanion.store = {"conns": snap.conns, "vars": dict(snap.vars), "custom": {"sp_state": json.dumps({"all": {"x": "ALL OK", "b": 1, "f": 2, "b2": 1, "f2": 2, "s": 20}}), "sp_hb": str(int(time.time())), "sp_cmd": "", "flash_tick": "0"}, "restarts": []} cls.fake = ThreadingHTTPServer(("127.0.0.1", 0), FakeCompanion) threading.Thread(target=cls.fake.serve_forever, daemon=True).start() cls.data = tempfile.mkdtemp(prefix="sp-settings-") cfg = json.loads(json.dumps(CONFIG)) cfg["companion"] = {"host": "127.0.0.1", "port": cls.fake.server_address[1]} cfg["site"]["editing_code"] = "apple river stone" json.dump(cfg, open(os.path.join(cls.data, "config.json"), "w")) now = time.time() json.dump({"pid": 1, "started_at": now - 100, "config_sha": "x", "version": "test"}, open(os.path.join(cls.data, "started.json"), "w")) json.dump({"live": True, "reason": "a HyperDeck is recording", "time": now}, open(os.path.join(cls.data, "live-now.json"), "w")) json.dump({"time": now, "conns": {}, "faults": {"atem": [{"check": "ATEM", "type": "conn", "sub": "conn", "level": "err", "msg": "NO ANSWER", "since": now - 60, "acked": False}], "midi": [{"check": "tracks/NA Tracks/Lighting PC", "type": "midi_peer", "sub": "peer", "level": "err", "msg": "FX LIGHTS MIDI LOST", "since": now - 60, "acked": False}]}, "midi": {"NA Ingest": {"peers": [{"name": "Lighting PC", "state": "running"}]}}, "remote_midi": {}, "http": {"http://10.6.7.13:8080/": True}, "ping": {"10.1.3.227": 0}, "service_on": True, "dante_map": {}}, open(os.path.join(cls.data, "snapshot.json"), "w")) sock = socket.socket(); sock.bind(("127.0.0.1", 0)); cls.port = sock.getsockname()[1]; sock.close() cls.site, cls.servers = server.serve(cls.data, cls.port, ["127.0.0.1"]) @classmethod def tearDownClass(cls): for s in cls.servers: s.shutdown() cls.fake.shutdown() def test_reads_are_open_and_secrets_are_masked(self): status, body = get(self.port, "/api/config") self.assertEqual(status, 200) cfg = json.loads(body)["config"] self.assertEqual(cfg["site"]["editing_code"], "set (17 characters)") self.assertNotIn("apple river stone", body.decode()) def test_overview_shows_the_deck_the_problems_and_the_live_state(self): status, body = get(self.port, "/api/overview") o = json.loads(body) self.assertEqual(o["deck"]["all"]["x"], "ALL OK") self.assertTrue(o["companion_ok"]) self.assertEqual([p["text"] for p in o["problems"]], ["ATEM: NO ANSWER", "MIDI: FX LIGHTS MIDI LOST"]) words = {c["alias"]: c["word"] for c in o["computers"]} self.assertEqual((words["tracks"], words["lighting"], words["prop"], words["ingest"]), ("Problem", "Problem", "Not looked yet", "OK")) self.assertIn("hold the ATEM key", o["problems"][0]["action"]) self.assertTrue(o["live"]["live"]) self.assertEqual(len(o["computers"]), len(CONFIG["hosts"])) self.assertEqual(o["watchdog"]["version"], "test") def test_companion_view_lists_connections_with_their_watchers(self): o = json.loads(get(self.port, "/api/companion")[1]) by = {c["label"]: c for c in o["connections"]} self.assertIn("hd2", by["HyperDeck_2"]["watched_by"]) self.assertTrue(o["lights"]["variable sp_state"]) self.assertEqual(o["missing"], []) def test_machines_use_what_the_watchdog_sees(self): o = json.loads(get(self.port, "/api/machines")[1]) ingest = next(c for c in o["computers"] if c["self"]) self.assertEqual(ingest["word"], "OK") bible = next(c for c in o["computers"] if c["alias"] == "bibletv") web = next(i for i in bible["items"] if i["kind"] == "web") self.assertEqual(web["light"], "ok") # from snapshot.json, not a probe self.assertIn("mac", o["setup_steps"]) def test_gear_and_keys_and_pages(self): gear = json.loads(get(self.port, "/api/gear")[1]) self.assertTrue(any(r["name"] == "HyperDeck_2" and "hd2" in r["watched_by"] for r in gear)) keys = json.loads(get(self.port, "/api/keys")[1]) hd2 = next(t for t in keys["tiles"] if t["id"] == "hd2") self.assertIn("NO DISK", hd2["says"]) self.assertIn("off and on", hd2["holding"]) self.assertEqual(keys["hold"], "HOLD 2s") for page in ("/key-guide.html", "/cheat.html", "/where.html", "/", "/app.js", "/style.css"): status, body = get(self.port, page) self.assertEqual(status, 200, page) guide = get(self.port, "/key-guide.html")[1].decode() for t in CONFIG["tiles"]: self.assertIn(t["label"], guide) self.assertNotIn("?", keyguide.cheat_sheet(CONFIG, "http://x/").split("