greentic-deployer-dev 1.1.27434236067

Greentic deployer runtime for plan construction and deployment-pack dispatch
use std::fs;
use std::path::{Path, PathBuf};

use greentic_deployer::contract::DeployerContractV1;
use serde_json::Value as JsonValue;
use serde_yaml_bw as serde_yaml;

fn fixture_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures/packs/helm")
}

fn load_json(path: &Path) -> JsonValue {
    let text = fs::read_to_string(path).expect("read json fixture");
    serde_json::from_str(&text).expect("parse json fixture")
}

fn validate_with_schema(schema_path: &Path, instance_path: &Path) {
    let schema = load_json(schema_path);
    let instance = load_json(instance_path);
    let compiled = jsonschema::validator_for(&schema).expect("compile schema");
    let errors = compiled.iter_errors(&instance).collect::<Vec<_>>();
    assert!(
        errors.is_empty(),
        "schema {} rejected {}: {:?}",
        schema_path.display(),
        instance_path.display(),
        errors
    );
}

#[test]
fn helm_contract_references_existing_assets() {
    let root = fixture_root();
    let contract_path = root.join("contract.greentic.deployer.v1.json");
    let contract: DeployerContractV1 =
        serde_json::from_value(load_json(&contract_path)).expect("parse contract");
    contract.validate().expect("valid contract");

    for capability in &contract.capabilities {
        for path in [
            capability.input_schema_ref.as_deref(),
            capability.output_schema_ref.as_deref(),
            capability.execution_output_schema_ref.as_deref(),
            capability.qa_spec_ref.as_deref(),
        ]
        .into_iter()
        .flatten()
        {
            assert!(
                root.join(path).exists(),
                "missing referenced asset {}",
                path
            );
        }
        for example in &capability.example_refs {
            assert!(root.join(example).exists(), "missing example {}", example);
        }
    }
}

#[test]
fn helm_examples_validate_against_pack_schemas() {
    let root = fixture_root();
    validate_with_schema(
        &root.join("assets/schemas/generate-input.schema.json"),
        &root.join("assets/examples/generate-request.json"),
    );
    validate_with_schema(
        &root.join("assets/schemas/generate-output.schema.json"),
        &root.join("assets/examples/generate-output.json"),
    );
    validate_with_schema(
        &root.join("assets/schemas/apply-execution-output.schema.json"),
        &root.join("assets/examples/apply-execution-output.json"),
    );
}

#[test]
fn helm_chart_has_required_structure() {
    let root = fixture_root().join("chart");
    for path in [
        "Chart.yaml",
        "values.yaml",
        "templates/deployment.yaml",
        "templates/service.yaml",
        "templates/ingress.yaml",
        "templates/configmap.yaml",
        "templates/networkpolicy.yaml",
        "templates/serviceaccount.yaml",
        "templates/rbac.yaml",
        "templates/hpa.yaml",
    ] {
        assert!(root.join(path).exists(), "missing chart file {}", path);
    }
}

#[test]
fn helm_chart_values_pin_image_digest_and_protect_admin_api() {
    let root = fixture_root().join("chart");
    let values_text = fs::read_to_string(root.join("values.yaml")).expect("read values");
    let values: serde_yaml::Value = serde_yaml::from_str(&values_text).expect("parse values");

    assert_eq!(
        values["image"]["digest"].as_str(),
        Some("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
    );
    assert_eq!(values["adminApi"]["public"].as_bool(), Some(false));
    assert_eq!(values["bundle"]["mode"].as_str(), Some("oci"));
}

#[test]
fn helm_templates_include_security_defaults_and_no_shell_generate() {
    let root = fixture_root().join("chart/templates");
    let deployment = fs::read_to_string(root.join("deployment.yaml")).expect("read deployment");
    assert!(deployment.contains("readOnlyRootFilesystem: true"));
    assert!(deployment.contains("allowPrivilegeEscalation: false"));
    assert!(deployment.contains("@{{ .Values.image.digest }}") || deployment.contains("@{{"));
    assert!(deployment.contains("GREENTIC_ADMIN_LISTEN"));

    let all_templates = [
        "deployment.yaml",
        "service.yaml",
        "ingress.yaml",
        "configmap.yaml",
        "networkpolicy.yaml",
        "serviceaccount.yaml",
        "rbac.yaml",
        "hpa.yaml",
        "_helpers.tpl",
    ]
    .into_iter()
    .map(|name| fs::read_to_string(root.join(name)).expect("read template"))
    .collect::<Vec<_>>()
    .join("\n");

    assert!(!all_templates.contains("exec "));
    assert!(!all_templates.contains("kubectl "));
    assert!(!all_templates.contains("helm template "));
    assert!(!all_templates.contains("sh -c"));
}

#[test]
fn helm_generate_output_declares_commands_and_guidance() {
    let output = load_json(&fixture_root().join("assets/examples/generate-output.json"));
    assert!(
        output["helm_upgrade_command"]
            .as_str()
            .expect("helm upgrade command")
            .contains("helm upgrade --install")
    );
    assert!(
        output["rollback_command_template"]
            .as_str()
            .expect("rollback command")
            .contains("helm rollback")
    );
    assert!(
        !output["values_diff_guidance"]
            .as_str()
            .expect("values diff guidance")
            .is_empty()
    );
}

#[test]
fn helm_rollback_example_matches_command_shape() {
    let text = fs::read_to_string(fixture_root().join("assets/examples/rollback-command.txt"))
        .expect("read rollback command");
    assert!(text.contains("helm rollback"));
    assert!(text.contains("<REVISION>"));
}