From 5acf94b6c2e1ff889a30f7074c670d68182c4cb9 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Sun, 22 Feb 2026 23:18:10 -0500 Subject: [PATCH] fix: update tests to match action node schema (next_node_id, not children) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update _make_valid_tree() in test_ai_tree_validator to use next_node_id on action nodes (solution is a sibling, not a child) - Fix test_dead_end_action_node → test_dead_end_decision_node (action nodes don't have child-based dead ends; dead ends are decision nodes with no children) - Add test_action_missing_next_node_id for the new validation rule - Update BRANCH_DETAIL_JSON in test_ai_endpoints to use next_node_id pattern - Update test_draft_trees.py to use "title" field for action/solution nodes (tree_validation.py was updated this branch to require "title" not "action"/"solution") Co-Authored-By: Claude Sonnet 4.6 --- backend/tests/test_ai_endpoints.py | 34 ++++++++++----------- backend/tests/test_ai_tree_validator.py | 39 +++++++++++++++---------- backend/tests/test_draft_trees.py | 38 ++++++++++++------------ 3 files changed, 59 insertions(+), 52 deletions(-) diff --git a/backend/tests/test_ai_endpoints.py b/backend/tests/test_ai_endpoints.py index 3eb7b596..339448dd 100644 --- a/backend/tests/test_ai_endpoints.py +++ b/backend/tests/test_ai_endpoints.py @@ -36,15 +36,14 @@ BRANCH_DETAIL_JSON = json.dumps({ "title": "Check Event Logs", "description": "Check Windows Event Viewer for errors.", "commands": ["Get-EventLog -LogName Application -Newest 20"], - "children": [ - { - "id": "svc-logs-resolved", - "type": "solution", - "title": "Issue Found in Logs", - "description": "Error identified and resolved.", - "resolution_steps": ["Fix the error", "Restart service"], - } - ], + "next_node_id": "svc-logs-resolved", + }, + { + "id": "svc-logs-resolved", + "type": "solution", + "title": "Issue Found in Logs", + "description": "Error identified and resolved.", + "resolution_steps": ["Fix the error", "Restart service"], }, { "id": "svc-restart", @@ -52,15 +51,14 @@ BRANCH_DETAIL_JSON = json.dumps({ "title": "Restart Service", "description": "Attempt to restart the service.", "commands": ["Restart-Service -Name 'TestService'"], - "children": [ - { - "id": "svc-restart-ok", - "type": "solution", - "title": "Service Restored", - "description": "Service is running after restart.", - "resolution_steps": ["Verify connectivity", "Document in ticket"], - } - ], + "next_node_id": "svc-restart-ok", + }, + { + "id": "svc-restart-ok", + "type": "solution", + "title": "Service Restored", + "description": "Service is running after restart.", + "resolution_steps": ["Verify connectivity", "Document in ticket"], }, ], }) diff --git a/backend/tests/test_ai_tree_validator.py b/backend/tests/test_ai_tree_validator.py index 6e95545d..1a9d7186 100644 --- a/backend/tests/test_ai_tree_validator.py +++ b/backend/tests/test_ai_tree_validator.py @@ -5,7 +5,11 @@ from app.core.ai_tree_validator import validate_generated_tree, count_tree_stats def _make_valid_tree(): - """Helper: minimal valid tree for testing.""" + """Helper: minimal valid tree for testing. + + Action nodes use next_node_id to point to a sibling (not children). + The solution following an action is a sibling under the parent decision. + """ return { "id": "root", "type": "decision", @@ -44,14 +48,13 @@ def _make_valid_tree(): "title": "Restart the Service", "description": "Restart the service and verify.", "commands": ["Restart-Service -Name 'TestService'"], - "children": [ - { - "id": "service-resolved", - "type": "solution", - "title": "Service Restored", - "description": "Service is running after restart.", - }, - ], + "next_node_id": "service-resolved", + }, + { + "id": "service-resolved", + "type": "solution", + "title": "Service Restored", + "description": "Service is running after restart.", }, ], } @@ -107,6 +110,12 @@ class TestNodeValidation: errors = validate_generated_tree(tree) assert any("at least 2 options" in e for e in errors) + def test_action_missing_next_node_id(self): + tree = _make_valid_tree() + del tree["children"][1]["next_node_id"] + errors = validate_generated_tree(tree) + assert any("missing 'next_node_id'" in e for e in errors) + class TestReferenceIntegrity: def test_option_references_nonexistent_child(self): @@ -156,18 +165,18 @@ class TestGlobalChecks: {"id": "o1", "label": "A", "next_node_id": "only-solution"}, {"id": "o2", "label": "B", "next_node_id": "only-solution"}, ] - # Now restart-service branch has 1 solution, check-logs has 1 = total 2 - # Remove one more to get to 1 - tree["children"][1]["children"] = [] + # Remove the solution that restart-service points to + tree["children"].pop(2) # remove service-resolved errors = validate_generated_tree(tree) assert any("solution" in e.lower() for e in errors) class TestDeadEndDetection: - def test_dead_end_action_node(self): + def test_dead_end_decision_node(self): + """A decision node with no children is a dead end.""" tree = _make_valid_tree() - # Remove restart-service's children — becomes dead end - tree["children"][1]["children"] = [] + # Remove children from check-logs decision node — becomes dead end + tree["children"][0]["children"] = [] errors = validate_generated_tree(tree) assert any("dead end" in e for e in errors) diff --git a/backend/tests/test_draft_trees.py b/backend/tests/test_draft_trees.py index 3ab4c7b2..97aae49a 100644 --- a/backend/tests/test_draft_trees.py +++ b/backend/tests/test_draft_trees.py @@ -20,13 +20,13 @@ class TestTreeValidation: { "id": "yes", "type": "solution", - "solution": "Server is healthy", + "title": "Server is healthy", "children": [] }, { "id": "no", "type": "action", - "action": "Restart the server", + "title": "Restart the server", "children": [] } ] @@ -70,15 +70,15 @@ class TestTreeValidation: "type": "decision", "question": "Test?", "children": [ - {"id": "child1", "type": "solution", "solution": "Fix"} + {"id": "child1", "type": "solution", "title": "Fix"} ] } is_valid, errors = validate_tree_structure(tree_structure) assert not is_valid assert any("at least 2" in error["message"] for error in errors) - def test_action_node_missing_action(self): - """Test validation when action node has no action.""" + def test_action_node_missing_title(self): + """Test validation when action node has no title.""" tree_structure = { "id": "root", "type": "action", @@ -86,10 +86,10 @@ class TestTreeValidation: } is_valid, errors = validate_tree_structure(tree_structure) assert not is_valid - assert any("action" in error["field"] for error in errors) + assert any("title" in error["field"] for error in errors) - def test_solution_node_missing_solution(self): - """Test validation when solution node has no solution.""" + def test_solution_node_missing_title(self): + """Test validation when solution node has no title.""" tree_structure = { "id": "root", "type": "solution", @@ -97,7 +97,7 @@ class TestTreeValidation: } is_valid, errors = validate_tree_structure(tree_structure) assert not is_valid - assert any("solution" in error["field"] for error in errors) + assert any("title" in error["field"] for error in errors) def test_unknown_node_type(self): """Test validation with unknown node type.""" @@ -112,14 +112,14 @@ class TestTreeValidation: def test_can_publish_with_empty_name(self): """Test can_publish with empty name.""" - tree_structure = {"id": "root", "type": "solution", "solution": "Fix"} + tree_structure = {"id": "root", "type": "solution", "title": "Fix"} can_publish, errors = can_publish_tree(tree_structure, "", None) assert not can_publish assert any("name" in error["field"] for error in errors) def test_can_publish_valid_tree(self): """Test can_publish with valid tree and name.""" - tree_structure = {"id": "root", "type": "solution", "solution": "Fix"} + tree_structure = {"id": "root", "type": "solution", "title": "Fix"} can_publish, errors = can_publish_tree(tree_structure, "Valid Tree", "Description") assert can_publish assert len(errors) == 0 @@ -157,8 +157,8 @@ class TestDraftTreesAPI: "type": "decision", "question": "Is it working?", "children": [ - {"id": "yes", "type": "solution", "solution": "Great!"}, - {"id": "no", "type": "action", "action": "Fix it"} + {"id": "yes", "type": "solution", "title": "Great!"}, + {"id": "no", "type": "action", "title": "Fix it"} ] }, "status": "published" @@ -193,8 +193,8 @@ class TestDraftTreesAPI: name="Draft to Published", description="Test tree", tree_structure={"id": "root", "type": "decision", "question": "Test?", "children": [ - {"id": "yes", "type": "solution", "solution": "Yes"}, - {"id": "no", "type": "solution", "solution": "No"} + {"id": "yes", "type": "solution", "title": "Yes"}, + {"id": "no", "type": "solution", "title": "No"} ]}, author_id=UUID(test_user["user_data"]["id"]), account_id=UUID(test_user["user_data"]["account_id"]), @@ -252,8 +252,8 @@ class TestDraftTreesAPI: "type": "decision", "question": "Is it working?", "children": [ - {"id": "yes", "type": "solution", "solution": "Great!"}, - {"id": "no", "type": "action", "action": "Fix it"} + {"id": "yes", "type": "solution", "title": "Great!"}, + {"id": "no", "type": "action", "title": "Fix it"} ] }, author_id=UUID(test_user["user_data"]["id"]), @@ -315,7 +315,7 @@ class TestDraftTreesAPI: tree = Tree( name="Test Tree", description="Test", - tree_structure={"id": "root", "type": "solution", "solution": "Fix"}, + tree_structure={"id": "root", "type": "solution", "title": "Fix"}, author_id=UUID(test_user["user_data"]["id"]), account_id=UUID(test_user["user_data"]["account_id"]), status='published' @@ -337,7 +337,7 @@ class TestDraftTreesAPI: tree = Tree( name="Legacy Tree", description="Created before status field", - tree_structure={"id": "root", "type": "solution", "solution": "Fix"}, + tree_structure={"id": "root", "type": "solution", "title": "Fix"}, author_id=None, account_id=None )