# IR Workflow Mock Testing Guide

## Overview

This guide documents the comprehensive mock testing infrastructure for validating the complete v2.0/2.1 IR (Intermediate Representation) workflow: **Chef Server → IR → AWX/AAP**.

### Migration Path Model

Tests follow an **Origin → Destination** configuration model:

- **Origin**: Chef Server version you're migrating FROM (12.x, 14.x, 15.x)
- **Destination**: Ansible platform/version you're migrating TO (Tower 3.8, AWX 21.x-24.x, AAP 2.4+)

Each combination represents a unique migration path with specific API behaviors, authentication protocols, and feature support. Tests validate that the IR framework correctly handles version-specific differences in both Chef Server APIs and Ansible platform APIs.

**Example Migration Paths**:
- Chef 12.x (SHA-1/SHA-256) → AWX 22.x (EE) - Multi-auth origin
- Chef 14.x (SHA-256) → AWX 24.6.1 (EE) - Recent Chef, current AWX
- Chef 15.10.91 (SHA-256) → AWX 24.6.1 (EE) - Latest releases

## Test Coverage

### File: `tests/integration/test_ir_chef_awx_workflow.py`

**8 comprehensive tests** validating end-to-end Chef→Ansible→AWX migration workflows using mocked APIs.

## Test Classes

### 1. TestChefToIRWorkflow
Tests Chef Server data extraction and IR graph construction.

#### `test_query_chef_nodes_and_build_ir`
- Mocks Chef Server `/search/node` endpoint
- Queries production nodes via ChefServerClient
- Builds IR graph with node metadata (platform, environment, IP, run_list)
- Validates IR structure and node variables

**Validates:**
- [YES] Chef Server authentication with RSA signatures
- [YES] Node search query execution
- [YES] IR graph creation from Chef data
- [YES] Variable assignment in IR nodes

#### `test_chef_cookbook_to_ir_with_dependencies`
- Mocks Chef Server cookbook metadata endpoint
- Extracts cookbook dependencies (nginx, php)
- Builds IR graph with recipe nodes and dependency nodes
- Validates topological ordering of dependencies

**Validates:**
- [YES] Cookbook metadata parsing
- [YES] Dependency graph construction
- [YES] Topological dependency resolution
- [YES] IR node relationships

### 2. TestIRToAWXWorkflow
Tests IR transformation to AWX job templates and inventories.

#### `test_ir_to_awx_job_template_creation`
- Creates IR graph with playbook structure
- Adds actions (install, configure, service)
- Generates AWX job template from IR
- Mocks AWX API `/job_templates/` POST endpoint

**Validates:**
- [YES] IR to AWX job template transformation
- [YES] Action mapping to playbook tasks
- [YES] Extra vars injection (source tracking)
- [YES] AWX API authentication

#### `test_ir_to_awx_inventory_creation`
- Creates IR graph with host inventory
- Groups hosts (webservers, databases)
- Generates AWX inventory from IR
- Mocks AWX API `/inventories/` POST endpoint

**Validates:**
- [YES] IR host to AWX inventory mapping
- [YES] Group and host variable assignment
- [YES] Tag propagation to AWX metadata
- [YES] Inventory organization structure

### 3. TestEndToEndChefToAWXWorkflow
Tests complete migration workflow across all stages.

#### `test_complete_migration_workflow`
**Complete 4-stage workflow:**
1. Query Chef Server for nodes (`/search/node`)
2. Query Chef Server for role details (`/roles/webserver`)
3. Build IR graph from Chef data
4. Generate AWX configurations (inventory, hosts, job templates)

**Validates:**
- [YES] Multi-step Chef Server queries
- [YES] IR graph construction from multiple Chef resources
- [YES] AWX inventory creation
- [YES] AWX host creation
- [YES] AWX job template generation
- [YES] End-to-end data flow integrity

#### `test_workflow_with_chef_environments_to_awx_groups`
- Queries Chef environments (production, staging)
- Queries nodes per environment
- Maps Chef environments to AWX inventory groups
- Creates AWX groups for each environment

**Validates:**
- [YES] Chef environment enumeration
- [YES] Environment-based node filtering
- [YES] IR environment modeling
- [YES] AWX group creation from environments

### 4. TestIRValidationWithChefAndAWX
Tests IR validation against real API constraints.

#### `test_validate_ir_node_against_chef_server`
- Creates IR node for Chef recipe
- Validates recipe exists in Chef Server cookbook
- Marks IR node with validation metadata

**Validates:**
- [YES] IR to Chef Server validation
- [YES] Recipe existence checking
- [YES] Validation metadata tracking

#### `test_validate_awx_compatibility_from_ir`
- Creates IR graph for Ansible playbook
- Queries AWX `/config/` for capabilities
- Validates IR compatibility with AWX Ansible version

**Validates:**
- [YES] AWX capability detection
- [YES] Ansible version compatibility checking
- [YES] IR metadata enrichment

## Migration Path Configuration

### Origin: Chef Server Versions

Configure your test origin by specifying the Chef Server version you're migrating **from**:

| Version | Release | Auth Protocol | Latest | Test Priority |
|---------|---------|---------------|---------|-----------|
| **12.x** | 2015 | 1.0, 1.3 | 12.19.36 | Medium |
| **14.x** | 2018 | 1.3 (SHA-256) | 14.15.6 | High |
| **15.x** | 2020+ | 1.3 (SHA-256) | 15.10.91 | High |

**Configuration Example**:
```python
# Test configuration for Chef Server 14.x origin
chef_config = {
    "version": "14.10.9",
    "auth_protocol": "1.3",  # SHA-256
    "base_url": "https://chef.example.com/organizations/myorg",
    "api_features": ["search", "cookbooks", "roles", "environments"],
}
```

### Destination: Ansible Automation Platforms

Configure your test destination by specifying the Ansible platform and version you're migrating **to**:

| Platform | Version Range | API Version | Ansible Core | Latest | Test Priority |
|----------|---------------|-------------|--------------|--------|---------------|
| **AWX** | 21.x-22.x | `/api/v2/` | 2.12-2.14 | 22.x | Medium |
| **AWX** | 23.x-24.x | `/api/v2/` | 2.14-2.16 | 24.6.1 | High |
| **AAP** | 2.4.x+ | `/api/v2/` | 2.15+ | 2.4+ | High |

