Files
resolutionflow/backend/tests/test_tree_validation.py
chihlasm 064bc0aa48 test: add 113 unit tests for permissions, tree validation, and settings
Cover all permission functions (59 tests), tree validation logic (25 tests),
settings manager parse/infer helpers (21 tests), and Stripe webhook stubs (8 tests).
Key modules now at 100% coverage: permissions.py, tree_validation.py, stripe_handlers.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 17:58:48 -05:00

228 lines
7.2 KiB
Python

"""Unit tests for tree validation logic.
Tests validate_tree_structure, can_publish_tree, and edge cases.
No database required.
"""
from app.core.tree_validation import (
TreeValidationError,
can_publish_tree,
validate_tree_structure,
)
class TestValidateTreeStructure:
def test_valid_solution_tree(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "solution", "solution": "Done"
})
assert valid
assert errors == []
def test_valid_decision_tree_with_branches(self):
valid, errors = validate_tree_structure({
"id": "root",
"type": "decision",
"question": "Is it on?",
"children": [
{"id": "yes", "type": "solution", "solution": "Great"},
{"id": "no", "type": "action", "action": "Turn it on"},
],
})
assert valid
assert errors == []
def test_empty_structure_fails(self):
valid, errors = validate_tree_structure({})
assert not valid
assert any("empty" in e["message"].lower() for e in errors)
def test_missing_id_on_root(self):
valid, errors = validate_tree_structure({"type": "solution", "solution": "X"})
assert not valid
assert any("id" in e["field"] for e in errors)
def test_missing_type_on_root(self):
valid, errors = validate_tree_structure({"id": "root"})
assert not valid
assert any("type" in e["field"] for e in errors)
def test_decision_missing_question(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "decision"
})
assert not valid
assert any("question" in e["message"].lower() for e in errors)
def test_decision_with_empty_question(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "decision", "question": ""
})
assert not valid
def test_decision_with_one_child_fails(self):
valid, errors = validate_tree_structure({
"id": "root",
"type": "decision",
"question": "Q?",
"children": [
{"id": "only", "type": "solution", "solution": "S"},
],
})
assert not valid
assert any("2 branches" in e["message"] for e in errors)
def test_decision_with_zero_children_passes(self):
"""Decision with no children is valid (leaf decision)."""
valid, errors = validate_tree_structure({
"id": "root", "type": "decision", "question": "Q?"
})
assert valid
def test_action_missing_action_field(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "action"
})
assert not valid
assert any("action" in e["message"].lower() for e in errors)
def test_action_with_empty_action(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "action", "action": ""
})
assert not valid
def test_solution_missing_solution_field(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "solution"
})
assert not valid
assert any("solution" in e["message"].lower() for e in errors)
def test_solution_with_empty_solution(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "solution", "solution": ""
})
assert not valid
def test_unknown_node_type(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "banana"
})
assert not valid
assert any("unknown" in e["message"].lower() for e in errors)
def test_child_missing_id(self):
valid, errors = validate_tree_structure({
"id": "root",
"type": "decision",
"question": "Q?",
"children": [
{"type": "solution", "solution": "S1"},
{"id": "c2", "type": "solution", "solution": "S2"},
],
})
assert not valid
assert any("id" in e["field"] for e in errors)
def test_child_missing_type(self):
valid, errors = validate_tree_structure({
"id": "root",
"type": "decision",
"question": "Q?",
"children": [
{"id": "c1"},
{"id": "c2", "type": "solution", "solution": "S2"},
],
})
assert not valid
assert any("type" in e["field"] for e in errors)
def test_deeply_nested_validation(self):
"""Validates 3 levels deep."""
valid, errors = validate_tree_structure({
"id": "root",
"type": "decision",
"question": "Level 1?",
"children": [
{
"id": "l2a",
"type": "decision",
"question": "Level 2?",
"children": [
{"id": "l3a", "type": "solution", "solution": "Deep"},
{"id": "l3b", "type": "solution"}, # Missing solution
],
},
{"id": "l2b", "type": "solution", "solution": "Shallow"},
],
})
assert not valid
assert any("l3b" in str(e) or "children[1]" in e["field"] for e in errors)
def test_multiple_errors_collected(self):
valid, errors = validate_tree_structure({
"id": "root",
"type": "decision",
"question": "Q?",
"children": [
{"id": "c1", "type": "solution"}, # missing solution
{"id": "c2", "type": "action"}, # missing action
],
})
assert not valid
assert len(errors) >= 2
class TestCanPublishTree:
def test_valid_tree_can_publish(self):
can, errors = can_publish_tree(
{"id": "root", "type": "solution", "solution": "Done"},
"My Tree"
)
assert can
assert errors == []
def test_empty_name_cannot_publish(self):
can, errors = can_publish_tree(
{"id": "root", "type": "solution", "solution": "Done"},
""
)
assert not can
assert any("name" in e["field"] for e in errors)
def test_whitespace_name_cannot_publish(self):
can, errors = can_publish_tree(
{"id": "root", "type": "solution", "solution": "Done"},
" "
)
assert not can
def test_none_name_cannot_publish(self):
can, errors = can_publish_tree(
{"id": "root", "type": "solution", "solution": "Done"},
None
)
assert not can
def test_invalid_structure_cannot_publish(self):
can, errors = can_publish_tree({}, "My Tree")
assert not can
assert len(errors) >= 1
def test_both_name_and_structure_errors(self):
can, errors = can_publish_tree({}, "")
assert not can
assert len(errors) >= 2 # name error + structure error
class TestTreeValidationError:
def test_exception_attributes(self):
err = TreeValidationError("field.name", "is required")
assert err.field == "field.name"
assert err.message == "is required"
assert "field.name" in str(err)