# Problem Description
This is a graph path validation problem where we need to determine if a given sequence of node values represents a valid path through the undirected graph. The graph contains specially marked "start" "end" and "x" nodes, and the path must follow specific rules to be considered valid.

# Visual Facts
1. The image shows two example graphs:
   - Example 1: A simple graph with 3 nodes (1=START, 3=END)
   - Example 2: A larger graph with 6 nodes (2=START, 6=END)

2. Each graph contains:
   - A green "START" node
   - A red "END" node
   - Intermediate nodes (unmarked/white)
   - Forbidden nodes (marked with a 'x' symbol)
   - Undirected edges connecting nodes

3. Multiple example paths are provided for each graph:
   - Example 1 shows 4 paths (1 valid, 3 invalid)
   - Example 2 shows 6 paths (3 valid, 3 invalid)

4. Each path is marked as either "valid" or "invalid" with a reason for invalidity

# Visual Patterns
1. Path Validity Rules:
   - Must start from the START node (green)
   - Must end at the END node (red)
   - Can only move along connected edges
   - Must visit all the nodes in the graph except "x" nodes.

2. Invalid Path Patterns:
   - Paths not starting from START node are invalid
   - Paths not ending at END node are invalid
   - Paths visited forbidden nodes with symbol 'x'
   - Paths missing any node (except for forbidden node) in the graph are invalid
   - Paths must follow the graph's edge connections

3. Valid Path Patterns:
   - Multiple valid paths can exist between START and END
   - The same node can be visited multiple times in a valid path

# Solution
from typing import List

class GraphNode:
    """
    Represents a node in a graph. Each node contains an integer value, a list of connected nodes, and a type attribute.

    Attributes:
        value (int): The integer value of the node.
        type (str): The type of the node ('start', 'end', 'x', or '').
        connected (list): A list of connected nodes (GraphNode objects).
    """
    def __init__(self, value: int, type: str = '', connected: list = None):
        self.value = value
        self.type = type
        self.connected = connected if connected is not None else []

def solution(start: GraphNode, path: List[int]) -> bool:
    """
    Determines whether a given path through the graph is valid according to the specified rules.

    Args:
        start: The starting node of the path, represented as a GraphNode object
        path: A sequence of node values representing the traversal path through the graph. path[0] = start.value

    Returns:
        True if the path is valid according to the rules, False otherwise.
    """
    if not start or not path:
        return False

    # Create a mapping from node value to GraphNode for quick lookup
    node_map = {}
    stack = [start]
    while stack:
        node = stack.pop()
        if node.value in node_map:
            continue
        node_map[node.value] = node
        for connected_node in node.connected:
            stack.append(connected_node)

    # Check if all nodes in the path are in the graph and not of type 'x'
    for node_value in path:
        if node_value not in node_map:
            return False
        if node_map[node_value].type == 'x':
            return False

    # Check if the path starts with 'start' and ends with 'end'
    if node_map[path[0]].type != 'start' or node_map[path[-1]].type != 'end':
        return False

    # Check if each step in the path is valid (i.e., nodes are connected)
    for i in range(len(path) - 1):
        current_node = node_map[path[i]]
        next_node = node_map[path[i + 1]]
        if next_node not in current_node.connected:
            return False

    return True


# Test Script
from typing import List
class GraphNode:
    """
    Represents a node in a graph. Each node contains an integer value, a list of connected nodes, and a type attribute.
    
    Attributes:
        value (int): The integer value of the node.
        type (str): The type of the node, can be 'start', 'end', or ''.
        connected (list): A list of connected nodes (GraphNode objects).
    """
    def __init__(self, value: int, type: str = '', connected: list = None):
        self.value = value
        self.type = type
        self.connected = connected if connected is not None else []

def print_graph(node: GraphNode, visited=None) -> str:
    if visited is None:
        visited = set()
    if node in visited:
        return ""
    visited.add(node)
    
    result = f"Node(value={node.value}, type='{node.type}')"
    for conn in node.connected:
        if conn not in visited:
            result += f"\n  -> {print_graph(conn, visited)}"
    return result