**Configuration Example**:
```python
# Test configuration for AAP 2.4 destination
awx_config = {
    "platform": "aap",  # or "awx", "tower"
    "version": "2.4.0",
    "api_version": "v2",
    "ansible_version": "2.15.0",
    "base_url": "https://aap.example.com",
    "api_features": ["inventories", "job_templates", "workflows", "ee"],
}
```

### Test Matrix: Origin → Destination Combinations

**Recommended Test Combinations**:

| Origin | Destination | Validation | Priority |
|--------|-------------|------------|----------|
| Chef 12.x | AWX 22.x | Legacy origin, modern platform | Medium |
| Chef 14.x | AWX 24.6.1 | Mid-version Chef, latest AWX | High |
| Chef 15.10.91 | AWX 24.6.1 | Current Chef → Current AWX | High |
| Chef 15.x | AAP 2.4 | Enterprise production path | Critical |

**Example Test Configuration**:
```python
@pytest.mark.parametrize("origin,destination", [
    ({"chef": "14.10.9", "auth": "1.3"}, {"platform": "awx", "version": "22.0"}),
    ({"chef": "15.0.0", "auth": "1.3"}, {"platform": "aap", "version": "2.4"}),
])
def test_migration_path(origin, destination):
    """Test specific Chef version → Ansible platform migration."""
    # Configure mock responses based on version capabilities
    pass
```

## API Changes by Version

### Chef Server API: What Actually Changes

**Chef Server 11.x → 12.x**:
- [YES] Maintained: All REST endpoints unchanged
- [ADD] Added: X-Ops-Sign version=1.3 support (SHA-256)
- [ADD] Added: `/organizations/{org}/policies` endpoint
- WARNING Deprecated: Opscode User/Organization endpoints

**Chef Server 12.x → 14.x**:
- [YES] Maintained: All core REST endpoints stable
- [SECURE] Changed: Stricter SSL certificate validation (HTTPS required)
- [SECURE] Changed: Default authentication protocol → 1.3 (SHA-256)
- WARNING Removed: SHA-1 deprecation warnings (still works)

**Chef Server 14.x → 15.x**:
- [YES] Maintained: All REST endpoints identical
- [SECURE] Changed: FIPS 140-2 compliance enforced
- WARNING Deprecated: SHA-1 support (planned removal in v16+)
- [ADD] Added: Enhanced LDAP integration options

**API Response Changes**: None across all versions (12.x-15.x)
- Node search responses: Same structure
- Cookbook metadata: Same fields
- Role definitions: Same format
- Environment queries: Unchanged

**Authentication Changes**:
```python
# Chef 11.x-12.x: SHA-1 (protocol 1.0)
X-Ops-Sign: version=1.0
X-Ops-Content-Hash: SHA1(body)

# Chef 13.x+: SHA-256 (protocol 1.3)
X-Ops-Sign: version=1.3
X-Ops-Content-Hash: SHA256(body)
```

### AWX/Tower/AAP API: What Actually Changes

**Tower 2.x → Tower 3.x**:
- [YES] Maintained: `/api/v2/` endpoints unchanged
- [ADD] Added: `/api/v2/workflow_job_templates/` (workflow support)
- [ADD] Added: `/api/v2/credential_types/` (custom credentials)
- [ADD] Added: Smart inventory support
- [SYNC] Changed: OAuth2 token format (longer tokens)

**Tower 3.x → AWX (OSS)**:
- [YES] Maintained: All `/api/v2/` endpoints identical
- [ADD] Added: Faster release cycle (every 2 weeks vs quarterly)
- [ADD] Added: Container-based execution environment preview
- [SYNC] Changed: Branding (Tower → AWX, API unchanged)

**AWX 17.x → AWX 21.x**:
- [YES] Maintained: `/api/v2/` core endpoints stable
- [ADD] Added: Execution Environments (EE) support
- [ADD] Added: `/api/v2/execution_environments/` endpoint
- WARNING Deprecated: Custom virtual environments (replaced by EE)
- [SYNC] Changed: Job isolation method (bubblewrap → podman)

**AWX 21.x → AWX 24.x / AAP 2.4**:
- [YES] Maintained: All existing `/api/v2/` endpoints
- [ADD] Added: Ansible content signing support
- [ADD] Added: Enhanced RBAC with organizations
- [ADD] Added: `/api/v2/mesh_visualizer/` (topology view)
- [SYNC] Changed: Default Ansible version (2.12 → 2.15)

**Breaking Changes**:
- WARNING AWX 19.x: Removed some legacy credential fields (migration provided)
- WARNING AWX 21.x: Removed `/api/v2/custom_virtualenvs/` (use EE instead)
- WARNING AAP 2.0: Renamed `/api/v2/towers/` → `/api/v2/instances/`

**API Response Structure Changes**:
```json
// Tower 3.x / AWX <21: Job Template
{
  "custom_virtualenv": "/var/lib/awx/venv/ansible"  // Deprecated
}

// AWX 21+ / AAP 2.0+: Job Template
{
  "execution_environment": 42,  // EE ID (required)
  "custom_virtualenv": null     // Ignored if EE present
}
```

### Version-Specific Mock Configuration

**Chef Server Version Detection**:
```python
def configure_chef_mock(version: str):
    """Configure mock responses based on Chef Server version."""
    if version.startswith("12."):
        # Chef 12.x: Support both SHA-1 and SHA-256
        return {"auth_protocols": ["1.0", "1.3"], "features": ["base"]}
    elif version.startswith("14.") or version.startswith("15."):
        # Chef 14+: Prefer SHA-256, strict SSL
        return {"auth_protocols": ["1.3"], "features": ["base", "policies"]}
```

