2688c85769
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 19s
CI / lint (pull_request) Successful in 23s
CI / quality (pull_request) Successful in 28s
CI / security (pull_request) Successful in 54s
CI / typecheck (pull_request) Successful in 1m25s
CI / e2e_tests (pull_request) Successful in 2m33s
CI / unit_tests (pull_request) Successful in 3m4s
CI / integration_tests (pull_request) Successful in 3m33s
CI / docker (pull_request) Successful in 1m7s
CI / coverage (pull_request) Successful in 5m50s
CI / lint (push) Successful in 18s
CI / build (push) Successful in 33s
CI / quality (push) Successful in 47s
CI / typecheck (push) Successful in 49s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 52s
CI / e2e_tests (push) Successful in 2m9s
CI / unit_tests (push) Successful in 3m32s
CI / integration_tests (push) Successful in 3m41s
CI / docker (push) Successful in 56s
CI / coverage (push) Successful in 6m10s
CI / benchmark-publish (push) Has been cancelled
CI / benchmark-regression (pull_request) Successful in 40m21s
Implement SandboxStrategyProtocol, a 9-method @runtime_checkable Protocol enabling third-party sandbox strategy registration. Includes: - SandboxStrategyProtocol with create/read/write/diff/commit/rollback/checkpoint/ restore_checkpoint/cleanup methods - SandboxRef (frozen dataclass) and DiffView/DiffEntry (Pydantic models) - SandboxStrategyRegistry with config-driven registration, Protocol validation, thread safety, and clear/list/has operations - BuiltInSandboxStrategyAdapter wrapping existing Sandbox implementations to conform to the new Protocol - CustomStrategyConfig for YAML/dict-based strategy registration - SandboxFactory integration with custom_registry parameter, has_custom_strategy() and get_custom_strategy_class() - 25 Behave BDD scenarios (85 steps) covering protocol, registry, adapter, config, and factory integration - 8 Robot Framework integration tests with real filesystem operations - ASV benchmarks for registry and adapter operations - Developer documentation ISSUES CLOSED: #586
603 lines
19 KiB
Python
603 lines
19 KiB
Python
"""Behave step definitions for Custom Sandbox Strategy Registration.
|
|
|
|
Covers SandboxRef, DiffView, DiffEntry, SandboxStrategyProtocol,
|
|
SandboxStrategyRegistry, BuiltInSandboxStrategyAdapter,
|
|
CustomStrategyConfig, and factory integration.
|
|
|
|
Based on issue #586.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import tempfile
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from behave import given, then, when # type: ignore[import-untyped]
|
|
from behave.runner import Context # type: ignore[import-untyped]
|
|
from pydantic import ValidationError
|
|
|
|
from cleveragents.domain.models.core.resource import (
|
|
PhysVirt,
|
|
Resource,
|
|
ResourceCapabilities,
|
|
)
|
|
from cleveragents.domain.models.core.resource import (
|
|
SandboxStrategy as SandboxStrategyEnum,
|
|
)
|
|
from cleveragents.domain.models.core.sandbox_strategy import (
|
|
DiffEntry,
|
|
DiffView,
|
|
SandboxRef,
|
|
SandboxStrategyProtocol,
|
|
)
|
|
from cleveragents.infrastructure.plugins.exceptions import (
|
|
ProtocolMismatchError,
|
|
)
|
|
from cleveragents.infrastructure.sandbox.factory import SandboxFactory
|
|
from cleveragents.infrastructure.sandbox.strategy_adapter import (
|
|
BuiltInSandboxStrategyAdapter,
|
|
)
|
|
from cleveragents.infrastructure.sandbox.strategy_registry import (
|
|
CustomStrategyConfig,
|
|
SandboxStrategyRegistry,
|
|
)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ULID helper
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_COUNTER = 0
|
|
|
|
|
|
def _ulid(name: str) -> str:
|
|
global _COUNTER
|
|
_COUNTER += 1
|
|
base = abs(hash(name)) % (10**20)
|
|
raw = f"{_COUNTER:06d}{base:020d}"
|
|
charset = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
|
result = ""
|
|
for ch in raw:
|
|
result += charset[int(ch)]
|
|
return (result + "0" * 26)[:26]
|
|
|
|
|
|
def _make_resource(name: str, location: str | None = None) -> Resource:
|
|
return Resource(
|
|
resource_id=_ulid(name),
|
|
name=name,
|
|
resource_type_name="fs-directory",
|
|
classification=PhysVirt.PHYSICAL,
|
|
sandbox_strategy=SandboxStrategyEnum.NONE,
|
|
location=location,
|
|
capabilities=ResourceCapabilities(
|
|
readable=True, writable=True, sandboxable=True, checkpointable=False
|
|
),
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SandboxRef
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('a SandboxRef with sandbox_id "{sid}" and plan_id "{pid}"')
|
|
def step_create_sandbox_ref(context: Context, sid: str, pid: str) -> None:
|
|
context.sandbox_ref = SandboxRef(
|
|
sandbox_id=sid,
|
|
plan_id=pid,
|
|
resource_id="res-001",
|
|
created_at=datetime.now(),
|
|
)
|
|
|
|
|
|
@then('the SandboxRef sandbox_id should be "{expected}"')
|
|
def step_sandbox_ref_sid(context: Context, expected: str) -> None:
|
|
assert context.sandbox_ref.sandbox_id == expected
|
|
|
|
|
|
@then('the SandboxRef plan_id should be "{expected}"')
|
|
def step_sandbox_ref_pid(context: Context, expected: str) -> None:
|
|
assert context.sandbox_ref.plan_id == expected
|
|
|
|
|
|
@then("the SandboxRef should be immutable")
|
|
def step_sandbox_ref_frozen(context: Context) -> None:
|
|
import dataclasses
|
|
|
|
assert dataclasses.is_dataclass(context.sandbox_ref)
|
|
try:
|
|
context.sandbox_ref.sandbox_id = "mutated" # type: ignore[misc]
|
|
raise AssertionError("Expected FrozenInstanceError")
|
|
except (AttributeError, dataclasses.FrozenInstanceError):
|
|
pass
|
|
|
|
|
|
@given('a SandboxRef with metadata key "{key}" value "{value}"')
|
|
def step_sandbox_ref_metadata(context: Context, key: str, value: str) -> None:
|
|
context.sandbox_ref = SandboxRef(
|
|
sandbox_id="sb-meta",
|
|
plan_id="plan-meta",
|
|
resource_id="res-meta",
|
|
created_at=datetime.now(),
|
|
metadata={key: value},
|
|
)
|
|
|
|
|
|
@then('the SandboxRef metadata should contain key "{key}"')
|
|
def step_sandbox_ref_meta_key(context: Context, key: str) -> None:
|
|
assert key in context.sandbox_ref.metadata
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# DiffEntry / DiffView
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('a DiffEntry with path "{path}" and operation "{op}"')
|
|
def step_create_diff_entry(context: Context, path: str, op: str) -> None:
|
|
context.diff_entry = DiffEntry(path=path, operation=op)
|
|
|
|
|
|
@then('the DiffEntry path should be "{expected}"')
|
|
def step_diff_entry_path(context: Context, expected: str) -> None:
|
|
assert context.diff_entry.path == expected
|
|
|
|
|
|
@then('the DiffEntry operation should be "{expected}"')
|
|
def step_diff_entry_op(context: Context, expected: str) -> None:
|
|
assert context.diff_entry.operation == expected
|
|
|
|
|
|
@given("a DiffView with {count:d} entries")
|
|
def step_create_diff_view(context: Context, count: int) -> None:
|
|
entries = [
|
|
DiffEntry(path=f"file{i}.txt", operation="modified") for i in range(count)
|
|
]
|
|
context.diff_view = DiffView(
|
|
sandbox_id="dv-001",
|
|
entries=entries,
|
|
)
|
|
|
|
|
|
@then("the DiffView should have {count:d} entries")
|
|
def step_diff_view_count(context: Context, count: int) -> None:
|
|
assert len(context.diff_view.entries) == count
|
|
|
|
|
|
@then("the DiffView should have a sandbox_id")
|
|
def step_diff_view_sid(context: Context) -> None:
|
|
assert context.diff_view.sandbox_id
|
|
|
|
|
|
@when("I attempt to create a DiffEntry with empty path")
|
|
def step_create_diff_entry_empty(context: Context) -> None:
|
|
context.sandbox_error = None
|
|
try:
|
|
DiffEntry(path="", operation="modified")
|
|
except ValidationError as exc:
|
|
context.sandbox_error = exc
|
|
|
|
|
|
@then("a sandbox strategy validation error should be raised")
|
|
def step_sandbox_validation_error(context: Context) -> None:
|
|
assert context.sandbox_error is not None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SandboxStrategyProtocol
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("the SandboxStrategyProtocol is available")
|
|
def step_protocol_available(context: Context) -> None:
|
|
context.protocol = SandboxStrategyProtocol
|
|
|
|
|
|
@then("it should be a runtime_checkable Protocol")
|
|
def step_protocol_runtime_checkable(context: Context) -> None:
|
|
assert hasattr(context.protocol, "__protocol_attrs__") or hasattr(
|
|
context.protocol, "_is_runtime_protocol"
|
|
)
|
|
|
|
|
|
class _FullMockStrategy:
|
|
"""Mock satisfying all 9 SandboxStrategy methods."""
|
|
|
|
def create(self, plan_id: str, resource: Any) -> Any:
|
|
return None
|
|
|
|
def read(self, ref: Any, path: str) -> bytes:
|
|
return b""
|
|
|
|
def write(self, ref: Any, path: str, content: bytes) -> Any:
|
|
return None
|
|
|
|
def diff(self, ref: Any) -> Any:
|
|
return None
|
|
|
|
def commit(self, ref: Any) -> None:
|
|
pass
|
|
|
|
def rollback(self, ref: Any) -> None:
|
|
pass
|
|
|
|
def checkpoint(self, ref: Any, checkpoint_id: str) -> None:
|
|
pass
|
|
|
|
def restore_checkpoint(self, ref: Any, checkpoint_id: str) -> None:
|
|
pass
|
|
|
|
def cleanup(self, ref: Any) -> None:
|
|
pass
|
|
|
|
|
|
class _PartialMockStrategy:
|
|
"""Missing checkpoint method."""
|
|
|
|
def create(self, plan_id: str, resource: Any) -> Any:
|
|
return None
|
|
|
|
def read(self, ref: Any, path: str) -> bytes:
|
|
return b""
|
|
|
|
def write(self, ref: Any, path: str, content: bytes) -> Any:
|
|
return None
|
|
|
|
def diff(self, ref: Any) -> Any:
|
|
return None
|
|
|
|
def commit(self, ref: Any) -> None:
|
|
pass
|
|
|
|
def rollback(self, ref: Any) -> None:
|
|
pass
|
|
|
|
# Missing: checkpoint, restore_checkpoint
|
|
|
|
def cleanup(self, ref: Any) -> None:
|
|
pass
|
|
|
|
|
|
@given("a mock class implementing all 9 SandboxStrategy methods")
|
|
def step_full_mock(context: Context) -> None:
|
|
context.mock_cls = _FullMockStrategy
|
|
|
|
|
|
@then("the mock class should satisfy SandboxStrategyProtocol")
|
|
def step_mock_satisfies(context: Context) -> None:
|
|
instance = context.mock_cls()
|
|
assert isinstance(instance, SandboxStrategyProtocol)
|
|
|
|
|
|
@given("a class missing the checkpoint method")
|
|
def step_partial_mock(context: Context) -> None:
|
|
context.partial_cls = _PartialMockStrategy
|
|
|
|
|
|
@then("the class should not satisfy SandboxStrategyProtocol")
|
|
def step_partial_fails(context: Context) -> None:
|
|
instance = context.partial_cls()
|
|
assert not isinstance(instance, SandboxStrategyProtocol)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# SandboxStrategyRegistry
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a SandboxStrategyRegistry with test allowed prefixes")
|
|
def step_registry_with_prefixes(context: Context) -> None:
|
|
context.strategy_registry = SandboxStrategyRegistry(
|
|
allowed_prefixes=("cleveragents.",)
|
|
)
|
|
|
|
|
|
@given("an empty SandboxStrategyRegistry")
|
|
def step_empty_registry(context: Context) -> None:
|
|
context.strategy_registry = SandboxStrategyRegistry(
|
|
allowed_prefixes=("cleveragents.",)
|
|
)
|
|
|
|
|
|
@given(
|
|
"a valid custom strategy class in "
|
|
'"cleveragents.infrastructure.sandbox.strategy_adapter"'
|
|
)
|
|
def step_valid_strategy_class(context: Context) -> None:
|
|
context.strategy_module = "cleveragents.infrastructure.sandbox.strategy_adapter"
|
|
context.strategy_class = "BuiltInSandboxStrategyAdapter"
|
|
|
|
|
|
@when('I register the strategy as "{name}"')
|
|
def step_register_strategy(context: Context, name: str) -> None:
|
|
context.strategy_registry.register(
|
|
name, context.strategy_module, context.strategy_class
|
|
)
|
|
|
|
|
|
@then('the registry should contain strategy "{name}"')
|
|
def step_registry_contains(context: Context, name: str) -> None:
|
|
assert context.strategy_registry.has(name)
|
|
|
|
|
|
@then('listing strategies should include "{name}"')
|
|
def step_registry_list(context: Context, name: str) -> None:
|
|
assert name in context.strategy_registry.list_strategies()
|
|
|
|
|
|
@when('I attempt to register a non-protocol class as "{name}"')
|
|
def step_register_nonprotocol(context: Context, name: str) -> None:
|
|
context.sandbox_error = None
|
|
try:
|
|
context.strategy_registry.register(
|
|
name,
|
|
"cleveragents.infrastructure.sandbox.protocol",
|
|
"SandboxError",
|
|
)
|
|
except ProtocolMismatchError as exc:
|
|
context.sandbox_error = exc
|
|
|
|
|
|
@then("a sandbox ProtocolMismatchError should be raised")
|
|
def step_sandbox_protocol_mismatch(context: Context) -> None:
|
|
assert isinstance(context.sandbox_error, ProtocolMismatchError)
|
|
|
|
|
|
@when("I attempt to register a strategy with empty name")
|
|
def step_register_empty_name(context: Context) -> None:
|
|
context.sandbox_error = None
|
|
try:
|
|
context.strategy_registry.register(
|
|
"",
|
|
"cleveragents.infrastructure.sandbox.strategy_adapter",
|
|
"BuiltInSandboxStrategyAdapter",
|
|
)
|
|
except ValueError as exc:
|
|
context.sandbox_error = exc
|
|
|
|
|
|
@then("a sandbox ValueError should be raised")
|
|
def step_sandbox_value_error(context: Context) -> None:
|
|
assert isinstance(context.sandbox_error, (ValueError, type(None))) is False or (
|
|
context.sandbox_error is not None
|
|
)
|
|
assert context.sandbox_error is not None
|
|
|
|
|
|
@when('I look up strategy "{name}"')
|
|
def step_lookup_strategy(context: Context, name: str) -> None:
|
|
context.strategy_lookup_result = context.strategy_registry.get(name)
|
|
|
|
|
|
@then("the strategy lookup result should be None")
|
|
def step_strategy_result_none(context: Context) -> None:
|
|
assert context.strategy_lookup_result is None
|
|
|
|
|
|
@given("a config dictionary with 2 custom strategies")
|
|
def step_config_dict(context: Context) -> None:
|
|
context.config_dict = {
|
|
"adapter1": {
|
|
"module": "cleveragents.infrastructure.sandbox.strategy_adapter",
|
|
"class": "BuiltInSandboxStrategyAdapter",
|
|
},
|
|
"adapter2": {
|
|
"module": "cleveragents.infrastructure.sandbox.strategy_adapter",
|
|
"class": "BuiltInSandboxStrategyAdapter",
|
|
},
|
|
}
|
|
|
|
|
|
@when("I register all from config")
|
|
def step_register_all_config(context: Context) -> None:
|
|
context.registered = context.strategy_registry.register_all_from_config(
|
|
context.config_dict
|
|
)
|
|
|
|
|
|
@then("{count:d} strategies should be registered")
|
|
def step_count_registered(context: Context, count: int) -> None:
|
|
assert len(context.registered) == count
|
|
|
|
|
|
@given("a SandboxStrategyRegistry with one registered strategy")
|
|
def step_registry_one(context: Context) -> None:
|
|
context.strategy_registry = SandboxStrategyRegistry(
|
|
allowed_prefixes=("cleveragents.",)
|
|
)
|
|
context.strategy_registry.register(
|
|
"single",
|
|
"cleveragents.infrastructure.sandbox.strategy_adapter",
|
|
"BuiltInSandboxStrategyAdapter",
|
|
)
|
|
|
|
|
|
@when("I clear the sandbox strategy registry")
|
|
def step_clear_sandbox_registry(context: Context) -> None:
|
|
context.strategy_registry.clear()
|
|
|
|
|
|
@then("the sandbox strategy registry should be empty")
|
|
def step_sandbox_registry_empty(context: Context) -> None:
|
|
assert len(context.strategy_registry.list_strategies()) == 0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# BuiltInSandboxStrategyAdapter
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('a BuiltInSandboxStrategyAdapter for "{strategy}" strategy')
|
|
def step_adapter(context: Context, strategy: str) -> None:
|
|
context.adapter = BuiltInSandboxStrategyAdapter(strategy_name=strategy) # type: ignore[arg-type]
|
|
|
|
|
|
@given("a test Resource with location")
|
|
def step_resource_with_location(context: Context) -> None:
|
|
context.tmpdir = tempfile.mkdtemp(prefix="ca-test-")
|
|
context.test_resource = _make_resource("test-res", location=context.tmpdir)
|
|
|
|
|
|
@given("a test Resource with a temporary directory")
|
|
def step_resource_with_tmpdir(context: Context) -> None:
|
|
context.tmpdir = tempfile.mkdtemp(prefix="ca-test-cow-")
|
|
# Create a seed file so CoW sandbox has something to copy
|
|
with open(os.path.join(context.tmpdir, "seed.txt"), "w") as f:
|
|
f.write("seed")
|
|
context.test_resource = Resource(
|
|
resource_id=_ulid("cow-res"),
|
|
name="cow-res",
|
|
resource_type_name="fs-directory",
|
|
classification=PhysVirt.PHYSICAL,
|
|
sandbox_strategy=SandboxStrategyEnum.COPY_ON_WRITE,
|
|
location=context.tmpdir,
|
|
capabilities=ResourceCapabilities(
|
|
readable=True, writable=True, sandboxable=True, checkpointable=False
|
|
),
|
|
)
|
|
|
|
|
|
@when('I call adapter.create with plan "{plan_id}"')
|
|
def step_adapter_create(context: Context, plan_id: str) -> None:
|
|
context.adapter_ref = context.adapter.create(plan_id, context.test_resource)
|
|
|
|
|
|
@given('the adapter sandbox is created for plan "{plan_id}"')
|
|
def step_adapter_create_given(context: Context, plan_id: str) -> None:
|
|
context.adapter_ref = context.adapter.create(plan_id, context.test_resource)
|
|
|
|
|
|
@then('I should get a SandboxRef with plan_id "{expected}"')
|
|
def step_adapter_ref_check(context: Context, expected: str) -> None:
|
|
assert isinstance(context.adapter_ref, SandboxRef)
|
|
assert context.adapter_ref.plan_id == expected
|
|
|
|
|
|
@when('I call adapter.write with path "{path}" and content "{content}"')
|
|
def step_adapter_write(context: Context, path: str, content: str) -> None:
|
|
context.write_result = context.adapter.write(
|
|
context.adapter_ref, path, content.encode()
|
|
)
|
|
|
|
|
|
@given('I write "{content}" to "{path}" via the adapter')
|
|
def step_adapter_write_given(context: Context, content: str, path: str) -> None:
|
|
context.adapter.write(context.adapter_ref, path, content.encode())
|
|
|
|
|
|
@then('I should get a DiffEntry with operation "{expected}"')
|
|
def step_adapter_write_op(context: Context, expected: str) -> None:
|
|
assert isinstance(context.write_result, DiffEntry)
|
|
assert context.write_result.operation == expected
|
|
|
|
|
|
@when('I call adapter.read for "{path}"')
|
|
def step_adapter_read(context: Context, path: str) -> None:
|
|
context.read_result = context.adapter.read(context.adapter_ref, path)
|
|
|
|
|
|
@then('I should get content "{expected}"')
|
|
def step_adapter_read_content(context: Context, expected: str) -> None:
|
|
assert context.read_result == expected.encode()
|
|
|
|
|
|
@when("I call adapter.diff")
|
|
def step_adapter_diff(context: Context) -> None:
|
|
context.diff_result = context.adapter.diff(context.adapter_ref)
|
|
|
|
|
|
@then("I should get a DiffView")
|
|
def step_adapter_diff_check(context: Context) -> None:
|
|
assert isinstance(context.diff_result, DiffView)
|
|
|
|
|
|
@given('I checkpoint as "{cp_id}"')
|
|
def step_adapter_checkpoint(context: Context, cp_id: str) -> None:
|
|
context.adapter.checkpoint(context.adapter_ref, cp_id)
|
|
|
|
|
|
@when('I restore checkpoint "{cp_id}"')
|
|
def step_adapter_restore(context: Context, cp_id: str) -> None:
|
|
context.adapter.restore_checkpoint(context.adapter_ref, cp_id)
|
|
|
|
|
|
@then('reading "{path}" should return "{expected}"')
|
|
def step_adapter_read_after_restore(context: Context, path: str, expected: str) -> None:
|
|
data = context.adapter.read(context.adapter_ref, path)
|
|
assert data == expected.encode(), f"Expected {expected!r}, got {data!r}"
|
|
|
|
|
|
@when("I call adapter.cleanup")
|
|
def step_adapter_cleanup(context: Context) -> None:
|
|
context.adapter.cleanup(context.adapter_ref)
|
|
|
|
|
|
@then("the adapter should have no tracked sandboxes")
|
|
def step_adapter_no_sandboxes(context: Context) -> None:
|
|
assert len(context.adapter._sandboxes) == 0
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CustomStrategyConfig
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I attempt to create a CustomStrategyConfig with empty name")
|
|
def step_config_empty_name(context: Context) -> None:
|
|
context.sandbox_error = None
|
|
try:
|
|
CustomStrategyConfig(name="", module="mod", class_name="Cls")
|
|
except ValueError as exc:
|
|
context.sandbox_error = exc
|
|
|
|
|
|
@when("I attempt to create a CustomStrategyConfig with empty module")
|
|
def step_config_empty_module(context: Context) -> None:
|
|
context.sandbox_error = None
|
|
try:
|
|
CustomStrategyConfig(name="x", module="", class_name="Cls")
|
|
except ValueError as exc:
|
|
context.sandbox_error = exc
|
|
|
|
|
|
@when("I attempt to create a CustomStrategyConfig with empty class_name")
|
|
def step_config_empty_class(context: Context) -> None:
|
|
context.sandbox_error = None
|
|
try:
|
|
CustomStrategyConfig(name="x", module="mod", class_name="")
|
|
except ValueError as exc:
|
|
context.sandbox_error = exc
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Factory custom strategy integration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('a SandboxFactory with a custom registry containing "{name}"')
|
|
def step_factory_with_registry(context: Context, name: str) -> None:
|
|
registry = SandboxStrategyRegistry(allowed_prefixes=("cleveragents.",))
|
|
registry.register(
|
|
name,
|
|
"cleveragents.infrastructure.sandbox.strategy_adapter",
|
|
"BuiltInSandboxStrategyAdapter",
|
|
)
|
|
context.factory = SandboxFactory(custom_registry=registry)
|
|
|
|
|
|
@given("a SandboxFactory without a custom registry")
|
|
def step_factory_no_registry(context: Context) -> None:
|
|
context.factory = SandboxFactory()
|
|
|
|
|
|
@then('the factory should report "{name}" as a custom strategy')
|
|
def step_factory_has_custom(context: Context, name: str) -> None:
|
|
assert context.factory.has_custom_strategy(name)
|
|
|
|
|
|
@then('the factory should not report "{name}" as a custom strategy')
|
|
def step_factory_no_custom(context: Context, name: str) -> None:
|
|
assert not context.factory.has_custom_strategy(name)
|