66 lines
3.2 KiB
Python
66 lines
3.2 KiB
Python
import re
|
|||
|
|
import unittest
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
|
||
|
|
ROOT = Path(__file__).resolve().parents[1]
|
||
|
|
ASSETS = ROOT / "Sense" / "internal" / "console" / "assets"
|
||
|
|
|
||
|
|
|
||
|
|
class SenseConsoleContractTests(unittest.TestCase):
|
||
|
|
@classmethod
|
||
|
|
def setUpClass(cls):
|
||
|
|
cls.html = (ASSETS / "index.html").read_text(encoding="utf-8")
|
||
|
|
cls.css = (ASSETS / "app.css").read_text(encoding="utf-8")
|
||
|
|
cls.javascript = (ASSETS / "app.js").read_text(encoding="utf-8")
|
||
|
|
|
||
|
|
def test_console_is_self_contained_and_preserves_five_entry_ia(self):
|
||
|
|
self.assertIn('href="/sense-console/app.css"', self.html)
|
||
|
|
self.assertIn('src="/sense-console/app.js"', self.html)
|
||
|
|
self.assertNotRegex(self.html, r"https?://")
|
||
|
|
self.assertNotIn("<script>", self.html)
|
||
|
|
self.assertNotIn("<style>", self.html)
|
||
|
|
for view in ("overview", "monitor", "devices", "onboarding", "operations"):
|
||
|
|
self.assertGreaterEqual(self.html.count(f'data-view="{view}"'), 2)
|
||
|
|
self.assertIn(f'data-page="{view}"', self.html)
|
||
|
|
|
||
|
|
def test_capacity_and_preview_bounds_are_explicit(self):
|
||
|
|
self.assertIn('params.set("limit", String(state.config.page_size))', self.javascript)
|
||
|
|
self.assertIn("state.selected.size >= state.config.max_active_previews", self.javascript)
|
||
|
|
self.assertIn('state.config.page_size !== 16', self.javascript)
|
||
|
|
self.assertIn('state.config.max_active_previews !== 4', self.javascript)
|
||
|
|
self.assertIn('state.config.recording_and_playback !== false', self.javascript)
|
||
|
|
self.assertIn('$("#nextPage").disabled = loading || !state.nextCursor', self.javascript)
|
||
|
|
self.assertIn('$("#prevPage").disabled = loading || state.pageIndex === 0', self.javascript)
|
||
|
|
self.assertNotRegex(self.javascript, r"\.slice\(\s*0\s*,\s*16\s*\)")
|
||
|
|
|
||
|
|
def test_token_is_memory_only_and_sensitive_addresses_are_not_rendered(self):
|
||
|
|
for forbidden in ("local" + "Storage", "session" + "Storage", "document.cookie"):
|
||
|
|
self.assertNotIn(forbidden, self.javascript)
|
||
|
|
self.assertIn('state.token = ""', self.javascript)
|
||
|
|
self.assertIn('tokenInput.value = ""', self.javascript)
|
||
|
|
self.assertNotRegex(self.html, r"rtsp://|onvif://|/whep")
|
||
|
|
self.assertNotRegex(self.javascript, r"rtsp://|onvif://")
|
||
|
|
self.assertNotIn("innerHTML", self.javascript)
|
||
|
|
|
||
|
|
def test_accessibility_and_responsive_guards_are_present(self):
|
||
|
|
self.assertIn('href="#main-content"', self.html)
|
||
|
|
self.assertIn('aria-live="polite"', self.html)
|
||
|
|
self.assertIn('aria-current="page"', self.html)
|
||
|
|
self.assertIn("@media (max-width: 760px)", self.css)
|
||
|
|
self.assertIn("min-height: 44px", self.css)
|
||
|
|
self.assertIn("prefers-reduced-motion", self.css)
|
||
|
|
self.assertIn(":focus-visible", self.css)
|
||
|
|
ids = re.findall(r'\bid="([^"]+)"', self.html)
|
||
|
|
self.assertEqual(len(ids), len(set(ids)))
|
||
|
|
for attributes, content in re.findall(r"<button\b([^>]*)>(.*?)</button>", self.html, re.S):
|
||
|
|
visible_text = re.sub(r"<[^>]+>", "", content).strip()
|
||
|
|
self.assertTrue(
|
||
|
|
"aria-label=" in attributes or visible_text,
|
||
|
|
f"button lacks an accessible name: {attributes}",
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|