---
phase: 02-host-management
plan: 01
type: execute
wave: 0
depends_on: []
files_modified:
  - tests/test_hosts.py
  - tests/test_hardware.py
  - tests/test_ssh_connection.py
  - tests/test_cli_host.py
  - tests/conftest.py
autonomous: true
requirements:
  - HOST-01
  - HOST-02
  - HOST-03
  - HOST-04
  - HOST-05

must_haves:
  truths:
    - "Test stubs exist for all host management functionality"
    - "SSH connection can be mocked in tests without real connections"
    - "Ansible-runner can be mocked in tests without real remote hosts"
    - "Tests fail before implementation (RED phase ready)"
  artifacts:
    - path: "tests/test_hosts.py"
      provides: "Host storage test stubs"
      contains: "def test_"
    - path: "tests/test_hardware.py"
      provides: "Hardware detection test stubs"
      contains: "def test_"
    - path: "tests/test_ssh_connection.py"
      provides: "SSH connection test stubs"
      contains: "def test_"
    - path: "tests/test_cli_host.py"
      provides: "CLI host command test stubs"
      contains: "def test_"
    - path: "tests/conftest.py"
      provides: "SSH and ansible-runner mock fixtures"
      contains: "mock_ssh"
  key_links:
    - from: "tests/test_hosts.py"
      to: "tests/conftest.py"
      via: "fixture imports"
      pattern: "isolated_config|mock_ssh"
---

<objective>
Create test scaffolding for Phase 2 host management functionality.

Purpose: Satisfy Nyquist rule - all subsequent implementation tasks can verify via automated tests. Without Wave 0 test stubs, Wave 1-2 tasks would have MISSING verification.

Output: Test files with failing stubs that define expected behavior for host storage, hardware detection, SSH connection testing, and CLI commands.
</objective>

<execution_context>
@/home/devashish/workspace/ric03uec/clawrium/.claude/get-shit-done/workflows/execute-plan.md
@/home/devashish/workspace/ric03uec/clawrium/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/02-host-management/02-CONTEXT.md
@.planning/phases/02-host-management/02-RESEARCH.md
@.planning/phases/02-host-management/02-VALIDATION.md

<interfaces>
<!-- Existing code patterns the executor needs to follow -->

From tests/conftest.py (existing fixtures to extend):
```python
@pytest.fixture
def tmp_config_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    """Create a temporary config directory and set XDG_CONFIG_HOME."""
    monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
    return tmp_path

@pytest.fixture
def isolated_config(tmp_config_dir: Path) -> Path:
    """Return path where clawrium config should be created."""
    return tmp_config_dir / "clawrium"
```

