tfmcp 0.2.2

Terraform Model Context Protocol Tool - A CLI tool to manage Terraform through MCP
Documentation
use serde_json::Value;
use tfmcp::mcp::server::ToolFilter;

#[test]
fn server_json_matches_mcp_registry_oci_requirements() {
    let metadata: Value = serde_json::from_str(include_str!("../server.json"))
        .expect("server.json must be valid JSON");

    assert_eq!(
        metadata["$schema"],
        "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json"
    );
    assert_eq!(metadata["name"], "io.github.nwiizo/tfmcp");
    assert_eq!(metadata["version"], env!("CARGO_PKG_VERSION"));
    assert_eq!(
        metadata["repository"]["url"],
        "https://github.com/nwiizo/tfmcp"
    );
    assert_eq!(metadata["repository"]["source"], "github");

    let packages = metadata["packages"]
        .as_array()
        .expect("server.json packages must be an array");
    assert_eq!(packages.len(), 1);

    let package = &packages[0];
    assert_eq!(package["registryType"], "oci");
    assert_eq!(
        package["identifier"],
        format!("ghcr.io/nwiizo/tfmcp:{}", env!("CARGO_PKG_VERSION"))
    );
    assert_eq!(package["transport"]["type"], "stdio");

    let env_vars = package["environmentVariables"]
        .as_array()
        .expect("server.json package environmentVariables must be an array");
    let tfe_token = env_vars
        .iter()
        .find(|env| env["name"] == "TFE_TOKEN")
        .expect("TFE_TOKEN must be documented in server.json");
    assert_eq!(tfe_token["isSecret"], true);

    for name in [
        "ENABLE_TF_OPERATIONS",
        "TFE_MAX_RESPONSE_BYTES",
        "TRANSPORT_MODE",
        "TRANSPORT_HOST",
        "TRANSPORT_PORT",
        "MCP_ENDPOINT",
        "MCP_HEALTH_ENDPOINT",
        "MCP_METRICS_ENDPOINT",
        "MCP_SESSION_MODE",
        "MCP_CORS_MODE",
        "MCP_ALLOWED_ORIGINS",
        "MCP_ALLOWED_HOSTS",
        "MCP_HEARTBEAT_INTERVAL",
        "MCP_ORGANIZATION_ALLOWLIST",
        "MCP_RATE_LIMIT_GLOBAL",
        "MCP_RATE_LIMIT_SESSION",
        "MCP_TLS_CERT_FILE",
        "MCP_TLS_KEY_FILE",
    ] {
        assert!(
            env_vars.iter().any(|env| env["name"] == name),
            "{name} must be documented in server.json"
        );
    }
}

