"""Protocol stubs, UKOTriple validation, and direct internal-call steps. Coverage scenarios that exercise Protocol ``raise NotImplementedError`` stubs, Pydantic validator error paths, and internal helper functions called directly (bypassing the public API) to ensure slipcover traces the code. All steps prefixed with ``idx`` to avoid AmbiguousStep collisions. """ from __future__ import annotations from typing import Any from behave import then # type: ignore[import-untyped] from cleveragents.domain.models.acms.analyzers import UKOTriple from cleveragents.domain.models.acms.provenance import IndexResult from features.steps.uko_indexer_common import ULID_1, _make_resource __all__: list[str] = [] # --------------------------------------------------------------------------- # Protocol stub NotImplementedError steps # --------------------------------------------------------------------------- @then( "idx calling ContentReader.read_content directly should raise NotImplementedError" ) def step_proto_content_reader(context: Any) -> None: from cleveragents.application.services.uko_indexer_protocols import ( ContentReader, ) class _Bare(ContentReader): # type: ignore[misc] pass bare = _Bare() # type: ignore[abstract] try: bare.read_content(_make_resource(ULID_1)) assert False, "Expected NotImplementedError" # noqa: B011 except NotImplementedError: pass @then( "idx calling IndexLifecycleHook.on_indexed directly" " should raise NotImplementedError" ) def step_proto_hook_indexed(context: Any) -> None: from cleveragents.application.services.uko_indexer_protocols import ( IndexLifecycleHook, ) class _Bare(IndexLifecycleHook): # type: ignore[misc] pass bare = _Bare() # type: ignore[abstract] try: bare.on_indexed(IndexResult(resource_id=ULID_1)) assert False, "Expected NotImplementedError" # noqa: B011 except NotImplementedError: pass @then( "idx calling IndexLifecycleHook.on_removed directly" " should raise NotImplementedError" ) def step_proto_hook_removed(context: Any) -> None: from cleveragents.application.services.uko_indexer_protocols import ( IndexLifecycleHook, ) class _Bare(IndexLifecycleHook): # type: ignore[misc] pass bare = _Bare() # type: ignore[abstract] try: bare.on_removed(ULID_1, "p") assert False, "Expected NotImplementedError" # noqa: B011 except NotImplementedError: pass @then( "idx calling IndexLifecycleHook.on_error directly should raise NotImplementedError" ) def step_proto_hook_error(context: Any) -> None: from cleveragents.application.services.uko_indexer_protocols import ( IndexLifecycleHook, ) class _Bare(IndexLifecycleHook): # type: ignore[misc] pass bare = _Bare() # type: ignore[abstract] try: bare.on_error(ULID_1, "err") assert False, "Expected NotImplementedError" # noqa: B011 except NotImplementedError: pass @then( "idx calling AnalyzerProtocol.supported_extensions directly" " should raise NotImplementedError" ) def step_proto_analyzer_ext(context: Any) -> None: from cleveragents.domain.models.acms.analyzers import AnalyzerProtocol class _Bare(AnalyzerProtocol): # type: ignore[misc] pass bare = _Bare() # type: ignore[abstract] try: _ = bare.supported_extensions assert False, "Expected NotImplementedError" # noqa: B011 except NotImplementedError: pass @then("idx calling AnalyzerProtocol.domain directly should raise NotImplementedError") def step_proto_analyzer_domain(context: Any) -> None: from cleveragents.domain.models.acms.analyzers import AnalyzerProtocol class _Bare(AnalyzerProtocol): # type: ignore[misc] pass bare = _Bare() # type: ignore[abstract] try: _ = bare.domain assert False, "Expected NotImplementedError" # noqa: B011 except NotImplementedError: pass @then("idx calling AnalyzerProtocol.analyze directly should raise NotImplementedError") def step_proto_analyzer_analyze(context: Any) -> None: from cleveragents.domain.models.acms.analyzers import AnalyzerProtocol class _Bare(AnalyzerProtocol): # type: ignore[misc] pass bare = _Bare() # type: ignore[abstract] try: bare.analyze("content", "uko://r") assert False, "Expected NotImplementedError" # noqa: B011 except NotImplementedError: pass # --------------------------------------------------------------------------- # UKOTriple validation edge cases # --------------------------------------------------------------------------- @then("idx creating UKOTriple with whitespace-only subject_uri should raise ValueError") def step_triple_ws_subject(context: Any) -> None: try: UKOTriple( subject_uri=" ", predicate="p", object_uri="uko://o", ) assert False, "Expected ValueError" # noqa: B011 except ValueError: pass @then("idx creating UKOTriple with whitespace-only predicate should raise ValueError") def step_triple_ws_predicate(context: Any) -> None: try: UKOTriple( subject_uri="uko://s", predicate=" ", object_uri="uko://o", ) assert False, "Expected ValueError" # noqa: B011 except ValueError: pass @then( "idx creating UKOTriple with empty object_uri and empty object_value" " should raise ValueError" ) def step_triple_no_object(context: Any) -> None: try: UKOTriple( subject_uri="uko://s", predicate="rdf:type", object_uri="", object_value="", ) assert False, "Expected ValueError" # noqa: B011 except ValueError: pass # --------------------------------------------------------------------------- # fire_on_removed direct call # --------------------------------------------------------------------------- @then("idx calling fire_on_removed with a raising hook should not raise") def step_fire_on_removed_direct(context: Any) -> None: from cleveragents.application.services.uko_indexer_internals import ( fire_on_removed, ) class _RaisingHook: def on_indexed(self, result: IndexResult) -> None: pass def on_removed(self, resource_id: str, project: str) -> None: raise RuntimeError("boom") def on_error(self, resource_id: str, error: str) -> None: pass # Should not raise — fire_on_removed swallows the exception fire_on_removed( _RaisingHook(), # type: ignore[arg-type] ULID_1, "local/test", ) # --------------------------------------------------------------------------- # AnalyzerRegistry validation and listing steps # --------------------------------------------------------------------------- @then("idx registering a non-protocol object should raise TypeError") def step_registry_non_protocol(context: Any) -> None: from cleveragents.domain.models.acms.analyzers import AnalyzerRegistry registry = AnalyzerRegistry() try: registry.register("not_an_analyzer") # type: ignore[arg-type] assert False, "Expected TypeError" # noqa: B011 except TypeError: pass @then("idx registering an analyzer with empty extensions should raise ValueError") def step_registry_empty_extensions(context: Any) -> None: from cleveragents.domain.models.acms.analyzers import AnalyzerRegistry class _NoExtensions: supported_extensions: frozenset[str] = frozenset() domain = "empty" def analyze(self, content: str, resource_uri: str) -> list[UKOTriple]: return [] registry = AnalyzerRegistry() try: registry.register(_NoExtensions()) # type: ignore[arg-type] assert False, "Expected ValueError" # noqa: B011 except ValueError: pass @then("idx looking up empty extension should return None") def step_registry_empty_ext(context: Any) -> None: assert context.idx_registry.get_for_extension("") is None @then('idx list_extensions should include ".py"') def step_registry_list_ext(context: Any) -> None: exts = context.idx_registry.list_extensions() assert ".py" in exts, f".py not in {exts}" @then("idx list_analyzers should return {n:d} analyzer") def step_registry_list_analyzers(context: Any, n: int) -> None: analyzers = context.idx_registry.list_analyzers() assert len(analyzers) == n, f"Expected {n} analyzers, got {len(analyzers)}"