From src/clawrium/core/config.py (functions tests will use):
```python
def get_config_dir() -> Path
def init_config_dir() -> Path
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create SSH and Ansible mock fixtures</name>
  <files>tests/conftest.py</files>
  <read_first>
    - tests/conftest.py
    - .planning/phases/02-host-management/02-RESEARCH.md (Pattern 3 and 4 for mock structure)
  </read_first>
  <action>
Extend tests/conftest.py with mock fixtures for SSH and Ansible operations:

1. Add mock_ssh_client fixture that returns a mock paramiko.SSHClient:
   - Mock connect() to succeed by default
   - Mock exec_command() to return ("", "Connection OK", "")
   - Mock close() as no-op
   - Provide parametrization hook for failure scenarios

2. Add mock_ssh_client_fail fixture for testing connection failures:
   - connect() raises paramiko.AuthenticationException

3. Add mock_ansible_runner fixture that returns mock ansible_runner.run():
   - Returns object with status='successful'
   - get_fact_cache() returns sample facts dict with:
     - ansible_architecture: "x86_64"
     - ansible_processor_cores: 4
     - ansible_processor_count: 1
     - ansible_memtotal_mb: 16384
     - ansible_mounts: [{"mount": "/", "size_total": 500000000000, "size_available": 200000000000}]
   - events list with runner_on_ok event containing GPU lspci output

4. Add mock_ssh_config fixture that returns mock SSHConfig:
   - lookup() returns {"hostname": "192.168.1.100", "user": "xclm", "port": "22"}

5. Add sample_host_data fixture returning a complete host dict per D-04 schema:
   ```python
   {
       "hostname": "192.168.1.100",
       "port": 22,
       "user": "xclm",
       "auth_method": "key",
       "alias": "testhost",
       "hardware": {
           "architecture": "x86_64",
           "processor_cores": 4,
           "processor_count": 1,
           "memtotal_mb": 16384,
           "mounts": [{"mount": "/", "size_total": 500000000000, "size_available": 200000000000}],
           "gpu": {"present": True, "vendor": "nvidia"}
       },
       "metadata": {
           "added_at": "2026-03-21T00:00:00Z",
           "last_seen": "2026-03-21T00:00:00Z",
           "tags": ["dev", "gpu"]
       }
   }
   ```
  </action>
  <acceptance_criteria>
    - tests/conftest.py contains `def mock_ssh_client`
    - tests/conftest.py contains `def mock_ssh_client_fail`
    - tests/conftest.py contains `def mock_ansible_runner`
    - tests/conftest.py contains `def mock_ssh_config`
    - tests/conftest.py contains `def sample_host_data`
    - tests/conftest.py contains `ansible_architecture`
    - tests/conftest.py contains `paramiko` import or mock reference
  </acceptance_criteria>
  <verify>
    <automated>pytest tests/conftest.py --collect-only 2>&1 | grep -E "(fixture|error)" || echo "Collection check"</automated>
  </verify>
  <done>All five fixtures defined and importable without errors</done>
</task>

<task type="auto">
  <name>Task 2: Create host storage test stubs</name>
  <files>tests/test_hosts.py</files>
  <read_first>
    - tests/conftest.py (for fixtures after Task 1)
    - tests/test_config.py (for existing test patterns)
    - .planning/phases/02-host-management/02-RESEARCH.md (Pattern 6 for storage API)
  </read_first>
  <action>
Create tests/test_hosts.py with test stubs for host storage operations (per D-01, D-02, D-03, D-04):

1. test_load_hosts_empty_file():
   - When hosts.json doesn't exist, load_hosts() returns empty list

2. test_load_hosts_with_data():
   - When hosts.json has data, load_hosts() returns list of host dicts

3. test_save_hosts_creates_file():
   - save_hosts([host]) creates hosts.json in config dir

4. test_add_host_appends():
   - add_host(host) appends to existing hosts list

5. test_add_host_first():
   - add_host(host) on empty file creates file with single host

6. test_remove_host_by_hostname():
   - remove_host("hostname") removes matching host, returns True

7. test_remove_host_not_found():
   - remove_host("nonexistent") returns False, leaves file unchanged

8. test_get_host_by_hostname():
   - get_host("hostname") returns host dict if found

9. test_get_host_by_alias():
   - get_host("alias") returns host dict if alias matches

10. test_get_host_not_found():
    - get_host("nonexistent") returns None

Each test should:
- Use isolated_config fixture for temp directory
- Use sample_host_data fixture for test data
- Assert specific expected behaviors
- Currently xfail (expected to fail until implementation)

Add pytest.mark.xfail(reason="Not implemented") to each test.
  </action>
  <acceptance_criteria>
    - tests/test_hosts.py contains `def test_load_hosts_empty_file`
    - tests/test_hosts.py contains `def test_load_hosts_with_data`
    - tests/test_hosts.py contains `def test_save_hosts_creates_file`
    - tests/test_hosts.py contains `def test_add_host_appends`
    - tests/test_hosts.py contains `def test_remove_host_by_hostname`
    - tests/test_hosts.py contains `def test_get_host_by_hostname`
    - tests/test_hosts.py contains `def test_get_host_by_alias`
    - tests/test_hosts.py contains `pytest.mark.xfail`
    - tests/test_hosts.py contains `isolated_config`
  </acceptance_criteria>
  <verify>
    <automated>pytest tests/test_hosts.py --collect-only 2>&1 | grep "test_" | wc -l</automated>
  </verify>
  <done>10 test stubs created, all marked xfail, all collect successfully</done>
</task>

<task type="auto">
  <name>Task 3: Create SSH connection and hardware detection test stubs</name>
  <files>tests/test_ssh_connection.py, tests/test_hardware.py</files>
  <read_first>
    - tests/conftest.py (for mock fixtures)
    - .planning/phases/02-host-management/02-RESEARCH.md (Patterns 2, 3, 4 for SSH and hardware APIs)
  </read_first>
  <action>
Create tests/test_ssh_connection.py with test stubs for SSH operations (per D-07 to D-12):

1. test_parse_ssh_config_found():
   - get_ssh_config("myhost") returns dict with user, port, hostname from ~/.ssh/config

2. test_parse_ssh_config_not_found():
   - get_ssh_config("unknown") returns empty dict

3. test_connection_success():
   - test_ssh_connection(hostname, port, user) returns (True, "Connection successful")

4. test_connection_auth_failure():
   - test_ssh_connection with bad auth returns (False, "Authentication failed...")

5. test_connection_network_error():
   - test_ssh_connection with unreachable host returns (False, "Network error...")

6. test_connection_with_explicit_key():
   - test_ssh_connection with key_filename parameter uses that key

Create tests/test_hardware.py with test stubs for hardware detection (per D-13 to D-15):

1. test_parse_ansible_facts_basic():
   - Given ansible facts dict, extract_hardware() returns architecture, cores, memory

2. test_parse_ansible_facts_mounts():
   - extract_hardware() includes mounts with size_total and size_available

3. test_detect_gpu_nvidia():
   - parse_gpu_output("VGA compatible controller: NVIDIA...") returns {"present": True, "vendor": "nvidia"}

4. test_detect_gpu_amd():
   - parse_gpu_output("VGA compatible controller: AMD...") returns {"present": True, "vendor": "amd"}

5. test_detect_gpu_intel():
   - parse_gpu_output("VGA compatible controller: Intel...") returns {"present": True, "vendor": "intel"}

6. test_detect_gpu_none():
   - parse_gpu_output("") returns {"present": False}

7. test_gather_hardware_full():
   - gather_hardware(hostname, user) returns complete hardware dict with all fields

All tests should use mock fixtures and be marked xfail.
  </action>
  <acceptance_criteria>
    - tests/test_ssh_connection.py contains `def test_parse_ssh_config_found`
    - tests/test_ssh_connection.py contains `def test_connection_success`
    - tests/test_ssh_connection.py contains `def test_connection_auth_failure`
    - tests/test_ssh_connection.py contains `pytest.mark.xfail`
    - tests/test_hardware.py contains `def test_parse_ansible_facts_basic`
    - tests/test_hardware.py contains `def test_detect_gpu_nvidia`
    - tests/test_hardware.py contains `def test_gather_hardware_full`
    - tests/test_hardware.py contains `pytest.mark.xfail`
  </acceptance_criteria>
  <verify>
    <automated>pytest tests/test_ssh_connection.py tests/test_hardware.py --collect-only 2>&1 | grep "test_" | wc -l</automated>
  </verify>
  <done>13 test stubs created across both files (6 SSH + 7 hardware), all marked xfail</done>
</task>

<task type="auto">
  <name>Task 4: Create CLI host command test stubs</name>
  <files>tests/test_cli_host.py</files>
  <read_first>
    - tests/test_cli_init.py (for existing CLI test patterns)
    - tests/conftest.py (for fixtures)
    - .planning/phases/02-host-management/02-CONTEXT.md (D-16 to D-19 for CLI design)
  </read_first>
  <action>
Create tests/test_cli_host.py with test stubs for CLI host commands (per D-16 to D-19):

HOST ADD (HOST-01):
1. test_host_add_success():
   - `clm host add 192.168.1.100` with valid connection saves host

2. test_host_add_with_flags():
   - `clm host add 192.168.1.100 --user xclm --port 22 --alias myhost` uses provided values

3. test_host_add_connection_failed():
   - `clm host add badhost` with connection failure shows error, exits 1

4. test_host_add_duplicate():
   - Adding same hostname twice shows error, exits 1

HOST LIST (HOST-02):
5. test_host_list_empty():
   - `clm host list` with no hosts shows "No hosts registered"

6. test_host_list_table():
   - `clm host list` with hosts shows table with Alias, Host, Architecture columns

HOST REMOVE (HOST-03):
7. test_host_remove_with_confirmation():
   - `clm host remove hostname` prompts for confirmation

8. test_host_remove_force():
   - `clm host remove hostname --force` skips confirmation

9. test_host_remove_not_found():
   - `clm host remove nonexistent` shows error, exits 1

HOST STATUS (HOST-04):
10. test_host_status_connected():
    - `clm host status hostname` with reachable host shows "Connected"

11. test_host_status_disconnected():
    - `clm host status hostname` with unreachable host shows "Disconnected"

12. test_host_status_refresh():
    - `clm host status hostname --refresh` updates hardware info

Tests should:
- Use typer.testing.CliRunner pattern from test_cli_init.py
- Use isolated_config fixture
- Use mock_ssh_client and mock_ansible_runner fixtures
- Be marked xfail
  </action>
  <acceptance_criteria>
    - tests/test_cli_host.py contains `def test_host_add_success`
    - tests/test_cli_host.py contains `def test_host_add_connection_failed`
    - tests/test_cli_host.py contains `def test_host_list_empty`
    - tests/test_cli_host.py contains `def test_host_list_table`
    - tests/test_cli_host.py contains `def test_host_remove_with_confirmation`
    - tests/test_cli_host.py contains `def test_host_remove_force`
    - tests/test_cli_host.py contains `def test_host_status_connected`
    - tests/test_cli_host.py contains `def test_host_status_refresh`
    - tests/test_cli_host.py contains `CliRunner`
    - tests/test_cli_host.py contains `pytest.mark.xfail`
  </acceptance_criteria>
  <verify>
    <automated>pytest tests/test_cli_host.py --collect-only 2>&1 | grep "test_" | wc -l</automated>
  </verify>
  <done>12 test stubs created, all marked xfail, all using CliRunner pattern</done>
</task>

</tasks>

<verification>
After all tasks complete:

```bash
# All test files exist
ls -la tests/test_hosts.py tests/test_hardware.py tests/test_ssh_connection.py tests/test_cli_host.py

# All tests collect without import errors
pytest tests/test_hosts.py tests/test_hardware.py tests/test_ssh_connection.py tests/test_cli_host.py --collect-only

# Count of tests (should be ~35)
pytest tests/test_hosts.py tests/test_hardware.py tests/test_ssh_connection.py tests/test_cli_host.py --collect-only 2>&1 | grep "test_" | wc -l

# All tests are xfail (expected failures)
pytest tests/test_hosts.py tests/test_hardware.py tests/test_ssh_connection.py tests/test_cli_host.py -v 2>&1 | grep -E "(XFAIL|xfail)"
```
</verification>

<success_criteria>
- All 4 test files created with proper structure
- 35+ test stubs defined covering HOST-01 through HOST-05
- All tests marked as xfail (expected to fail)
- All tests collect without import errors
- Mock fixtures in conftest.py for SSH and Ansible
- Tests use established project patterns (isolated_config, CliRunner)
</success_criteria>

<output>
After completion, create `.planning/phases/02-host-management/02-01-SUMMARY.md`
</output>
