Files
temp/features/steps/tdd_container_instance_stop_2588_steps.py
freemo 9664271562 fix(resources): allow agents resource stop to stop container-instance and devcontainer-instance resources
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
2026-04-05 08:22:45 +00:00

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}"
)