forked from HAL9000/cleveragents-core
9664271562
Add container-instance to _STOPPABLE_TYPES so that agents resource stop accepts both container-instance and devcontainer-instance resources, as required by the specification. The stop_container function already uses docker stop with the container_id from the lifecycle tracker, which works generically for both resource types. - Add container-instance to _STOPPABLE_TYPES frozenset - Update resource_stop() docstring to reflect both stoppable types - Update devcontainer_cleanup.feature: F19 scenario now expects success for container-instance stop (was incorrectly expecting rejection) - Add TDD feature file and step definitions for issue #2588 regression guard ISSUES CLOSED: #2588
166 lines
5.7 KiB
Python
166 lines
5.7 KiB
Python
"""Step definitions for TDD issue #2588.
|
|
|
|
Verifies that ``agents resource stop`` accepts both ``container-instance``
|
|
and ``devcontainer-instance`` resource types, and still rejects other types.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
from typer.testing import CliRunner
|
|
|
|
from cleveragents.cli.commands.resource import _STOPPABLE_TYPES
|
|
from cleveragents.cli.commands.resource import app as resource_app
|
|
from cleveragents.domain.models.core.container_lifecycle import (
|
|
ContainerLifecycleState,
|
|
ContainerLifecycleTracker,
|
|
)
|
|
from cleveragents.resource.handlers.devcontainer import set_lifecycle_tracker
|
|
|
|
_CLI_PATCH_SERVICE = "cleveragents.cli.commands.resource._get_registry_service"
|
|
_CLI_PATCH_STOP = "cleveragents.cli.commands.resource.stop_container"
|
|
|
|
|
|
def _make_mock_resource(
|
|
*,
|
|
resource_id: str,
|
|
name: str,
|
|
resource_type_name: str,
|
|
location: str | None = None,
|
|
) -> MagicMock:
|
|
res = MagicMock()
|
|
res.resource_id = resource_id
|
|
res.name = name
|
|
res.resource_type_name = resource_type_name
|
|
res.location = location
|
|
res.properties = None
|
|
return res
|
|
|
|
|
|
@given('a running container-instance resource "{name}" with id "{resource_id}"')
|
|
def step_running_container_instance(
|
|
context: Context, name: str, resource_id: str
|
|
) -> None:
|
|
"""Set up a running container-instance resource for stop tests."""
|
|
mock_service = MagicMock()
|
|
mock_resource = _make_mock_resource(
|
|
resource_id=resource_id,
|
|
name=name,
|
|
resource_type_name="container-instance",
|
|
location="/workspace/project",
|
|
)
|
|
mock_service.show_resource.return_value = mock_resource
|
|
context.tdd2588_mock_service = mock_service
|
|
context.tdd2588_resource_id = resource_id
|
|
# Set up lifecycle tracker in running state
|
|
tracker = ContainerLifecycleTracker(
|
|
resource_id=resource_id,
|
|
current_state=ContainerLifecycleState.RUNNING,
|
|
container_id="ctr-2588-ci",
|
|
workspace_path="/workspace/project",
|
|
)
|
|
set_lifecycle_tracker(tracker)
|
|
|
|
|
|
@given('a running devcontainer-instance resource "{name}" with id "{resource_id}"')
|
|
def step_running_devcontainer_instance(
|
|
context: Context, name: str, resource_id: str
|
|
) -> None:
|
|
"""Set up a running devcontainer-instance resource for stop tests."""
|
|
mock_service = MagicMock()
|
|
mock_resource = _make_mock_resource(
|
|
resource_id=resource_id,
|
|
name=name,
|
|
resource_type_name="devcontainer-instance",
|
|
location="/workspace/project",
|
|
)
|
|
mock_service.show_resource.return_value = mock_resource
|
|
context.tdd2588_mock_service = mock_service
|
|
context.tdd2588_resource_id = resource_id
|
|
# Set up lifecycle tracker in running state
|
|
tracker = ContainerLifecycleTracker(
|
|
resource_id=resource_id,
|
|
current_state=ContainerLifecycleState.RUNNING,
|
|
container_id="ctr-2588-dc",
|
|
workspace_path="/workspace/project",
|
|
)
|
|
set_lifecycle_tracker(tracker)
|
|
|
|
|
|
@given('a git-checkout resource "{name}" with id "{resource_id}"')
|
|
def step_git_checkout_resource(context: Context, name: str, resource_id: str) -> None:
|
|
"""Set up a git-checkout resource (non-stoppable) for stop tests."""
|
|
mock_service = MagicMock()
|
|
mock_resource = _make_mock_resource(
|
|
resource_id=resource_id,
|
|
name=name,
|
|
resource_type_name="git-checkout",
|
|
)
|
|
mock_service.show_resource.return_value = mock_resource
|
|
context.tdd2588_mock_service = mock_service
|
|
context.tdd2588_resource_id = resource_id
|
|
|
|
|
|
@when('I invoke resource stop for "{name}" with --yes')
|
|
def step_invoke_resource_stop(context: Context, name: str) -> None:
|
|
"""Invoke the resource stop CLI command with --yes flag."""
|
|
runner = CliRunner()
|
|
mock_service = context.tdd2588_mock_service
|
|
|
|
with (
|
|
patch(_CLI_PATCH_SERVICE, return_value=mock_service),
|
|
patch(_CLI_PATCH_STOP) as mock_stop,
|
|
):
|
|
mock_stop.return_value = MagicMock()
|
|
result = runner.invoke(resource_app, ["stop", name, "--yes"])
|
|
|
|
context.tdd2588_result = result
|
|
context.tdd2588_mock_stop = mock_stop
|
|
|
|
|
|
@when("I inspect the _STOPPABLE_TYPES constant")
|
|
def step_inspect_stoppable_types(context: Context) -> None:
|
|
"""Capture the _STOPPABLE_TYPES constant for inspection."""
|
|
context.tdd2588_stoppable_types = _STOPPABLE_TYPES
|
|
|
|
|
|
@then("the stop exit code should be 0")
|
|
def step_stop_exit_code_zero(context: Context) -> None:
|
|
result = context.tdd2588_result
|
|
assert result.exit_code == 0, (
|
|
f"Expected exit code 0, got {result.exit_code}. Output:\n{result.output}"
|
|
)
|
|
|
|
|
|
@then("the stop exit code should be non-zero")
|
|
def step_stop_exit_code_nonzero(context: Context) -> None:
|
|
result = context.tdd2588_result
|
|
assert result.exit_code != 0, (
|
|
f"Expected non-zero exit code, got {result.exit_code}. Output:\n{result.output}"
|
|
)
|
|
|
|
|
|
@then('the stop output should contain "{text}"')
|
|
def step_stop_output_contains(context: Context, text: str) -> None:
|
|
result = context.tdd2588_result
|
|
assert text in result.output, f"Expected '{text}' in output, got:\n{result.output}"
|
|
|
|
|
|
@then('_STOPPABLE_TYPES should contain "{resource_type}"')
|
|
def step_stoppable_types_contains(context: Context, resource_type: str) -> None:
|
|
stoppable = context.tdd2588_stoppable_types
|
|
assert resource_type in stoppable, (
|
|
f"Expected '{resource_type}' in _STOPPABLE_TYPES, got: {stoppable}"
|
|
)
|
|
|
|
|
|
@then('_STOPPABLE_TYPES should not contain "{resource_type}"')
|
|
def step_stoppable_types_not_contains(context: Context, resource_type: str) -> None:
|
|
stoppable = context.tdd2588_stoppable_types
|
|
assert resource_type not in stoppable, (
|
|
f"Expected '{resource_type}' NOT in _STOPPABLE_TYPES, got: {stoppable}"
|
|
)
|