Files
temp/features/steps/uko_layer3_detail_map_steps.py
hamza.khyari 89eaee008d feat(acms): implement UKO Layer 3 Technology Vocabularies (uko-py, uko-ts, uko-rs, uko-java)
Implement Layer 3 technology-specific UKO vocabulary extensions for Python,
TypeScript, Rust, and Java with language-specific classes, properties, and
DetailLevelMap insertions.

- 4 OWL/Turtle ontology files with language-specific semantic classes
- DetailLevelMap insertion logic with correct integer reassignment
- Provenance contract (5 required fields per spec)
- Full 4-layer chain resolution (Layer 3 -> Layer 2 -> Layer 1 -> Layer 0)
- Comprehensive Behave test suite (63 scenarios)

ISSUES CLOSED: #576
2026-03-16 12:11:08 +00:00

404 lines
14 KiB
Python

"""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