"""Tests related to the CompoundNodeType python APIs"""
import carb
import omni.graph.core as og
import omni.graph.core._unstable as ogu
import omni.graph.core.tests as ogts
import omni.kit.test
import omni.usd


class TestCompoundNodesAPI(ogts.OmniGraphTestCase):
    """Tests that validate the ICompoundNodeType + python wrapper"""

    @classmethod
    def setUpClass(cls):
        # this is required when running a single test to make expectedError work correctly...not sure why
        carb.log_warn("This class expects error. This preflushes to avoid problems with ExpectedError")

    async def test_default_compound_node_type_folder(self):
        """Tests the default folder commmand of the compound node type"""

        # with no default prim, "/World" is used
        await omni.usd.get_context().new_stage_async()
        self.assertEquals(ogu.get_default_compound_node_type_folder(), "/World/Compounds")

        # if there is a default set, the compounds folder is off of that.
        stage = omni.usd.get_context().get_stage()
        prim = stage.DefinePrim("/MyDefault")
        stage.SetDefaultPrim(prim)

        self.assertEquals(ogu.get_default_compound_node_type_folder(), "/MyDefault/Compounds")

    async def test_compound_nodes_cannot_add_state(self):
        """API test for compound nodes that verifies set state calls have no effect."""
        compound = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        with ogts.ExpectedError():
            compound.node_type.add_state("state", "int", True)

        with ogts.ExpectedError():
            compound.node_type.add_extended_state(
                "extended", "numerics", True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION
            )

        self.assertFalse(compound.node_type.has_state())

    async def test_get_compound_node_type(self):
        """API test for get_compound_node_type"""
        (result, error) = await ogts.load_test_file("TestCompoundGraph.usda", use_caller_subdirectory=True)
        self.assertTrue(result, error)

        compound_type = og.get_node_type("local.nodes.Compounds")
        self.assertFalse(ogu.get_compound_node_type(compound_type).is_valid())

        non_compound_type = og.get_node_type("omni.graph.nodes.Add")
        self.assertFalse(ogu.get_compound_node_type(non_compound_type).is_valid())

    async def test_find_compound_node_type_by_path(self):
        """API test for find_compound_node_type_by_path"""

        self.assertFalse(ogu.find_compound_node_type_by_path("/World/Compounds/Compound").is_valid())
        (result, error) = await ogts.load_test_file("TestCompoundGraph.usda", use_caller_subdirectory=True)
        self.assertTrue(result, error)
        self.assertTrue(ogu.find_compound_node_type_by_path("/World/Compounds/Compound").is_valid())
        self.assertFalse(ogu.find_compound_node_type_by_path("/World/Compounds/Compound1").is_valid())
        self.assertFalse(ogu.find_compound_node_type_by_path("").is_valid())

    async def test_create_compound_node_type(self):
        """API test for create compound node type"""

        def expect_fail(compound_name, graph_name, namespace=None, folder=None):
            with ogts.ExpectedError():
                self.assertFalse(ogu.create_compound_node_type(compound_name, graph_name, namespace, folder).is_valid())

        self.assertTrue(ogu.create_compound_node_type("ValidCompound_01", "Graph").is_valid())
        self.assertTrue(ogu.create_compound_node_type("ValidCompound_02", "Graph", "other.namespace").is_valid())
        self.assertTrue(
            ogu.create_compound_node_type(
                "ValidCompound_03", "Graph", "other.namespace", "/World/OtherCompounds"
            ).is_valid()
        )

        expect_fail("/INVALID_COMPOUND_NAME", "ValidGraphName")
        expect_fail("ValidCompoundName", "//!@#INVALID_GRAPH_NAME")
        expect_fail("ValidCompoundName", "ValidGraphName", "valid.namespace", "#@#$#$#INVALID_FOLDER##$@!#$")
        expect_fail("ValidCompound_01", "Graph")  # same namespace & path
        expect_fail("ValidCompound_01", "Graph", "local.nodes", "/World/Compounds2")

    async def test_as_node_type(self):
        """Test conversion between compound node type and node type"""
        ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        node_type = og.get_node_type("test.nodes.TestType")
        self.assertTrue(node_type.is_valid())

        compound_node_type = ogu.get_compound_node_type(node_type)
        self.assertTrue(compound_node_type.is_valid())

        add_type = og.get_node_type("omni.graph.nodes.Add")
        self.assertTrue(add_type.is_valid())
        self.assertFalse(ogu.get_compound_node_type(add_type).is_valid())

    async def test_node_type_add_attributes(self):
        """Tests that attributes added through the INodeType ABI update compound node types correctly"""

        stage = omni.usd.get_context().get_stage()

        # create the compound
        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        self.assertTrue(compound_node_type.is_valid())
        prim = stage.GetPrimAtPath("/World/Compounds/TestType")
        self.assertTrue(prim.IsValid())

        # downcast to the given node type
        node_type = compound_node_type.node_type
        self.assertTrue(node_type.is_valid())

        def validate_input(input_name, type_name, expect_typed_data):
            input_attr = compound_node_type.find_input(input_name)
            self.assertTrue(input_attr.is_valid())
            self.assertEqual(input_attr.name, f"inputs:{input_name}")
            self.assertEqual(input_attr.type_name, type_name)
            path = prim.GetPath().AppendProperty(f"omni:graph:input:{input_name}")
            rel = stage.GetRelationshipAtPath(path)
            self.assertTrue(rel.IsValid(), f"Failed to get input at {path}")
            if expect_typed_data:
                self.assertIsNotNone(rel.GetCustomDataByKey("omni:graph:type"))

        # add regular input
        node_type.add_input("input_a", "int", True)
        validate_input("input_a", "int", True)

        # add an any input
        node_type.add_extended_input("input_b", None, True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY)
        validate_input("input_b", "token", False)

        # add a union input
        node_type.add_extended_input("input_c", "int, int2", True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION)
        validate_input("input_c", "token", True)

        def validate_output(output_name, type_name, expect_typed_data):
            output = compound_node_type.find_output(output_name)
            self.assertTrue(output.is_valid())
            self.assertEqual(output.name, f"outputs:{output_name}")
            self.assertEqual(output.type_name, type_name)
            path = prim.GetPath().AppendProperty(f"omni:graph:output:{output_name}")
            rel = stage.GetRelationshipAtPath(path)
            self.assertTrue(rel.IsValid(), f"Failed to get output at {path}")
            if expect_typed_data:
                self.assertIsNotNone(rel.GetCustomDataByKey("omni:graph:type"))

        # add regular output
        node_type.add_output("output_a", "int", True)
        validate_output("output_a", "int", True)

        # add an any output
        node_type.add_extended_output("output_b", None, True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_ANY)
        validate_output("output_b", "token", False)

        # add a union output
        node_type.add_extended_output("output_c", "int, int2", True, og.ExtendedAttributeType.EXTENDED_ATTR_TYPE_UNION)
        validate_output("output_c", "token", True)

    async def test_add_attributes_with_default_data(self):
        """Tests that compound nodes can handle adding attributes that use default data"""
        stage = omni.usd.get_context().get_stage()

        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        self.assertTrue(compound_node_type.is_valid())
        prim = stage.GetPrimAtPath("/World/Compounds/TestType")
        self.assertTrue(prim.IsValid())
        prim_path = prim.GetPath()

        def validate_typed_attributes(attr_name, attr_type, is_required, var_value):

            compound_node_type.add_input(attr_name, attr_type, is_required, var_value)
            compound_node_type.add_output(attr_name, attr_type, is_required, var_value)
            input_rel = stage.GetRelationshipAtPath(prim_path.AppendProperty(f"omni:graph:input:{attr_name}"))
            output_rel = stage.GetRelationshipAtPath(prim_path.AppendProperty(f"omni:graph:output:{attr_name}"))

            self.assertTrue(input_rel.IsValid())
            self.assertTrue(output_rel.IsValid())

            input_data = input_rel.GetCustomDataByKey("omni:graph:default")
            self.assertIsNotNone(input_data)

            output_data = output_rel.GetCustomDataByKey("omni:graph:default")
            self.assertIsNotNone(output_data)

            # token[] rearranges quotes and quat4 is out of order (input takes w first)
            if attr_type != "token[]" and "quat" not in attr_type:
                var_value_as_string = str(var_value).replace(" ", "")
                input_data_as_string = str(input_data).replace(" ", "")
                output_data_as_string = str(output_data).replace(" ", "")
                self.assertEqual(var_value_as_string, input_data_as_string)
                self.assertEqual(var_value_as_string, output_data_as_string)

        validate_typed_attributes("a_bool", "bool", True, True)
        validate_typed_attributes("a_bool_array", "bool[]", True, [0, 1])
        validate_typed_attributes("a_colord_3", "colord[3]", True, (0.01625, 0.14125, 0.26625))
        validate_typed_attributes(
            "a_colord_3_array", "colord[3][]", True, [(0.01625, 0.14125, 0.26625), (0.26625, 0.14125, 0.01625)]
        )
        validate_typed_attributes("a_colord_4", "colord[4]", True, (0.01625, 0.14125, 0.26625, 0.39125))
        validate_typed_attributes(
            "a_colord_4_array",
            "colord[4][]",
            True,
            [(0.01625, 0.14125, 0.26625, 0.39125), (0.39125, 0.26625, 0.14125, 0.01625)],
        )
        validate_typed_attributes("a_colorf_3", "colorf[3]", True, (0.125, 0.25, 0.375))
        validate_typed_attributes("a_colorf_3_array", "colorf[3][]", True, [(0.125, 0.25, 0.375), (0.375, 0.25, 0.125)])
        validate_typed_attributes("a_colorf_4", "colorf[4]", True, (0.125, 0.25, 0.375, 0.5))
        validate_typed_attributes(
            "a_colorf_4_array", "colorf[4][]", True, [(0.125, 0.25, 0.375, 0.5), (0.5, 0.375, 0.25, 0.125)]
        )
        validate_typed_attributes("a_colorh_3", "colorh[3]", True, (0.5, 0.625, 0.75))
        validate_typed_attributes("a_colorh_3_array", "colorh[3][]", True, [(0.5, 0.625, 0.75), (0.75, 0.625, 0.5)])
        validate_typed_attributes("a_colorh_4", "colorh[4]", True, (0.5, 0.625, 0.75, 0.875))
        validate_typed_attributes(
            "a_colorh_4_array", "colorh[4][]", True, [(0.5, 0.625, 0.75, 0.875), (0.875, 0.75, 0.625, 0.5)]
        )
        validate_typed_attributes("a_double", "double", True, 4.125)
        validate_typed_attributes("a_double_2", "double[2]", True, (4.125, 4.25))
        validate_typed_attributes("a_double_2_array", "double[2][]", True, [(4.125, 4.25), (2.125, 2.25)])
        validate_typed_attributes("a_double_3", "double[3]", True, (4.125, 4.25, 4.375))
        validate_typed_attributes("a_double_3_array", "double[3][]", True, [(4.125, 4.25, 4.375), (2.125, 2.25, 2.375)])
        validate_typed_attributes("a_double_4", "double[4]", True, (4.125, 4.25, 4.375, 4.5))
        validate_typed_attributes(
            "a_double_4_array", "double[4][]", True, [(4.125, 4.25, 4.375, 4.5), (2.125, 2.25, 2.375, 2.5)]
        )
        validate_typed_attributes("a_double_array", "double[]", True, [4.125, 2.125])
        validate_typed_attributes("a_execution", "execution", True, 0)
        validate_typed_attributes("a_float", "float", True, 4.5)
        validate_typed_attributes("a_float_2", "float[2]", True, (4.5, 4.625))
        validate_typed_attributes("a_float_2_array", "float[2][]", True, [(4.5, 4.625), (2.5, 2.625)])
        validate_typed_attributes("a_float_3", "float[3]", True, (4.5, 4.625, 4.75))
        validate_typed_attributes("a_float_3_array", "float[3][]", True, [(4.5, 4.625, 4.75), (2.5, 2.625, 2.75)])
        validate_typed_attributes("a_float_4", "float[4]", True, (4.5, 4.625, 4.75, 4.875))
        validate_typed_attributes(
            "a_float_4_array", "float[4][]", True, [(4.5, 4.625, 4.75, 4.875), (2.5, 2.625, 2.75, 2.875)]
        )
        validate_typed_attributes("a_float_array", "float[]", True, [4.5, 2.5])
        validate_typed_attributes(
            "a_frame_4", "frame[4]", True, ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
        )
        validate_typed_attributes(
            "a_frame_4_array",
            "frame[4][]",
            True,
            [
                ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)),
                ((2, 3, 3, 3), (3, 2, 3, 3), (3, 3, 2, 3), (3, 3, 3, 2)),
            ],
        )
        validate_typed_attributes("a_half", "half", True, 2.5)
        validate_typed_attributes("a_half_2", "half[2]", True, (2.5, 2.625))
        validate_typed_attributes("a_half_2_array", "half[2][]", True, [(2.5, 2.625), (4.5, 4.625)])
        validate_typed_attributes("a_half_3", "half[3]", True, (2.5, 2.625, 2.75))
        validate_typed_attributes("a_half_3_array", "half[3][]", True, [(2.5, 2.625, 2.75), (4.5, 4.625, 4.75)])
        validate_typed_attributes("a_half_4", "half[4]", True, (2.5, 2.625, 2.75, 2.875))
        validate_typed_attributes(
            "a_half_4_array", "half[4][]", True, [(2.5, 2.625, 2.75, 2.875), (4.5, 4.625, 4.75, 4.875)]
        )
        validate_typed_attributes("a_half_array", "half[]", True, [2.5, 4.5])
        validate_typed_attributes("a_int", "int", True, -32)
        validate_typed_attributes("a_int64", "int64", True, -46)
        validate_typed_attributes("a_int64_array", "int64[]", True, [-46, -64])
        validate_typed_attributes("a_int_2", "int[2]", True, (-32, -31))
        validate_typed_attributes("a_int_2_array", "int[2][]", True, [(-32, -31), (-23, -22)])
        validate_typed_attributes("a_int_3", "int[3]", True, (-32, -31, -30))
        validate_typed_attributes("a_int_3_array", "int[3][]", True, [(-32, -31, -30), (-23, -22, -21)])
        validate_typed_attributes("a_int_4", "int[4]", True, (-32, -31, -30, -29))
        validate_typed_attributes("a_int_4_array", "int[4][]", True, [(-32, -31, -30, -29), (-23, -22, -21, -20)])
        validate_typed_attributes("a_int_array", "int[]", True, [-32, -23])
        validate_typed_attributes("a_matrixd_2", "matrixd[2]", True, ((1, 0), (0, 1)))
        validate_typed_attributes("a_matrixd_2_array", "matrixd[2][]", True, [((1, 0), (0, 1)), ((2, 3), (3, 2))])
        validate_typed_attributes("a_matrixd_3", "matrixd[3]", True, ((1, 0, 0), (0, 1, 0), (0, 0, 1)))
        validate_typed_attributes(
            "a_matrixd_3_array",
            "matrixd[3][]",
            True,
            [((1, 0, 0), (0, 1, 0), (0, 0, 1)), ((2, 3, 3), (3, 2, 3), (3, 3, 2))],
        )
        validate_typed_attributes(
            "a_matrixd_4", "matrixd[4]", True, ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
        )
        validate_typed_attributes(
            "a_matrixd_4_array",
            "matrixd[4][]",
            True,
            [
                ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1)),
                ((2, 3, 3, 3), (3, 2, 3, 3), (3, 3, 2, 3), (3, 3, 3, 2)),
            ],
        )
        validate_typed_attributes("a_normald_3", "normald[3]", True, (0.01625, 0.14125, 0.26625))
        validate_typed_attributes(
            "a_normald_3_array", "normald[3][]", True, [(0.01625, 0.14125, 0.26625), (0.26625, 0.14125, 0.01625)]
        )
        validate_typed_attributes("a_normalf_3", "normalf[3]", True, (0.125, 0.25, 0.375))
        validate_typed_attributes(
            "a_normalf_3_array", "normalf[3][]", True, [(0.125, 0.25, 0.375), (0.375, 0.25, 0.125)]
        )
        validate_typed_attributes("a_normalh_3", "normalh[3]", True, (0.5, 0.625, 0.75))
        validate_typed_attributes("a_normalh_3_array", "normalh[3][]", True, [(0.5, 0.625, 0.75), (0.75, 0.625, 0.5)])
        validate_typed_attributes("a_objectId", "objectId", True, 46)
        validate_typed_attributes("a_objectId_array", "objectId[]", True, [46, 64])
        validate_typed_attributes("a_path", "path", True, "/This/Is")
        validate_typed_attributes("a_pointd_3", "pointd[3]", True, (0.01625, 0.14125, 0.26625))
        validate_typed_attributes(
            "a_pointd_3_array", "pointd[3][]", True, [(0.01625, 0.14125, 0.26625), (0.26625, 0.14125, 0.01625)]
        )
        validate_typed_attributes("a_pointf_3", "pointf[3]", True, (0.125, 0.25, 0.375))
        validate_typed_attributes("a_pointf_3_array", "pointf[3][]", True, [(0.125, 0.25, 0.375), (0.375, 0.25, 0.125)])
        validate_typed_attributes("a_pointh_3", "pointh[3]", True, (0.5, 0.625, 0.75))
        validate_typed_attributes("a_pointh_3_array", "pointh[3][]", True, [(0.5, 0.625, 0.75), (0.75, 0.625, 0.5)])
        validate_typed_attributes("a_quatd_4", "quatd[4]", True, (0.78, 0.01625, 0.14125, 0.26625))
        validate_typed_attributes(
            "a_quatd_4_array",
            "quatd[4][]",
            True,
            [(0.78, 0.01625, 0.14125, 0.26625), (0.51625, 0.14125, 0.26625, 0.39125)],
        )
        validate_typed_attributes("a_quatf_4", "quatf[4]", True, (0.5, 0.125, 0.25, 0.375))
        validate_typed_attributes(
            "a_quatf_4_array", "quatf[4][]", True, [(0.5, 0.125, 0.25, 0.375), (0.625, 0.25, 0.375, 0.5)]
        )
        validate_typed_attributes("a_quath_4", "quath[4]", True, (0.75, 0, 0.25, 0.5))
        validate_typed_attributes(
            "a_quath_4_array", "quath[4][]", True, [(0.75, 0, 0.25, 0.5), (0.875, 0.125, 0.375, 0.625)]
        )
        validate_typed_attributes("a_string", "string", True, "Anakin")
        validate_typed_attributes("a_texcoordd_2", "texcoordd[2]", True, (0.01625, 0.14125))
        validate_typed_attributes(
            "a_texcoordd_2_array", "texcoordd[2][]", True, [(0.01625, 0.14125), (0.14125, 0.01625)]
        )
        validate_typed_attributes("a_texcoordd_3", "texcoordd[3]", True, (0.01625, 0.14125, 0.26625))
        validate_typed_attributes(
            "a_texcoordd_3_array", "texcoordd[3][]", True, [(0.01625, 0.14125, 0.26625), (0.26625, 0.14125, 0.01625)]
        )
        validate_typed_attributes("a_texcoordf_2", "texcoordf[2]", True, (0.125, 0.25))
        validate_typed_attributes("a_texcoordf_2_array", "texcoordf[2][]", True, [(0.125, 0.25), (0.25, 0.125)])
        validate_typed_attributes("a_texcoordf_3", "texcoordf[3]", True, (0.125, 0.25, 0.375))
        validate_typed_attributes(
            "a_texcoordf_3_array", "texcoordf[3][]", True, [(0.125, 0.25, 0.375), (0.375, 0.25, 0.125)]
        )
        validate_typed_attributes("a_texcoordh_2", "texcoordh[2]", True, (0.5, 0.625))
        validate_typed_attributes("a_texcoordh_2_array", "texcoordh[2][]", True, [(0.5, 0.625), (0.625, 0.5)])
        validate_typed_attributes("a_texcoordh_3", "texcoordh[3]", True, (0.5, 0.625, 0.75))
        validate_typed_attributes(
            "a_texcoordh_3_array", "texcoordh[3][]", True, [(0.5, 0.625, 0.75), (0.75, 0.625, 0.5)]
        )
        validate_typed_attributes("a_timecode", "timecode", True, 5)
        validate_typed_attributes("a_timecode_array", "timecode[]", True, [5, 6])
        validate_typed_attributes("a_token", "token", True, "Ahsoka")
        validate_typed_attributes("a_token_array", "token[]", True, ["Ahsoka", "Tano"])
        validate_typed_attributes("a_uchar", "uchar", True, 12)
        validate_typed_attributes("a_uchar_array", "uchar[]", True, [12, 8])
        validate_typed_attributes("a_uint", "uint", True, 32)
        validate_typed_attributes("a_uint64", "uint64", True, 46)
        validate_typed_attributes("a_uint64_array", "uint64[]", True, [46, 64])
        validate_typed_attributes("a_uint_array", "uint[]", True, [32, 23])
        validate_typed_attributes("a_vectord_3", "vectord[3]", True, (0.01625, 0.14125, 0.26625))
        validate_typed_attributes(
            "a_vectord_3_array", "vectord[3][]", True, [(0.01625, 0.14125, 0.26625), (0.26625, 0.14125, 0.01625)]
        )
        validate_typed_attributes("a_vectorf_3", "vectorf[3]", True, (0.125, 0.25, 0.375))
        validate_typed_attributes(
            "a_vectorf_3_array", "vectorf[3][]", True, [(0.125, 0.25, 0.375), (0.375, 0.25, 0.125)]
        )
        validate_typed_attributes("a_vectorh_3", "vectorh[3]", True, (0.5, 0.625, 0.75))
        validate_typed_attributes("a_vectorh_3_array", "vectorh[3][]", True, [(0.5, 0.625, 0.75), (0.75, 0.625, 0.5)])

    async def test_remove_attribute_commands(self):
        """Validates the API/ABI commands to remove attributes function as expected"""
        stage = omni.usd.get_context().get_stage()

        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        self.assertTrue(compound_node_type.is_valid())
        prim = stage.GetPrimAtPath("/World/Compounds/TestType")
        self.assertTrue(prim.IsValid())

        for i in range(1, 20):
            compound_node_type.add_input(f"input_{i}", "int", True)
            compound_node_type.add_output(f"output_{i}", "float", True)
            self.assertTrue(prim.GetRelationship(f"omni:graph:input:input_{i}").IsValid())
            self.assertTrue(prim.GetRelationship(f"omni:graph:output:output_{i}").IsValid())

        for i in range(1, 20, 2):
            compound_node_type.remove_input_by_name(f"input_{i}")
            compound_node_type.remove_output_by_name(f"output_{i}")

        for i in range(1, 20):
            input_attr = compound_node_type.find_input(f"input_{i}")
            output_attr = compound_node_type.find_output(f"output_{i}")
            valid = (i % 2) == 0
            self.assertEqual(input_attr.is_valid(), valid)
            self.assertEqual(output_attr.is_valid(), valid)
            self.assertEqual(prim.GetRelationship(f"omni:graph:input:input_{i}").IsValid(), valid)
            self.assertEqual(prim.GetRelationship(f"omni:graph:output:output_{i}").IsValid(), valid)

    async def test_get_attributes_commands(self):
        """Validates the methods to get inputs and outputs"""
        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        for i in range(1, 20):
            compound_node_type.add_input(f"input_{i}", "int", True)
            compound_node_type.add_output(f"output_{i}", "float", True)
        compound_node_type.add_output("output_20", "bool", True)

        self.assertEquals(compound_node_type.input_count, 19)
        self.assertEquals(compound_node_type.output_count, 20)

        inputs = compound_node_type.get_inputs()
        input_names = [input_attr.name for input_attr in inputs]
        outputs = compound_node_type.get_outputs()
        output_names = [output.name for output in outputs]

        for i in range(1, 20):
            self.assertTrue(f"inputs:input_{i}" in input_names)

        for i in range(1, 21):
            self.assertTrue(f"outputs:output_{i}" in output_names)

    async def test_find_attribute_commands(self):
        """Validates the find input and find output commands"""
        compound_node_type = ogu.create_compound_node_type("TestType", "Graph", "test.nodes")
        # create some without prefix
        for i in range(1, 20):
            compound_node_type.add_input(f"input_{i}", "int", True)
            compound_node_type.add_output(f"output_{i}", "float", True)

        # some with prefix
        for i in range(20, 40):
            compound_node_type.add_input(f"inputs:input_{i}", "int", True)
            compound_node_type.add_output(f"outputs:output_{i}", "float", True)

        # validate that the prefix is optional
        for i in range(1, 40):
            self.assertTrue(compound_node_type.find_input(f"inputs:input_{i}").is_valid())
            self.assertTrue(compound_node_type.find_input(f"input_{i}").is_valid())
            self.assertEqual(
                compound_node_type.find_input(f"inputs:input_{i}"), compound_node_type.find_input(f"input_{i}")
            )
            self.assertTrue(compound_node_type.find_output(f"outputs:output_{i}").is_valid())
            self.assertTrue(compound_node_type.find_output(f"output_{i}").is_valid())
            self.assertEqual(
                compound_node_type.find_output(f"outputs:output_{i}"), compound_node_type.find_output(f"output_{i}")
            )
