3c014a9565
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 19s
CI / e2e_tests (pull_request) Successful in 23s
CI / typecheck (pull_request) Successful in 32s
CI / security (pull_request) Successful in 43s
CI / unit_tests (pull_request) Successful in 2m2s
CI / integration_tests (pull_request) Successful in 2m41s
CI / docker (pull_request) Successful in 49s
CI / coverage (pull_request) Successful in 5m32s
CI / benchmark-regression (pull_request) Successful in 36m2s
428 lines
15 KiB
Python
428 lines
15 KiB
Python
"""Behave steps for UKO Layer 2 vocabulary model and registry tests.
|
|
|
|
All step definitions use the ``for uko_l2`` suffix to avoid
|
|
AmbiguousStep collisions with other feature files.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from _uko_l2_test_helpers import capture_error
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
from pydantic import ValidationError
|
|
|
|
from cleveragents.acms.uko.vocabularies import (
|
|
VocabularyClass,
|
|
VocabularyProperty,
|
|
get_func_vocabulary,
|
|
get_oo_vocabulary,
|
|
get_proc_vocabulary,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# VocabularyClass -- construction
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('a simple vocabulary class with uri "{uri}" and label "{label}" for uko_l2')
|
|
def step_create_vocab_class(ctx: Context, uri: str, label: str) -> None:
|
|
ctx.vocab_class = VocabularyClass(uri=uri, label=label)
|
|
|
|
|
|
@given(
|
|
'a vocabulary class with uri "{uri}" and label "{label}" '
|
|
'and parents "{parents}" for uko_l2'
|
|
)
|
|
def step_create_vocab_class_parents(
|
|
ctx: Context, uri: str, label: str, parents: str
|
|
) -> None:
|
|
parent_uris = tuple(p.strip() for p in parents.split(","))
|
|
ctx.vocab_class = VocabularyClass(uri=uri, label=label, parent_uris=parent_uris)
|
|
|
|
|
|
@then('the vocabulary class uri should be "{expected}" for uko_l2')
|
|
def step_check_vocab_class_uri(ctx: Context, expected: str) -> None:
|
|
assert ctx.vocab_class.uri == expected
|
|
|
|
|
|
@then('the vocabulary class label should be "{expected}" for uko_l2')
|
|
def step_check_vocab_class_label(ctx: Context, expected: str) -> None:
|
|
assert ctx.vocab_class.label == expected
|
|
|
|
|
|
@then("the vocabulary class comment should be empty for uko_l2")
|
|
def step_check_vocab_class_comment_empty(ctx: Context) -> None:
|
|
assert ctx.vocab_class.comment == ""
|
|
|
|
|
|
@then("the vocabulary class parent_uris should be empty for uko_l2")
|
|
def step_check_vocab_class_parents_empty(ctx: Context) -> None:
|
|
assert ctx.vocab_class.parent_uris == ()
|
|
|
|
|
|
@then("the vocabulary class should have {count:d} parent URIs for uko_l2")
|
|
def step_check_vocab_class_parent_count(ctx: Context, count: int) -> None:
|
|
assert len(ctx.vocab_class.parent_uris) == count
|
|
|
|
|
|
@then('the vocabulary class parent {index:d} should be "{expected}" for uko_l2')
|
|
def step_check_vocab_class_parent_idx(ctx: Context, index: int, expected: str) -> None:
|
|
assert ctx.vocab_class.parent_uris[index] == expected
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# VocabularyClass -- validation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I create a vocabulary class with empty uri for uko_l2")
|
|
def step_create_vocab_class_empty_uri(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyClass(uri="", label="Test")
|
|
|
|
|
|
@when("I create a vocabulary class with whitespace uri for uko_l2")
|
|
def step_create_vocab_class_whitespace_uri(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyClass(uri=" ", label="Test")
|
|
|
|
|
|
@when("I create a vocabulary class with empty label for uko_l2")
|
|
def step_create_vocab_class_empty_label(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyClass(uri="http://example.org/test", label="")
|
|
|
|
|
|
@then("a validation error should be raised for uko_l2")
|
|
def step_check_validation_error(ctx: Context) -> None:
|
|
assert ctx.error is not None, "Expected a validation error but none was raised"
|
|
assert isinstance(ctx.error, (ValidationError, ValueError))
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# VocabularyClass -- frozen
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I try to mutate the vocabulary class uri for uko_l2")
|
|
def step_mutate_vocab_class(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
ctx.vocab_class.uri = "http://changed" # type: ignore[misc] # deliberate mutation to test frozen model
|
|
|
|
|
|
@then("a frozen mutation error should be raised for uko_l2")
|
|
def step_check_frozen_error(ctx: Context) -> None:
|
|
assert ctx.error is not None, "Expected a frozen mutation error"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# VocabularyProperty -- construction
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given(
|
|
'a basic vocabulary property with uri "{uri}" and label "{label}" '
|
|
'and domain "{domain}" and range "{range_uri}" for uko_l2'
|
|
)
|
|
def step_create_vocab_property(
|
|
ctx: Context, uri: str, label: str, domain: str, range_uri: str
|
|
) -> None:
|
|
ctx.vocab_property = VocabularyProperty(
|
|
uri=uri, label=label, domain_uri=domain, range_uri=range_uri
|
|
)
|
|
|
|
|
|
@given(
|
|
'a vocabulary property with uri "{uri}" and label "{label}" '
|
|
'and domain "{domain}" and range "{range_uri}" '
|
|
'and sub_property_of "{sub_prop}" for uko_l2'
|
|
)
|
|
def step_create_vocab_property_sub(
|
|
ctx: Context,
|
|
uri: str,
|
|
label: str,
|
|
domain: str,
|
|
range_uri: str,
|
|
sub_prop: str,
|
|
) -> None:
|
|
ctx.vocab_property = VocabularyProperty(
|
|
uri=uri,
|
|
label=label,
|
|
domain_uri=domain,
|
|
range_uri=range_uri,
|
|
sub_property_of=sub_prop,
|
|
)
|
|
|
|
|
|
@then('the vocabulary property uri should be "{expected}" for uko_l2')
|
|
def step_check_vocab_prop_uri(ctx: Context, expected: str) -> None:
|
|
assert ctx.vocab_property.uri == expected
|
|
|
|
|
|
@then('the vocabulary property label should be "{expected}" for uko_l2')
|
|
def step_check_vocab_prop_label(ctx: Context, expected: str) -> None:
|
|
assert ctx.vocab_property.label == expected
|
|
|
|
|
|
@then("the vocabulary property sub_property_of should be empty for uko_l2")
|
|
def step_check_vocab_prop_sub_empty(ctx: Context) -> None:
|
|
assert ctx.vocab_property.sub_property_of is None
|
|
|
|
|
|
@then('the vocabulary property sub_property_of should be "{expected}" for uko_l2')
|
|
def step_check_vocab_prop_sub(ctx: Context, expected: str) -> None:
|
|
assert ctx.vocab_property.sub_property_of == expected
|
|
|
|
|
|
@when("I create a vocabulary property with empty uri for uko_l2")
|
|
def step_create_vocab_prop_empty_uri(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyProperty(
|
|
uri="",
|
|
label="test",
|
|
domain_uri="https://example.com/d",
|
|
range_uri="https://example.com/r",
|
|
)
|
|
|
|
|
|
@when("I try to mutate the vocabulary property uri for uko_l2")
|
|
def step_mutate_vocab_property(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
ctx.vocab_property.uri = "http://changed" # type: ignore[misc] # deliberate mutation to test frozen model
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Paradigm vocabularies -- OO, Func, Proc
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("the uko-oo vocabulary for uko_l2")
|
|
def step_get_oo_vocab(ctx: Context) -> None:
|
|
ctx.vocabulary = get_oo_vocabulary()
|
|
|
|
|
|
@given("the uko-func vocabulary for uko_l2")
|
|
def step_get_func_vocab(ctx: Context) -> None:
|
|
ctx.vocabulary = get_func_vocabulary()
|
|
|
|
|
|
@given("the uko-proc vocabulary for uko_l2")
|
|
def step_get_proc_vocab(ctx: Context) -> None:
|
|
ctx.vocabulary = get_proc_vocabulary()
|
|
|
|
|
|
@then('the vocabulary should have prefix "{expected}" for uko_l2')
|
|
def step_check_vocab_prefix(ctx: Context, expected: str) -> None:
|
|
assert ctx.vocabulary.prefix == expected
|
|
|
|
|
|
@then('the vocabulary should have IRI "{expected}" for uko_l2')
|
|
def step_check_vocab_iri(ctx: Context, expected: str) -> None:
|
|
assert ctx.vocabulary.iri == expected
|
|
|
|
|
|
@then("the vocabulary should have layer {layer:d} for uko_l2")
|
|
def step_check_vocab_layer(ctx: Context, layer: int) -> None:
|
|
assert ctx.vocabulary.layer == layer
|
|
|
|
|
|
@then("the vocabulary should have {count:d} classes for uko_l2")
|
|
def step_check_vocab_class_count(ctx: Context, count: int) -> None:
|
|
assert len(ctx.vocabulary.classes) == count
|
|
|
|
|
|
@then("the vocabulary should have {count:d} properties for uko_l2")
|
|
def step_check_vocab_prop_count(ctx: Context, count: int) -> None:
|
|
assert len(ctx.vocabulary.properties) == count
|
|
|
|
|
|
@then('the vocabulary should contain class "{name}" for uko_l2')
|
|
def step_check_vocab_has_class(ctx: Context, name: str) -> None:
|
|
found = ctx.vocabulary.get_class(name)
|
|
assert found is not None, f"Class '{name}' not found"
|
|
|
|
|
|
@then('the vocabulary should contain property "{name}" for uko_l2')
|
|
def step_check_vocab_has_property(ctx: Context, name: str) -> None:
|
|
found = ctx.vocabulary.get_property(name)
|
|
assert found is not None, f"Property '{name}' not found"
|
|
|
|
|
|
@when('I look up class "{name}" for uko_l2')
|
|
def step_lookup_class(ctx: Context, name: str) -> None:
|
|
ctx.looked_up_class = ctx.vocabulary.get_class(name)
|
|
|
|
|
|
@when('I look up property "{name}" for uko_l2')
|
|
def step_lookup_property(ctx: Context, name: str) -> None:
|
|
ctx.looked_up_property = ctx.vocabulary.get_property(name)
|
|
|
|
|
|
@then('the class should have parent "{expected}" for uko_l2')
|
|
def step_check_class_parent(ctx: Context, expected: str) -> None:
|
|
assert ctx.looked_up_class is not None, "Class was not looked up"
|
|
assert expected in ctx.looked_up_class.parent_uris, (
|
|
f"Expected parent '{expected}' not found in {ctx.looked_up_class.parent_uris}"
|
|
)
|
|
|
|
|
|
@then('the property domain should be "{expected}" for uko_l2')
|
|
def step_check_prop_domain(ctx: Context, expected: str) -> None:
|
|
assert ctx.looked_up_property is not None
|
|
assert ctx.looked_up_property.domain_uri == expected
|
|
|
|
|
|
@then('the property range should be "{expected}" for uko_l2')
|
|
def step_check_prop_range(ctx: Context, expected: str) -> None:
|
|
assert ctx.looked_up_property is not None
|
|
assert ctx.looked_up_property.range_uri == expected
|
|
|
|
|
|
@then('the property sub_property_of should be "{expected}" for uko_l2')
|
|
def step_check_prop_sub_property(ctx: Context, expected: str) -> None:
|
|
assert ctx.looked_up_property is not None
|
|
assert ctx.looked_up_property.sub_property_of == expected
|
|
|
|
|
|
@then("the looked up class should not be None for uko_l2")
|
|
def step_class_not_none(ctx: Context) -> None:
|
|
assert ctx.looked_up_class is not None
|
|
|
|
|
|
@then("the looked up class should be None for uko_l2")
|
|
def step_class_is_none(ctx: Context) -> None:
|
|
assert ctx.looked_up_class is None
|
|
|
|
|
|
@then('the looked up class uri should be "{expected}" for uko_l2')
|
|
def step_check_looked_up_class_uri(ctx: Context, expected: str) -> None:
|
|
assert ctx.looked_up_class is not None
|
|
assert ctx.looked_up_class.uri == expected
|
|
|
|
|
|
@then("the looked up property should not be None for uko_l2")
|
|
def step_prop_not_none(ctx: Context) -> None:
|
|
assert ctx.looked_up_property is not None
|
|
|
|
|
|
@then("the looked up property should be None for uko_l2")
|
|
def step_prop_is_none(ctx: Context) -> None:
|
|
assert ctx.looked_up_property is None
|
|
|
|
|
|
@then("class_uris should have {count:d} entries for uko_l2")
|
|
def step_check_class_uris_count(ctx: Context, count: int) -> None:
|
|
assert len(ctx.vocabulary.class_uris()) == count
|
|
|
|
|
|
@then("property_uris should have {count:d} entries for uko_l2")
|
|
def step_check_property_uris_count(ctx: Context, count: int) -> None:
|
|
assert len(ctx.vocabulary.property_uris()) == count
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Review findings: M8 — empty label/domain/range rejection
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I create a vocabulary property with empty label for uko_l2")
|
|
def step_create_vocab_prop_empty_label(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyProperty(
|
|
uri="https://example.com/prop",
|
|
label="",
|
|
domain_uri="https://example.com/d",
|
|
range_uri="https://example.com/r",
|
|
)
|
|
|
|
|
|
@when("I create a vocabulary property with empty domain for uko_l2")
|
|
def step_create_vocab_prop_empty_domain(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyProperty(
|
|
uri="https://example.com/prop",
|
|
label="test",
|
|
domain_uri="",
|
|
range_uri="https://example.com/r",
|
|
)
|
|
|
|
|
|
@when("I create a vocabulary property with empty range for uko_l2")
|
|
def step_create_vocab_prop_empty_range(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyProperty(
|
|
uri="https://example.com/prop",
|
|
label="test",
|
|
domain_uri="https://example.com/d",
|
|
range_uri="",
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Review findings: M9 — invalid URI scheme rejection
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I create a vocabulary class with non-http uri for uko_l2")
|
|
def step_create_vocab_class_non_http(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyClass(
|
|
uri="ftp://example.com/BadClass",
|
|
label="BadClass",
|
|
)
|
|
|
|
|
|
@when("I create a vocabulary property with non-http uri for uko_l2")
|
|
def step_create_vocab_prop_non_http(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyProperty(
|
|
uri="urn:example:bad",
|
|
label="bad",
|
|
domain_uri="https://example.com/d",
|
|
range_uri="https://example.com/r",
|
|
)
|
|
|
|
|
|
@when("I create a vocabulary class with non-http parent uri for uko_l2")
|
|
def step_create_vocab_class_non_http_parent(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyClass(
|
|
uri="https://example.com/Good",
|
|
label="Good",
|
|
parent_uris=("ftp://example.com/BadParent",),
|
|
)
|
|
|
|
|
|
@when("I create a vocabulary property with non-http domain uri for uko_l2")
|
|
def step_create_vocab_prop_non_http_domain(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyProperty(
|
|
uri="https://example.com/prop",
|
|
label="prop",
|
|
domain_uri="ftp://example.com/BadDomain",
|
|
range_uri="https://example.com/r",
|
|
)
|
|
|
|
|
|
@when("I create a vocabulary property with non-http range uri for uko_l2")
|
|
def step_create_vocab_prop_non_http_range(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyProperty(
|
|
uri="https://example.com/prop",
|
|
label="prop",
|
|
domain_uri="https://example.com/d",
|
|
range_uri="file:///etc/passwd",
|
|
)
|
|
|
|
|
|
@when("I create a vocabulary property with non-http sub_property_of for uko_l2")
|
|
def step_create_vocab_prop_non_http_subprop(ctx: Context) -> None:
|
|
with capture_error(ctx, ValidationError):
|
|
VocabularyProperty(
|
|
uri="https://example.com/prop",
|
|
label="prop",
|
|
domain_uri="https://example.com/d",
|
|
range_uri="https://example.com/r",
|
|
sub_property_of="javascript:alert(1)",
|
|
)
|