#[test]
fn dockerfile_declares_matching_mcp_registry_label() {
    let dockerfile = include_str!("../Dockerfile");
    assert!(
        dockerfile
            .contains(r#"LABEL io.modelcontextprotocol.server.name="io.github.nwiizo/tfmcp""#),
        "Dockerfile must declare the OCI ownership label required by the MCP Registry"
    );
}

#[test]
fn dockerfile_declares_matching_oci_release_metadata() {
    let dockerfile = include_str!("../Dockerfile");

    assert!(
        dockerfile.contains(&format!(
            r#"ARG TFMCP_VERSION="{}""#,
            env!("CARGO_PKG_VERSION")
        )),
        "Dockerfile TFMCP_VERSION must match Cargo package version"
    );

    for required_label in [
        r#"org.opencontainers.image.title="tfmcp""#,
        r#"org.opencontainers.image.source="https://github.com/nwiizo/tfmcp""#,
        r#"org.opencontainers.image.version="${TFMCP_VERSION}""#,
        r#"org.opencontainers.image.revision="${TFMCP_REVISION}""#,
    ] {
        assert!(
            dockerfile.contains(required_label),
            "Dockerfile must declare OCI label {required_label}"
        );
    }
}

#[test]
fn default_toolset_exposes_release_baseline_without_write_operations() {
    let toolsets = vec!["default".to_string()];
    let filter = ToolFilter::from_cli(&toolsets, None);

    for tool in [
        "search_providers",
        "get_provider_details",
        "get_provider_capabilities",
        "get_latest_provider_version",
        "search_modules",
        "get_module_details",
        "get_latest_module_version",
        "search_policies",
        "get_policy_details",
        "search_private_modules",
        "get_private_module_details",
        "search_private_providers",
        "get_private_provider_details",
        "inspect_terraform_project",
        "detect_terraform_entrypoints",
        "analyze_terraform",
        "analyze_module_health",
        "review_terraform_plan",
        "summarize_plan_for_pr",
        "check_provider_lockfile",
        "run_terraform_quality_checks",
        "inspect_state_safety",
        "detect_drift_candidates",
        "prepare_terraform_change",
        "validate_terraform",
        "validate_terraform_detailed",
        "get_security_status",
        "get_token_permissions",
        "list_terraform_orgs",
        "list_terraform_projects",
        "list_workspaces",
        "get_workspace_details",
        "list_runs",
        "get_run_details",
        "get_plan_details",
        "get_apply_details",
        "get_workspace_policy_sets",
        "list_workspace_variables",
        "list_variable_sets",
        "read_workspace_tags",
        "list_stacks",
        "get_stack_details",
    ] {
        assert!(
            filter.is_enabled(tool),
            "{tool} should be in default toolset"
        );
    }

    for tool in [
        "apply_terraform",
        "destroy_terraform",
        "terraform_import",
        "terraform_taint",
        "terraform_refresh",
        "create_workspace",
        "update_workspace",
        "delete_workspace_safely",
        "create_run",
        "action_run",
        "create_workspace_variable",
        "update_workspace_variable",
        "attach_policy_set_to_workspace",
        "create_variable_set",
        "create_variable_in_variable_set",
        "delete_variable_in_variable_set",
        "attach_variable_set_to_workspaces",
        "detach_variable_set_from_workspaces",
        "create_workspace_tags",
    ] {
        assert!(
            !filter.is_enabled(tool),
            "{tool} should not be in default toolset"
        );
    }
}

#[test]
fn operations_toolset_exposes_only_gated_remote_operations() {
    let toolsets = vec!["operations".to_string()];
    let filter = ToolFilter::from_cli(&toolsets, None);

    for tool in [
        "create_workspace",
        "update_workspace",
        "delete_workspace_safely",
        "create_run",
        "action_run",
        "create_workspace_variable",
        "update_workspace_variable",
        "attach_policy_set_to_workspace",
        "create_variable_set",
        "create_variable_in_variable_set",
        "delete_variable_in_variable_set",
        "attach_variable_set_to_workspaces",
        "detach_variable_set_from_workspaces",
        "create_workspace_tags",
    ] {
        assert!(
            filter.is_enabled(tool),
            "{tool} should be in operations toolset"
        );
    }

    assert!(!filter.is_enabled("apply_terraform"));
    assert!(!filter.is_enabled("search_providers"));
    assert!(!filter.is_enabled("list_workspace_variables"));
}

#[test]
fn terraform_toolset_exposes_hashicorp_v1_read_surface_and_tfmcp_strengths() {
    let toolsets = vec!["terraform".to_string()];
    let filter = ToolFilter::from_cli(&toolsets, None);

    for tool in [
        "get_token_permissions",
        "list_terraform_orgs",
        "list_terraform_projects",
        "list_workspaces",
        "get_workspace_details",
        "list_runs",
        "get_run_details",
        "get_plan_details",
        "get_plan_logs",
        "get_plan_json_output",
        "get_apply_details",
        "get_apply_logs",
        "get_workspace_policy_sets",
        "list_workspace_variables",
        "list_variable_sets",
        "read_workspace_tags",
        "list_stacks",
        "get_stack_details",
        "inspect_terraform_project",
        "detect_terraform_entrypoints",
        "run_terraform_quality_checks",
        "inspect_state_safety",
        "detect_drift_candidates",
        "prepare_terraform_change",
    ] {
        assert!(
            filter.is_enabled(tool),
            "{tool} should be in terraform toolset"
        );
    }

    assert!(!filter.is_enabled("search_providers"));
    assert!(!filter.is_enabled("create_workspace"));
    assert!(!filter.is_enabled("action_run"));
}

#[test]
fn registry_toolset_keeps_hashicorp_aliases_and_tfmcp_names() {
    let toolsets = vec!["registry".to_string()];
    let filter = ToolFilter::from_cli(&toolsets, None);

    for tool in [
        "search_providers",
        "search_terraform_providers",
        "get_provider_details",
        "get_provider_info",
        "get_provider_docs",
        "get_provider_capabilities",
        "search_modules",
        "search_terraform_modules",
        "get_module_details",
        "get_latest_module_version",
        "get_latest_provider_version",
        "search_policies",
        "get_policy_details",
        "search_private_modules",
        "get_private_module_details",
        "search_private_providers",
        "get_private_provider_details",
    ] {
        assert!(
            filter.is_enabled(tool),
            "{tool} should be in registry toolset"
        );
    }

    assert!(!filter.is_enabled("apply_terraform"));
    assert!(!filter.is_enabled("inspect_terraform_project"));
}

#[test]
fn registry_private_toolset_matches_hashicorp_name_without_removing_registry_behavior() {
    let toolsets = vec!["registry-private".to_string()];
    let filter = ToolFilter::from_cli(&toolsets, None);

    for tool in [
        "search_private_modules",
        "get_private_module_details",
        "search_private_providers",
        "get_private_provider_details",
    ] {
        assert!(
            filter.is_enabled(tool),
            "{tool} should be in registry-private toolset"
        );
    }

    assert!(!filter.is_enabled("search_providers"));
    assert!(!filter.is_enabled("list_workspaces"));
}

#[test]
fn explicit_tools_override_toolsets() {
    let toolsets = vec!["all".to_string()];
    let tools = vec!["validate_terraform".to_string()];
    let filter = ToolFilter::from_cli(&toolsets, Some(&tools));

    assert!(filter.is_enabled("validate_terraform"));
    assert!(!filter.is_enabled("search_providers"));
    assert!(!filter.is_enabled("apply_terraform"));
}

#[test]
fn unknown_toolset_does_not_fall_back_to_all_tools() {
    let toolsets = vec!["regsitry".to_string()];
    let filter = ToolFilter::from_cli(&toolsets, None);

    assert!(!filter.is_enabled("search_providers"));
    assert!(!filter.is_enabled("apply_terraform"));
}

#[test]
fn changelog_documents_hashicorp_compatibility_without_erasing_tfmcp_strengths() {
    let release_notes = include_str!("../CHANGELOG.md");

    for expected in [
        "## HashiCorp v1.0.x Compatibility",
        "registry-private",
        "get_plan_json_output",
        "get_apply_logs",
        "list_variable_sets",
        "attach_variable_set_to_workspaces",
        "read_workspace_tags",
        "list_stacks",
        "/terraform/providers/{namespace}/name/{name}/version/{version}",
        "local Terraform CLI workflows",
        "entrypoint detection",
        "module health analysis",
        "state safety checks",
        "local dangerous-operation gates",
    ] {
        assert!(
            release_notes.contains(expected),
            "changelog must document {expected}"
        );
    }
}