ba7870da3e
CI / helm (pull_request) Successful in 55s
CI / e2e_tests (pull_request) Failing after 1m5s
CI / build (pull_request) Failing after 44s
CI / security (pull_request) Failing after 1m7s
CI / typecheck (pull_request) Failing after 1m7s
CI / integration_tests (pull_request) Failing after 1m6s
CI / push-validation (pull_request) Successful in 1m21s
CI / lint (pull_request) Successful in 1m46s
CI / quality (pull_request) Successful in 2m5s
CI / unit_tests (pull_request) Failing after 7m12s
CI / coverage (pull_request) Has been skipped
CI / docker (pull_request) Has been skipped
CI / benchmark-publish (pull_request) Has been skipped
CI / status-check (pull_request) Failing after 3s
CI / benchmark-regression (pull_request) Failing after 31s
767 lines
28 KiB
Python
767 lines
28 KiB
Python
"""Step implementations for UKO Graph Persistence coverage tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import json
|
|
import tempfile
|
|
from collections.abc import Generator
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from unittest.mock import MagicMock
|
|
|
|
import structlog
|
|
from behave import given, then, when
|
|
|
|
from cleveragents.application.services import uko_persistence as _uko_mod
|
|
from cleveragents.application.services.uko_persistence import (
|
|
InMemoryPersistenceBackend,
|
|
JSONFilePersistenceBackend,
|
|
UKOGraphPersistence,
|
|
)
|
|
from cleveragents.domain.models.acms.index_backends import GraphIndexBackend
|
|
|
|
|
|
# =================================================================
|
|
# Fixtures and helpers
|
|
# =================================================================
|
|
|
|
|
|
def create_mock_graph_backend() -> MagicMock:
|
|
"""Create a mock GraphIndexBackend."""
|
|
backend = MagicMock(spec=GraphIndexBackend)
|
|
return backend
|
|
|
|
|
|
def create_sample_triples() -> list[dict[str, str]]:
|
|
"""Create sample valid triples."""
|
|
return [
|
|
{
|
|
"subject": "uko://code/module/foo",
|
|
"predicate": "uko:type",
|
|
"object": "uko-py:Module",
|
|
},
|
|
{
|
|
"subject": "uko://code/class/Foo",
|
|
"predicate": "uko:type",
|
|
"object": "uko-py:Class",
|
|
},
|
|
]
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _capture_uko_logs() -> Generator[list[dict[str, Any]]]:
|
|
"""Capture structlog entries emitted by the uko_persistence module.
|
|
|
|
Uses ``structlog.testing.LogCapture`` and temporarily replaces the
|
|
module-level logger to work around ``cache_logger_on_first_use=True``.
|
|
This approach avoids modifying global structlog configuration.
|
|
"""
|
|
cap = structlog.testing.LogCapture()
|
|
old_logger = _uko_mod.logger
|
|
# Build a fresh bound logger that routes through LogCapture without
|
|
# touching the global structlog configuration.
|
|
bound = structlog.wrap_logger(
|
|
structlog.PrintLogger(),
|
|
processors=[cap],
|
|
wrapper_class=structlog.stdlib.BoundLogger,
|
|
cache_logger_on_first_use=False,
|
|
)
|
|
_uko_mod.logger = bound # type: ignore[assignment]
|
|
try:
|
|
yield cap.entries
|
|
finally:
|
|
_uko_mod.logger = old_logger
|
|
|
|
|
|
# =================================================================
|
|
# JSONFilePersistenceBackend.load() — corrupted JSON file handling
|
|
# =================================================================
|
|
|
|
|
|
@given("uko a JSON file persistence backend in a non-existent temp directory")
|
|
def step_json_file_persistence_backend_nonexistent(context: Any) -> None:
|
|
"""Create a JSON file persistence backend in a non-existent temp directory."""
|
|
temp_dir = Path(tempfile.gettempdir()) / "uko_test_nonexistent" / "subdir"
|
|
context.uko_temp_dir = str(temp_dir)
|
|
context.uko_json_backend = JSONFilePersistenceBackend(temp_dir)
|
|
# Set uko_test_triples so parameterized save steps work without a prior
|
|
# "uko an in-memory persistence backend" step.
|
|
context.uko_test_triples = create_sample_triples()
|
|
|
|
|
|
@given("uko the JSON file contains corrupted JSON data")
|
|
def step_json_file_corrupted(context: Any) -> None:
|
|
"""Create a JSON file with corrupted data."""
|
|
backend: JSONFilePersistenceBackend = context.uko_json_backend
|
|
path = backend._path_for("local/test")
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text("{invalid json content", encoding="utf-8")
|
|
|
|
|
|
@given('uko the JSON file contains valid JSON but missing "triples" key')
|
|
def step_json_file_missing_triples_key(context: Any) -> None:
|
|
"""Create a JSON file with valid JSON but missing triples key."""
|
|
backend: JSONFilePersistenceBackend = context.uko_json_backend
|
|
path = backend._path_for("local/test")
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
data = {"version": "1", "project": "local/test"}
|
|
path.write_text(json.dumps(data), encoding="utf-8")
|
|
|
|
|
|
@given("uko the JSON file exists but is unreadable (permission denied)")
|
|
def step_json_file_unreadable(context: Any) -> None:
|
|
"""Create a JSON file and make it unreadable."""
|
|
backend: JSONFilePersistenceBackend = context.uko_json_backend
|
|
path = backend._path_for("local/test")
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(
|
|
json.dumps({"version": "1", "project": "local/test", "triples": []}),
|
|
encoding="utf-8",
|
|
)
|
|
path.chmod(0o000)
|
|
context.uko_unreadable_file = path
|
|
|
|
|
|
@when('uko I load from the JSON file backend for project "local/test"')
|
|
def step_load_from_json_backend(context: Any) -> None:
|
|
"""Load from JSON file backend, capturing any log output."""
|
|
backend: JSONFilePersistenceBackend = context.uko_json_backend
|
|
with _capture_uko_logs() as entries:
|
|
context.uko_load_result = backend.load("local/test")
|
|
context.uko_captured_logs = entries
|
|
|
|
|
|
@then("uko the result should be an empty list")
|
|
def step_result_is_empty_list(context: Any) -> None:
|
|
"""Verify result is an empty list."""
|
|
assert context.uko_load_result == [], (
|
|
f"Expected empty list, got {context.uko_load_result}"
|
|
)
|
|
|
|
|
|
@then("uko a warning should be logged for load failure")
|
|
def step_warning_logged_for_load_failure(context: Any) -> None:
|
|
"""Verify a warning was logged for load failure."""
|
|
logs: list[dict[str, Any]] = context.uko_captured_logs
|
|
warning_events = [
|
|
e
|
|
for e in logs
|
|
if e.get("log_level") == "warning" and "load_failed" in e.get("event", "")
|
|
]
|
|
assert warning_events, (
|
|
f"Expected a warning log with 'load_failed' event, got: {logs}"
|
|
)
|
|
|
|
|
|
# =================================================================
|
|
# UKOGraphPersistence.save() — query failure path
|
|
# =================================================================
|
|
|
|
|
|
@given("uko a graph backend that raises an exception on query")
|
|
def step_graph_backend_raises_on_query(context: Any) -> None:
|
|
"""Create a mock graph backend that raises on query."""
|
|
backend = create_mock_graph_backend()
|
|
backend.query.side_effect = RuntimeError("Query failed")
|
|
context.uko_failing_graph_backend = backend
|
|
|
|
|
|
@given("uko a UKOGraphPersistence service with the failing backend")
|
|
def step_persistence_service_with_failing_backend(context: Any) -> None:
|
|
"""Create a UKOGraphPersistence service with a failing backend."""
|
|
graph_backend = context.uko_failing_graph_backend
|
|
persistence_backend = InMemoryPersistenceBackend()
|
|
context.uko_persistence_service = UKOGraphPersistence(
|
|
graph_backend=graph_backend,
|
|
project="local/test",
|
|
persistence_backend=persistence_backend,
|
|
)
|
|
|
|
|
|
@when("uko I call save on the persistence service")
|
|
def step_call_save_on_persistence_service(context: Any) -> None:
|
|
"""Call save on the persistence service, capturing log output."""
|
|
service: UKOGraphPersistence = context.uko_persistence_service
|
|
with _capture_uko_logs() as entries:
|
|
context.uko_save_result = service.save()
|
|
context.uko_captured_logs = entries
|
|
|
|
|
|
@then("uko a warning should be logged for query failure")
|
|
def step_warning_logged_for_query_failure(context: Any) -> None:
|
|
"""Verify a warning was logged for query failure."""
|
|
logs: list[dict[str, Any]] = context.uko_captured_logs
|
|
warning_events = [
|
|
e
|
|
for e in logs
|
|
if e.get("log_level") == "warning" and "query_failed" in e.get("event", "")
|
|
]
|
|
assert warning_events, (
|
|
f"Expected a warning log with 'query_failed' event, got: {logs}"
|
|
)
|
|
|
|
|
|
# =================================================================
|
|
# UKOGraphPersistence.restore() — incomplete triple handling
|
|
# =================================================================
|
|
|
|
|
|
@given('uko a persistence backend with triples missing the "subject" key')
|
|
def step_persistence_backend_missing_subject(context: Any) -> None:
|
|
"""Create a persistence backend with triples missing subject."""
|
|
backend = InMemoryPersistenceBackend()
|
|
backend.save(
|
|
"local/test",
|
|
[
|
|
{
|
|
"predicate": "uko:type",
|
|
"object": "uko-py:Module",
|
|
}
|
|
],
|
|
)
|
|
context.uko_persistence_backend = backend
|
|
|
|
|
|
@given('uko a persistence backend with triples missing the "predicate" key')
|
|
def step_persistence_backend_missing_predicate(context: Any) -> None:
|
|
"""Create a persistence backend with triples missing predicate."""
|
|
backend = InMemoryPersistenceBackend()
|
|
backend.save(
|
|
"local/test",
|
|
[
|
|
{
|
|
"subject": "uko://code/module/foo",
|
|
"object": "uko-py:Module",
|
|
}
|
|
],
|
|
)
|
|
context.uko_persistence_backend = backend
|
|
|
|
|
|
@given('uko a persistence backend with triples missing the "object" key')
|
|
def step_persistence_backend_missing_object(context: Any) -> None:
|
|
"""Create a persistence backend with triples missing object."""
|
|
backend = InMemoryPersistenceBackend()
|
|
backend.save(
|
|
"local/test",
|
|
[
|
|
{
|
|
"subject": "uko://code/module/foo",
|
|
"predicate": "uko:type",
|
|
}
|
|
],
|
|
)
|
|
context.uko_persistence_backend = backend
|
|
|
|
|
|
@given("uko a persistence backend with triples having empty subject value")
|
|
def step_persistence_backend_empty_subject(context: Any) -> None:
|
|
"""Create a persistence backend with triples having empty subject."""
|
|
backend = InMemoryPersistenceBackend()
|
|
backend.save(
|
|
"local/test",
|
|
[
|
|
{
|
|
"subject": "",
|
|
"predicate": "uko:type",
|
|
"object": "uko-py:Module",
|
|
}
|
|
],
|
|
)
|
|
context.uko_persistence_backend = backend
|
|
|
|
|
|
@given("uko a persistence backend with triples having empty predicate value")
|
|
def step_persistence_backend_empty_predicate(context: Any) -> None:
|
|
"""Create a persistence backend with triples having empty predicate."""
|
|
backend = InMemoryPersistenceBackend()
|
|
backend.save(
|
|
"local/test",
|
|
[
|
|
{
|
|
"subject": "uko://code/module/foo",
|
|
"predicate": "",
|
|
"object": "uko-py:Module",
|
|
}
|
|
],
|
|
)
|
|
context.uko_persistence_backend = backend
|
|
|
|
|
|
@given("uko a persistence backend with triples having empty object value")
|
|
def step_persistence_backend_empty_object(context: Any) -> None:
|
|
"""Create a persistence backend with triples having empty object."""
|
|
backend = InMemoryPersistenceBackend()
|
|
backend.save(
|
|
"local/test",
|
|
[
|
|
{
|
|
"subject": "uko://code/module/foo",
|
|
"predicate": "uko:type",
|
|
"object": "",
|
|
}
|
|
],
|
|
)
|
|
context.uko_persistence_backend = backend
|
|
|
|
|
|
@given("uko a UKOGraphPersistence service with the persistence backend")
|
|
def step_persistence_service_with_backend(context: Any) -> None:
|
|
"""Create a UKOGraphPersistence service with the persistence backend."""
|
|
graph_backend = create_mock_graph_backend()
|
|
persistence_backend = context.uko_persistence_backend
|
|
context.uko_persistence_service = UKOGraphPersistence(
|
|
graph_backend=graph_backend,
|
|
project="local/test",
|
|
persistence_backend=persistence_backend,
|
|
)
|
|
|
|
|
|
@when("uko I call restore on the persistence service")
|
|
def step_call_restore_on_persistence_service(context: Any) -> None:
|
|
"""Call restore on the persistence service, capturing log output."""
|
|
service: UKOGraphPersistence = context.uko_persistence_service
|
|
with _capture_uko_logs() as entries:
|
|
context.uko_restore_result = service.restore()
|
|
context.uko_captured_logs = entries
|
|
|
|
|
|
@then("uko no triples should be added to the graph backend")
|
|
def step_no_triples_added(context: Any) -> None:
|
|
"""Verify no triples were added to the graph backend."""
|
|
service: UKOGraphPersistence = context.uko_persistence_service
|
|
service._graph_backend.add_triple.assert_not_called()
|
|
|
|
|
|
# =================================================================
|
|
# UKOGraphPersistence.restore() — add_triple failure path
|
|
# =================================================================
|
|
|
|
|
|
@given("uko a persistence backend with valid triples")
|
|
def step_persistence_backend_with_valid_triples(context: Any) -> None:
|
|
"""Create a persistence backend with valid triples."""
|
|
backend = InMemoryPersistenceBackend()
|
|
backend.save("local/test", create_sample_triples())
|
|
context.uko_persistence_backend = backend
|
|
|
|
|
|
@given("uko a graph backend that raises an exception on add_triple")
|
|
def step_graph_backend_raises_on_add_triple(context: Any) -> None:
|
|
"""Create a mock graph backend that raises on add_triple."""
|
|
backend = create_mock_graph_backend()
|
|
backend.add_triple.side_effect = RuntimeError("Add triple failed")
|
|
context.uko_failing_graph_backend = backend
|
|
|
|
|
|
@then("uko a warning should be logged for triple failure")
|
|
def step_warning_logged_for_triple_failure(context: Any) -> None:
|
|
"""Verify a warning was logged for triple failure."""
|
|
logs: list[dict[str, Any]] = context.uko_captured_logs
|
|
warning_events = [
|
|
e
|
|
for e in logs
|
|
if e.get("log_level") == "warning" and "triple_failed" in e.get("event", "")
|
|
]
|
|
assert warning_events, (
|
|
f"Expected a warning log with 'triple_failed' event, got: {logs}"
|
|
)
|
|
|
|
|
|
@given("uko a persistence backend with mixed valid and invalid triples")
|
|
def step_persistence_backend_with_mixed_triples(context: Any) -> None:
|
|
"""Create a persistence backend with mixed valid and invalid triples."""
|
|
backend = InMemoryPersistenceBackend()
|
|
triples = [
|
|
*create_sample_triples(),
|
|
{
|
|
"subject": "",
|
|
"predicate": "uko:type",
|
|
"object": "uko-py:Module",
|
|
},
|
|
]
|
|
backend.save("local/test", triples)
|
|
context.uko_persistence_backend = backend
|
|
|
|
|
|
@given("uko a graph backend that raises an exception only for specific triples")
|
|
def step_graph_backend_selective_failure(context: Any) -> None:
|
|
"""Create a mock graph backend that fails only for specific triples."""
|
|
backend = create_mock_graph_backend()
|
|
|
|
def add_triple_side_effect(
|
|
project: str, subject: str, predicate: str, obj: str
|
|
) -> None:
|
|
if subject == "uko://code/module/foo":
|
|
raise RuntimeError("Add triple failed for foo")
|
|
|
|
backend.add_triple.side_effect = add_triple_side_effect
|
|
context.uko_failing_graph_backend = backend
|
|
|
|
|
|
@given("uko a UKOGraphPersistence service with the mixed backend")
|
|
def step_persistence_service_with_mixed_backend(context: Any) -> None:
|
|
"""Create a UKOGraphPersistence service with mixed backend."""
|
|
graph_backend = context.uko_failing_graph_backend
|
|
persistence_backend = context.uko_persistence_backend
|
|
context.uko_persistence_service = UKOGraphPersistence(
|
|
graph_backend=graph_backend,
|
|
project="local/test",
|
|
persistence_backend=persistence_backend,
|
|
)
|
|
|
|
|
|
@then("uko the restore result should be greater than 0")
|
|
def step_restore_result_greater_than_zero(context: Any) -> None:
|
|
"""Verify restore result is greater than 0."""
|
|
assert context.uko_restore_result > 0, (
|
|
f"Expected > 0, got {context.uko_restore_result}"
|
|
)
|
|
|
|
|
|
@then("uko warnings should be logged for failed triples")
|
|
def step_warnings_logged_for_failed_triples(context: Any) -> None:
|
|
"""Verify warnings were logged for failed triples."""
|
|
logs: list[dict[str, Any]] = context.uko_captured_logs
|
|
warning_events = [
|
|
e
|
|
for e in logs
|
|
if e.get("log_level") == "warning" and "triple_failed" in e.get("event", "")
|
|
]
|
|
assert warning_events, (
|
|
f"Expected warning logs with 'triple_failed' event, got: {logs}"
|
|
)
|
|
|
|
|
|
# =================================================================
|
|
# InMemoryPersistenceBackend.clear() method
|
|
# =================================================================
|
|
|
|
|
|
@when('uko I clear the persistence backend for project "local/test"')
|
|
def step_clear_persistence_backend(context: Any) -> None:
|
|
"""Clear the persistence backend for a project."""
|
|
backend: InMemoryPersistenceBackend = context.uko_persistence_backend
|
|
backend.clear("local/test")
|
|
|
|
|
|
@then(
|
|
'uko loading from the persistence backend for project "local/test" should return empty list'
|
|
)
|
|
def step_load_after_clear_returns_empty(context: Any) -> None:
|
|
"""Verify loading after clear returns empty list."""
|
|
backend: InMemoryPersistenceBackend = context.uko_persistence_backend
|
|
result = backend.load("local/test")
|
|
assert result == [], f"Expected empty list, got {result}"
|
|
|
|
|
|
@when('uko I clear the persistence backend for project "local/test1"')
|
|
def step_clear_persistence_backend_test1(context: Any) -> None:
|
|
"""Clear the persistence backend for project test1."""
|
|
backend: InMemoryPersistenceBackend = context.uko_persistence_backend
|
|
backend.clear("local/test1")
|
|
|
|
|
|
@then(
|
|
'uko loading from the persistence backend for project "local/test1" should return empty list'
|
|
)
|
|
def step_load_test1_returns_empty(context: Any) -> None:
|
|
"""Verify loading test1 after clear returns empty list."""
|
|
backend: InMemoryPersistenceBackend = context.uko_persistence_backend
|
|
result = backend.load("local/test1")
|
|
assert result == [], f"Expected empty list, got {result}"
|
|
|
|
|
|
@then(
|
|
'uko loading from the persistence backend for project "local/test2" should return the saved triples'
|
|
)
|
|
def step_load_test2_returns_saved(context: Any) -> None:
|
|
"""Verify loading test2 returns the saved triples."""
|
|
backend: InMemoryPersistenceBackend = context.uko_persistence_backend
|
|
result = backend.load("local/test2")
|
|
assert len(result) == 2, f"Expected 2 triples, got {len(result)}"
|
|
|
|
|
|
@when('uko I clear the persistence backend for project "local/nonexistent"')
|
|
def step_clear_nonexistent_project(context: Any) -> None:
|
|
"""Clear a non-existent project."""
|
|
backend: InMemoryPersistenceBackend = context.uko_persistence_backend
|
|
try:
|
|
backend.clear("local/nonexistent")
|
|
context.uko_clear_error = None
|
|
except Exception as e:
|
|
context.uko_clear_error = e
|
|
|
|
|
|
@then("uko no error should be raised")
|
|
def step_no_error_raised(context: Any) -> None:
|
|
"""Verify no error was raised."""
|
|
assert context.uko_clear_error is None, (
|
|
f"Expected no error, got {context.uko_clear_error}"
|
|
)
|
|
|
|
|
|
# =================================================================
|
|
# UKOGraphPersistence.project property
|
|
# =================================================================
|
|
|
|
|
|
@given('uko a UKOGraphPersistence service with project "local/my-app"')
|
|
def step_persistence_service_with_project(context: Any) -> None:
|
|
"""Create a UKOGraphPersistence service with a specific project."""
|
|
graph_backend = context.uko_graph_backend
|
|
persistence_backend = InMemoryPersistenceBackend()
|
|
context.uko_persistence_service = UKOGraphPersistence(
|
|
graph_backend=graph_backend,
|
|
project="local/my-app",
|
|
persistence_backend=persistence_backend,
|
|
)
|
|
|
|
|
|
@when("uko I access the project property")
|
|
def step_access_project_property(context: Any) -> None:
|
|
"""Access the project property."""
|
|
service: UKOGraphPersistence = context.uko_persistence_service
|
|
context.uko_project_value = service.project
|
|
|
|
|
|
@then('uko the project property should return "local/my-app"')
|
|
def step_project_property_returns_value(context: Any) -> None:
|
|
"""Verify the project property returns the correct value."""
|
|
assert context.uko_project_value == "local/my-app", (
|
|
f"Expected 'local/my-app', got {context.uko_project_value}"
|
|
)
|
|
|
|
|
|
@then("uko attempting to set the project property should raise AttributeError")
|
|
def step_project_property_is_readonly(context: Any) -> None:
|
|
"""Verify the project property is read-only."""
|
|
service: UKOGraphPersistence = context.uko_persistence_service
|
|
try:
|
|
service.project = "local/other" # type: ignore[misc]
|
|
raise AssertionError("Expected AttributeError")
|
|
except AttributeError:
|
|
pass
|
|
|
|
|
|
# =================================================================
|
|
# Integration: Full save/restore lifecycle with JSONFilePersistenceBackend
|
|
# =================================================================
|
|
|
|
|
|
@given("uko a mock graph backend with two sample triples")
|
|
def step_graph_backend_with_triples_020(context: Any) -> None:
|
|
"""Create a mock graph backend with two sample triples for lifecycle tests."""
|
|
backend = create_mock_graph_backend()
|
|
backend.query.return_value = [
|
|
{
|
|
"s": "uko://code/module/foo",
|
|
"p": "uko:type",
|
|
"o": "uko-py:Module",
|
|
},
|
|
{
|
|
"s": "uko://code/class/Foo",
|
|
"p": "uko:type",
|
|
"o": "uko-py:Class",
|
|
},
|
|
]
|
|
context.uko_graph_backend = backend
|
|
|
|
|
|
@given("uko a UKOGraphPersistence service with the JSON file backend")
|
|
def step_persistence_service_with_json_backend(context: Any) -> None:
|
|
"""Create a UKOGraphPersistence service with the JSON file backend."""
|
|
graph_backend = context.uko_graph_backend
|
|
persistence_backend = context.uko_json_backend
|
|
context.uko_persistence_service = UKOGraphPersistence(
|
|
graph_backend=graph_backend,
|
|
project="local/test",
|
|
persistence_backend=persistence_backend,
|
|
)
|
|
|
|
|
|
@when("uko I save the graph state via the persistence service")
|
|
def step_save_graph_state(context: Any) -> None:
|
|
"""Save the graph state via the pre-configured persistence service."""
|
|
service: UKOGraphPersistence = context.uko_persistence_service
|
|
context.uko_save_result = service.save()
|
|
|
|
|
|
@when(
|
|
"uko I restore the graph state into a fresh backend with the same JSON file backend"
|
|
)
|
|
def step_restore_graph_state_with_json_backend(context: Any) -> None:
|
|
"""Restore the graph state into a fresh backend."""
|
|
fresh_backend = create_mock_graph_backend()
|
|
persistence_backend = context.uko_json_backend
|
|
service = UKOGraphPersistence(
|
|
graph_backend=fresh_backend,
|
|
project="local/test",
|
|
persistence_backend=persistence_backend,
|
|
)
|
|
context.uko_restore_result = service.restore()
|
|
context.uko_fresh_backend = fresh_backend
|
|
|
|
|
|
@then("uko the restored backend should have the same triples")
|
|
def step_restored_backend_has_same_triples(context: Any) -> None:
|
|
"""Verify the restored backend has the same triples."""
|
|
fresh_backend = context.uko_fresh_backend
|
|
assert fresh_backend.add_triple.call_count == 2, (
|
|
f"Expected 2 add_triple calls, got {fresh_backend.add_triple.call_count}"
|
|
)
|
|
|
|
|
|
@then("uko the base directory should be created")
|
|
def step_base_directory_created(context: Any) -> None:
|
|
"""Verify the base directory was created."""
|
|
temp_dir = Path(context.uko_temp_dir)
|
|
assert temp_dir.exists(), f"Expected directory to exist: {temp_dir}"
|
|
|
|
|
|
@then("uko the JSON file should exist")
|
|
def step_json_file_exists(context: Any) -> None:
|
|
"""Verify the JSON file exists."""
|
|
backend: JSONFilePersistenceBackend = context.uko_json_backend
|
|
path = backend._path_for("local/test")
|
|
assert path.exists(), f"Expected file to exist: {path}"
|
|
|
|
|
|
# =================================================================
|
|
# Edge cases and validation
|
|
# =================================================================
|
|
|
|
|
|
@then(
|
|
"uko creating UKOGraphPersistence with whitespace-only project should raise ValueError"
|
|
)
|
|
def step_persistence_service_whitespace_project(context: Any) -> None:
|
|
"""Verify creating service with whitespace-only project raises ValueError."""
|
|
graph_backend = context.uko_graph_backend
|
|
try:
|
|
UKOGraphPersistence(
|
|
graph_backend=graph_backend,
|
|
project=" ",
|
|
persistence_backend=InMemoryPersistenceBackend(),
|
|
)
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then(
|
|
"uko saving to JSON file backend with whitespace-only project should raise ValueError"
|
|
)
|
|
def step_save_json_whitespace_project(context: Any) -> None:
|
|
"""Verify saving with whitespace-only project raises ValueError."""
|
|
backend: JSONFilePersistenceBackend = context.uko_json_backend
|
|
try:
|
|
backend.save(" ", create_sample_triples())
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then(
|
|
"uko loading from JSON file backend with whitespace-only project should raise ValueError"
|
|
)
|
|
def step_load_json_whitespace_project(context: Any) -> None:
|
|
"""Verify loading with whitespace-only project raises ValueError."""
|
|
backend: JSONFilePersistenceBackend = context.uko_json_backend
|
|
try:
|
|
backend.load(" ")
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then(
|
|
"uko saving to persistence backend with whitespace-only project should raise ValueError"
|
|
)
|
|
def step_save_memory_whitespace_project(context: Any) -> None:
|
|
"""Verify saving with whitespace-only project raises ValueError."""
|
|
backend: InMemoryPersistenceBackend = context.uko_persistence_backend
|
|
try:
|
|
backend.save(" ", create_sample_triples())
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@then(
|
|
"uko loading from persistence backend with whitespace-only project should raise ValueError"
|
|
)
|
|
def step_load_memory_whitespace_project(context: Any) -> None:
|
|
"""Verify loading with whitespace-only project raises ValueError."""
|
|
backend: InMemoryPersistenceBackend = context.uko_persistence_backend
|
|
try:
|
|
backend.load(" ")
|
|
raise AssertionError("Expected ValueError")
|
|
except ValueError:
|
|
pass
|
|
|
|
|
|
@given("uko a graph backend that returns empty bindings on query")
|
|
def step_graph_backend_empty_bindings(context: Any) -> None:
|
|
"""Create a graph backend that returns empty bindings."""
|
|
backend = create_mock_graph_backend()
|
|
backend.query.return_value = []
|
|
context.uko_graph_backend = backend
|
|
|
|
|
|
@given("uko a UKOGraphPersistence service with the backend")
|
|
def step_persistence_service_with_graph_backend(context: Any) -> None:
|
|
"""Create a UKOGraphPersistence service with the graph backend."""
|
|
graph_backend = context.uko_graph_backend
|
|
persistence_backend = InMemoryPersistenceBackend()
|
|
context.uko_persistence_service = UKOGraphPersistence(
|
|
graph_backend=graph_backend,
|
|
project="local/test",
|
|
persistence_backend=persistence_backend,
|
|
)
|
|
|
|
|
|
@given("uko a graph backend that returns bindings with missing keys")
|
|
def step_graph_backend_missing_keys(context: Any) -> None:
|
|
"""Create a graph backend that returns bindings with missing keys."""
|
|
backend = create_mock_graph_backend()
|
|
backend.query.return_value = [
|
|
{
|
|
"s": "uko://code/module/foo",
|
|
# missing "p" and "o"
|
|
},
|
|
{
|
|
"p": "uko:type",
|
|
# missing "s" and "o"
|
|
},
|
|
]
|
|
context.uko_graph_backend = backend
|
|
|
|
|
|
@then("uko only complete triples should be persisted")
|
|
def step_only_complete_triples_persisted(context: Any) -> None:
|
|
"""Verify only complete triples were persisted."""
|
|
service: UKOGraphPersistence = context.uko_persistence_service
|
|
persistence_backend = service._persistence_backend
|
|
triples = persistence_backend.load("local/test")
|
|
assert len(triples) == 0, f"Expected 0 triples, got {len(triples)}"
|
|
|
|
|
|
@given("uko a persistence backend with no data")
|
|
def step_persistence_backend_no_data(context: Any) -> None:
|
|
"""Create a persistence backend with no data."""
|
|
context.uko_persistence_backend = InMemoryPersistenceBackend()
|
|
|
|
|
|
@then("uko an info log should be recorded for no data")
|
|
def step_info_log_no_data(context: Any) -> None:
|
|
"""Verify an info log was recorded for no data."""
|
|
logs: list[dict[str, Any]] = context.uko_captured_logs
|
|
info_events = [
|
|
e
|
|
for e in logs
|
|
if e.get("log_level") == "info" and "no_data" in e.get("event", "")
|
|
]
|
|
assert info_events, f"Expected an info log with 'no_data' event, got: {logs}"
|