Blueforce-Linux/tests/test_web_static.py
emirhanmamak-blueforce b6a14ca8e0 refine(operators): wave code back in the fleet row; drop a dead helper
The fleet row named the wave by its short name only ("PILOT-2") while the
detail view and the filter carry the full label ("P2 — PILOT-2"). An operator
matching a row against a work order speaks in codes, so the row carries the
full label now -- still named once, which the row test asserts.

Also takes the console agent's leftover: a dead state_style helper removed and
the unit convention on ALL_TOKENS documented (lengths are unitless; Tk padding
is already pixels, the parity test strips the CSS unit).
2026-09-16 22:47:12 +03:00

1292 lines
58 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""web panel — static and contract tests (stdlib unittest).
No port is bound, no browser is opened and no server process is started: the
whole HTTP surface is covered through ``web.panel.route()``, which is a pure
function, and every rendering claim is checked on the rendered bytes.
What these tests defend (the things that could silently regress):
* the Turkish status vocabulary still matches docs/DESIGN.md exactly, so the
device console and the central panel cannot drift apart;
* every user-visible string is non-empty, Turkish and free of placeholders;
* no external URL, no webfont, no hex colour and no new colour token anywhere
in the templates or stylesheets: colour comes from tokens.css only;
* the panel imports nothing outside the standard library and can therefore be
installed on the air-gapped fleet it manages;
* only GET/HEAD are served, static assets are allow-listed, and a full sweep
over every endpoint leaves the filesystem untouched;
* the first viewport is ordered and populated as docs/DESIGN.md requires.
Run:
python3 tests/test_web_static.py -v
"""
from __future__ import annotations
import ast
import contextlib
import io
import json
import re
import sys
import tempfile
import unittest
from pathlib import Path
from typing import Any
REPO_ROOT = Path(__file__).resolve().parents[1]
WEB_DIR = REPO_ROOT / "web"
TEMPLATES_DIR = WEB_DIR / "templates"
STATIC_DIR = WEB_DIR / "static"
TOKENS_CSS = STATIC_DIR / "tokens.css"
DESIGN_DOC = REPO_ROOT / "docs" / "DESIGN.md"
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
import web # noqa: E402 (path set above)
from web import fleet_data, panel, render, server # noqa: E402
# The panel must be the one in this checkout, not some unrelated "web" package.
assert Path(web.__file__).resolve().parent == WEB_DIR, f"yanlis paket: {web.__file__}"
NOW = "2026-09-16T22:00:00+03:00"
STATE_WORDS = ("KURULDU", "KAYITLI", "HAZIR", "HATA", "YOK")
# Everything an air-gapped, framework-free surface must never contain.
FORBIDDEN_ASSET_MARKERS = (
"http://",
"https://",
"//cdn",
"cdn.jsdelivr",
"cdnjs",
"unpkg",
"fonts.googleapis",
"fonts.gstatic",
"@import",
"@font-face",
"src=",
"xlink:href=",
)
FORBIDDEN_IMPORT_ROOTS = ("fastapi", "flask", "jinja2", "uvicorn", "django", "werkzeug", "starlette", "requests")
FORBIDDEN_IMPORTS = ("urllib.request", "http.client", "socket", "subprocess", "shutil", "ftplib")
FORBIDDEN_SOURCE_MARKERS = ("os.system(", "subprocess.", "urllib.request", "requests.", "eval(", "exec(")
HEX_COLOUR = re.compile(r"#[0-9a-fA-F]{3,8}\b")
CUSTOM_PROPERTY = re.compile(r"^\s*--[A-Za-z0-9-]+\s*:", re.M)
# A custom-property declaration, e.g. `--state-ready: var(--ok);`
CUSTOM_PROPERTY_DECL = re.compile(r"^\s*--([A-Za-z0-9-]+)\s*:\s*([^;]+);", re.M)
# A single `var(--name)` reference inside a value.
VAR_REFERENCE = re.compile(r"var\(\s*--([A-Za-z0-9-]+)\s*\)")
DEFINED_TOKEN = re.compile(r"^\s*(--[a-z0-9-]+)\s*:", re.M)
USED_TOKEN = re.compile(r"var\((--[a-z0-9-]+)\)")
TEXT_TAGS = ("td", "th", "span", "h1", "h2", "h3", "p", "li", "option", "dd", "dt", "button", "a", "caption", "label")
ELEMENT = re.compile(r"<(%s)\b[^>]*>(.*?)</\1>" % "|".join(TEXT_TAGS), re.S)
ROW_BADGE = re.compile(r'<span class="badge badge--state-([a-z]+)"[^>]*>([^<]*)</span>')
# --------------------------------------------------------------------------
# helpers
# --------------------------------------------------------------------------
def token_values(text: str) -> dict[str, str]:
"""Every custom-property declaration in a stylesheet, name -> raw value."""
return {
name: value.strip()
for name, value in CUSTOM_PROPERTY_DECL.findall(text)
}
def resolved_colour(name: str, values: dict[str, str], limit: int = 12) -> str | None:
"""Follow ``var(--x)`` aliases to the colour a token actually paints.
Comparing raw declarations is not enough to prove two tokens differ: a
mutation writing ``--state-provisioned: var(--accent)`` leaves the strings
unequal while painting exactly the same blue. Only the resolved value is
evidence.
"""
seen: set[str] = set()
while limit > 0:
value = values.get(name)
if value is None:
return None
reference = VAR_REFERENCE.fullmatch(value)
if reference is None:
return value
name = reference.group(1)
if name in seen:
return value # a cycle: report what was declared rather than loop
seen.add(name)
limit -= 1
return values.get(name)
def design_vocabulary() -> list[tuple[str, str]]:
"""Read the status vocabulary table out of docs/DESIGN.md."""
text = DESIGN_DOC.read_text(encoding="utf-8")
row = re.compile(r"^\|\s*`([^`]+)`\s*\|\s*`(--state-[a-z-]+)`\s*\|", re.M)
return row.findall(text)
def text_files() -> list[Path]:
"""Templates and stylesheets: everything the browser receives from disk."""
return sorted(TEMPLATES_DIR.glob("*.html")) + sorted(STATIC_DIR.glob("*.css"))
def empty_elements(markup: str) -> list[str]:
"""Visible elements that carry no text at all (an empty label is a bug)."""
problems: list[str] = []
for match in ELEMENT.finditer(markup):
inner = re.sub(r"<[^>]+>", "", match.group(2))
if not inner.strip():
problems.append(match.group(0)[:140])
return problems
def tree_fingerprint(root: Path) -> tuple:
"""Sizes and mtimes of every source file, ignoring interpreter caches."""
entries = []
for path in sorted(root.rglob("*")):
if "__pycache__" in path.parts or not path.is_file():
continue
stat = path.stat()
entries.append((str(path.relative_to(root)), stat.st_size, stat.st_mtime_ns))
return tuple(entries)
class PanelCase(unittest.TestCase):
"""Shared fixture: the labelled demo snapshot and both privilege levels."""
@classmethod
def setUpClass(cls) -> None:
cls.snapshot = fleet_data.demo_snapshot(NOW)
cls.read_only = render.privilege_from_level("read-only")
cls.full = render.privilege_from_level("full")
cls.info = render.ServerInfo(
host="127.0.0.1",
port=8787,
privilege=cls.read_only,
python_version="3.13.0",
started_at=NOW,
)
cls.context = panel.PanelContext(
snapshot=cls.snapshot, privilege=cls.read_only, server_info=cls.info
)
full_info = render.ServerInfo(
host="127.0.0.1",
port=8787,
privilege=cls.full,
python_version="3.13.0",
started_at=NOW,
)
cls.full_context = panel.PanelContext(
snapshot=cls.snapshot, privilege=cls.full, server_info=full_info
)
# -- request helpers ---------------------------------------------------
def get(self, target: str, method: str = "GET", context: panel.PanelContext | None = None):
return panel.route(context or self.context, method, target)
def page(self, target: str = "/", method: str = "GET", context: panel.PanelContext | None = None) -> str:
response = self.get(target, method, context)
self.assertEqual(response.status, 200, f"{target}: {response.status} (beklenen 200)")
return response.body.decode("utf-8")
def list_region(self, page: str) -> str:
"""The device table only, so about/detail badges never leak into a scan."""
start = page.index('data-region="list"')
end = page.index('data-region="detail"')
return page[start:end]
def row_words(self, page: str) -> list[str]:
return [word for _, word in ROW_BADGE.findall(self.list_region(page))]
def snapshot_from(self, document: object, *, now: str = NOW, filename: str = "fleet.json"):
"""Write a temporary inventory document and load it through the loader."""
directory = tempfile.TemporaryDirectory(prefix="bf-panel-test-")
self.addCleanup(directory.cleanup)
path = Path(directory.name) / filename
if isinstance(document, str):
path.write_text(document, encoding="utf-8")
else:
path.write_text(json.dumps(document, ensure_ascii=False), encoding="utf-8")
return fleet_data.load_fleet(path, env={}, now=now)
# --------------------------------------------------------------------------
# vocabulary and design contract
# --------------------------------------------------------------------------
class TestVocabularyMatchesDesign(PanelCase):
def test_vocabulary_matches_docs_design_exactly(self) -> None:
"""One word per state, shared with the device console; no fifth word."""
self.assertEqual(
design_vocabulary(),
[(spec.word, spec.token) for spec in fleet_data.STATE_VOCABULARY],
)
def test_design_document_still_has_the_five_words(self) -> None:
words = [word for word, _ in design_vocabulary()]
self.assertEqual(words, list(STATE_WORDS))
def test_every_word_is_upper_case_turkish_and_non_empty(self) -> None:
for spec in fleet_data.STATE_VOCABULARY:
with self.subTest(state=spec.state):
self.assertTrue(spec.word.strip(), spec.state)
self.assertEqual(spec.word, spec.word.upper())
self.assertTrue(spec.meaning.strip(), spec.word)
self.assertNotIn("None", spec.meaning)
def test_tokens_exist_in_tokens_css(self) -> None:
css = TOKENS_CSS.read_text(encoding="utf-8")
for spec in fleet_data.STATE_VOCABULARY:
with self.subTest(state=spec.state):
self.assertIn(f"{spec.token}:", css)
self.assertIn(f"{spec.soft_token}:", css)
def test_non_ready_states_sort_before_ready(self) -> None:
self.assertEqual(fleet_data.STATE_ORDER[-1], "READY")
self.assertEqual(set(fleet_data.STATE_ORDER), set(fleet_data.STATE_BY_NAME))
def test_demo_data_exercises_every_state_word(self) -> None:
counts = self.snapshot.counts
self.assertEqual(list(counts), list(STATE_WORDS))
for word, count in counts.items():
with self.subTest(word=word):
self.assertGreaterEqual(count, 1, f"{word} demo veride yok; sözlük test edilemez")
def test_demo_data_exercises_every_wave(self) -> None:
counts = fleet_data.wave_counts(self.snapshot.devices)
self.assertEqual(list(counts), [wave.code for wave in fleet_data.WAVES])
for code, count in counts.items():
with self.subTest(wave=code):
self.assertGreaterEqual(count, 1)
def test_waves_match_the_rollout_document(self) -> None:
"""docs/18: LAB(2) -> P1(5) -> P2(20) -> W1(50) -> W2(100) -> PROD."""
self.assertEqual(
[(wave.code, wave.target) for wave in fleet_data.WAVES],
[("LAB", 2), ("P1", 5), ("P2", 20), ("W1", 50), ("W2", 100), ("PROD", 523)],
)
def test_four_documented_channels(self) -> None:
self.assertEqual(
[channel.label for channel in fleet_data.CHANNELS],
["SSH-over-WG", "xRDP", "RustDesk", "MeshCentral"],
)
self.assertEqual(
[channel.key for channel in fleet_data.CHANNELS],
["ssh_over_wg", "xrdp", "rustdesk", "meshcentral"],
)
def test_channel_status_words_are_turkish_and_non_empty(self) -> None:
for status, word in fleet_data.CHANNEL_STATUS_WORDS.items():
with self.subTest(status=status):
self.assertTrue(word.strip())
self.assertNotIn("None", word)
self.assertEqual(set(fleet_data.CHANNEL_STATUS_WORDS), set(fleet_data.CHANNEL_STATUSES))
# --------------------------------------------------------------------------
# no external assets, no invented colour
# --------------------------------------------------------------------------
class TestAssetsAndColour(PanelCase):
def test_no_external_url_or_remote_asset_in_any_template_or_stylesheet(self) -> None:
for path in text_files():
text = path.read_text(encoding="utf-8").lower()
for marker in FORBIDDEN_ASSET_MARKERS:
with self.subTest(file=path.name, marker=marker):
self.assertNotIn(marker, text)
def test_no_javascript_ships_with_the_panel(self) -> None:
for path in text_files():
text = path.read_text(encoding="utf-8").lower()
with self.subTest(file=path.name):
self.assertNotIn("<script", text)
self.assertNotIn("onclick=", text)
self.assertNotIn("javascript:", text)
def test_hex_colour_literals_live_only_in_tokens_css(self) -> None:
for path in text_files():
text = path.read_text(encoding="utf-8")
if path == TOKENS_CSS:
self.assertTrue(HEX_COLOUR.search(text), "tokens.css palet içermiyor")
continue
with self.subTest(file=path.name):
self.assertEqual(HEX_COLOUR.findall(text), [], f"{path.name} hex renk içeriyor")
def test_templates_and_app_css_declare_no_custom_property(self) -> None:
for path in text_files():
if path == TOKENS_CSS:
continue
text = path.read_text(encoding="utf-8")
with self.subTest(file=path.name):
self.assertEqual(CUSTOM_PROPERTY.findall(text), [], f"{path.name} belirteç tanımlıyor")
def test_every_colour_token_used_is_defined_in_tokens_css(self) -> None:
defined = set(DEFINED_TOKEN.findall(TOKENS_CSS.read_text(encoding="utf-8")))
self.assertTrue(defined)
used: dict[str, str] = {}
for path in text_files():
if path == TOKENS_CSS:
continue
for token in USED_TOKEN.findall(path.read_text(encoding="utf-8")):
used[token] = path.name
unknown = {token: name for token, name in used.items() if token not in defined}
self.assertEqual(unknown, {}, f"tokens.css'te tanımsız belirteç: {unknown}")
self.assertTrue(used, "hiç belirteç kullanılmıyor")
def test_the_action_accent_is_not_a_device_state_colour(self) -> None:
"""The accent means "the action you came to take" and "the selected row".
A state that borrows it makes a KURULDU badge and a selected row the same
blue, so the list can no longer say which of the two it is showing. Aliases
are resolved before comparing: a token may chain (``--state-ready`` to
``--ok``), and ``var(--accent)`` is the accent whatever the string says.
"""
values = token_values(TOKENS_CSS.read_text(encoding="utf-8"))
self.assertIn("accent", values, "tokens.css'te --accent yok")
accent = resolved_colour("accent", values)
states = {n: v for n, v in values.items() if n.startswith("state-")}
self.assertTrue(states, "tokens.css'te cihaz durumu belirteci yok")
for name in states:
with self.subTest(token=name):
resolved = resolved_colour(name, values)
self.assertIsNotNone(resolved, f"--{name} çözümlenemedi")
self.assertNotEqual(
resolved, accent,
f"--{name} ile --accent aynı rengi veriyor ({resolved}); "
"seçili satır ile durum rozeti ayırt edilemez",
)
def test_wave_is_rendered_once_and_not_as_the_bare_code_plus_full_label(self) -> None:
"""``detail.html`` printed ``$wave_code`` and then ``$wave_label``, and the
label already begins with the code, so one device read "P2 P2 — PILOT-2"
(the list row was the same). A wave cell must name each wave once.
"""
device = next(
d for d in self.snapshot.devices if d.wave_code == "P2"
)
target = f"/?cihaz={device.device_id}"
body = panel.route(self.context, "GET", target).body.decode("utf-8")
self.assertNotIn("$wave_name", body, "şablon değişkeni değiştirilmedi")
self.assertNotIn("$wave_code", body, "şablon değişkeni değiştirilmedi")
self.assertNotIn(device.wave_code * 2, body, f"dalga kodu iki kez basılıyor: {device.wave_code}")
self.assertIn(device.wave_label, body, "dalga tam etiketi görünmüyor")
def test_only_local_stylesheets_are_linked(self) -> None:
base = (TEMPLATES_DIR / "base.html").read_text(encoding="utf-8")
linked = re.findall(r'<link[^>]*href="([^"]+)"', base)
self.assertEqual(linked, ["/static/tokens.css", "/static/app.css"])
def test_templates_are_utf8_with_turkish_characters(self) -> None:
for path in sorted(TEMPLATES_DIR.glob("*.html")):
text = path.read_text(encoding="utf-8")
with self.subTest(file=path.name):
self.assertTrue(text.strip())
base = (TEMPLATES_DIR / "base.html").read_text(encoding="utf-8")
self.assertIn('<html lang="tr"', base)
self.assertIn('<meta charset="utf-8">', base)
def test_state_badge_styles_use_the_shared_tokens(self) -> None:
css = (STATIC_DIR / "app.css").read_text(encoding="utf-8")
for spec in fleet_data.STATE_VOCABULARY:
with self.subTest(state=spec.state):
self.assertIn(f".badge--state-{spec.token_suffix}", css)
self.assertIn("var(--accent-soft)", css)
self.assertIn(".fleet-row--selected", css)
# --------------------------------------------------------------------------
# standard library only: the fleet installs offline
# --------------------------------------------------------------------------
class TestStdlibOnly(PanelCase):
def web_sources(self) -> list[Path]:
return sorted(WEB_DIR.glob("*.py"))
def test_web_modules_exist(self) -> None:
names = {path.name for path in self.web_sources()}
self.assertEqual(
names, {"__init__.py", "fleet_data.py", "render.py", "panel.py", "server.py"}
)
def test_every_import_resolves_to_the_standard_library(self) -> None:
stdlib = set(sys.stdlib_module_names)
for path in self.web_sources():
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for node in ast.walk(tree):
if isinstance(node, ast.Import):
roots = [alias.name.split(".")[0] for alias in node.names]
elif isinstance(node, ast.ImportFrom):
if node.level:
continue
roots = [(node.module or "").split(".")[0]]
else:
continue
for root in roots:
with self.subTest(file=path.name, module=root):
self.assertIn(root, stdlib | {"web"})
def test_no_third_party_web_framework_is_referenced(self) -> None:
for path in self.web_sources():
text = path.read_text(encoding="utf-8").lower()
for marker in FORBIDDEN_IMPORT_ROOTS:
with self.subTest(file=path.name, marker=marker):
self.assertNotIn(f"import {marker}", text)
self.assertNotIn(f"from {marker}", text)
def test_no_outbound_client_and_no_process_execution(self) -> None:
for path in self.web_sources():
text = path.read_text(encoding="utf-8")
for marker in FORBIDDEN_IMPORTS:
with self.subTest(file=path.name, marker=marker):
self.assertNotIn(f"import {marker}", text)
lowered = text.lower()
for marker in FORBIDDEN_SOURCE_MARKERS:
with self.subTest(file=path.name, marker=marker):
self.assertNotIn(marker, lowered)
def test_route_is_a_pure_function_with_a_small_signature(self) -> None:
import inspect
signature = inspect.signature(panel.route)
self.assertEqual(list(signature.parameters), ["context", "method", "target"])
def test_server_module_takes_host_and_port_from_the_command_line(self) -> None:
args = server.parse_args([])
self.assertEqual(args.host, "127.0.0.1")
self.assertEqual(args.host, web.DEFAULT_HOST)
self.assertEqual(args.port, web.DEFAULT_PORT)
self.assertEqual(args.privilege, "read-only")
self.assertEqual(args.data, None)
def test_check_mode_reports_without_binding_a_port(self) -> None:
buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
code = server.main(["--check"])
self.assertEqual(code, 0)
output = buffer.getvalue()
self.assertIn(web.PANEL_VERSION, output)
self.assertIn("Veri kaynağı", output)
self.assertIn("Yetki", output)
self.assertIn("sunucu başlatılmadı", output)
def test_version_and_privilege_flags(self) -> None:
with contextlib.redirect_stdout(io.StringIO()):
with self.assertRaises(SystemExit) as raised:
server.parse_args(["--version"])
self.assertEqual(raised.exception.code, 0)
with contextlib.redirect_stderr(io.StringIO()):
with self.assertRaises(SystemExit):
server.parse_args(["--privilege", "root"])
def test_data_flag_selects_the_inventory_file(self) -> None:
snapshot = self.snapshot_from(self.example_document())
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "fleet.json"
path.write_text(self.example_document(), encoding="utf-8")
context = server.build_context(server.parse_args(["--data", str(path)]))
self.assertTrue(context.snapshot.is_file)
self.assertEqual(context.snapshot.total, snapshot.total)
self.assertEqual(context.server_info.data_path, str(path))
def example_document(self) -> str:
return (WEB_DIR / "data" / "fleet.example.json").read_text(encoding="utf-8")
def test_example_document_matches_the_panel_schema(self) -> None:
"""The shipped example must load cleanly, or it teaches the wrong schema."""
snapshot = self.snapshot_from(self.example_document())
self.assertTrue(snapshot.is_file, snapshot.error or "örnek belge okunamadı")
self.assertEqual(snapshot.invalid, ())
self.assertGreaterEqual(snapshot.total, 5)
self.assertGreater(snapshot.ready_count, 0)
# --------------------------------------------------------------------------
# loader: state machines, refusal paths, provenance
# --------------------------------------------------------------------------
class TestLoader(PanelCase):
READY_RECORD: dict[str, Any] = {
"dealer_id": "12010193",
"wave": "P1",
"state": "READY",
"channels": {
"ssh_over_wg": "verified",
"xrdp": "verified",
"rustdesk": "verified",
"meshcentral": "verified",
},
}
def document(self, *records: object) -> dict:
return {"schema_version": 1, "generated_at": NOW, "devices": list(records)}
def test_missing_default_file_falls_back_to_labelled_demo(self) -> None:
snapshot = fleet_data.load_fleet(None, env={}, now=NOW, default_path="/yok/fleet.json")
self.assertTrue(snapshot.is_demo)
self.assertEqual(snapshot.total, len(fleet_data.DEMO_DEVICES))
self.assertIn("demo", snapshot.source_label.lower())
self.assertEqual(snapshot.banner_word, "DEMO VERİ")
self.assertIn("sentetik", snapshot.banner_text)
def test_missing_explicit_file_is_an_error_and_does_not_become_demo(self) -> None:
snapshot = fleet_data.load_fleet("/yok/boyle/bir/dosya.json", env={}, now=NOW)
self.assertTrue(snapshot.is_error)
self.assertEqual(snapshot.total, 0)
self.assertEqual(snapshot.banner_word, "VERİ YOK")
self.assertIn("bulunamadı", snapshot.error or "")
self.assertIn("Demo veriye düşülmedi", snapshot.banner_text)
def test_environment_variable_selects_the_document(self) -> None:
document = json.dumps(self.document(self.READY_RECORD))
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "fleet.json"
path.write_text(document, encoding="utf-8")
snapshot = fleet_data.load_fleet(None, env={fleet_data.DATA_PATH_ENV: str(path)}, now=NOW)
self.assertTrue(snapshot.is_file)
self.assertEqual(snapshot.total, 1)
def test_identity_is_derived_from_the_dealer_number(self) -> None:
snapshot = self.snapshot_from(self.document(self.READY_RECORD))
device = snapshot.devices[0]
self.assertEqual(device.device_id, "BF-12010193")
self.assertEqual(device.hostname, "bf-12010193")
self.assertEqual(device.dealer_id, "12010193")
self.assertEqual(snapshot.schema_version, 1)
self.assertEqual(snapshot.generated_at, NOW)
def test_wave_aliases_resolve_to_the_rollout_codes(self) -> None:
for raw, expected in (
("pilot_1", "P1"),
("LAB", "LAB"),
("wave_2", "W2"),
("production", "PROD"),
):
with self.subTest(wave=raw):
record = dict(self.READY_RECORD, wave=raw)
snapshot = self.snapshot_from(self.document(record))
self.assertEqual(snapshot.devices[0].wave_code, expected)
def test_turkish_state_words_are_accepted_as_input(self) -> None:
for raw, expected in (("HAZIR", "READY"), ("Hazır", "READY"), ("hazir", "READY")):
with self.subTest(state=raw):
record = dict(self.READY_RECORD, state=raw)
snapshot = self.snapshot_from(self.document(record))
self.assertEqual(snapshot.devices[0].state, expected)
def test_broken_json_is_an_error_that_names_the_document(self) -> None:
snapshot = self.snapshot_from('{"schema_version": 1, "devices": [')
self.assertTrue(snapshot.is_error)
self.assertIn("JSON", snapshot.error or "")
def test_unsupported_schema_version_is_refused(self) -> None:
for version in (2, 99):
with self.subTest(version=version):
document = {"schema_version": version, "devices": []}
snapshot = self.snapshot_from(document)
self.assertTrue(snapshot.is_error)
self.assertIn("şema sürümü", snapshot.error or "")
def test_missing_schema_version_or_devices_list_is_refused(self) -> None:
for document in ({"devices": []}, {"schema_version": 1}):
with self.subTest(document=document):
snapshot = self.snapshot_from(document)
self.assertTrue(snapshot.is_error)
def test_bad_dealer_number_is_shown_not_dropped(self) -> None:
record = dict(self.READY_RECORD, dealer_id="1201019")
snapshot = self.snapshot_from(self.document(record))
self.assertEqual(snapshot.total, 0)
self.assertEqual(len(snapshot.invalid), 1)
reason = snapshot.invalid[0].reason
self.assertIn("8 hane", reason)
self.assertIn("1201019", reason)
def test_duplicate_dealer_number_is_refused(self) -> None:
snapshot = self.snapshot_from(self.document(self.READY_RECORD, dict(self.READY_RECORD)))
self.assertEqual(snapshot.total, 1)
self.assertEqual(len(snapshot.invalid), 1)
self.assertIn("Yinelenen", snapshot.invalid[0].reason)
def test_unknown_wave_is_refused_with_a_turkish_reason(self) -> None:
record = dict(self.READY_RECORD, wave="WAVE-9")
snapshot = self.snapshot_from(self.document(record))
self.assertEqual(snapshot.total, 0)
self.assertIn("Bilinmeyen dalga", snapshot.invalid[0].reason)
def test_mismatching_device_id_is_refused(self) -> None:
record = dict(self.READY_RECORD, device_id="BF-12010999")
snapshot = self.snapshot_from(self.document(record))
self.assertEqual(snapshot.total, 0)
self.assertIn("kurala uymuyor", snapshot.invalid[0].reason)
def test_unknown_state_becomes_hata_with_the_reason(self) -> None:
record = dict(self.READY_RECORD, state="BOZUK")
snapshot = self.snapshot_from(self.document(record))
device = snapshot.devices[0]
self.assertEqual(device.state_label, "HATA")
self.assertEqual(device.claimed_state, "BOZUK")
self.assertIn("tanınmıyor", device.reason or "")
def test_missing_state_becomes_hata_and_is_never_invented(self) -> None:
record = {key: value for key, value in self.READY_RECORD.items() if key != "state"}
snapshot = self.snapshot_from(self.document(record))
device = snapshot.devices[0]
self.assertEqual(device.state_label, "HATA")
self.assertIn("Durum alanı yok", device.reason or "")
def test_ready_claim_without_verified_channels_is_refused(self) -> None:
"""docs/27: READY is refused unless every channel reports verified."""
record = dict(self.READY_RECORD)
record["channels"] = dict(self.READY_RECORD["channels"], xrdp="pending")
snapshot = self.snapshot_from(self.document(record))
device = snapshot.devices[0]
self.assertEqual(device.state_label, "HATA")
self.assertEqual(device.claimed_state, "READY")
self.assertIn("HAZIR iddiası reddedildi", device.reason or "")
self.assertIn("xRDP", device.reason or "")
self.assertIn("xRDP", device.unverified_channels)
def test_ready_claim_with_every_channel_verified_is_ready(self) -> None:
snapshot = self.snapshot_from(self.document(self.READY_RECORD))
device = snapshot.devices[0]
self.assertEqual(device.state_label, "HAZIR")
self.assertEqual(device.verified_channel_count, 4)
self.assertEqual(device.unverified_channels, ())
self.assertIn("dört kanalın dördü de doğrulandı", device.ready_gate_note)
def test_failed_channel_marks_the_device_hata(self) -> None:
record = dict(self.READY_RECORD, state="ENROLLED")
record["channels"] = dict(self.READY_RECORD["channels"], rustdesk="failed")
snapshot = self.snapshot_from(self.document(record))
device = snapshot.devices[0]
self.assertEqual(device.state_label, "HATA")
self.assertIn("başarısız", device.reason or "")
def test_panel_never_promotes_a_device(self) -> None:
record = dict(self.READY_RECORD, state="ENROLLED")
snapshot = self.snapshot_from(self.document(record))
device = snapshot.devices[0]
self.assertEqual(device.state_label, "KAYITLI")
self.assertTrue(any("yükseltmez" in note for note in device.notes), device.notes)
def test_missing_channels_are_all_reported_as_missing(self) -> None:
record = {"dealer_id": "12010193", "wave": "P1", "state": "PROVISIONED_OFFLINE"}
snapshot = self.snapshot_from(self.document(record))
device = snapshot.devices[0]
self.assertEqual(len(device.channels), 4)
self.assertEqual({check.status for check in device.channels}, {"missing"})
self.assertEqual({check.status_word for check in device.channels}, {"yok"})
self.assertTrue(any("Kanallar bildirilmemiş" in note for note in device.notes))
def test_unknown_channel_keys_are_reported_not_silently_ignored(self) -> None:
record = dict(self.READY_RECORD)
record["channels"] = dict(self.READY_RECORD["channels"], telnet="verified")
snapshot = self.snapshot_from(self.document(record))
device = snapshot.devices[0]
self.assertTrue(any("telnet" in note for note in device.notes), device.notes)
self.assertEqual(len(device.channels), 4)
def test_loader_is_deterministic(self) -> None:
document = self.document(self.READY_RECORD)
first = self.snapshot_from(document)
second = self.snapshot_from(document)
self.assertEqual(first.devices, second.devices)
# --------------------------------------------------------------------------
# ordering and filtering
# --------------------------------------------------------------------------
class TestOrderingAndFiltering(PanelCase):
def sorted_demo(self):
return fleet_data.sort_devices(self.snapshot.devices)
def test_non_ready_devices_come_first(self) -> None:
flags = [device.is_ready for device in self.sorted_demo()]
self.assertEqual(flags, sorted(flags), "hazır cihaz hazır olmayanın önünde")
self.assertIn(True, flags)
self.assertIn(False, flags)
def test_failed_devices_are_at_the_top(self) -> None:
ordered = self.sorted_demo()
self.assertEqual(ordered[0].state_label, "HATA")
def test_sort_keeps_every_device_exactly_once(self) -> None:
ordered = self.sorted_demo()
self.assertEqual(len(ordered), self.snapshot.total)
self.assertEqual(
{device.device_id for device in ordered},
{device.device_id for device in self.snapshot.devices},
)
def test_query_filter_matches_identity_hostname_and_dealer(self) -> None:
for needle in ("BF-12010193", "bf-12010193", "12010193"):
with self.subTest(needle=needle):
criteria, warnings = fleet_data.parse_filter({"q": [needle]})
shown = fleet_data.filter_devices(self.snapshot.devices, criteria)
self.assertEqual(len(shown), 1, needle)
self.assertEqual(shown[0].device_id, "BF-12010193")
self.assertEqual(warnings, ())
def test_filter_query_is_case_insensitive(self) -> None:
criteria, _ = fleet_data.parse_filter({"q": ["bf-1201019"]})
shown = fleet_data.filter_devices(self.snapshot.devices, criteria)
self.assertEqual(
{device.device_id for device in shown},
{"BF-12010193", "BF-12010194", "BF-12010195"},
)
def test_state_filter_accepts_word_machine_name_and_folded_input(self) -> None:
for raw in ("HAZIR", "ready", "hazir", "Hazır"):
with self.subTest(raw=raw):
criteria, warnings = fleet_data.parse_filter({"durum": [raw]})
self.assertEqual(criteria.state, "READY")
self.assertEqual(warnings, ())
shown = fleet_data.filter_devices(self.snapshot.devices, criteria)
self.assertTrue(shown)
self.assertEqual({device.state_label for device in shown}, {"HAZIR"})
def test_wave_filter_accepts_repo_group_names(self) -> None:
for raw, code in (("pilot_1", "P1"), ("W1", "W1"), ("production", "PROD")):
with self.subTest(raw=raw):
criteria, _ = fleet_data.parse_filter({"dalga": [raw]})
self.assertEqual(criteria.wave, code)
shown = fleet_data.filter_devices(self.snapshot.devices, criteria)
self.assertTrue(shown)
self.assertEqual({device.wave_code for device in shown}, {code})
def test_unknown_filter_values_are_ignored_with_a_turkish_warning(self) -> None:
criteria, warnings = fleet_data.parse_filter({"durum": ["MOR"], "dalga": ["W9"]})
self.assertEqual(criteria.state, "")
self.assertEqual(criteria.wave, "")
self.assertEqual(len(warnings), 2)
for warning in warnings:
self.assertIn("yok sayıldı", warning)
page = self.page("/?durum=MOR&dalga=W9")
self.assertIn("yok sayıldı", page)
self.assertIn("Uyarılar", page)
def test_only_not_ready_filter(self) -> None:
criteria, _ = fleet_data.parse_filter({"hazir_degil": ["1"]})
shown = fleet_data.filter_devices(self.snapshot.devices, criteria)
self.assertTrue(shown)
self.assertTrue(all(not device.is_ready for device in shown))
def test_counts_are_zero_filled_and_add_up(self) -> None:
counts = fleet_data.state_counts(self.snapshot.devices)
self.assertEqual(list(counts), list(STATE_WORDS))
self.assertEqual(sum(counts.values()), self.snapshot.total)
self.assertEqual(counts, self.snapshot.counts)
self.assertEqual(self.snapshot.ready_count + self.snapshot.not_ready_count, self.snapshot.total)
def test_filter_matches_the_rendered_count(self) -> None:
criteria, _ = fleet_data.parse_filter({"durum": ["YOK"]})
shown = fleet_data.filter_devices(self.snapshot.devices, criteria)
page = self.page("/?durum=YOK")
self.assertEqual(len(self.row_words(page)), len(shown))
self.assertTrue(all(word == "YOK" for word in self.row_words(page)))
# --------------------------------------------------------------------------
# the first viewport
# --------------------------------------------------------------------------
class TestFleetPage(PanelCase):
def test_served_as_utf8_turkish_html(self) -> None:
response = self.get("/")
self.assertEqual(response.status, 200)
self.assertEqual(response.content_type, "text/html; charset=utf-8")
markup = response.body.decode("utf-8")
self.assertIn('<html lang="tr"', markup)
self.assertIn('<meta charset="utf-8">', markup)
# Turkish characters must survive the byte round trip, not just the string.
self.assertIn("doğrulandı".encode("utf-8"), response.body)
self.assertEqual(response.content_length, len(response.body))
def test_first_viewport_regions_are_ordered(self) -> None:
page = self.page("/")
positions = [
page.index('data-region="header"'),
page.index('data-region="summary"'),
page.index('data-region="list"'),
page.index('data-region="detail"'),
]
self.assertEqual(positions, sorted(positions), "bölge sırası DESIGN.md'ye uymuyor")
def test_header_carries_product_target_and_privilege(self) -> None:
page = self.page("/")
header = page[: page.index('data-region="summary"')]
self.assertIn(web.PANEL_NAME, header)
self.assertIn(web.PANEL_SURFACE, header)
self.assertIn("Hedef", header)
self.assertIn(f"Tüm filo · {self.snapshot.total} cihaz", header)
self.assertIn("Yetki", header)
self.assertIn("Salt-okunur", header)
self.assertIn(self.read_only.reason, header)
def test_privilege_level_is_the_right_most_header_readout(self) -> None:
"""docs/DESIGN.md: 'privilege level on the right -- never a buried sentence'."""
page = self.page("/")
header = page[: page.index('data-region="summary"')]
privilege = header.index("app-header__privilege")
self.assertGreater(privilege, header.index("app-header__identity"))
self.assertGreater(privilege, header.index("app-header__target"))
self.assertGreater(privilege, header.index("app-header__actions"))
def test_primary_action_sits_in_the_header_not_below_the_fold(self) -> None:
page = self.page("/")
self.assertLess(
page.index("Filo durumunu yenile"),
page.index('data-region="summary"'),
"birincil eylem başlık bandında değil",
)
self.assertIn('class="button button--primary"', page)
def test_unprivileged_write_action_is_disabled_with_the_reason_on_the_control(self) -> None:
page = self.page("/")
self.assertIn('disabled aria-disabled="true" aria-describedby="gated-reason"', page)
self.assertIn('id="gated-reason"', page)
self.assertIn(self.read_only.reason, page)
def test_every_state_word_appears_in_the_summary_strip(self) -> None:
page = self.page("/")
summary = page[page.index('data-region="summary"') : page.index('data-region="list"')]
for word in STATE_WORDS:
with self.subTest(word=word):
self.assertIn(word, summary)
def test_summary_strip_has_exactly_one_large_figure(self) -> None:
page = self.page("/")
self.assertEqual(page.count("summary__lead-figure"), 1)
self.assertEqual(
page.count("summary__cell-value"), len(fleet_data.STATE_VOCABULARY)
)
counts = self.snapshot.counts
summary = page[page.index('data-region="summary"') : page.index('data-region="list"')]
self.assertIn(f'>{counts["HAZIR"]}<', summary)
def test_every_row_carries_a_state_word_in_monospace_columns(self) -> None:
page = self.page("/")
self.assertEqual(len(self.row_words(page)), self.snapshot.total)
for device in self.snapshot.devices:
with self.subTest(device=device.device_id):
self.assertIn(
f'<td class="mono"><a class="row-select" href="/?cihaz={device.device_id}">'
f"{device.device_id}</a></td>",
page,
)
self.assertIn(f'class="badge badge--state-{device.state_token_suffix}"', page)
self.assertIn(f'<td class="mono">{device.hostname}</td>', page)
def test_wave_column_names_the_wave_once_in_monospace(self) -> None:
"""The Dalga column is a data cell, and it names each wave exactly once."""
region = self.list_region(self.page("/"))
rows = re.findall(r'<tr class="fleet-row.*?</tr>', region, flags=re.S)
self.assertEqual(len(rows), self.snapshot.total)
by_id = {device.device_id: device for device in self.snapshot.devices}
for row in rows:
match = re.search(r">(BF-[0-9]{8})</a>", row)
if match is None:
self.fail(f"satırda cihaz kimliği yok: {row[:120]}")
device = by_id[match.group(1)]
cells = re.findall(r"<td[^>]*>(.*?)</td>", row, flags=re.S)
self.assertEqual(len(cells), 5, row[:200])
self.assertEqual(
cells[3], f'<span class="mono">{device.wave_label}</span>', row[:200]
)
def test_rows_are_sorted_non_ready_first(self) -> None:
words = self.row_words(self.page("/"))
ready_flags = [
fleet_data.STATE_BY_WORD[word].state == "READY" for word in words
]
self.assertEqual(ready_flags, sorted(ready_flags))
self.assertEqual(ready_flags[0], False)
self.assertEqual(ready_flags[-1], True)
def test_selected_row_uses_the_accent_soft_fill(self) -> None:
page = self.page("/")
self.assertEqual(page.count("fleet-row--selected"), 1)
selected_row = page[page.index("fleet-row--selected") :]
selected_row = selected_row[: selected_row.index("</tr>")]
detail = page[page.index('data-region="detail"') :]
device_id = re.search(r">(BF-[0-9]{8})</a>", selected_row).group(1)
self.assertIn(device_id, detail)
css = (STATIC_DIR / "app.css").read_text(encoding="utf-8")
self.assertIn(".fleet-row--selected td { background: var(--accent-soft); }", css)
def test_default_selection_is_the_first_row_of_the_list(self) -> None:
page = self.page("/")
first_row = self.list_region(page)
first_row = first_row[first_row.index("<tbody>") :]
device_id = re.search(r">(BF-[0-9]{8})</a>", first_row).group(1)
detail = page[page.index('data-region="detail"') :]
self.assertIn(device_id, detail)
self.assertIn("Hedef kimliği", detail)
def test_detail_region_shows_state_identity_wave_and_channels(self) -> None:
page = self.page("/?cihaz=BF-12010193")
detail = page[page.index('data-region="detail"') :]
device = next(d for d in self.snapshot.devices if d.device_id == "BF-12010193")
self.assertIn(device.state_label, detail)
self.assertIn(device.device_id, detail)
self.assertIn(device.hostname, detail)
self.assertIn(device.wave_code, detail)
self.assertIn("Kanal doğrulaması", detail)
for channel in fleet_data.CHANNELS:
with self.subTest(channel=channel.label):
self.assertIn(channel.label, detail)
self.assertIn("Doğrulanan kanal: 4/4", detail)
self.assertIn("doğrulandı", detail)
def test_channel_status_words_are_shown_in_turkish(self) -> None:
"""Every channel status word reaches the screen, not only a colour."""
cases = {
"BF-12010193": ("doğrulandı",), # all four verified
"BF-12010194": ("bekliyor",), # RustDesk pending
"BF-12010205": ("başarısız", "bilinmiyor"), # failed + unknown
"BF-12010195": ("yok",), # never checked
}
for device_id, words in cases.items():
with self.subTest(device=device_id):
page = self.page(f"/?cihaz={device_id}")
detail = page[page.index('data-region="detail"') :]
for word in words:
self.assertIn(f">{word}</span>", detail)
def test_ready_device_detail_confirms_the_gate(self) -> None:
page = self.page("/?cihaz=BF-12010193")
self.assertIn("dört kanalın dördü de doğrulandı", page)
self.assertNotIn("HAZIR için eksik kanallar", page)
def test_not_ready_device_detail_names_the_blocking_channels(self) -> None:
page = self.page("/?cihaz=BF-12010194")
self.assertIn("HAZIR için eksik kanallar: RustDesk", page)
self.assertIn("Doğrulanan kanal: 3/4", page)
def test_hostname_selects_the_same_device_as_device_id(self) -> None:
by_id = self.page("/?cihaz=BF-12010193")
by_host = self.page("/?cihaz=bf-12010193")
self.assertEqual(self.row_words(by_id), self.row_words(by_host))
self.assertIn("BF-12010193", by_host[by_host.index('data-region="detail"') :])
def test_unknown_device_id_is_reported_and_not_invented(self) -> None:
page = self.page("/?cihaz=BF-99999999")
detail = page[page.index('data-region="detail"') :]
self.assertIn("BF-99999999", detail)
self.assertIn("envanterde yok", detail.lower())
self.assertIn("uydurmaz", detail)
def test_filter_is_reflected_in_the_list_and_the_header(self) -> None:
page = self.page("/?durum=HAZIR")
self.assertEqual(set(self.row_words(page)), {"HAZIR"})
self.assertIn("Durum HAZIR", page)
self.assertIn(f"Gösterilen: <span class=\"mono\">{self.snapshot.ready_count}</span>", page)
def test_selection_outside_the_filter_is_flagged_not_hidden(self) -> None:
page = self.page("/?durum=HAZIR&cihaz=BF-12010205")
detail = page[page.index('data-region="detail"') :]
self.assertIn("BF-12010205", detail)
self.assertIn("geçerli filtreye uymuyor", detail)
def test_empty_result_gets_a_turkish_note_and_keeps_the_header(self) -> None:
page = self.page("/?durum=YOK&dalga=PROD")
self.assertEqual(self.row_words(page), [])
self.assertIn("Bu filtreye uyan cihaz yok", page)
self.assertIn("Filtreyi temizle", page)
self.assertIn('data-region="header"', page)
def test_wave_strip_shows_every_wave_against_its_target(self) -> None:
page = self.page("/")
counts = fleet_data.wave_counts(self.snapshot.devices)
for wave in fleet_data.WAVES:
with self.subTest(wave=wave.code):
self.assertIn(
f'<span class="mono">{wave.code}</span> {counts[wave.code]}/{wave.target}',
page,
)
def test_demo_data_is_labelled_on_the_page(self) -> None:
page = self.page("/")
self.assertIn("DEMO VERİ", page)
self.assertIn("sentetik", page)
self.assertIn("hiçbir cihazı temsil etmez", page)
self.assertIn("Veri kaynağı: sentetik demo veri", page)
def test_inventory_file_source_is_labelled_on_the_page(self) -> None:
document = {
"schema_version": 1,
"generated_at": NOW,
"devices": [
{
"dealer_id": "12010193",
"wave": "P1",
"state": "ENROLLED",
"channels": {"ssh_over_wg": "verified"},
}
],
}
snapshot = self.snapshot_from(document)
context = panel.PanelContext(
snapshot=snapshot, privilege=self.read_only, server_info=self.info
)
page = self.page("/", context=context)
self.assertIn("ENVANTER", page)
self.assertIn(snapshot.source_path, page)
self.assertNotIn("DEMO VERİ", page)
def test_error_source_says_veri_yok_and_never_shows_demo_data(self) -> None:
snapshot = fleet_data.load_fleet("/yok/boyle/bir/dosya.json", env={}, now=NOW)
context = panel.PanelContext(
snapshot=snapshot, privilege=self.read_only, server_info=self.info
)
page = self.page("/", context=context)
self.assertIn("VERİ YOK", page)
self.assertIn("Demo veriye düşülmedi", page)
self.assertNotIn("DEMO VERİ", page)
self.assertEqual(self.row_words(page), [])
self.assertIn("Envanterde gösterilecek cihaz yok", page)
def test_invalid_records_are_surfaced_with_their_reason(self) -> None:
document = {
"schema_version": 1,
"devices": [{"dealer_id": "1201019", "wave": "P1", "state": "READY"}],
}
snapshot = self.snapshot_from(document)
context = panel.PanelContext(
snapshot=snapshot, privilege=self.read_only, server_info=self.info
)
page = self.page("/", context=context)
self.assertIn("Geçersiz kayıtlar", page)
self.assertIn("8 hane", page)
def test_every_visible_string_is_non_empty(self) -> None:
for target in ("/", "/?durum=HAZIR", "/?cihaz=BF-99999999", "/hakkinda"):
with self.subTest(target=target):
self.assertEqual(empty_elements(self.page(target)), [])
def test_error_pages_keep_the_frame_and_stay_readable(self) -> None:
for target, status in (("/yok", 404),):
with self.subTest(target=target):
response = self.get(target)
self.assertEqual(response.status, status)
markup = response.body.decode("utf-8")
self.assertEqual(empty_elements(markup), [])
self.assertIn('data-region="header"', markup)
def test_no_placeholder_or_python_none_leaks_into_the_page(self) -> None:
for target in ("/", "/hakkinda", "/?cihaz=BF-12010193", "/?durum=MOR"):
with self.subTest(target=target):
page = self.page(target)
self.assertNotIn("$", page)
self.assertNotIn("None", page)
self.assertNotIn("KeyError", page)
def test_page_renders_without_a_live_server_context(self) -> None:
"""render_fleet_page is usable on its own (tests, --check, future CLI)."""
page = render.render_fleet_page(self.snapshot)
self.assertIn('<html lang="tr"', page)
self.assertIn("Salt-okunur", page)
# --------------------------------------------------------------------------
# about / durum
# --------------------------------------------------------------------------
class TestAboutPage(PanelCase):
def test_reports_version_privilege_and_python(self) -> None:
page = self.page("/hakkinda")
self.assertIn(web.PANEL_VERSION, page)
self.assertIn(render.PANEL_VERSION, page)
self.assertIn("Panel sürümü", page)
self.assertIn("127.0.0.1:8787", page)
self.assertIn("Salt-okunur", page)
self.assertIn(self.read_only.reason, page)
self.assertIn(self.info.python_version, page)
def test_reports_the_data_source_and_its_limits(self) -> None:
page = self.page("/hakkinda")
self.assertIn("Veri kaynağı", page)
self.assertIn("sentetik demo veri", page)
self.assertIn("kullanılmadı (demo veri)", page)
self.assertIn(f"{self.snapshot.ready_count} hazır", page)
self.assertIn("Okuma hatası", page)
self.assertIn("yok", page)
def test_lists_the_vocabulary_with_tokens_and_meanings(self) -> None:
page = self.page("/hakkinda")
for spec in fleet_data.STATE_VOCABULARY:
with self.subTest(state=spec.state):
self.assertIn(spec.word, page)
self.assertIn(spec.token, page)
self.assertIn(spec.meaning, page)
def test_lists_every_wave_and_channel(self) -> None:
page = self.page("/hakkinda")
for wave in fleet_data.WAVES:
with self.subTest(wave=wave.code):
self.assertIn(wave.code, page)
for channel in fleet_data.CHANNELS:
with self.subTest(channel=channel.key):
self.assertIn(channel.key, page)
self.assertIn(channel.label, page)
def test_states_the_panel_rules(self) -> None:
page = self.page("/hakkinda")
self.assertGreaterEqual(len(render.PANEL_RULES), 5)
for rule in render.PANEL_RULES:
with self.subTest(rule=rule[:30]):
self.assertTrue(rule.strip())
self.assertIn(rule[:24], page)
self.assertIn("Panel kuralları", page)
def test_full_privilege_level_is_distinct_and_honest(self) -> None:
page = self.page("/", context=self.full_context)
self.assertIn("badge--privilege-full", page)
self.assertIn("Tam yetki", page)
self.assertIn("yazma uç noktası uygulanmadı", page)
self.assertNotIn("badge--privilege-read-only", page)
def test_about_page_has_no_external_url(self) -> None:
page = self.page("/hakkinda")
self.assertNotIn("http://", page)
self.assertNotIn("https://", page)
# --------------------------------------------------------------------------
# routing and the privilege gate
# --------------------------------------------------------------------------
class TestRoutingAndGate(PanelCase):
def test_only_read_methods_are_served(self) -> None:
for method in ("POST", "PUT", "PATCH", "DELETE", "OPTIONS", "TRACE"):
with self.subTest(method=method):
response = self.get("/", method)
self.assertEqual(response.status, 405)
markup = response.body.decode("utf-8")
self.assertIn("Yöntem reddedildi", markup)
self.assertIn(method, markup)
self.assertIn("yalnız okuma yapar", markup)
headers = dict(response.headers)
self.assertEqual(headers.get("Allow"), "GET, HEAD")
def test_write_methods_are_refused_even_with_full_privilege(self) -> None:
response = self.get("/", "POST", self.full_context)
self.assertEqual(response.status, 405)
def test_head_returns_the_same_length_without_a_body(self) -> None:
head = self.get("/", "HEAD")
get = self.get("/", "GET")
self.assertEqual(head.status, 200)
self.assertEqual(head.body, b"")
self.assertEqual(head.content_length, get.content_length)
self.assertEqual(head.content_type, get.content_type)
def test_static_assets_are_served_from_disk_byte_for_byte(self) -> None:
for name in ("tokens.css", "app.css"):
with self.subTest(name=name):
response = self.get(f"/static/{name}")
self.assertEqual(response.status, 200)
self.assertEqual(response.content_type, "text/css; charset=utf-8")
self.assertEqual(response.body, (STATIC_DIR / name).read_bytes())
def test_unknown_static_asset_is_a_404(self) -> None:
response = self.get("/static/gizli.txt")
self.assertEqual(response.status, 404)
self.assertIn("Sayfa yok", response.body.decode("utf-8"))
def test_path_traversal_is_refused(self) -> None:
for target in (
"/static/../server.py",
"/static/..%2fserver.py",
"/static/%2e%2e/fleet_data.py",
"/../web/server.py",
"/static/.%2e/%2e%2e/etc/passwd",
):
with self.subTest(target=target):
response = self.get(target)
self.assertEqual(response.status, 404)
body = response.body.decode("utf-8")
self.assertNotIn("BaseHTTPRequestHandler", body)
self.assertNotIn("route(context", body)
def test_unknown_path_is_a_turkish_404(self) -> None:
response = self.get("/boyle-bir-sayfa-yok")
self.assertEqual(response.status, 404)
markup = response.body.decode("utf-8")
self.assertIn("Sayfa yok", markup)
self.assertIn("/boyle-bir-sayfa-yok", markup)
def test_security_headers_keep_the_panel_local_and_script_free(self) -> None:
headers = dict(self.get("/").headers)
self.assertEqual(headers["X-Content-Type-Options"], "nosniff")
self.assertEqual(headers["Referrer-Policy"], "no-referrer")
csp = headers["Content-Security-Policy"]
self.assertIn("default-src 'self'", csp)
self.assertIn("script-src 'none'", csp)
self.assertIn("form-action 'self'", csp)
self.assertNotIn("http", csp)
self.assertEqual(headers["Cache-Control"], "no-store")
def test_route_is_pure_and_repeatable(self) -> None:
targets = ("/", "/hakkinda", "/static/app.css", "/?durum=HAZIR&q=1201", "/yok", "/static/../x")
for target in targets:
with self.subTest(target=target):
first = self.get(target)
second = self.get(target)
self.assertEqual(first, second)
def test_bogus_query_values_do_not_crash_the_page(self) -> None:
for target in (
"/?q=&durum=&dalga=&cihaz=&hazir_degil=abc",
"/?durum=READY&durum=YOK",
"/?q=%C4%B0stanbul%20%2F%20test",
"/?cihaz=" + "x" * 500,
):
with self.subTest(target=target[:40]):
response = self.get(target)
self.assertEqual(response.status, 200)
self.assertEqual(empty_elements(response.body.decode("utf-8")), [])
def test_a_full_sweep_of_every_endpoint_writes_nothing(self) -> None:
"""Read-only is a property of the surface, not a promise in a docstring."""
before = tree_fingerprint(WEB_DIR)
requests = (
("GET", "/"),
("GET", "/filo"),
("GET", "/fleet"),
("GET", "/hakkinda"),
("GET", "/hakkinda/"),
("GET", "/about"),
("GET", "/static/tokens.css"),
("GET", "/static/app.css"),
("GET", "/?q=1201&durum=HAZIR&dalga=P1&hazir_degil=1&cihaz=BF-12010193"),
("GET", "/yok"),
("POST", "/"),
("PUT", "/hakkinda"),
("DELETE", "/static/app.css"),
("HEAD", "/"),
)
for method, target in requests:
with self.subTest(method=method, target=target):
response = self.get(target, method)
self.assertIn(response.status, (200, 404, 405))
self.assertEqual(tree_fingerprint(WEB_DIR), before)
if __name__ == "__main__":
unittest.main(verbosity=2)