def test():
    # Test case 1
    g1 = GraphNode(1, "start")
    g2 = GraphNode(2, "end") 
    g1.connected = [g2]
    g2.connected = [g1]

    try:
        output = solution(g1,[1,2])
    except Exception as e:
        error_message = (
            f"An exception occurred while running test case 1:\n"
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2]}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message

    if output != True:
        error_message = (
            f"Test case 1 failed:\n"
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2]}\n\n"
            f"Expected Output: {True}\n"
            f"Actual Output: {output}"
        )
        assert False, error_message

    # Test case 2
    g1 = GraphNode(1, "start")
    g2 = GraphNode(2, "end")
    g1.connected = [g2]
    g2.connected = [g1]

    try:
        output = solution(g1,[1,2,1])
    except Exception as e:
        error_message = (
            f"An exception occurred while running test case 2:\n"
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2,1]}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message

    if output != False:
        error_message = (
            f"Test case 2 failed:\n" 
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2,1]}\n\n"
            f"Expected Output: {False}\n"
            f"Actual Output: {output}"
        )
        assert False, error_message

    # Test case 3
    g1 = GraphNode(1, "start")
    g2 = GraphNode(2, "x")
    g3 = GraphNode(3, "end")
    g1.connected = [g2]
    g2.connected = [g1, g3]
    g3.connected = [g2]

    try:
        output = solution(g1,[1,2,3])
    except Exception as e:
        error_message = (
            f"An exception occurred while running test case 3:\n"
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2,3]}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message

    if output != False:
        error_message = (
            f"Test case 3 failed:\n"
            f"`start`: \n{print_graph(g1)}\n" 
            f"`path`: {[1,2,3]}\n\n"
            f"Expected Output: {False}\n"
            f"Actual Output: {output}"
        )
        assert False, error_message

    # Test case 4
    g1 = GraphNode(1, "start")
    g2 = GraphNode(2, "")
    g3 = GraphNode(3, "end")
    g4 = GraphNode(4, "x")
    g1.connected = [g2]
    g2.connected = [g1, g3]
    g3.connected = [g2, g4]
    g4.connected = [g3]

    try:
        output = solution(g1,[1,2,3])
    except Exception as e:
        error_message = (
            f"An exception occurred while running test case 4:\n"
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2,3]}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message

    if output != True:
        error_message = (
            f"Test case 4 failed:\n"
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2,3]}\n\n" 
            f"Expected Output: {True}\n"
            f"Actual Output: {output}"
        )
        assert False, error_message

    # Test case 5
    g1 = GraphNode(1, "")
    g2 = GraphNode(2, "start")
    g3 = GraphNode(3, "end")
    g1.connected = [g2]
    g2.connected = [g1, g3]
    g3.connected = [g2]

    try:
        output = solution(g2,[2,1,2,3])
    except Exception as e:
        error_message = (
            f"An exception occurred while running test case 5:\n"
            f"`start`: \n{print_graph(g2)}\n"
            f"`path`: {[2,1,2,3]}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message

    if output != True:
        error_message = (
            f"Test case 5 failed:\n"
            f"`start`: \n{print_graph(g2)}\n"
            f"`path`: {[2,1,2,3]}\n\n"
            f"Expected Output: {True}\n"
            f"Actual Output: {output}"
        )
        assert False, error_message

    # Test case 6
    g1 = GraphNode(1, "start")
    g2 = GraphNode(2, "")
    g3 = GraphNode(3, "end") 
    g1.connected = [g2, g3]
    g2.connected = [g1]
    g3.connected = [g1]

    try:
        output = solution(g1,[1,2,1,3])
    except Exception as e:
        error_message = (
            f"An exception occurred while running test case 6:\n"
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2,1,3]}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message

    if output != True:
        error_message = (
            f"Test case 6 failed:\n"
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2,1,3]}\n\n"
            f"Expected Output: {True}\n"
            f"Actual Output: {output}"
        )
        assert False, error_message

    # Test case 7
    g1 = GraphNode(1, "start")
    g2 = GraphNode(2, "")
    g3 = GraphNode(3, "end")
    g1.connected = [g2, g3]
    g2.connected = [g1]
    g3.connected = [g1]

    try:
        output = solution(g1,[1,2,3])
    except Exception as e:
        error_message = (
            f"An exception occurred while running test case 7:\n"
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2,3]}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message

    if output != False:
        error_message = (
            f"Test case 7 failed:\n"
            f"`start`: \n{print_graph(g1)}\n"
            f"`path`: {[1,2,3]}\n\n"
            f"Expected Output: {False}\n"
            f"Actual Output: {output}"
        )
        assert False, error_message

    # Test case 8
    g1 = GraphNode(1, "x")
    g2 = GraphNode(2, "start")
    g3 = GraphNode(3, "")
    g4 = GraphNode(4, "")
    g5 = GraphNode(5, "")
    g6 = GraphNode(6, "end")
    g1.connected = [g2]
    g2.connected = [g1, g3, g4]
    g3.connected = [g2]
    g4.connected = [g2, g5, g6]
    g5.connected = [g4]
    g6.connected = [g4]

    try:
        output = solution(g2,[2,3,2,1,2,4,5,4,6])
    except Exception as e:
        error_message = (
            f"An exception occurred while running test case 8:\n"
            f"`start`: \n{print_graph(g2)}\n"
            f"`path`: {[2,3,2,1,2,4,5,4,6]}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message

    if output != False:
        error_message = (
            f"Test case 8 failed:\n"
            f"`start`: \n{print_graph(g2)}\n"
            f"`path`: {[2,3,2,1,2,4,5,4,6]}\n\n"
            f"Expected Output: {False}\n"
            f"Actual Output: {output}"
        )
        assert False, error_message

    # Test case 9
    g1 = GraphNode(1, "x")
    g2 = GraphNode(2, "start")
    g3 = GraphNode(3, "")
    g4 = GraphNode(4, "")
    g5 = GraphNode(5, "")
    g6 = GraphNode(6, "end")
    g1.connected = [g2]
    g2.connected = [g1, g3, g4]
    g3.connected = [g2]
    g4.connected = [g2, g5, g6]
    g5.connected = [g4]
    g6.connected = [g4]

    try:
        output = solution(g2,[2,1,2,4,5,4,6])
    except Exception as e:
        error_message = (
            f"An exception occurred while running test case 9:\n"
            f"`start`: \n{print_graph(g2)}\n"
            f"`path`: {[2,1,2,4,5,4,6]}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message

    if output != False:
        error_message = (
            f"Test case 9 failed:\n"
            f"`start`: \n{print_graph(g2)}\n"
            f"`path`: {[2,1,2,4,5,4,6]}\n\n"
            f"Expected Output: {False}\n"
            f"Actual Output: {output}"
        )
        assert False, error_message

    # Test case 10
    g1 = GraphNode(1, "x")
    g2 = GraphNode(2, "start")
    g3 = GraphNode(3, "")
    g4 = GraphNode(4, "")
    g5 = GraphNode(5, "")
    g6 = GraphNode(6, "end")
    g1.connected = [g2]
    g2.connected = [g1, g3, g4]
    g3.connected = [g2]
    g4.connected = [g2, g5, g6]
    g5.connected = [g4]
    g6.connected = [g4]

    try:
        output = solution(g2,[2,4,5,4,2,3,2,4,6])
    except Exception as e:
        error_message = (
            f"An exception occurred while running test case 10:\n"
            f"`start`: \n{print_graph(g2)}\n"
            f"`path`: {[2,4,5,4,2,3,2,4,6]}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message

    if output != True:
        error_message = (
            f"Test case 10 failed:\n"
            f"`start`: \n{print_graph(g2)}\n"
            f"`path`: {[2,4,5,4,2,3,2,4,6]}\n\n"
            f"Expected Output: {True}\n"
            f"Actual Output: {output}"
        )
        assert False, error_message

test()