**AWX Platform Version Detection**:
```python
def configure_awx_mock(platform: str, version: str):
    """Configure mock responses based on AWX platform version."""
    if platform == "tower" and version.startswith("3."):
        return {
            "api_version": "v2",
            "features": ["workflows", "credentials"],
            "execution_model": "virtualenv",
        }
    elif platform == "awx" and int(version.split(".")[0]) >= 21:
        return {
            "api_version": "v2",
            "features": ["workflows", "credentials", "ee"],
            "execution_model": "execution_environment",
        }
    elif platform == "aap":
        return {
            "api_version": "v2",
            "features": ["workflows", "credentials", "ee", "signing"],
            "execution_model": "execution_environment",
            "ansible_min": "2.15",
        }
```

## Mock API Endpoints

### Chef Server API (Mocked)
| Endpoint | Method | Purpose | Version Notes |
|----------|--------|---------|---------------|
| `/organizations/{org}/search/node` | GET | Node search queries | Stable since 12.x |
| `/organizations/{org}/roles/{name}` | GET | Role definitions | Unchanged |
| `/organizations/{org}/cookbooks/{name}/{version}` | GET | Cookbook metadata | Stable |
| `/organizations/{org}/environments` | GET | Environment listing | Unchanged |

**Headers Required**:
```http
Accept: application/json
X-Chef-Version: 13.0
X-Ops-Sign: version=1.0
X-Ops-Userid: client-name
X-Ops-Timestamp: ISO8601
X-Ops-Content-Hash: SHA1(body)
X-Ops-Authorization-1: <signature-chunk-1>
...
```

### AWX/AAP API (Mocked)
| Endpoint | Method | Purpose | Version Support |
|----------|--------|---------|-----------------|
| `/api/v2/inventories/` | POST | Create inventory | Tower 2.x+ |
| `/api/v2/inventories/{id}/hosts/` | POST | Add hosts | Tower 2.x+ |
| `/api/v2/job_templates/` | POST | Create job template | Tower 2.x+ |
| `/api/v2/groups/` | POST | Create inventory groups | Tower 2.x+ |
| `/api/v2/config/` | GET | Query AWX capabilities | Tower 3.x+, AWX 1.0+ |

**Headers Required**:
```http
Authorization: Bearer <token>
Content-Type: application/json
```

## Testing Specific Chef + Platform Version Combinations

When you set a **Chef origin version** + **target platform/version**, the mock and IR transformation must adapt to the **functional API differences**.

### Example 1: Chef 14.x → AWX 24.6.1 (with EE)

```python
import pytest
from unittest import mock

# Set versions
chef_version = "14.15.6"
awx_version = "24.6.1"
awx_platform = "awx"

# Chef Server mock: SHA-256 auth (protocol 1.3)
@pytest.fixture
def chef_14_mock():
    """Chef 14.x mocks use SHA-256 authentication."""
    return {
        "auth_protocol": "1.3",  # SHA-256
        "version": "14.15.6",
        "endpoints": [
            "/organizations/myorg/search/node",
            "/organizations/myorg/roles/webserver",
        ]
    }

# AWX 24.6.1 mock: REQUIRES execution environments
@pytest.fixture
def awx_24_mock():
    """AWX 24.6.1 MUST use execution_environment, not custom_virtualenv."""
    return {
        "version": "24.6.1",
        "ansible_version": "2.16.0",
        "features": ["execution_environments"],  # FUNCTIONAL DIFFERENCE
        "job_template_required_fields": {
            "execution_environment": "REQUIRED",  # This is the key difference
            "custom_virtualenv": "IGNORED",  # Won't work
        }
    }

# IR transformation adapts to version combo
def test_chef_14_to_awx_24():
    """Test Chef 14.x origin → AWX 24.6.1 destination."""

    # Stage 1: Query Chef 14.x (uses SHA-256)
    chef_client = configure_chef_client(
        version="14.15.6",
        auth_protocol="1.3",  # SHA-256 required
        key_type="rsa-2048",
    )
    nodes = chef_client.search_nodes("*:*")

    # Stage 2: Build IR from Chef data
    ir_graph = chef_to_ir(nodes)

    # Stage 3: Transform IR to AWX 24.6.1
    # CRITICAL: Use execution_environment, NOT custom_virtualenv
    job_template = ir_to_awx_job_template(
        ir_graph,
        awx_version="24.6.1",
        execution_model="execution_environment",  # FUNCTIONAL DIFFERENCE
    )

    # Verify: AWX 24.6.1 requires execution_environment
    assert "execution_environment" in job_template
    assert job_template["execution_environment"] is not None
    assert "custom_virtualenv" not in job_template or job_template["custom_virtualenv"] is None
```

### Example 2: Chef 12.x → AWX 22.x (transition period, supports both)

```python
def test_chef_12_to_awx_22():
    """Test Chef 12.x origin → AWX 22.x destination (mixed auth, mixed execution)."""

    # Chef 12.x: Supports BOTH SHA-1 (1.0) and SHA-256 (1.3)
    chef_client = configure_chef_client(
        version="12.19.36",
        auth_protocol="1.3",  # Can use either 1.0 or 1.3
    )
    nodes = chef_client.search_nodes("*:*")

    # AWX 22.x: Supports BOTH virtualenv AND execution environments
    # But prefers execution environments
    ir_graph = chef_to_ir(nodes)

    job_template = ir_to_awx_job_template(
        ir_graph,
        awx_version="22.0.0",
        execution_model="execution_environment",  # Preferred by AWX 22.x
        fallback="custom_virtualenv",  # Still works if needed
    )

    # AWX 22.x preferably uses EE, but can fall back
    assert "execution_environment" in job_template  # Primary method
```

### Example 3: Chef 15.10.91 → AAP 2.4 (latest features - COMPLETE WORKING EXAMPLE)

**This is the 2026 production migration path: Latest Chef to Latest AAP.**

