GCMD_Keyword_Classifier_MVP / tests /test_vocabulary.py
igerasimov's picture
MVP Milestone 2
0f2ecac
Raw
History Blame Contribute Delete
8.96 kB
from __future__ import annotations
import copy
import json
from pathlib import Path
import pytest
from gcmd_classifier.errors import (
DuplicateCanonicalPathError,
DuplicateUUIDError,
InvalidHierarchyTransitionError,
MalformedHierarchyError,
VocabularyLookupError,
)
from gcmd_classifier.vocabulary import build_vocabulary_index, calculate_file_hash, load_vocabulary
FIXTURE_PATH = Path("tests/fixtures/gcmd_hierarchy_small.json")
FULL_HIERARCHY_PATH = Path("data/gcmd_hierarchy.json")
def load_fixture_data() -> dict:
return json.loads(FIXTURE_PATH.read_text())
def build_fixture_index():
return load_vocabulary(FIXTURE_PATH)
def test_recursive_traversal_and_variable_depth_branches() -> None:
index = build_fixture_index()
assert len(index) == 10
assert index.root_level == "Category"
assert index.root_name == "EARTH SCIENCE"
assert index.children_of(None) == ("topic-atmosphere", "topic-oceans")
assert index.children_of("term-weather-events") == ()
assert index.get("vl3-carbon-dioxide-profiles").level == "Variable_Level_3"
def test_uuid_preservation_and_record_fields() -> None:
index = build_fixture_index()
record = index.get("vl3-carbon-dioxide-profiles")
assert record.UUID == "vl3-carbon-dioxide-profiles"
assert record.name == "CARBON DIOXIDE PROFILES"
assert record.level == "Variable_Level_3"
assert record.topic == "ATMOSPHERE"
assert record.term == "ATMOSPHERIC CHEMISTRY"
assert record.path_components == (
"ATMOSPHERE",
"ATMOSPHERIC CHEMISTRY",
"CARBON",
"CARBON DIOXIDE",
"CARBON DIOXIDE PROFILES",
)
assert (
record.canonical_path == "ATMOSPHERE > ATMOSPHERIC CHEMISTRY > CARBON > CARBON DIOXIDE > "
"CARBON DIOXIDE PROFILES"
)
assert record.parent_uuid == "vl2-carbon-dioxide"
assert record.parent_name == "CARBON DIOXIDE"
assert record.child_uuids == ()
assert record.has_children is False
assert record.assignable is True
assert record.definition == "Vertical carbon dioxide profiles."
assert record.vocabulary_version == calculate_file_hash(FIXTURE_PATH)
def test_topic_term_and_variable_records_are_assignable() -> None:
index = build_fixture_index()
topic = index.get("topic-atmosphere")
term = index.get("term-atmospheric-chemistry")
internal_variable = index.get("vl1-atmosphere-carbon")
leaf_variable = index.get("vl2-methane")
assert topic.level == "Topic"
assert term.level == "Term"
assert internal_variable.level == "Variable_Level_1"
assert leaf_variable.level == "Variable_Level_2"
assert topic.assignable is True
assert term.assignable is True
assert internal_variable.assignable is True
assert leaf_variable.assignable is True
assert internal_variable.has_children is True
assert leaf_variable.has_children is False
def test_root_category_is_non_assignable_context() -> None:
index = build_fixture_index()
assert "EARTH SCIENCE" not in index.records_by_path
assert all(record.level != "Category" for record in index.records_by_uuid.values())
assert index.parent_of("topic-atmosphere") is None
assert index.get("topic-atmosphere").parent_name == "EARTH SCIENCE"
def test_optional_and_missing_definitions() -> None:
index = build_fixture_index()
assert index.get("topic-atmosphere").definition == "Atmospheric science."
assert index.get("vl2-methane").definition is None
def test_canonical_path_excludes_root_category() -> None:
index = build_fixture_index()
assert index.get("topic-atmosphere").canonical_path == "ATMOSPHERE"
assert index.get("term-weather-events").canonical_path == "ATMOSPHERE > WEATHER EVENTS"
assert not index.get("topic-atmosphere").canonical_path.startswith("EARTH SCIENCE")
def test_identical_names_in_different_branches_are_allowed_when_paths_differ() -> None:
index = build_fixture_index()
atmosphere_carbon = index.get("vl1-atmosphere-carbon")
ocean_carbon = index.get("vl1-ocean-carbon")
assert atmosphere_carbon.name == ocean_carbon.name == "CARBON"
assert atmosphere_carbon.canonical_path != ocean_carbon.canonical_path
def test_duplicate_uuid_detection() -> None:
data = load_fixture_data()
data["children"][1]["UUID"] = "topic-atmosphere"
with pytest.raises(DuplicateUUIDError):
build_vocabulary_index(data, vocabulary_version="test")
def test_duplicate_canonical_path_detection() -> None:
data = load_fixture_data()
duplicate = copy.deepcopy(data["children"][0]["children"][1])
duplicate["UUID"] = "term-weather-events-duplicate"
data["children"][0]["children"].append(duplicate)
with pytest.raises(DuplicateCanonicalPathError):
build_vocabulary_index(data, vocabulary_version="test")
@pytest.mark.parametrize("field", ["level", "name"])
def test_missing_required_fields(field: str) -> None:
data = load_fixture_data()
del data["children"][0][field]
with pytest.raises(MalformedHierarchyError):
build_vocabulary_index(data, vocabulary_version="test")
def test_unexpected_uuidless_non_root_node() -> None:
data = load_fixture_data()
del data["children"][0]["UUID"]
with pytest.raises(MalformedHierarchyError):
build_vocabulary_index(data, vocabulary_version="test")
def test_valid_hierarchy_level_transitions() -> None:
index = build_fixture_index()
assert index.get("topic-atmosphere").child_uuids == (
"term-atmospheric-chemistry",
"term-weather-events",
)
assert index.get("term-atmospheric-chemistry").child_uuids == ("vl1-atmosphere-carbon",)
assert index.get("vl1-atmosphere-carbon").child_uuids == (
"vl2-carbon-dioxide",
"vl2-methane",
)
assert index.get("vl2-carbon-dioxide").child_uuids == ("vl3-carbon-dioxide-profiles",)
def test_invalid_hierarchy_level_transition() -> None:
data = load_fixture_data()
data["children"][0]["children"][0]["level"] = "Variable_Level_1"
with pytest.raises(InvalidHierarchyTransitionError):
build_vocabulary_index(data, vocabulary_version="test")
def test_parent_and_direct_child_lookup() -> None:
index = build_fixture_index()
assert index.parent_of("vl2-carbon-dioxide") == "vl1-atmosphere-carbon"
assert index.children_of("vl1-atmosphere-carbon") == (
"vl2-carbon-dioxide",
"vl2-methane",
)
with pytest.raises(VocabularyLookupError):
index.parent_of("missing-uuid")
def test_topic_to_term_lookup() -> None:
index = build_fixture_index()
terms = index.terms_for_topic("topic-atmosphere")
assert tuple(term.UUID for term in terms) == (
"term-atmospheric-chemistry",
"term-weather-events",
)
with pytest.raises(VocabularyLookupError):
index.terms_for_topic("term-weather-events")
def test_term_to_variable_lookup() -> None:
index = build_fixture_index()
variables = index.variables_for_parent("term-atmospheric-chemistry")
assert tuple(variable.UUID for variable in variables) == ("vl1-atmosphere-carbon",)
assert index.variables_for_parent("vl2-methane") == ()
with pytest.raises(VocabularyLookupError):
index.variables_for_parent("topic-atmosphere")
def test_ancestor_and_descendant_checks() -> None:
index = build_fixture_index()
assert index.is_ancestor("topic-atmosphere", "vl3-carbon-dioxide-profiles") is True
assert index.is_descendant("vl3-carbon-dioxide-profiles", "topic-atmosphere") is True
assert index.is_ancestor("term-weather-events", "vl3-carbon-dioxide-profiles") is False
assert index.ancestors_of("vl3-carbon-dioxide-profiles") == (
"vl2-carbon-dioxide",
"vl1-atmosphere-carbon",
"term-atmospheric-chemistry",
"topic-atmosphere",
)
def test_vocabulary_file_hash_stability() -> None:
assert calculate_file_hash(FIXTURE_PATH) == calculate_file_hash(FIXTURE_PATH)
assert build_fixture_index().vocabulary_version == calculate_file_hash(FIXTURE_PATH)
def test_source_file_immutability() -> None:
before = FULL_HIERARCHY_PATH.read_bytes()
load_vocabulary(FULL_HIERARCHY_PATH)
after = FULL_HIERARCHY_PATH.read_bytes()
assert after == before
def test_every_current_topic_and_term_has_uuid() -> None:
index = load_vocabulary(FULL_HIERARCHY_PATH)
assert all(topic.UUID for topic in index.topics())
assert all(term.UUID for topic in index.topics() for term in index.terms_for_topic(topic.UUID))
def test_full_data_smoke_counts_for_current_vocabulary() -> None:
index = load_vocabulary(FULL_HIERARCHY_PATH)
counts = index.count_by_level()
assert counts["Topic"] == 14
assert counts["Term"] == 144
assert counts["Variable_Level_1"] == 1368
assert counts["Variable_Level_2"] == 1456
assert counts["Variable_Level_3"] == 553
assert len(index) == 3535