Files
resolutionflow/backend/tests/test_tree_validation.py
chihlasm 8475f780d1 fix: update remaining tests and session_to_tree for title field rename
- test_tree_validation.py: replace "action"/"solution" content fields with "title"
- test_procedural_flows.py: update solution node fixtures to use "title"
- test_save_session_as_tree.py: update fixtures and assertions for "title" field
- session_to_tree.py: generate "title" instead of "action"/"solution" on converted nodes;
  fall back to legacy field names when reading from old tree snapshots for compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 23:55:37 -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", "title": "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", "title": "Great"},
{"id": "no", "type": "action", "title": "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", "title": "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", "title": "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_title_field(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "action"
})
assert not valid
assert any("title" in e["message"].lower() for e in errors)
def test_action_with_empty_title(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "action", "title": ""
})
assert not valid
def test_solution_missing_title_field(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "solution"
})
assert not valid
assert any("title" in e["message"].lower() for e in errors)
def test_solution_with_empty_title(self):
valid, errors = validate_tree_structure({
"id": "root", "type": "solution", "title": ""
})
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", "title": "S1"},
{"id": "c2", "type": "solution", "title": "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", "title": "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", "title": "Deep"},
{"id": "l3b", "type": "solution"}, # Missing title
],
},
{"id": "l2b", "type": "solution", "title": "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 title
{"id": "c2", "type": "action"}, # missing title
],
})
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", "title": "Done"},
"My Tree"
)
assert can
assert errors == []
def test_empty_name_cannot_publish(self):
can, errors = can_publish_tree(
{"id": "root", "type": "solution", "title": "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", "title": "Done"},
" "
)
assert not can
def test_none_name_cannot_publish(self):
can, errors = can_publish_tree(
{"id": "root", "type": "solution", "title": "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)