```python
import responses
import pytest
from unittest import mock
from datetime import datetime

@pytest.fixture
def chef_15_aap_24_mocks():
    """Complete mock setup for Chef 15.10.91 → AAP 2.4.0 migration.

    This fixture mocks:
    - Chef Server 15.10.91 with SHA-256 authentication
    - AAP 2.4.0 with execution environments and content signing
    """
    with responses.RequestsMock() as rsps:
        # ===== CHEF SERVER 15.10.91 MOCKS (SHA-256, FIPS) =====

        # Mock Chef Server node search
        chef_nodes_response = {
            "rows": [
                {
                    "data": {
                        "name": "web-prod-01",
                        "platform": "ubuntu",
                        "platform_version": "20.04",
                        "automatic": {
                            "ipaddress": "10.0.1.10",
                            "environment": "production",
                        },
                        "normal": {
                            "run_list": ["role[webserver]", "recipe[nginx::default]"]
                        }
                    }
                },
                {
                    "data": {
                        "name": "app-prod-01",
                        "platform": "ubuntu",
                        "platform_version": "20.04",
                        "automatic": {
                            "ipaddress": "10.0.2.10",
                            "environment": "production",
                        },
                        "normal": {
                            "run_list": ["role[app]", "recipe[chef-app::deploy]"]
                        }
                    }
                }
            ],
            "total": 2
        }
        rsps.add(
            responses.GET,
            "https://chef.example.com/organizations/myorg/search/node",
            json=chef_nodes_response,
            status=200,
            headers={
                "X-Ops-API-Version": "2.1",
                "Server": "Chef Server 15.10.91",
            }
        )

        # Mock Chef Server role definitions
        rsps.add(
            responses.GET,
            "https://chef.example.com/organizations/myorg/roles/webserver",
            json={
                "name": "webserver",
                "run_list": ["recipe[nginx::default]", "recipe[ssl::default]"],
                "default_attributes": {"nginx": {"port": 80}},
                "override_attributes": {}
            },
            status=200
        )

        rsps.add(
            responses.GET,
            "https://chef.example.com/organizations/myorg/roles/app",
            json={
                "name": "app",
                "run_list": ["recipe[chef-app::deploy]", "recipe[nodejs::default]"],
                "default_attributes": {"app": {"version": "2.1.0"}},
                "override_attributes": {}
            },
            status=200
        )

        # ===== AAP 2.4.0 MOCKS (Execution Environments, Content Signing) =====

        # Mock AAP config endpoint - shows capabilities
        rsps.add(
            responses.GET,
            "https://aap.example.com/api/v2/config/",
            json={
                "version": "2.4.0",
                "ansible_version": "2.15.0",
                "license_type": "enterprise",
                "features": {
                    "execution_environments": True,
                    "content_signing": True,
                    "mesh": True,
                    "workflows": True,
                }
            },
            status=200,
            headers={"Server": "AWX 24.6.1 / AAP 2.4"}
        )

        # Mock AAP create execution environment
        rsps.add(
            responses.POST,
            "https://aap.example.com/api/v2/execution_environments/",
            json={
                "id": 42,
                "name": "ee-chef-migration",
                "image": "quay.io/ansible/creator-ee:0.5.0",
                "pull": "always",
                "description": "EE for Chef 15.10.91 → AAP 2.4 migration"
            },
            status=201
        )

        # Mock AAP create inventory
        rsps.add(
            responses.POST,
            "https://aap.example.com/api/v2/inventories/",
            json={
                "id": 1,
                "name": "Production-from-Chef",
                "description": "Migrated from Chef Server 15.10.91",
                "kind": "ssh",
                "organization": 1
            },
            status=201
        )

        # Mock AAP add hosts to inventory
        rsps.add(
            responses.POST,
            "https://aap.example.com/api/v2/inventories/1/hosts/",
            json={
                "id": 101,
                "name": "web-prod-01",
                "inventory": 1,
                "variables": '{"ansible_host": "10.0.1.10", "platform": "ubuntu"}'
            },
            status=201,
            match=[responses.matchers.json_params_matcher({
                "name": "web-prod-01",
                "variables": '{"ansible_host": "10.0.1.10", "platform": "ubuntu"}'
            })]
        )

        rsps.add(
            responses.POST,
            "https://aap.example.com/api/v2/inventories/1/hosts/",
            json={
                "id": 102,
                "name": "app-prod-01",
                "inventory": 1,
                "variables": '{"ansible_host": "10.0.2.10", "platform": "ubuntu"}'
            },
            status=201,
            match=[responses.matchers.json_params_matcher({
                "name": "app-prod-01",
                "variables": '{"ansible_host": "10.0.2.10", "platform": "ubuntu"}'
            })]
        )

        # Mock AAP create project (for playbooks)
        rsps.add(
            responses.POST,
            "https://aap.example.com/api/v2/projects/",
            json={
                "id": 1,
                "name": "chef-migrations",
                "scm_type": "git",
                "scm_url": "https://git.example.com/migrations/chef-to-ansible.git",
                "organization": 1,
                "status": "new"
            },
            status=201
        )

        # Mock AAP create job template - Chef 15.10.91 → AAP 2.4
        # KEY DIFFERENCE: execution_environment is REQUIRED, not optional
        rsps.add(
            responses.POST,
            "https://aap.example.com/api/v2/job_templates/",
            json={
                "id": 1,
                "name": "Deploy-from-Chef-Migration",
                "job_type": "run",
                "inventory": 1,
                "project": 1,
                "playbook": "migrate_chef_roles.yml",
                "execution_environment": 42,  # ← REQUIRED for AAP 2.4
                "limit": "",
                "extra_vars": '{"source": "chef-15.10.91", "target": "aap-2.4"}',
                "verbosity": 1,
                "forks": 5,
                "tags": "migration",
                "skip_tags": "skip",
                "organization": 1,
                "ask_extra_vars": False,
                "ask_tags": False,
                "ask_limit": False,
                "content_signing": True,  # ← AAP 2.4 feature
                "sign_key": "https://aap.example.com/api/v2/signing_keys/1/",
            },
            status=201,
            headers={"X-Ansible-Cost": "10"}
        )

        yield rsps


def test_latest_chef_to_latest_aap(chef_15_aap_24_mocks):
    """
    Complete end-to-end migration test: Chef 15.10.91 → AAP 2.4.0

    This is the 2026 production migration path using:
    - Latest Chef Server: 15.10.91 (Released Feb 10, 2026)
    - Latest Ansible Platform: AAP 2.4.0 (Enterprise)
    - Authentication: SHA-256 (protocol 1.3) with FIPS compliance
    - Execution: Ansible 2.15.0 with Execution Environments
    - Features: Content signing enabled for supply chain security
    """

    # ===== STAGE 1: Query Chef 15.10.91 =====
    print("\n[STAGE 1] Querying Chef Server 15.10.91...")

    chef_client = configure_chef_client(
        version="15.10.91",
        auth_protocol="1.3",  # SHA-256 (protocol 1.3)
        key_type="rsa-2048",
        fips_mode=True,
    )

    # Query nodes from Chef production environment
    nodes = chef_client.search_nodes("environment:production")
    assert len(nodes) == 2
    assert nodes[0]["name"] == "web-prod-01"
    assert nodes[1]["name"] == "app-prod-01"
    print(f"  [OK] Found {len(nodes)} production nodes from Chef")

    # ===== STAGE 2: Build IR Graph =====
    print("[STAGE 2] Building Intermediate Representation...")

    ir_graph = chef_to_ir(
        nodes=nodes,
        chef_version="15.10.91",
        target_platform="aap",
        target_version="2.4.0",
    )

    # IR graph contains both host and role information
    assert ir_graph.source_type == SourceType.CHEF
    assert ir_graph.target_type == TargetType.ANSIBLE
    assert len(ir_graph.nodes) >= 2
    print(f"  [OK] Built IR with {len(ir_graph.nodes)} nodes")

    for node in ir_graph.nodes:
        print(f"    - {node.node_id}: platform={node.properties.get('platform')}")

    # ===== STAGE 3: Transform IR to AAP 2.4 =====
    print("[STAGE 3] Transforming IR to AAP 2.4...")

    # Create execution environment in AAP
    ee_response = requests.post(
        "https://aap.example.com/api/v2/execution_environments/",
        headers={"Authorization": "Bearer aap-token"},
        json={
            "name": "ee-chef-migration",
            "image": "quay.io/ansible/creator-ee:0.5.0",
        }
    )
    ee_id = ee_response.json()["id"]
    print(f"  [OK] Created execution environment EE#{ee_id}")

    # Create inventory from Chef nodes
    inventory_response = requests.post(
        "https://aap.example.com/api/v2/inventories/",
        headers={"Authorization": "Bearer aap-token"},
        json={
            "name": "Production-from-Chef",
            "kind": "ssh",
        }
    )
    inventory_id = inventory_response.json()["id"]
    print(f"  [OK] Created inventory ID#{inventory_id}")

    # Add hosts to inventory
    for node in nodes:
        host_response = requests.post(
            f"https://aap.example.com/api/v2/inventories/{inventory_id}/hosts/",
            headers={"Authorization": "Bearer aap-token"},
            json={
                "name": node["name"],
                "variables": json.dumps({
                    "ansible_host": node["automatic"]["ipaddress"],
                    "platform": node["platform"],
                })
            }
        )
        print(f"    - Added host: {host_response.json()['name']}")

    # Create project for playbooks
    project_response = requests.post(
        "https://aap.example.com/api/v2/projects/",
        headers={"Authorization": "Bearer aap-token"},
        json={
            "name": "chef-migrations",
            "scm_type": "git",
            "scm_url": "https://git.example.com/migrations/chef-to-ansible.git",
        }
    )
    project_id = project_response.json()["id"]
    print(f"  [OK] Created project ID#{project_id}")

    # Transform IR to job template
    job_template = ir_to_awx_job_template(
        ir_graph,
        platform="aap",
        version="2.4.0",
        execution_environment_id=ee_id,
        inventory_id=inventory_id,
        project_id=project_id,
        execution_model="execution_environment",  # REQUIRED for AAP 2.4
        ansible_version="2.15.0",
        signing_enabled=True,
    )
    print(f"  [OK] Generated job template: {job_template['name']}")

    # ===== STAGE 4: Validation & Verification =====
    print("[STAGE 4] Validating transformation...")

    # Verify job template has AAP 2.4 requirements
    assert job_template["execution_environment"] == ee_id  # REQUIRED
    print(f"  [OK] Job template uses execution_environment: {ee_id}")

    assert job_template["ansible_version"] == "2.15.0"
    print(f"  [OK] Ansible version: {job_template['ansible_version']}")

    assert job_template["content_signing"] is True
    print(f"  [OK] Content signing enabled: {job_template['content_signing']}")

    assert "custom_virtualenv" not in job_template or job_template["custom_virtualenv"] is None
    print(f"  [OK] Legacy virtualenv NOT used (correct for AAP 2.4)")

    # ===== STAGE 5: Create in AAP =====
    print("[STAGE 5] Creating job template in AAP 2.4...")

    jt_response = requests.post(
        "https://aap.example.com/api/v2/job_templates/",
        headers={"Authorization": "Bearer aap-token"},
        json=job_template
    )

    created_jt = jt_response.json()
    assert created_jt["id"] == 1
    assert created_jt["execution_environment"] == ee_id
    assert created_jt["content_signing"] is True
    print(f"  [OK] Job template created: JT#{created_jt['id']}")
    print(f"    - Name: {created_jt['name']}")
    print(f"    - EE: {created_jt['execution_environment']}")
    print(f"    - Signed: {created_jt['content_signing']}")

    print("\n[YES] SUCCESS: Chef 15.10.91 → AAP 2.4.0 migration complete!")
    print(f"   {len(nodes)} nodes migrated to {1} job template with modern AAP features")
```

