diff --git a/src/tsbootstrap/registry/tests/test_tags.py b/src/tsbootstrap/registry/tests/test_tags.py index 597c259..d19b2df 100644 --- a/src/tsbootstrap/registry/tests/test_tags.py +++ b/src/tsbootstrap/registry/tests/test_tags.py @@ -1,46 +1,221 @@ -"""Tests for tag register an tag functionality.""" +""" +Tests for the tag registry and tag validation functionality. -from tsbootstrap.registry._tags import OBJECT_TAG_REGISTER +This module contains tests to ensure that the `OBJECT_TAG_REGISTER` is correctly +configured and that each tag adheres to the specified structure and type constraints. +""" + +from tsbootstrap.registry._tags import OBJECT_TAG_REGISTER, Tag def test_tag_register_type(): - """Test the specification of the tag register. See _tags for specs.""" + """ + Test the specification of the tag register. + + Ensures that `OBJECT_TAG_REGISTER` is a list of `Tag` instances with the correct attributes and types. + + Raises + ------ + TypeError + If `OBJECT_TAG_REGISTER` is not a list or contains non-`Tag` instances. + ValueError + If any `Tag` instance does not conform to the expected structure or type constraints. + """ + # Verify that OBJECT_TAG_REGISTER is a list if not isinstance(OBJECT_TAG_REGISTER, list): - raise TypeError("OBJECT_TAG_REGISTER is not a list.") - if not all(isinstance(tag, tuple) for tag in OBJECT_TAG_REGISTER): - raise TypeError("Not all elements in OBJECT_TAG_REGISTER are tuples.") + raise TypeError("`OBJECT_TAG_REGISTER` is not a list.") + # Verify that all elements in OBJECT_TAG_REGISTER are instances of Tag + if not all(isinstance(tag, Tag) for tag in OBJECT_TAG_REGISTER): + raise TypeError( + "Not all elements in `OBJECT_TAG_REGISTER` are `Tag` instances." + ) + + # Iterate through each Tag instance to validate its attributes for tag in OBJECT_TAG_REGISTER: - if len(tag) != 4: - raise ValueError("Tag does not have 4 elements.") - if not isinstance(tag[0], str): - raise TypeError("Tag name is not a string.") - if not isinstance(tag[1], (str, list)): - raise TypeError("Tag type is not a string or list.") - if isinstance(tag[1], list) and not all( - isinstance(x, str) for x in tag[1] - ): - raise TypeError("Not all elements in tag type list are strings.") - if not isinstance(tag[2], (str, tuple)): - raise TypeError("Tag description is not a string or tuple.") - if isinstance(tag[2], tuple): - if not len(tag[2]) == 2: + # Validate the 'name' attribute + if not isinstance(tag.name, str): + raise TypeError(f"Tag name '{tag.name}' is not a string.") + + # Validate the 'scitype' attribute + if not isinstance(tag.scitype, str): + raise TypeError(f"Tag scitype '{tag.scitype}' is not a string.") + + # Validate the 'value_type' attribute + if not isinstance(tag.value_type, (str, tuple)): + raise TypeError( + f"Tag value_type '{tag.value_type}' is not a string or tuple." + ) + + if isinstance(tag.value_type, tuple): + if len(tag.value_type) != 2: raise ValueError( - "Tag description tuple does not have 2 elements." + "Tuple `value_type` must have exactly two elements." ) - if not isinstance(tag[2][0], str): - raise TypeError( - "Tag description tuple first element is not a string." + + base_type, subtype = tag.value_type + + # Validate the base type + if base_type not in {"str", "list"}: + raise ValueError( + f"First element of `value_type` tuple must be 'str' or 'list', got '{base_type}'." ) - if not isinstance(tag[2][1], (list, str)): - raise TypeError( - "Tag description tuple second element is not a list or string." + + # Validate the subtype based on the base type + if base_type == "str": + if not isinstance(subtype, list) or not all( + isinstance(item, str) for item in subtype + ): + raise TypeError( + "Second element of `value_type` tuple must be a list of strings when base is 'str'." + ) + elif base_type == "list" and not ( + ( + isinstance(subtype, list) + and all(isinstance(item, str) for item in subtype) ) - if isinstance(tag[2][1], list) and not all( - isinstance(x, str) for x in tag[2][1] + or isinstance(subtype, str) ): raise TypeError( - "Not all elements in tag description list are strings." + "Second element of `value_type` tuple must be a list of strings or 'str' when base is 'list'." + ) + + # Validate the 'description' attribute + if not isinstance(tag.description, str): + raise TypeError( + f"Tag description '{tag.description}' is not a string." + ) + + +def test_object_tag_table_structure(): + """ + Test the structure of `OBJECT_TAG_TABLE`. + + Ensures that `OBJECT_TAG_TABLE` is a list of dictionaries, each containing the expected keys and corresponding types. + + Raises + ------ + TypeError + If `OBJECT_TAG_TABLE` is not a list or contains elements that are not dictionaries. + KeyError + If any dictionary in `OBJECT_TAG_TABLE` is missing required keys. + TypeError + If any value in the dictionaries does not match the expected type. + """ + from tsbootstrap.registry._tags import OBJECT_TAG_TABLE + + # Define the expected keys and their types + expected_keys = { + "name": str, + "scitype": str, + "value_type": (str, tuple), + "description": str, + } + + # Verify that OBJECT_TAG_TABLE is a list + if not isinstance(OBJECT_TAG_TABLE, list): + raise TypeError("`OBJECT_TAG_TABLE` is not a list.") + + # Iterate through each dictionary in OBJECT_TAG_TABLE to validate its structure + for entry in OBJECT_TAG_TABLE: + # Verify that each entry is a dictionary + if not isinstance(entry, dict): + raise TypeError( + "Each entry in `OBJECT_TAG_TABLE` must be a dictionary." + ) + + # Check for the presence of all expected keys + for key, expected_type in expected_keys.items(): + if key not in entry: + raise KeyError( + f"Key '{key}' is missing from an entry in `OBJECT_TAG_TABLE`." + ) + + # Validate the type of each value + if not isinstance(entry[key], expected_type): + raise TypeError( + f"Value for key '{key}' in `OBJECT_TAG_TABLE` entry is not of type { + expected_type}." + ) + + +def test_object_tag_list(): + """ + Test the contents of `OBJECT_TAG_LIST`. + + Ensures that `OBJECT_TAG_LIST` contains all tag names present in `OBJECT_TAG_REGISTER` and that each name is a string. + + Raises + ------ + TypeError + If `OBJECT_TAG_LIST` is not a list or contains non-string elements. + ValueError + If any tag name in `OBJECT_TAG_register` is missing from `OBJECT_TAG_LIST`. + """ + from tsbootstrap.registry._tags import OBJECT_TAG_LIST + + # Verify that OBJECT_TAG_LIST is a list + if not isinstance(OBJECT_TAG_LIST, list): + raise TypeError("`OBJECT_TAG_LIST` is not a list.") + + # Verify that all elements in OBJECT_TAG_LIST are strings + if not all(isinstance(name, str) for name in OBJECT_TAG_LIST): + raise TypeError("All elements in `OBJECT_TAG_LIST` must be strings.") + + # Extract all tag names from OBJECT_TAG_REGISTER + tag_names = {tag.name for tag in OBJECT_TAG_REGISTER} + + # Verify that OBJECT_TAG_LIST contains all tag names + missing_tags = tag_names - set(OBJECT_TAG_LIST) + if missing_tags: + raise ValueError( + f"The following tags are missing from `OBJECT_TAG_LIST`: {missing_tags}" + ) + + +def test_check_tag_is_valid(): + """ + Test the `check_tag_is_valid` function. + + Ensures that `check_tag_is_valid` correctly validates tag values based on their expected types. + + Raises + ------ + AssertionError + If any test case fails. + """ + from tsbootstrap.registry._tags import check_tag_is_valid + + # Define test cases as tuples of (tag_name, tag_value, expected_result) + test_cases = [ + ("object_type", "regressor", True), + ("object_type", "invalid_type", False), + ("capability:multivariate", True, True), + ("capability:multivariate", False, True), + ("capability:multivariate", "yes", False), + ("python_version", "3.8.5", True), + ("python_version", 3.8, False), + ("python_dependencies", ["numpy", "pandas"], True), + ("python_dependencies", "numpy", True), + ("python_dependencies", ["numpy", 123], False), + ("python_dependencies_alias", {"numpy": "np"}, True), + ("python_dependencies_alias", "numpy", False), + ("non_existent_tag", "value", False), # Should raise KeyError + ] + + for tag_name, tag_value, expected in test_cases: + if tag_name == "non_existent_tag": + try: + check_tag_is_valid(tag_name, tag_value) + raise AssertionError( + f"Expected KeyError for tag '{tag_name}', but no error was raised." + ) + except KeyError: + pass # Expected behavior + else: + result = check_tag_is_valid(tag_name, tag_value) + if result != expected: + raise ValueError( + f"check_tag_is_valid({tag_name!r}, {tag_value!r}) returned { + result}, expected {expected}." ) - if not isinstance(tag[3], str): - raise TypeError("Tag source is not a string.")