"""Step definitions for features/uko_layer3_vocabularies.feature (detail level map scenarios). Steps for UKO Layer 3 -- detail level maps and verification. All steps use the suffix "for uko_l3" to avoid AmbiguousStep errors. """ from __future__ import annotations from typing import Any from behave import given, then, when from cleveragents.acms.uko import ( JAVA_VOCABULARY as _JAVA_VOCABULARY_import, ) from cleveragents.acms.uko import ( PYTHON_VOCABULARY as _PYTHON_VOCABULARY_import, ) from cleveragents.acms.uko import ( RUST_VOCABULARY as _RUST_VOCABULARY_import, ) from cleveragents.acms.uko import ( TYPESCRIPT_VOCABULARY as _TYPESCRIPT_VOCABULARY_import, ) from cleveragents.acms.uko import ( ProvenanceInfo as _ProvenanceInfo_import, ) from cleveragents.acms.uko import ( build_detail_level_map as _build_detail_level_map_import, ) from cleveragents.acms.uko import ( resolve_detail_level as _resolve_detail_level_import, ) from cleveragents.acms.uko.layer3_java import JAVA_DETAIL_LEVELS from cleveragents.acms.uko.layer3_py import ( PYTHON_DETAIL_LEVELS, PYTHON_VOCABULARY, ) from cleveragents.acms.uko.layer3_rs import ( RUST_DETAIL_LEVELS, RUST_VOCABULARY, ) from cleveragents.acms.uko.layer3_ts import TYPESCRIPT_DETAIL_LEVELS from cleveragents.acms.uko.vocabulary import ( ProvenanceInfo, build_detail_level_map, resolve_detail_level, ) __all__: list[str] = [] # =========================================================================== # Helpers # =========================================================================== def _parse_entries(entries_str: str) -> tuple[tuple[str, int], ...]: """Parse "A=0,B=1,C=2" into tuple of (name, depth) pairs.""" result: list[tuple[str, int]] = [] for pair in entries_str.split(","): name, val = pair.strip().split("=") result.append((name.strip(), int(val.strip()))) return tuple(result) # =========================================================================== # build_detail_level_map steps # =========================================================================== @given('a parent level map with entries "{entries}" for uko_l3') def step_given_parent_map(context: Any, entries: str) -> None: context.parent_levels = _parse_entries(entries) @when("I build a detail level map with no insertions for uko_l3") def step_when_build_no_insertions(context: Any) -> None: context.result_map = build_detail_level_map(context.parent_levels, ()) @when('I build a detail level map with insertion "{entry}" for uko_l3') def step_when_build_one_insertion(context: Any, entry: str) -> None: insertions = _parse_entries(entry) context.result_map = build_detail_level_map(context.parent_levels, insertions) @when('I build a detail level map with insertions "{entries}" for uko_l3') def step_when_build_multiple_insertions(context: Any, entries: str) -> None: insertions = _parse_entries(entries) context.result_map = build_detail_level_map(context.parent_levels, insertions) @then("the resulting map should have {count:d} entries for uko_l3") def step_then_result_map_count(context: Any, count: int) -> None: assert len(context.result_map) == count @then('the resulting map entry "{name}" should have depth {depth:d} for uko_l3') def step_then_result_map_entry(context: Any, name: str, depth: int) -> None: level_dict = dict(context.result_map) assert name in level_dict, f"{name!r} not in {level_dict}" assert level_dict[name] == depth, f"Expected {name}={depth}, got {level_dict[name]}" @then( "building a detail level map with duplicate insertion " '"{entry}" should raise ValueError for uko_l3' ) def step_then_build_duplicate_raises(context: Any, entry: str) -> None: insertions = _parse_entries(entry) try: build_detail_level_map(context.parent_levels, insertions) msg = "Expected ValueError for duplicate names" raise AssertionError(msg) except ValueError: pass # =========================================================================== # Python DetailLevelMap steps # =========================================================================== @then("the Python detail level map should have {count:d} entries for uko_l3") def step_then_py_map_count(context: Any, count: int) -> None: assert len(PYTHON_DETAIL_LEVELS) == count @then('the Python detail level "{name}" should resolve to depth {depth:d} for uko_l3') def step_then_py_level_depth(context: Any, name: str, depth: int) -> None: resolved = resolve_detail_level(name, PYTHON_DETAIL_LEVELS) assert resolved == depth, f"Expected {name}={depth}, got {resolved}" # =========================================================================== # resolve_detail_level steps # =========================================================================== @given('a detail level map with entries "{entries}" for uko_l3') def step_given_detail_map(context: Any, entries: str) -> None: context.detail_levels = _parse_entries(entries) @given('a parent detail level map with entries "{entries}" for uko_l3') def step_given_parent_detail_map(context: Any, entries: str) -> None: context.parent_detail_levels = _parse_entries(entries) @then('resolving "{name}" should return {depth:d} for uko_l3') def step_then_resolve_name(context: Any, name: str, depth: int) -> None: resolved = resolve_detail_level(name, context.detail_levels) assert resolved == depth @then('resolving "{name}" with parent fallback should return {depth:d} for uko_l3') def step_then_resolve_with_parent(context: Any, name: str, depth: int) -> None: resolved = resolve_detail_level( name, context.detail_levels, context.parent_detail_levels ) assert resolved == depth @then('resolving "{name}" with parent fallback should raise ValueError for uko_l3') def step_then_resolve_with_parent_raises(context: Any, name: str) -> None: try: resolve_detail_level(name, context.detail_levels, context.parent_detail_levels) msg = "Expected ValueError" raise AssertionError(msg) except ValueError: pass @then('resolving "{name}" should raise ValueError for uko_l3') def step_then_resolve_raises(context: Any, name: str) -> None: try: resolve_detail_level(name, context.detail_levels) msg = "Expected ValueError" raise AssertionError(msg) except ValueError: pass @then("resolving empty string should raise ValueError for uko_l3") def step_then_resolve_empty_raises(context: Any) -> None: try: resolve_detail_level("", context.detail_levels) msg = "Expected ValueError" raise AssertionError(msg) except ValueError: pass @then("resolving whitespace string should raise ValueError for uko_l3") def step_then_resolve_whitespace_raises(context: Any) -> None: try: resolve_detail_level(" ", context.detail_levels) msg = "Expected ValueError" raise AssertionError(msg) except ValueError: pass # =========================================================================== # 4-layer chain resolution steps # =========================================================================== @given('a layer 0 map with "{entries}" for uko_l3') def step_given_layer0_map(context: Any, entries: str) -> None: context.layer0_map = _parse_entries(entries) @given('a layer 1 map with "{entries}" inheriting layer 0 for uko_l3') def step_given_layer1_map(context: Any, entries: str) -> None: context.layer1_map = _parse_entries(entries) @given('a layer 2 map with "{entries}" inheriting layer 1 for uko_l3') def step_given_layer2_map(context: Any, entries: str) -> None: context.layer2_map = _parse_entries(entries) @given('a layer 3 map with "{entries}" inheriting layer 2 for uko_l3') def step_given_layer3_map(context: Any, entries: str) -> None: context.layer3_map = _parse_entries(entries) def _resolve_through_chain( name: str, layer_maps: tuple[tuple[tuple[str, int], ...], ...], ) -> int: """Resolve *name* through a chain of maps using ``resolve_detail_level``. Walks the chain from most-specific (index 0) to least-specific, calling ``resolve_detail_level`` with the current map and the next map as the parent fallback. The last map has no parent. """ for idx, current_map in enumerate(layer_maps): parent = layer_maps[idx + 1] if idx + 1 < len(layer_maps) else None try: return resolve_detail_level(name, current_map, parent) except ValueError: # Not found in current + parent -- continue to next pair. continue raise ValueError(f"Unknown detail level: {name!r}") @then('resolving "{name}" through layer 3 should return {depth:d} for uko_l3') def step_then_resolve_through_chain(context: Any, name: str, depth: int) -> None: chain = ( context.layer3_map, context.layer2_map, context.layer1_map, context.layer0_map, ) resolved = _resolve_through_chain(name, chain) assert resolved == depth, f"Expected {name}={depth}, got {resolved}" @then('resolving "{name}" through layer 3 should raise ValueError for uko_l3') def step_then_resolve_chain_raises(context: Any, name: str) -> None: chain = ( context.layer3_map, context.layer2_map, context.layer1_map, context.layer0_map, ) try: _resolve_through_chain(name, chain) msg = f"Expected ValueError for {name!r}" raise AssertionError(msg) except ValueError: pass # =========================================================================== # TypeScript / Rust / Java DetailLevelMap steps # =========================================================================== @then("the TypeScript detail level map should have {count:d} entries for uko_l3") def step_then_ts_map_count(context: Any, count: int) -> None: assert len(TYPESCRIPT_DETAIL_LEVELS) == count @then( 'the TypeScript detail level "{name}" should resolve to depth {depth:d} for uko_l3' ) def step_then_ts_level_depth(context: Any, name: str, depth: int) -> None: resolved = resolve_detail_level(name, TYPESCRIPT_DETAIL_LEVELS) assert resolved == depth @then("the Rust detail level map should have {count:d} entries for uko_l3") def step_then_rs_map_count(context: Any, count: int) -> None: assert len(RUST_DETAIL_LEVELS) == count @then('the Rust detail level "{name}" should resolve to depth {depth:d} for uko_l3') def step_then_rs_level_depth(context: Any, name: str, depth: int) -> None: resolved = resolve_detail_level(name, RUST_DETAIL_LEVELS) assert resolved == depth @then("the Java detail level map should have {count:d} entries for uko_l3") def step_then_java_map_count(context: Any, count: int) -> None: assert len(JAVA_DETAIL_LEVELS) == count @then('the Java detail level "{name}" should resolve to depth {depth:d} for uko_l3') def step_then_java_level_depth(context: Any, name: str, depth: int) -> None: resolved = resolve_detail_level(name, JAVA_DETAIL_LEVELS) assert resolved == depth # =========================================================================== # Provenance contract steps # =========================================================================== @then('ProvenanceInfo should have field "{field}" for uko_l3') def step_then_provenance_has_field(context: Any, field: str) -> None: assert hasattr(ProvenanceInfo, "model_fields"), ( "ProvenanceInfo is not a Pydantic model" ) assert field in ProvenanceInfo.model_fields, ( f"ProvenanceInfo missing field {field!r}" ) # =========================================================================== # Layer 2 dependency steps # =========================================================================== @then( "the Python vocabulary should have at least {count:d} " "layer2 dependencies for uko_l3" ) def step_then_py_layer2_dep_count(context: Any, count: int) -> None: assert len(PYTHON_VOCABULARY.layer2_dependencies) >= count @then( "the Python vocabulary layer2 dependencies should include " 'uri containing "{fragment}" for uko_l3' ) def step_then_py_layer2_dep_uri(context: Any, fragment: str) -> None: uris = tuple(d.uri for d in PYTHON_VOCABULARY.layer2_dependencies) assert any(fragment in u for u in uris), ( f"No dependency URI containing {fragment!r} in {uris}" ) @then( "the Rust vocabulary layer2 dependencies should include " 'prefix "{prefix}" for uko_l3' ) def step_then_rs_layer2_dep_prefix(context: Any, prefix: str) -> None: prefixes = tuple(d.prefix for d in RUST_VOCABULARY.layer2_dependencies) assert prefix in prefixes, f"{prefix!r} not in {prefixes}" # =========================================================================== # Package import steps # =========================================================================== @then("importing PythonVocabulary from acms.uko should succeed for uko_l3") def step_then_import_python_vocab(context: Any) -> None: assert _PYTHON_VOCABULARY_import is not None @then("importing TypeScriptVocabulary from acms.uko should succeed for uko_l3") def step_then_import_ts_vocab(context: Any) -> None: assert _TYPESCRIPT_VOCABULARY_import is not None @then("importing RustVocabulary from acms.uko should succeed for uko_l3") def step_then_import_rust_vocab(context: Any) -> None: assert _RUST_VOCABULARY_import is not None @then("importing JavaVocabulary from acms.uko should succeed for uko_l3") def step_then_import_java_vocab(context: Any) -> None: assert _JAVA_VOCABULARY_import is not None @then("importing ProvenanceInfo from acms.uko should succeed for uko_l3") def step_then_import_provenance(context: Any) -> None: assert _ProvenanceInfo_import is not None @then("importing build_detail_level_map from acms.uko should succeed for uko_l3") def step_then_import_build_map(context: Any) -> None: assert _build_detail_level_map_import is not None @then("importing resolve_detail_level from acms.uko should succeed for uko_l3") def step_then_import_resolve(context: Any) -> None: assert _resolve_detail_level_import is not None