"""Step definitions for Resource Registry domain model tests.""" from __future__ import annotations import time from behave import given, then, when from behave.runner import Context from pydantic import ValidationError from cleveragents.domain.models.core.resource import ( PhysVirt, Resource, ResourceCapabilities, SandboxStrategy, ) VALID_ULID = "01HZQX7K8M3N4P5R6S7T8V9W0X" # -- PhysVirt enum ----------------------------------------------------------- @then('the PhysVirt enum should have values "{values}"') def step_physvirt_values(context: Context, values: str) -> None: """Check PhysVirt enum has exactly the expected values.""" expected = {v.strip() for v in values.split(",")} actual = {member.value for member in PhysVirt} assert actual == expected, f"Expected {expected}, got {actual}" @given('a PhysVirt value of "{value}"') def step_create_physvirt(context: Context, value: str) -> None: """Create a PhysVirt enum value.""" context.physvirt = PhysVirt(value) @then('the PhysVirt string value should be "{expected}"') def step_check_physvirt_value(context: Context, expected: str) -> None: """Check PhysVirt string value.""" assert str(context.physvirt) == expected, ( f"Expected '{expected}', got '{context.physvirt}'" ) @when('I try to create a PhysVirt with value "{value}"') def step_try_create_physvirt(context: Context, value: str) -> None: """Try to create a PhysVirt enum value, expecting failure.""" try: PhysVirt(value) context.resource_error = None except ValueError as e: context.resource_error = e @then("a resource validation error should be raised") def step_check_resource_error(context: Context) -> None: """Check that a validation error was raised.""" assert context.resource_error is not None, ( "Expected a validation error but none was raised" ) # -- SandboxStrategy enum --------------------------------------------------- @then('the SandboxStrategy enum should have values "{values}"') def step_sandbox_strategy_values(context: Context, values: str) -> None: """Check SandboxStrategy enum has exactly the expected values.""" expected = {v.strip() for v in values.split(",")} actual = {member.value for member in SandboxStrategy} assert actual == expected, f"Expected {expected}, got {actual}" @given('a SandboxStrategy value of "{value}"') def step_create_sandbox_strategy(context: Context, value: str) -> None: """Create a SandboxStrategy enum value.""" context.sandbox_strategy = SandboxStrategy(value) @then('the SandboxStrategy string value should be "{expected}"') def step_check_sandbox_strategy_value(context: Context, expected: str) -> None: """Check SandboxStrategy string value.""" assert str(context.sandbox_strategy) == expected, ( f"Expected '{expected}', got '{context.sandbox_strategy}'" ) @when('I try to create a SandboxStrategy with value "{value}"') def step_try_create_sandbox_strategy(context: Context, value: str) -> None: """Try to create a SandboxStrategy, expecting failure.""" try: SandboxStrategy(value) context.resource_error = None except ValueError as e: context.resource_error = e # -- ResourceCapabilities --------------------------------------------------- @given("default ResourceCapabilities") def step_default_capabilities(context: Context) -> None: """Create default ResourceCapabilities.""" context.capabilities = ResourceCapabilities() @given("ResourceCapabilities with readable false and checkpointable true") def step_custom_capabilities(context: Context) -> None: """Create ResourceCapabilities with custom values.""" context.capabilities = ResourceCapabilities(readable=False, checkpointable=True) @then("the capability {field} should be {expected}") def step_check_capability(context: Context, field: str, expected: str) -> None: """Check a capability field value.""" actual = getattr(context.capabilities, field) expected_bool = expected.lower() == "true" assert actual == expected_bool, f"Expected {field}={expected_bool}, got {actual}" @when("I try to mutate a ResourceCapabilities field") def step_try_mutate_capabilities(context: Context) -> None: """Try to mutate a frozen ResourceCapabilities.""" try: context.capabilities.readable = False # type: ignore[misc] context.resource_error = None except ValidationError as e: context.resource_error = e # -- Resource model - creation ----------------------------------------------- @given( 'a Resource with resource_id "{rid}" type "{rtype}"' ' and classification "{classification}"' ) def step_create_minimal_resource( context: Context, rid: str, rtype: str, classification: str ) -> None: """Create a minimal Resource.""" context.resource = Resource( resource_id=rid, resource_type_name=rtype, classification=PhysVirt(classification), ) @given( 'a full Resource with id "{rid}" name "{name}" type "{rtype}"' ' classification "{classification}" description "{desc}"' ) def step_create_full_resource( context: Context, rid: str, name: str, rtype: str, classification: str, desc: str, ) -> None: """Create a full Resource with all fields.""" context.resource = Resource( resource_id=rid, name=name, resource_type_name=rtype, classification=PhysVirt(classification), description=desc, ) @given('a Resource with sandbox strategy "{strategy}"') def step_create_resource_with_sandbox(context: Context, strategy: str) -> None: """Create a Resource with a sandbox strategy override.""" context.resource = Resource( resource_id=VALID_ULID, resource_type_name="git-checkout", classification=PhysVirt.PHYSICAL, sandbox_strategy=SandboxStrategy(strategy), ) @given("a Resource with sandboxable false and checkpointable true") def step_create_resource_with_capabilities(context: Context) -> None: """Create a Resource with custom capabilities.""" context.resource = Resource( resource_id=VALID_ULID, resource_type_name="git-checkout", classification=PhysVirt.PHYSICAL, capabilities=ResourceCapabilities(sandboxable=False, checkpointable=True), ) @given("a Resource with writable false") def step_create_readonly_resource(context: Context) -> None: """Create a Resource with writable=false.""" context.resource = Resource( resource_id=VALID_ULID, resource_type_name="git-checkout", classification=PhysVirt.PHYSICAL, capabilities=ResourceCapabilities(writable=False), ) @given('a Resource with content_hash "{hash_val}"') def step_create_resource_with_hash(context: Context, hash_val: str) -> None: """Create a Resource with content_hash.""" context.resource = Resource( resource_id=VALID_ULID, resource_type_name="git-checkout", classification=PhysVirt.PHYSICAL, content_hash=hash_val, ) @given('a Resource with location "{location}"') def step_create_resource_with_location(context: Context, location: str) -> None: """Create a Resource with location.""" context.resource = Resource( resource_id=VALID_ULID, resource_type_name="git-checkout", classification=PhysVirt.PHYSICAL, location=location, ) @given("a Resource with parents and children") def step_create_resource_with_dag(context: Context) -> None: """Create a Resource with parent/child references.""" parent_id = "01HZQX7K8M3N4P5R6S7T8V9W01" child1_id = "01HZQX7K8M3N4P5R6S7T8V9W02" child2_id = "01HZQX7K8M3N4P5R6S7T8V9W03" context.resource = Resource( resource_id=VALID_ULID, resource_type_name="git-checkout", classification=PhysVirt.PHYSICAL, parents=[parent_id], children=[child1_id, child2_id], ) # -- Resource model - assertion steps ---------------------------------------- @then('the resource resource_id should be "{expected}"') def step_check_resource_id(context: Context, expected: str) -> None: """Check resource_id.""" assert context.resource.resource_id == expected @then('the resource type name should be "{expected}"') def step_check_resource_type_name(context: Context, expected: str) -> None: """Check resource_type_name.""" assert context.resource.resource_type_name == expected @then('the resource classification should be "{expected}"') def step_check_classification(context: Context, expected: str) -> None: """Check classification.""" assert context.resource.classification == expected @then("the resource name should be empty") def step_check_name_empty(context: Context) -> None: """Check name is None.""" assert context.resource.name is None @then('the resource name should be "{expected}"') def step_check_resource_name(context: Context, expected: str) -> None: """Check resource name.""" assert context.resource.name == expected @then("the resource description should be empty") def step_check_description_empty(context: Context) -> None: """Check description is None.""" assert context.resource.description is None @then('the resource description should be "{expected}"') def step_check_resource_description(context: Context, expected: str) -> None: """Check description.""" assert context.resource.description == expected @then("the resource properties should be empty") def step_check_properties_empty(context: Context) -> None: """Check properties dict is empty.""" assert context.resource.properties == {} @then("the resource parents should be empty") def step_check_parents_empty(context: Context) -> None: """Check parents list is empty.""" assert context.resource.parents == [] @then("the resource children should be empty") def step_check_children_empty(context: Context) -> None: """Check children list is empty.""" assert context.resource.children == [] @then("the resource linked_projects should be empty") def step_check_linked_projects_empty(context: Context) -> None: """Check linked_projects list is empty.""" assert context.resource.linked_projects == [] @then("the resource created_at should be set") def step_check_created_at(context: Context) -> None: """Check created_at is not None.""" assert context.resource.created_at is not None @then("the resource updated_at should be set") def step_check_updated_at(context: Context) -> None: """Check updated_at is not None.""" assert context.resource.updated_at is not None @then('the resource sandbox strategy should be "{expected}"') def step_check_sandbox_strategy(context: Context, expected: str) -> None: """Check sandbox_strategy.""" assert context.resource.sandbox_strategy == expected @then("the resource can_sandbox should be {expected}") def step_check_can_sandbox(context: Context, expected: str) -> None: """Check can_sandbox property.""" expected_bool = expected.lower() == "true" assert context.resource.can_sandbox == expected_bool @then("the resource can_checkpoint should be {expected}") def step_check_can_checkpoint(context: Context, expected: str) -> None: """Check can_checkpoint property.""" expected_bool = expected.lower() == "true" assert context.resource.can_checkpoint == expected_bool @then("the resource is_physical should be {expected}") def step_check_is_physical(context: Context, expected: str) -> None: """Check is_physical property.""" expected_bool = expected.lower() == "true" assert context.resource.is_physical == expected_bool @then("the resource is_virtual should be {expected}") def step_check_is_virtual(context: Context, expected: str) -> None: """Check is_virtual property.""" expected_bool = expected.lower() == "true" assert context.resource.is_virtual == expected_bool @then("the resource is_read_only should be {expected}") def step_check_is_read_only(context: Context, expected: str) -> None: """Check is_read_only property.""" expected_bool = expected.lower() == "true" assert context.resource.is_read_only == expected_bool @then('the resource content_hash should be "{expected}"') def step_check_content_hash(context: Context, expected: str) -> None: """Check content_hash.""" assert context.resource.content_hash == expected @then('the resource location should be "{expected}"') def step_check_location(context: Context, expected: str) -> None: """Check location.""" assert context.resource.location == expected @then("the resource should have {count:d} parent") def step_check_parent_count(context: Context, count: int) -> None: """Check parent count.""" assert len(context.resource.parents) == count @then("the resource should have {count:d} children") def step_check_child_count(context: Context, count: int) -> None: """Check children count.""" assert len(context.resource.children) == count # -- Resource model - validation failures ------------------------------------ @when('I try to create a Resource with resource_id "{rid}"') def step_try_create_resource_bad_ulid(context: Context, rid: str) -> None: """Try to create a Resource with invalid ULID.""" try: Resource( resource_id=rid, resource_type_name="git-checkout", classification=PhysVirt.PHYSICAL, ) context.resource_error = None except ValidationError as e: context.resource_error = e @when("I try to create a Resource with empty resource_type_name") def step_try_create_resource_empty_type(context: Context) -> None: """Try to create a Resource with empty type name.""" try: Resource( resource_id=VALID_ULID, resource_type_name="", classification=PhysVirt.PHYSICAL, ) context.resource_error = None except ValidationError as e: context.resource_error = e @when("I try to create a Resource with empty string name") def step_try_create_resource_empty_name(context: Context) -> None: """Try to create a Resource with empty string name.""" try: Resource( resource_id=VALID_ULID, name="", resource_type_name="git-checkout", classification=PhysVirt.PHYSICAL, ) context.resource_error = None except ValidationError as e: context.resource_error = e @when("I try to create a Resource with empty location") def step_try_create_resource_empty_location(context: Context) -> None: """Try to create a Resource with empty location.""" try: Resource( resource_id=VALID_ULID, resource_type_name="git-checkout", classification=PhysVirt.PHYSICAL, location="", ) context.resource_error = None except ValidationError as e: context.resource_error = e # -- Resource model - frozen ------------------------------------------------- @when("I try to mutate a Resource field") def step_try_mutate_resource(context: Context) -> None: """Try to mutate a frozen Resource.""" try: context.resource.name = "new-name" # type: ignore[misc] context.resource_error = None except ValidationError as e: context.resource_error = e # -- Resource model - with_updated_at --------------------------------------- @when("I call with_updated_at on the resource") def step_call_with_updated_at(context: Context) -> None: """Call with_updated_at and store old/new resources.""" context.old_resource = context.resource time.sleep(0.01) # Ensure timestamp differs context.new_resource = context.resource.with_updated_at() @then("the new resource should have a different updated_at") def step_check_different_updated_at(context: Context) -> None: """Check updated_at changed.""" assert context.new_resource.updated_at != context.old_resource.updated_at @then("the new resource should have the same resource_id") def step_check_same_resource_id(context: Context) -> None: """Check resource_id unchanged.""" assert context.new_resource.resource_id == context.old_resource.resource_id