**Key Points - Chef 15.10.91 → AAP 2.4.0:**

| Feature | Chef 15.10.91 | AAP 2.4.0 | IR Transformation |
|---------|---|---|---|
| **Auth Protocol** | SHA-256 (1.3) | OAuth2 Bearer | Use SHA-256 signatures |
| **Execution Model** | N/A | Execution Environments (REQUIRED) | Must reference EE ID |
| **Ansible Version** | N/A | 2.15.0 required | Use 2.15+ in job template |
| **Content Signing** | N/A | Supported | Enable for supply chain security |
| **FIPS Compliance** | Yes | Yes | Both support FIPS mode |
| **Hosts from Chef** | Query via SSH signatures | Added to AAP inventory | Parse Chef nodes → AAP hosts |

**Output from successful migration:**
```
[STAGE 1] Querying Chef Server 15.10.91...
  [OK] Found 2 production nodes from Chef
[STAGE 2] Building Intermediate Representation...
  [OK] Built IR with 2 nodes
    - web-prod-01: platform=ubuntu
    - app-prod-01: platform=ubuntu
[STAGE 3] Transforming IR to AAP 2.4...
  [OK] Created execution environment EE#42
  [OK] Created inventory ID#1
    - Added host: web-prod-01
    - Added host: app-prod-01
  [OK] Created project ID#1
  [OK] Generated job template: Deploy-from-Chef-Migration
[STAGE 4] Validating transformation...
  [OK] Job template uses execution_environment: 42
  [OK] Ansible version: 2.15.0
  [OK] Content signing enabled: True
  [OK] Legacy virtualenv NOT used (correct for AAP 2.4)
[STAGE 5] Creating job template in AAP 2.4...
  [OK] Job template created: JT#1
    - Name: Deploy-from-Chef-Migration
    - EE: 42
    - Signed: True

[YES] SUCCESS: Chef 15.10.91 → AAP 2.4.0 migration complete!
   2 nodes migrated to 1 job template with modern AAP features
```

