Feature: Devcontainer Session Cleanup and CLI Commands As a CleverAgents developer I want session-scoped container cleanup and CLI stop/rebuild commands So that containers are cleaned up when sessions end and can be managed via CLI # ── Session cleanup ──────────────────────────────────────── Scenario: Active containers stopped on session cleanup Given a mock devcontainer CLI runner And an active container "01TESTLIFECYCLE0000000040" with container ID "ctr-a" And an active container "01TESTLIFECYCLE0000000041" with container ID "ctr-b" When I run session cleanup for session "session-001" Then the container state for "01TESTLIFECYCLE0000000040" should be "stopped" And the container state for "01TESTLIFECYCLE0000000041" should be "stopped" # ── Cleanup service integration ──────────────────────────── Scenario: CleanupService stop_active_devcontainers is callable and returns list When I call CleanupService stop_active_devcontainers with no active containers Then the cleanup result should be an empty list And CleanupService should have a stop_active_devcontainers method Scenario: CleanupService stop_active_devcontainers calls handler cleanup When I call CleanupService stop_active_devcontainers with no active containers Then the cleanup result should be an empty list # ── stop_all_active_containers error path ────────────────── Scenario: Session cleanup handles stop failure gracefully Given a mock devcontainer CLI runner And the runner configured to raise exception on stop And an active container "01TESTLIFECYCLE0000000130" with container ID "ctr-fail" When I run session cleanup for session "session-err" Then the cleanup should return 0 stopped containers # ── CLI stop/rebuild commands (F6) ───────────────────────── Scenario: CLI stop succeeds for active devcontainer-instance Given a mock resource service with devcontainer "local/test-dc" id "01TESTLIFECYCLE0000000200" at "/workspace/project" And an active container "01TESTLIFECYCLE0000000200" with container ID "ctr-cli-stop" When I invoke CLI resource stop "local/test-dc" Then the CLI exit code should be 0 And the CLI output should contain "Stopped" Scenario: CLI stop rejects non-devcontainer resource type Given a mock resource service with git-checkout "local/my-repo" id "01TESTLIFECYCLE0000000201" When I invoke CLI resource stop "local/my-repo" Then the devcontainer CLI exit code should be non-zero And the CLI output should contain "not a stoppable container type" Scenario: CLI rebuild succeeds for stopped devcontainer-instance Given a mock resource service with devcontainer "local/test-dc-rb" id "01TESTLIFECYCLE0000000210" at "/workspace/project" And a stopped container "01TESTLIFECYCLE0000000210" When I invoke CLI resource rebuild "local/test-dc-rb" Then the CLI exit code should be 0 And the CLI output should contain "Rebuilt" Scenario: CLI rebuild rejects resource with no location Given a mock resource service with devcontainer "local/dc-no-loc" id "01TESTLIFECYCLE0000000211" with no location And a stopped container "01TESTLIFECYCLE0000000211" When I invoke CLI resource rebuild "local/dc-no-loc" Then the devcontainer CLI exit code should be non-zero And the CLI output should contain "no location" Scenario: CLI rebuild rejects non-devcontainer resource type Given a mock resource service with git-checkout "local/git-rb" id "01TESTLIFECYCLE0000000212" When I invoke CLI resource rebuild "local/git-rb" Then the devcontainer CLI exit code should be non-zero And the CLI output should contain "rebuild requires devcontainer" # ── R7: CLI test mock assertions ─────────────────────────── Scenario: CLI stop calls stop_container with correct resource_id Given a mock resource service with devcontainer "local/test-dc-r7" id "01TESTLIFECYCLE0000000220" at "/workspace/project" And an active container "01TESTLIFECYCLE0000000220" with container ID "ctr-r7" When I invoke CLI resource stop "local/test-dc-r7" Then the CLI exit code should be 0 And the CLI stop mock should have been called with "01TESTLIFECYCLE0000000220" Scenario: CLI rebuild calls rebuild_container with correct args Given a mock resource service with devcontainer "local/test-dc-r7rb" id "01TESTLIFECYCLE0000000221" at "/workspace/project" And a stopped container "01TESTLIFECYCLE0000000221" When I invoke CLI resource rebuild "local/test-dc-r7rb" Then the CLI exit code should be 0 And the CLI rebuild mock should have been called with "01TESTLIFECYCLE0000000221" and "/workspace/project" # ── R8: DevcontainerHandler coverage ─────────────────────── Scenario: DevcontainerHandler has expected class attributes and is instantiable Then DevcontainerHandler should have _default_strategy "snapshot" And DevcontainerHandler should have _type_label "devcontainer" And DevcontainerHandler should be instantiable Scenario: DevcontainerHandler extends BaseResourceHandler and inherits resolve interface Then DevcontainerHandler should be a subclass of BaseResourceHandler And DevcontainerHandler instance should have a resolve method # ── R12: CLI state precondition validation ───────────────── Scenario: CLI stop rejects container not in running state Given a mock resource service with devcontainer "local/dc-det" id "01TESTLIFECYCLE0000000230" at "/workspace/project" And a lifecycle tracker for resource "01TESTLIFECYCLE0000000230" When I invoke CLI resource stop "local/dc-det" Then the devcontainer CLI exit code should be non-zero And the CLI output should contain "Cannot stop" Scenario: CLI rebuild rejects container in running state Given a mock resource service with devcontainer "local/dc-run" id "01TESTLIFECYCLE0000000231" at "/workspace/project" And an active container "01TESTLIFECYCLE0000000231" with container ID "ctr-rb-state" When I invoke CLI resource rebuild "local/dc-run" Then the devcontainer CLI exit code should be non-zero And the CLI output should contain "Cannot rebuild" # ── R14: stop_all return list assertion ──────────────────── Scenario: Session cleanup returns list of stopped resource IDs Given a mock devcontainer CLI runner And an active container "01TESTLIFECYCLE0000000250" with container ID "ctr-r14a" And an active container "01TESTLIFECYCLE0000000251" with container ID "ctr-r14b" When I run session cleanup for session "session-r14" Then the cleanup stopped list should contain "01TESTLIFECYCLE0000000250" And the cleanup stopped list should contain "01TESTLIFECYCLE0000000251" # ── A2: Stop and rebuild accept --yes flag ───────────────── Scenario: CLI stop with --yes skips confirmation and calls stop_container Given a CLI-mockable running devcontainer resource "test-dc-yes-stop" When I invoke CLI resource stop "test-dc-yes-stop" Then the devcontainer CLI exit code should be 0 And the CLI stop mock should have been called once Scenario: CLI rebuild with --yes skips confirmation and calls rebuild_container Given a CLI-mockable stopped devcontainer resource "test-dc-yes-rebuild" When I invoke CLI resource rebuild "test-dc-yes-rebuild" Then the devcontainer CLI exit code should be 0 And the CLI rebuild mock should have been called once # ── Issue #2588: container-instance IS stoppable via CLI ──── Scenario: CLI stop accepts container-instance type (issue #2588 fix) Given a mock resource service with container-instance "local/ctr-stop" id "01TESTLIFECYCLE0000000370" at "/workspace/project" And an active container "01TESTLIFECYCLE0000000370" with container ID "ctr-ci-stop" When I invoke CLI resource stop "local/ctr-stop" Then the CLI exit code should be 0 And the CLI output should contain "Stopped" And the CLI stop mock should have been called with "01TESTLIFECYCLE0000000370" # ── F6-r6: CLI handler-level error paths ──────────────────── Scenario: CLI stop handles NotFoundError from show_resource Given a mock resource service that raises NotFoundError for "local/missing" When I invoke CLI resource stop "local/missing" Then the devcontainer CLI exit code should be non-zero And the CLI output should contain "Resource not found" Scenario: CLI rebuild handles NotFoundError from show_resource Given a mock resource service that raises NotFoundError for "local/missing-rb" When I invoke CLI resource rebuild "local/missing-rb" Then the devcontainer CLI exit code should be non-zero And the CLI output should contain "Resource not found" Scenario: CLI stop handles RuntimeError from stop_container Given a mock resource service with devcontainer "local/dc-rterr" id "01TESTLIFECYCLE0000000500" at "/workspace/project" And an active container "01TESTLIFECYCLE0000000500" with container ID "ctr-rterr" And CLI stop_container mock that raises RuntimeError When I invoke CLI resource stop with error mock "local/dc-rterr" Then the devcontainer CLI exit code should be non-zero And the CLI output should contain "Stop failed" Scenario: CLI rebuild handles RuntimeError from rebuild_container Given a mock resource service with devcontainer "local/dc-rberr" id "01TESTLIFECYCLE0000000501" at "/workspace/project" And a stopped container "01TESTLIFECYCLE0000000501" And CLI rebuild_container mock that raises RuntimeError When I invoke CLI resource rebuild with error mock "local/dc-rberr" Then the devcontainer CLI exit code should be non-zero And the CLI output should contain "Rebuild failed" # ── R7-F1: Session-scoped container cleanup ──────────────── Scenario: Session cleanup only stops containers belonging to that session Given a mock devcontainer CLI runner And a session-scoped active container "01TESTLIFECYCLE0000000600" with ID "ctr-s1" session "sess-A" And a session-scoped active container "01TESTLIFECYCLE0000000601" with ID "ctr-s2" session "sess-B" When I run session cleanup for session "sess-A" Then the container state for "01TESTLIFECYCLE0000000600" should be "stopped" And the container state for "01TESTLIFECYCLE0000000601" should be "running" Scenario: Session cleanup with no session_id stops all containers Given a mock devcontainer CLI runner And an active container "01TESTLIFECYCLE0000000610" with container ID "ctr-all1" And an active container "01TESTLIFECYCLE0000000611" with container ID "ctr-all2" When I run session cleanup with no session filter Then the container state for "01TESTLIFECYCLE0000000610" should be "stopped" And the container state for "01TESTLIFECYCLE0000000611" should be "stopped" # ── R8-F1: Eviction wired into production cleanup ──────────── Scenario: Session cleanup triggers terminal tracker eviction Given a mock devcontainer CLI runner And 210 stopped container trackers in the registry And an active container "01TESTLIFECYCLE0000000650" with container ID "ctr-evict" When I run session cleanup for session "session-evict" Then the container state for "01TESTLIFECYCLE0000000650" should be "stopped" And the registry should have at most 200 terminal trackers # ── TEST-3: Coverage gap scenarios ────────────────────────── Scenario: list_active_containers_for_session returns only matching session Given a mock devcontainer CLI runner And a session-scoped active container "01TESTLIFECYCLE0000000700" with ID "ctr-ts3a" session "sess-X" And a session-scoped active container "01TESTLIFECYCLE0000000701" with ID "ctr-ts3b" session "sess-Y" When I list active containers for session "sess-X" Then the session container list should contain "01TESTLIFECYCLE0000000700" And the session container list should not contain "01TESTLIFECYCLE0000000701" Scenario: list_active_containers_for_session with empty session returns all Given a mock devcontainer CLI runner And an active container "01TESTLIFECYCLE0000000710" with container ID "ctr-ts3c" And an active container "01TESTLIFECYCLE0000000711" with container ID "ctr-ts3d" When I list active containers for an empty session Then the session container list should contain "01TESTLIFECYCLE0000000710" And the session container list should contain "01TESTLIFECYCLE0000000711" Scenario: list_active_containers_for_session includes unscoped containers Given a mock devcontainer CLI runner And an active container "01TESTLIFECYCLE0000000720" with container ID "ctr-unscoped" And a session-scoped active container "01TESTLIFECYCLE0000000721" with ID "ctr-scoped" session "sess-Z" When I list active containers for session "sess-Z" Then the session container list should contain "01TESTLIFECYCLE0000000720" And the session container list should contain "01TESTLIFECYCLE0000000721" # ── R7-F4: Session close cleanup without session service ──── Scenario: Session close cleans up containers even without session service Given a facade with no session service And a session-scoped active container "01TESTLIFECYCLE0000000630" with no docker ID session "sess-f4" When I close session "sess-f4" via the facade Then the container state for "01TESTLIFECYCLE0000000630" should be "stopped"