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>
84 lines
2.6 KiB
Python
84 lines
2.6 KiB
Python
"""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
|