### Functional API Differences by Version Combo

**When IR transforms to AWX, it MUST adapt based on destination version:**

| Chef Origin | AWX Destination | Execution Model | Auth Protocol | IR Change |
|---|---|---|---|---|
| Chef 12.x | AWX 20.x | `custom_virtualenv` | 1.0 or 1.3 | Use `/path/to/venv` |
| Chef 12.x | AWX 22.x | `execution_environment` | 1.3 | Reference EE ID (required) |
| Chef 14.x | AWX 24.6.1 | `execution_environment` | 1.3 | Reference EE ID (required) |
| Chef 15.10.91 | AAP 2.4 | `execution_environment` | 1.3 | Reference EE ID + signing |

**Code Example: Version-Aware IR Transform**

```python
def ir_to_awx_job_template(ir_graph, platform, version, **options):
    """Transform IR to AWX job template, adapting to version differences."""

    job_template = {
        "name": ir_graph.name,
        "job_type": "run",
        "inventory": options.get("inventory_id"),
        "project": options.get("project_id"),
        "playbook": ir_graph.playbook_name,
    }

    # FUNCTIONAL DIFFERENCE: Execution model depends on version
    if platform == "tower" or (platform == "awx" and int(version.split(".")[0]) < 21):
        # Old: Tower 3.x, AWX <21 (VIRTUALENV model)
        job_template["custom_virtualenv"] = "/var/lib/awx/venv/ansible"
    else:
        # New: AWX 21+, AAP 2.x (EXECUTION ENVIRONMENT model)
        # CRITICAL: Must provide execution_environment ID
        job_template["execution_environment"] = options.get("execution_environment_id")
        if job_template["execution_environment"] is None:
            raise ValueError(f"{platform} {version} requires execution_environment")

    # AAP 2.4+ specific features
    if platform == "aap" and options.get("signing_enabled"):
        job_template["content_signing"] = True

    return job_template
```

## Authentication

### Chef Server
- **Method**: RSA-signed X-Ops-Authorization headers
- **Implementation**: Uses `cryptography` library to generate valid 2048-bit RSA keys
- **Headers**: Complete Chef Server authentication protocol (SHA-1)

### AWX/AAP
- **Method**: OAuth2 Bearer token
- **Header**: `Authorization: Bearer test-token`

## Running the Tests

```bash
# Run all IR workflow tests
poetry run pytest tests/integration/test_ir_chef_awx_workflow.py -v

# Run with coverage
poetry run pytest tests/integration/test_ir_chef_awx_workflow.py --cov=souschef/ir

# Run specific test class
poetry run pytest tests/integration/test_ir_chef_awx_workflow.py::TestEndToEndChefToAWXWorkflow -v
```

## Integration with Existing Tests

These IR workflow tests complement the existing mock infrastructure:

- **Chef Server Mocks** (`tests/integration/test_chef_server_mock.py`): ~100 tests for Chef API
- **AWX Mocks** (`tests/integration/test_awx_mock.py`): ~50 tests for AWX API
- **IR Schema Tests** (`tests/integration/test_ir_integration.py`): ~20 tests for IR operations

**Total Mock Test Coverage**: 178+ tests validating API emulation

## Test Data Structure

### IR Graph Example
```python
graph = IRGraph(
    graph_id="migration-001",
    source_type=SourceType.CHEF,
    target_type=TargetType.ANSIBLE,
    version="1.0.0",
)

node = IRNode(
    node_id="host-web-01",
    node_type=IRNodeType.RESOURCE,
    name="web-01",
    source_type=SourceType.CHEF,
)
node.set_variable("platform", "ubuntu")
node.set_variable("environment", "production")
graph.add_node(node)
```

### Chef Server Mock Response
```python
mock_nodes = {
    "rows": [
        {
            "name": "web-prod-01",
            "run_list": ["recipe[nginx]"],
            "chef_environment": "production",
            "platform": "ubuntu",
            "ipaddress": "10.0.1.10",
        }
    ],
    "total": 1,
}
```

### AWX API Mock Response
```python
awx_template = {
    "id": 42,
    "name": "Deploy webapp",
    "job_type": "run",
    "inventory": 1,
    "project": 1,
    "playbook": "deploy_webapp.yml",
}
```

## Design Patterns

### Pattern 1: Multi-Stage Workflow Testing
Tests follow the actual migration workflow:
1. **Discover** (Chef Server query)
2. **Model** (IR graph construction)
3. **Transform** (IR to Ansible)
4. **Deploy** (AWX configuration)

### Pattern 2: Mock Response Chaining
Multiple mock responses configured in sequence:
```python
responses.add(GET, chef_url, json=chef_data)  # Stage 1
responses.add(GET, role_url, json=role_data)   # Stage 2
responses.add(POST, awx_url, json=awx_data)    # Stage 3
```

### Pattern 3: Validation Metadata
Tests track validation status in IR:
```python
node.tags["validated"] = "true"
node.tags["validation_source"] = "chef_server"
graph.metadata["awx_compatible"] = "true"
```

## Coverage Metrics

