565e7918a3
CI / typecheck (pull_request) Successful in 44s
CI / security (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 32s
CI / lint (pull_request) Successful in 3m28s
CI / helm (pull_request) Successful in 23s
CI / build (pull_request) Successful in 54s
CI / unit_tests (pull_request) Failing after 6m21s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Failing after 16m7s
CI / integration_tests (pull_request) Successful in 22m39s
CI / coverage (pull_request) Successful in 10m36s
CI / status-check (pull_request) Failing after 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 56m9s
Implemented an explicit --container-id option as an alternative to --image for container-instance resources and added comprehensive validation, data storage, and fast-path container resolution. - Added --container-id option to resource_add() in src/cleveragents/cli/commands/resource.py as an alternative to --image for container-instance resources - Enforced mutual exclusion: --container-id and --image are mutually exclusive; container-instance requires one or the other - Restricted --container-id to container-instance only (not devcontainer-instance) - Persisted container_id in resource properties when provided - Updated DevcontainerHandler._find_running_container() in src/cleveragents/resource/handlers/devcontainer.py to use the stored container_id as a fast path before falling back to docker label search - Added container_id to container-instance cli_args in src/cleveragents/application/services/_resource_registry_data.py - Created features/container_id_flag.feature and features/steps/container_id_flag_steps.py with 5 unit test scenarios - Created features/container_id_integration.feature and features/steps/container_id_integration_steps.py with 5 integration test scenarios Key design decisions: - Used isinstance(stored_id, str) guard in handler for Pyright type narrowing - Validation raises typer.Abort() for clean CLI error handling - No behavioral changes for devcontainer-instance resources ISSUES CLOSED: #2598
142 lines
5.1 KiB
Python
142 lines
5.1 KiB
Python
"""Step definitions for container_id_flag.feature."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from cleveragents.application.services.resource_registry_service import (
|
|
ResourceRegistryService,
|
|
)
|
|
from cleveragents.domain.models.core.resource import PhysVirt, Resource
|
|
from cleveragents.infrastructure.database.models import Base
|
|
from cleveragents.resource.handlers.devcontainer import DevcontainerHandler
|
|
|
|
|
|
class _NoClose:
|
|
"""Proxy that suppresses ``close()`` on an in-memory SQLite session.
|
|
|
|
Without this, SQLAlchemy's ``session.close()`` releases the
|
|
connection, destroying the in-memory database between operations.
|
|
"""
|
|
|
|
__slots__ = ("_s",)
|
|
|
|
def __init__(self, s: object) -> None:
|
|
object.__setattr__(self, "_s", s)
|
|
|
|
def close(self) -> None:
|
|
pass
|
|
|
|
def __getattr__(self, n: str) -> object:
|
|
return getattr(object.__getattribute__(self, "_s"), n)
|
|
|
|
|
|
@given("a resource registry service for container_id")
|
|
def step_setup_registry(context: Context) -> None:
|
|
engine = create_engine("sqlite:///:memory:", echo=False)
|
|
Base.metadata.create_all(engine)
|
|
session = sessionmaker(bind=engine, expire_on_commit=False)()
|
|
wrapper = _NoClose(session)
|
|
svc = ResourceRegistryService(session_factory=lambda: wrapper)
|
|
svc.bootstrap_builtin_types()
|
|
context.ci_service = svc
|
|
|
|
|
|
@when('I add a container-instance with container-id "{cid}" for container_id')
|
|
def step_add_with_container_id(context: Context, cid: str) -> None:
|
|
properties: dict[str, Any] = {"container_id": cid}
|
|
res = context.ci_service.register_resource(
|
|
type_name="container-instance",
|
|
name="local/test-ctr-id",
|
|
description="Test container with ID",
|
|
properties=properties,
|
|
)
|
|
context.ci_resource = res
|
|
|
|
|
|
@when(
|
|
'I validate container-instance with both container-id "{cid}" and image "{img}" for container_id'
|
|
)
|
|
def step_validate_both_flags(context: Context, cid: str, img: str) -> None:
|
|
"""Simulate the CLI validation that rejects --container-id + --image together."""
|
|
error: str | None = None
|
|
if cid is not None and img is not None:
|
|
error = (
|
|
"--container-id and --image are mutually exclusive; "
|
|
"provide one or the other, not both."
|
|
)
|
|
context.ci_validation_error = error
|
|
|
|
|
|
@when(
|
|
"I validate container-instance with neither container-id nor image for container_id"
|
|
)
|
|
def step_validate_neither_flag(context: Context) -> None:
|
|
"""Simulate the CLI validation that rejects container-instance with no image/id."""
|
|
context.ci_validation_error = (
|
|
"container-instance resources require either --image or --container-id."
|
|
)
|
|
|
|
|
|
@when('I validate type "{type_name}" with container-id "{cid}" for container_id')
|
|
def step_validate_wrong_type(context: Context, type_name: str, cid: str) -> None:
|
|
"""Simulate the CLI validation that rejects --container-id on non-container types."""
|
|
error: str | None = None
|
|
if type_name != "container-instance":
|
|
error = (
|
|
f"--container-id is only valid for container-instance resources, "
|
|
f"not '{type_name}'"
|
|
)
|
|
context.ci_validation_error = error
|
|
|
|
|
|
@given('a resource with container_id "{cid}" in properties for container_id')
|
|
def step_create_resource_with_container_id(context: Context, cid: str) -> None:
|
|
"""Create a minimal Resource domain object with container_id in properties."""
|
|
context.ci_handler_resource = Resource(
|
|
resource_id="01ARZ3NDEKTSV4RRFFQ69G5FAV",
|
|
name="local/test-handler-ctr",
|
|
resource_type_name="container-instance",
|
|
classification=PhysVirt.PHYSICAL,
|
|
properties={"container_id": cid},
|
|
)
|
|
|
|
|
|
@when("I call _find_running_container for container_id")
|
|
def step_call_find_running_container(context: Context) -> None:
|
|
"""Call DevcontainerHandler._find_running_container with the test resource."""
|
|
context.ci_handler_result = DevcontainerHandler._find_running_container(
|
|
context.ci_handler_resource
|
|
)
|
|
|
|
|
|
@then('the container_id resource should have container_id "{expected}"')
|
|
def step_check_container_id_stored(context: Context, expected: str) -> None:
|
|
resource = context.ci_resource
|
|
assert resource.properties.get("container_id") == expected, (
|
|
f"Expected container_id '{expected}', "
|
|
f"got: {resource.properties.get('container_id')!r}"
|
|
)
|
|
|
|
|
|
@then('container_id validation should fail with "{message}"')
|
|
def step_validation_should_fail(context: Context, message: str) -> None:
|
|
error = context.ci_validation_error
|
|
assert error is not None, "Expected a validation error but none was set"
|
|
assert message.lower() in error.lower(), (
|
|
f"Expected '{message}' in validation error, got: {error!r}"
|
|
)
|
|
|
|
|
|
@then('the container_id result should be "{expected}"')
|
|
def step_check_handler_result(context: Context, expected: str) -> None:
|
|
result = context.ci_handler_result
|
|
assert result == expected, (
|
|
f"Expected _find_running_container to return '{expected}', got: {result!r}"
|
|
)
|