Test dashboard: full menu system, all-service tests, filtering
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m11s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m11s
- Run tests for all 10 services via per-service pytest subprocesses - Group results by service with section headers - Clickable summary cards filter by outcome (passed/failed/errors/skipped) - Service filter nav using ~nav-link buttons in menu bar - Full menu integration: ~header-row + ~header-child + ~menu-row - Show logo image via cart-mini rendering - Mount full service directories in docker-compose for test access - Add 24 unit test files across 9 services Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
83
blog/tests/test_lexical_validator.py
Normal file
83
blog/tests/test_lexical_validator.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Unit tests for lexical document validator."""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from blog.bp.blog.ghost.lexical_validator import (
|
||||
validate_lexical, ALLOWED_NODE_TYPES,
|
||||
)
|
||||
|
||||
|
||||
class TestValidateLexical:
|
||||
def test_valid_empty_doc(self):
|
||||
ok, reason = validate_lexical({"root": {"type": "root", "children": []}})
|
||||
assert ok is True
|
||||
assert reason is None
|
||||
|
||||
def test_non_dict_input(self):
|
||||
ok, reason = validate_lexical("not a dict")
|
||||
assert ok is False
|
||||
assert "JSON object" in reason
|
||||
|
||||
def test_list_input(self):
|
||||
ok, reason = validate_lexical([])
|
||||
assert ok is False
|
||||
|
||||
def test_missing_root(self):
|
||||
ok, reason = validate_lexical({"foo": "bar"})
|
||||
assert ok is False
|
||||
assert "'root'" in reason
|
||||
|
||||
def test_root_not_dict(self):
|
||||
ok, reason = validate_lexical({"root": "string"})
|
||||
assert ok is False
|
||||
|
||||
def test_valid_paragraph(self):
|
||||
doc = {"root": {"type": "root", "children": [
|
||||
{"type": "paragraph", "children": [
|
||||
{"type": "text", "text": "hello"}
|
||||
]}
|
||||
]}}
|
||||
ok, _ = validate_lexical(doc)
|
||||
assert ok is True
|
||||
|
||||
def test_disallowed_type(self):
|
||||
doc = {"root": {"type": "root", "children": [
|
||||
{"type": "script"}
|
||||
]}}
|
||||
ok, reason = validate_lexical(doc)
|
||||
assert ok is False
|
||||
assert "Disallowed node type: script" in reason
|
||||
|
||||
def test_nested_disallowed_type(self):
|
||||
doc = {"root": {"type": "root", "children": [
|
||||
{"type": "paragraph", "children": [
|
||||
{"type": "list", "children": [
|
||||
{"type": "evil-widget"}
|
||||
]}
|
||||
]}
|
||||
]}}
|
||||
ok, reason = validate_lexical(doc)
|
||||
assert ok is False
|
||||
assert "evil-widget" in reason
|
||||
|
||||
def test_node_without_type_allowed(self):
|
||||
"""Nodes with type=None are allowed by _walk."""
|
||||
doc = {"root": {"type": "root", "children": [
|
||||
{"children": []} # no "type" key
|
||||
]}}
|
||||
ok, _ = validate_lexical(doc)
|
||||
assert ok is True
|
||||
|
||||
def test_all_allowed_types(self):
|
||||
"""Every type in the allowlist should pass."""
|
||||
for node_type in ALLOWED_NODE_TYPES:
|
||||
doc = {"root": {"type": "root", "children": [
|
||||
{"type": node_type, "children": []}
|
||||
]}}
|
||||
ok, reason = validate_lexical(doc)
|
||||
assert ok is True, f"{node_type} should be allowed but got: {reason}"
|
||||
|
||||
def test_allowed_types_count(self):
|
||||
"""Sanity: at least 30 types in the allowlist."""
|
||||
assert len(ALLOWED_NODE_TYPES) >= 30
|
||||
Reference in New Issue
Block a user