**Test Coverage**: 8 tests covering critical v2.0/2.1 workflows
**API Endpoints**: 9 Chef + 5 AWX endpoints mocked
**Workflow Stages**: 4-stage end-to-end migration validated
**Authentication**: Both Chef RSA and AWX OAuth2 tested

## Related Documentation

- [Chef Server Mock Testing](MOCK_CHEF_SERVER.md)
- [AWX Mock Testing](MOCK_AWX.md)
- [IR Architecture](../ARCHITECTURE.md)
- [Migration Workflows](../migration-guide/overview.md)

## Contributing

When adding new v2.0/2.1 IR features:

1. [YES] Add mock tests for new Chef Server endpoints
2. [YES] Add mock tests for new AWX API interactions
3. [YES] Validate IR graph transformations
4. [YES] Test complete workflow integration
5. [YES] Document new mock responses

## Validation Checklist

Before merging v2.0/2.1 features:

- [ ] All 8 IR workflow tests pass
- [ ] No linting errors (`ruff check`)
- [ ] No type errors (`mypy`)
- [ ] Coverage maintained at 86%+
- [ ] Mock responses match official API specs
- [ ] Authentication headers validated
- [ ] **Origin configuration**: Chef Server version specified and tested
- [ ] **Destination configuration**: Ansible platform/version specified and tested
- [ ] **API version compatibility**: Verified origin→destination path works
- [ ] **Breaking changes**: Documented version-specific behaviors
- [ ] **Test matrix**: At least 2 origin versions and 2 destination versions tested

## Version Detection & Compatibility Testing

### Detecting Chef Server Version

**Via API**:
```python
response = client._request("GET", "/version")
version_data = response.json()
# Returns: {"chef_server_version": "14.10.9"}
```

**Via Headers** (response from any endpoint):
```python
server_version = response.headers.get("X-Ops-Server-API-Version", "unknown")
```

### Detecting AWX/Tower Version

**Via Config Endpoint**:
```python
response = requests.get(
    "https://awx.example.com/api/v2/config/",
    headers={"Authorization": "Bearer token"}
)
config = response.json()
print(f"AWX Version: {config['version']}")
print(f"Ansible Version: {config['ansible_version']}")
```

**Response Example**:
```json
{
  "version": "23.3.1",          // AWX version
  "ansible_version": "2.15.0",  // Ansible core version
  "project_base_dir": "/var/lib/awx/projects",
  "custom_virtualenvs": []
}
```

### Configuring Test Migrations

**Step 1: Define Origin (Chef Server)**:
```python
# Example: Chef Server 15.x origin
chef_origin = {
    "version": "15.0.0",
    "organization": "myorg",
    "auth_protocol": "1.3",  # SHA-256
    "base_url": "https://chef.example.com",
}

# Mock Chef Server responses for 15.x
responses.add(
    method=responses.GET,
    url=f"{chef_origin['base_url']}/version",
    json={"chef_server_version": chef_origin["version"]},
    headers={"X-Ops-Server-API-Version": "1.3"},
)
```

**Step 2: Define Destination (Ansible Platform)**:
```python
# Example: AAP 2.4 destination
awx_destination = {
    "platform": "aap",
    "version": "2.4.0",
    "ansible_version": "2.15.0",
    "api_version": "v2",
    "base_url": "https://aap.example.com",
    "features": ["execution_environments", "workflows"],
}

# Mock AWX/AAP config endpoint
responses.add(
    method=responses.GET,
    url=f"{awx_destination['base_url']}/api/v2/config/",
    json={
        "version": awx_destination["version"],
        "ansible_version": awx_destination["ansible_version"],
    },
)
```

**Step 3: Configure Version-Specific Behaviors**:
```python
# Chef 15.x: Uses SHA-256 authentication
if chef_origin["auth_protocol"] == "1.3":
    auth_headers = generate_chef_auth_sha256(...)
else:
    auth_headers = generate_chef_auth_sha1(...)

# AAP 2.4: Requires execution environments
if "execution_environments" in awx_destination["features"]:
    job_template["execution_environment"] = 1  # Required
else:
    job_template["custom_virtualenv"] = "/venv/ansible"  # Legacy
```

### Testing Cross-Version Compatibility

**Test Origin Compatibility** (Chef Server versions):
```python
@pytest.mark.parametrize("chef_version,auth_protocol", [
    ("12.0", "1.0"),  # SHA-1 only
    ("12.19", "1.3"), # First 1.3 support
    ("14.0", "1.3"),  # SHA-256 preferred
    ("15.0", "1.3"),  # SHA-256 default
])
def test_chef_origin_versions(chef_version, auth_protocol):
    """Test Chef Server versions as migration origins."""
    # Configure mock with version-specific auth
    client = ChefServerClient(
        base_url="https://chef.example.com",
        client_name="validator",
        private_key=test_key,
        auth_version=auth_protocol,
    )
    # All versions should return same data structure
    nodes = client.search_nodes("name:*")
    assert "rows" in nodes
```

**Test Destination Compatibility** (Ansible platforms):
```python
@pytest.mark.parametrize("platform,version,ansible_version", [
    ("tower", "3.8.6", "2.9.27"),   # Legacy
    ("awx", "21.0", "2.12.0"),      # Transition (virtualenv → EE)
    ("awx", "24.0", "2.16.0"),      # Current
    ("aap", "2.4.0", "2.15.0"),     # Enterprise
])
def test_awx_destination_versions(platform, version, ansible_version):
    """Test Ansible platforms as migration destinations."""
    # Configure version-specific API mock
    responses.add(
        responses.GET,
        "https://awx.example.com/api/v2/config/",
        json={"version": version, "ansible_version": ansible_version},
    )

    # Generate IR and transform to platform-specific config
    ir_graph = create_test_ir_graph()

    # Version-specific transformations
    if platform == "aap" or (platform == "awx" and int(version.split(".")[0]) >= 21):
        # Use execution environments (required in AAP, AWX 21+)
        job_template = ir_to_awx_job_template(ir_graph, use_ee=True)
        assert "execution_environment" in job_template
    else:
        # Use legacy virtualenv (Tower 3.x, early AWX)
        job_template = ir_to_awx_job_template(ir_graph, use_ee=False)
        assert "custom_virtualenv" in job_template
```

