Files
temp/features/steps/uko_loader_coverage_boost_steps.py
T
freemo a808c395f9 test(coverage): add Behave BDD tests to improve unit test coverage across 53 source modules
Add 53 new .feature files and corresponding step definition files targeting
uncovered lines identified in build/coverage.xml. Fix AmbiguousStep conflicts
in 7 pre-existing step files by disambiguating step text.

New tests cover: ACP clients/facade, actor CLI/config, application container,
ACMS service/strategies, async worker, automation profile CLI, autonomy
guardrail, bridge, change model, config CLI/service, context service,
cross-plan correction, database models, decision service, decomposition
clustering/service, discovery handler, langchain chat provider, langgraph
nodes, materializers, multi-project service, plan apply/CLI/lifecycle/model/
preflight/resume/service, PostgreSQL analyzer, project CLI/context CLI,
provider registry, reactive application/route, repositories, resolver handler,
resource registry service, resume model, retry patterns, sandbox protocol,
server CLI, skill CLI/service, skills registry, subplan execution/service,
system CLI, UKO loader, UoW, and YAML template engine.

Closes #645
2026-03-09 13:01:58 -04:00

459 lines
15 KiB
Python

"""Behave step implementations for UKO loader coverage-boost scenarios.
Targets uncovered lines in src/cleveragents/application/services/uko_loader.py:
87-90, 101-102, 181, 249-259, 310, 361, 363, 370, 443, 451, 457, 469, 472,
517, 522, 531.
"""
from __future__ import annotations
import tempfile
from pathlib import Path
from behave import given, then, when
from behave.runner import Context
from cleveragents.application.services.uko_loader import (
UKOLoader,
UKOValidationError,
_detect_layer,
_extract_iri,
)
from cleveragents.domain.models.core.uko import (
UKONode,
UKOOntology,
UKOPrefix,
UKOVersion,
)
# ── helpers ────────────────────────────────────────────────────────────────
def _make_version(ver: str = "0.1.0") -> UKOVersion:
return UKOVersion(
version_iri=f"https://cleveragents.ai/ontology/uko/{ver}",
version=ver,
label="Test",
comment="Test",
)
def _make_prefix(prefix: str = "uko") -> UKOPrefix:
return UKOPrefix(
prefix=prefix,
uri="https://cleveragents.ai/ontology/uko#",
)
# ── Given ──────────────────────────────────────────────────────────────────
@given('a full IRI "{uri}"')
def step_given_full_iri(context: Context, uri: str) -> None:
context.uri = uri
@given('a value string "{value}"')
def step_given_value_string(context: Context, value: str) -> None:
context.value_string = value
@given("a TTL string with a property whose value starts with http:")
@given("a TTL string with a property whose value starts with http::")
def step_given_ttl_http_property(context: Context) -> None:
context.ttl_text = context.text
@given("a UKO ontology with a prefixed parent referencing an undefined prefix")
def step_given_undefined_prefix_parent(context: Context) -> None:
context.ontology = UKOOntology(
version=_make_version(),
prefixes=(_make_prefix("uko"),),
nodes=(
UKONode(
uri="https://cleveragents.ai/ontology/uko#Child",
rdf_type="owl:Class",
label="Child",
parent_uris=("nope:DoesNotExist",),
layer=0,
),
),
)
context.loader = UKOLoader()
@given("a UKO ontology with a prefixed parent referencing a non-existent node")
def step_given_nonexistent_prefixed_parent(context: Context) -> None:
context.ontology = UKOOntology(
version=_make_version(),
prefixes=(_make_prefix("uko"),),
nodes=(
UKONode(
uri="https://cleveragents.ai/ontology/uko#Child",
rdf_type="owl:Class",
label="Child",
parent_uris=("uko:Ghost",),
layer=0,
),
),
)
context.loader = UKOLoader()
@given("a UKO ontology where a node has an external parent URI")
def step_given_external_parent(context: Context) -> None:
context.ontology = UKOOntology(
version=_make_version(),
prefixes=(_make_prefix("uko"),),
nodes=(
UKONode(
uri="https://cleveragents.ai/ontology/uko#Leaf",
rdf_type="owl:Class",
label="Leaf",
# Parent points to a URI that does NOT exist in nodes
parent_uris=("https://example.org/external#Root",),
layer=0,
),
),
)
context.loader = UKOLoader()
@given("a UKO ontology with diamond inheritance shape")
def step_given_diamond(context: Context) -> None:
"""Diamond: D -> B, D -> C, B -> A, C -> A."""
ns = "https://cleveragents.ai/ontology/uko#"
context.ontology = UKOOntology(
version=_make_version(),
prefixes=(_make_prefix("uko"),),
nodes=(
UKONode(
uri=f"{ns}A",
rdf_type="owl:Class",
label="A",
layer=0,
),
UKONode(
uri=f"{ns}B",
rdf_type="owl:Class",
label="B",
parent_uris=(f"{ns}A",),
layer=0,
),
UKONode(
uri=f"{ns}C",
rdf_type="owl:Class",
label="C",
parent_uris=(f"{ns}A",),
layer=0,
),
UKONode(
uri=f"{ns}D",
rdf_type="owl:Class",
label="D",
parent_uris=(f"{ns}B", f"{ns}C"),
layer=0,
),
),
)
context.loader = UKOLoader()
@given("a UKO ontology where a node is its own parent")
def step_given_self_cycle(context: Context) -> None:
ns = "https://cleveragents.ai/ontology/uko#"
context.ontology = UKOOntology(
version=_make_version(),
prefixes=(_make_prefix("uko"),),
nodes=(
UKONode(
uri=f"{ns}SelfRef",
rdf_type="owl:Class",
label="SelfRef",
parent_uris=(f"{ns}SelfRef",),
layer=0,
),
),
)
context.loader = UKOLoader()
@given("a UKO ontology with some nodes")
def step_given_some_nodes(context: Context) -> None:
ns = "https://cleveragents.ai/ontology/uko#"
context.ontology = UKOOntology(
version=_make_version(),
prefixes=(_make_prefix("uko"),),
nodes=(
UKONode(
uri=f"{ns}Existing",
rdf_type="owl:Class",
label="Existing",
layer=0,
),
),
)
context.loader = UKOLoader()
@given("a TTL string with dots in bare subject names:")
def step_given_ttl_dots_in_bare_names(context: Context) -> None:
context.ttl_text = context.text
@given("a TTL string without trailing period:")
def step_given_ttl_no_trailing_period(context: Context) -> None:
context.ttl_text = context.text
@given("a TTL string with a stray single token:")
def step_given_ttl_stray_token(context: Context) -> None:
context.ttl_text = context.text
@given("a TTL string with trailing semicolons and bare keywords:")
def step_given_ttl_trailing_semicolons(context: Context) -> None:
context.ttl_text = context.text
@given("a UKO loader instance")
def step_given_loader_instance(context: Context) -> None:
context.loader = UKOLoader()
@given("a temporary TTL file with valid content")
def step_given_temp_ttl_file(context: Context) -> None:
ttl_content = (
"@prefix uko: <https://cleveragents.ai/ontology/uko#> .\n"
"@prefix owl: <http://www.w3.org/2002/07/owl#> .\n"
"@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n"
"\n"
"uko: a owl:Ontology ;\n"
" owl:versionIRI <https://cleveragents.ai/ontology/uko/0.1.0> ;\n"
' rdfs:label "Temp" ;\n'
' rdfs:comment "Temp test" .\n'
"\n"
"uko:Widget a owl:Class ;\n"
' rdfs:label "Widget" .\n'
)
with tempfile.NamedTemporaryFile(
mode="w",
suffix=".ttl",
delete=False,
encoding="utf-8",
) as tmp:
tmp.write(ttl_content)
context.ttl_tmp_path = Path(tmp.name)
context.loader = UKOLoader()
@given("a TTL string using rdf:type for ontology declaration:")
def step_given_rdf_type_ontology(context: Context) -> None:
context.ttl_text = context.text
# ── When ───────────────────────────────────────────────────────────────────
@when("I detect the layer for that URI")
def step_when_detect_layer(context: Context) -> None:
context.detected_layer = _detect_layer(context.uri)
@when("I extract the IRI from that value")
def step_when_extract_iri(context: Context) -> None:
context.extracted_iri = _extract_iri(context.value_string)
@when("I parse the TTL coverage string")
def step_when_parse_ttl_coverage(context: Context) -> None:
loader = UKOLoader()
context.parsed_ontology = loader.load_from_string(context.ttl_text)
context.loader = loader
@when("I run validation on the ontology")
def step_when_run_validation(context: Context) -> None:
loader: UKOLoader = context.loader
context.validation_errors = loader.validate(context.ontology)
@when("I resolve the inheritance chain for the node with external parent")
def step_when_resolve_external_parent(context: Context) -> None:
loader: UKOLoader = context.loader
context.inheritance_chain = loader.resolve_inheritance(
context.ontology,
"https://cleveragents.ai/ontology/uko#Leaf",
)
@when("I resolve the inheritance chain for the diamond leaf")
def step_when_resolve_diamond_leaf(context: Context) -> None:
loader: UKOLoader = context.loader
context.inheritance_chain = loader.resolve_inheritance(
context.ontology,
"https://cleveragents.ai/ontology/uko#D",
)
@when("I attempt to resolve inheritance for the self-referencing node")
def step_when_resolve_self_cycle(context: Context) -> None:
loader: UKOLoader = context.loader
try:
loader.resolve_inheritance(
context.ontology,
"https://cleveragents.ai/ontology/uko#SelfRef",
)
context.cycle_error = None
except UKOValidationError as exc:
context.cycle_error = str(exc)
@when("I resolve the inheritance chain for a URI not in the ontology")
def step_when_resolve_missing_uri(context: Context) -> None:
loader: UKOLoader = context.loader
context.inheritance_chain = loader.resolve_inheritance(
context.ontology,
"https://cleveragents.ai/ontology/uko#NoSuchNode",
)
@when('I resolve the prefixed URI "{uri}" with an empty prefix map')
def step_when_resolve_prefixed_uri(context: Context, uri: str) -> None:
context.resolved_uri = context.loader._resolve_uri(uri, {})
@when("I extract version string from an empty IRI")
def step_when_extract_empty_version(context: Context) -> None:
context.extracted_version = context.loader._extract_version_string("")
@when('I extract version string from "{iri}"')
def step_when_extract_version(context: Context, iri: str) -> None:
context.extracted_version = context.loader._extract_version_string(iri)
@when("I load the ontology from the temporary file")
def step_when_load_from_file(context: Context) -> None:
context.loaded_ontology = context.loader.load(context.ttl_tmp_path)
# ── Then ───────────────────────────────────────────────────────────────────
@then("the detected layer should be {layer:d}")
def step_then_detected_layer(context: Context, layer: int) -> None:
assert context.detected_layer == layer, (
f"Expected layer {layer}, got {context.detected_layer}"
)
@then('the extracted IRI should be "{expected}"')
def step_then_extracted_iri(context: Context, expected: str) -> None:
assert context.extracted_iri == expected, (
f"Expected '{expected}', got '{context.extracted_iri}'"
)
@then('the parsed ontology should contain a node labeled "{label}"')
def step_then_parsed_has_node(context: Context, label: str) -> None:
labels = [n.label for n in context.parsed_ontology.nodes]
assert label in labels, f"Label '{label}' not found in {labels}"
@then('node "{label}" property "{prop}" should equal "{expected}"')
def step_then_node_prop_equals(
context: Context,
label: str,
prop: str,
expected: str,
) -> None:
matching = [n for n in context.parsed_ontology.nodes if n.label == label]
assert matching, f"No node labeled '{label}'"
prop_dict = dict(matching[0].properties)
assert prop in prop_dict, (
f"Property '{prop}' not on '{label}'; have {list(prop_dict)}"
)
assert prop_dict[prop] == expected, (
f"Expected '{expected}', got '{prop_dict[prop]}'"
)
@then('the validation result should mention "{text}"')
def step_then_validation_mentions(context: Context, text: str) -> None:
combined = " ".join(context.validation_errors)
assert text in combined, f"Expected '{text}' in errors: {context.validation_errors}"
@then("the chain should contain the starting node only")
def step_then_chain_single(context: Context) -> None:
assert len(context.inheritance_chain) == 1, (
f"Expected 1 node, got {len(context.inheritance_chain)}"
)
assert context.inheritance_chain[0].label == "Leaf"
@then("the chain should include each ancestor at most once")
def step_then_chain_no_duplicates(context: Context) -> None:
uris = [n.uri for n in context.inheritance_chain]
assert len(uris) == len(set(uris)), f"Duplicate nodes found: {uris}"
# Diamond D->B,C->A => chain should be D, B, C, A (4 unique)
assert len(uris) == 4, f"Expected 4 nodes in diamond, got {len(uris)}: {uris}"
@then("a cycle error should be raised for that node")
def step_then_self_cycle_error(context: Context) -> None:
assert context.cycle_error is not None, "Expected cycle error but got none"
assert "Cycle" in context.cycle_error or "cycle" in context.cycle_error, (
f"Error message does not mention cycle: {context.cycle_error}"
)
@then("the chain should be empty")
def step_then_chain_empty(context: Context) -> None:
assert len(context.inheritance_chain) == 0, (
f"Expected empty chain, got {len(context.inheritance_chain)}"
)
@then('the parsed ontology version should be "{version}"')
def step_then_parsed_version(context: Context, version: str) -> None:
assert context.parsed_ontology.version.version == version, (
f"Expected '{version}', got '{context.parsed_ontology.version.version}'"
)
@then('the resolved URI should be "{expected}"')
def step_then_resolved_uri(context: Context, expected: str) -> None:
assert context.resolved_uri == expected, (
f"Expected '{expected}', got '{context.resolved_uri}'"
)
@then('the extracted version should be "{expected}"')
def step_then_extracted_version(context: Context, expected: str) -> None:
assert context.extracted_version == expected, (
f"Expected '{expected}', got '{context.extracted_version}'"
)
@then('the extracted version should be ""')
def step_then_extracted_version_empty(context: Context) -> None:
assert context.extracted_version == "", (
f"Expected empty string, got '{context.extracted_version}'"
)
@then('the loaded ontology should have version "{version}"')
def step_then_loaded_version(context: Context, version: str) -> None:
assert context.loaded_ontology.version.version == version, (
f"Expected '{version}', got '{context.loaded_ontology.version.version}'"
)
@then('the parsed ontology version label should be "{label}"')
def step_then_parsed_version_label(context: Context, label: str) -> None:
assert context.parsed_ontology.version.label == label, (
f"Expected '{label}', got '{context.parsed_ontology.version.label}'"
)