**Test End-to-End Paths**:
```python
@pytest.mark.parametrize("origin,destination", [
    # Minimum supported: Chef 12.x → Tower 3.8
    (
        {"chef": "12.19", "auth": "1.3"},
        {"platform": "tower", "version": "3.8.6", "ansible": "2.9"},
    ),
    # Mid-range: Chef 14.x → AWX 22.x
    (
        {"chef": "14.15", "auth": "1.3"},
        {"platform": "awx", "version": "22.0", "ansible": "2.14"},
    ),
    # Current: Chef 15.x → AAP 2.4
    (
        {"chef": "15.0", "auth": "1.3"},
        {"platform": "aap", "version": "2.4", "ansible": "2.15"},
    ),
])
def test_migration_path_compatibility(origin, destination):
    """Test complete Chef → Ansible migration paths."""
    # Stage 1: Query Chef Server (origin version)
    chef_client = configure_chef_client(origin)
    nodes = chef_client.search_nodes("*:*")

    # Stage 2: Build IR from Chef data
    ir_graph = chef_to_ir(nodes)

    # Stage 3: Transform IR to Ansible (destination version)
    awx_config = configure_awx_client(destination)
    inventory = ir_to_awx_inventory(ir_graph, awx_config)
    job_template = ir_to_awx_job_template(ir_graph, awx_config)

    # Stage 4: Validate destination compatibility
    assert validate_awx_compatibility(ir_graph, destination["ansible"])
```

### Version-Specific Breaking Changes

**When migrating FROM these Chef Server versions**:

| Origin Version | Breaking Changes | Migration Impact | Workaround |
|----------------|------------------|------------------|------------|
| Chef 11.x | SHA-1 only | Must upgrade to 12.x for SHA-256 | Use auth protocol 1.0 |
| Chef 12.x | Mixed auth support | None - fully compatible | Prefer protocol 1.3 |
| Chef 14.x | Strict SSL/TLS | Requires valid certificates | Update cert validation |
| Chef 15.x | FIPS compliance | Cipher suite restrictions | Use approved ciphers |

**When migrating TO these Ansible platforms**:

| Destination Version | Breaking Changes | Migration Impact | Workaround |
|---------------------|------------------|------------------|------------|
| Tower 3.8 | EOL (no EE support) | Limited to virtualenv | Use AWX 19+ for EE |
| AWX 19.x | Removed legacy creds | Credential migration needed | Remap credential types |
| AWX 21.x | EE required | Must provide EE or default | Create default EE |
| AAP 2.x | Renamed entities | API paths changed | Use new paths |
| AAP 2.4 | Ansible 2.15+ | Older playbooks may break | Test playbook syntax |

### Tested Migration Paths

**Current Test Coverage** (`test_ir_chef_awx_workflow.py`):

| Test | Origin | Destination | Status | API Validation |
|------|--------|-------------|--------|----------------|
| `test_query_chef_nodes_and_build_ir` | Chef 14.x | N/A | [YES] Passing | SHA-256 auth, node search |
| `test_chef_cookbook_to_ir_with_dependencies` | Chef 12.x+ | N/A | [YES] Passing | Cookbook metadata |
| `test_ir_to_awx_job_template_creation` | N/A | AWX 24.6.1 | [YES] Passing | EE-based templates |
| `test_ir_to_awx_inventory_creation` | N/A | AWX 24.6.1 | [YES] Passing | Inventory API v2 |
| `test_complete_migration_workflow` | Chef 14.x | AWX 24.6.1 | [YES] Passing | Full 4-stage pipeline |
| `test_workflow_with_chef_environments_to_awx_groups` | Chef 14.x | AWX 24.6.1 | [YES] Passing | Environment mapping |
| `test_validate_ir_node_against_chef_server` | Chef 14.x | N/A | [YES] Passing | Recipe validation |
| `test_validate_awx_compatibility_from_ir` | N/A | AWX 24.6.1 | [YES] Passing | Ansible version check |

**Default Test Configuration**:
- **Origin**: Chef Server 14.x with SHA-256 authentication (protocol 1.3)
- **Destination**: AWX 24.6.1 with Ansible 2.16 and Execution Environment support
- **API Versions**: Chef Server (no URL version), AWX `/api/v2/`

### Extending Coverage (recommended):
```python
# Add tests for other Chef origins
test_chef_12x_mixed_auth()  # Protocol 1.0/1.3 support
test_chef_15_fips()  # FIPS 140-2 compliance

# Add tests for other AWX versions
test_chef_to_awx_22()  # AWX 22.x with EE
test_chef_to_awx_23()  # AWX 23.x releases

# Add tests for AAP
test_chef_to_aap_24()  # AAP 2.4 features
```

## Future Enhancements

Potential additions to mock coverage:

### Expanded Version Testing
- **Chef 11.x origins**: Test SHA-1-only legacy migrations
- **Chef 12.x mixed auth**: Test protocol 1.0/1.3 transitions
- **Chef 15.x FIPS**: Test FIPS 140-2 compliant origins
- **Tower 3.8 destinations**: Test legacy AWX Tower targets
- **AWX 24.x destinations**: Test latest AWX features
- **AAP 2.5+ destinations**: Test future AAP releases

### Additional API Coverage
- **Chef Policyfiles**: Mock policy group queries and policy revisions
- **AWX Workflows**: Multi-step workflow node creation and dependencies
- **Chef Search**: Advanced search query patterns (boolean, wildcard, range)
- **AWX Surveys**: Job template survey generation from Chef attributes
- **AWX Execution Environments**: Custom EE image configuration

### Error Scenario Testing
- **4xx/5xx responses**: API error handling across versions
- **Rate limiting**: API throttling simulation (429 responses)
- **Authentication failures**: Invalid keys, expired tokens
- **Version mismatches**: Incompatible origin→destination combinations

### Performance Testing
- **Large-scale migrations**: 1000+ nodes, 100+ cookbooks
- **Concurrent API calls**: Parallel Chef Server queries
- **AWX batch operations**: Bulk inventory/host creation
