helm-schema 0.0.4

Generate an accurate JSON schema for any helm chart
Documentation
use indoc::indoc;
use std::fs;
use std::path::PathBuf;
use test_util::prelude::sim_assert_eq;

use serde_json::Value;

use crate::output_pipeline::{
    OutputPipelineOptions, PolicyInputOptions, ReferenceMode, apply_schema_output_pipeline,
    load_policy_inputs,
};

fn test_temp_dir(name: &str) -> PathBuf {
    std::env::temp_dir().join(format!(
        "helm-schema-output-pipeline-{name}-{}",
        std::process::id()
    ))
}

fn policy_options(reference_mode: ReferenceMode) -> PolicyInputOptions {
    PolicyInputOptions {
        reference_mode,
        fetch_policy: crate::fetch_policy::FetchPolicy::new(true, false),
        load_budget: crate::load_budget::LoadBudget::default(),
    }
}

fn output_options(reference_mode: ReferenceMode) -> OutputPipelineOptions {
    OutputPipelineOptions {
        reference_mode,
        strip_descriptions: false,
        minimize: false,
    }
}

#[test]
fn prepared_override_schemas_bundle_refs_before_merge() {
    let temp_dir = test_temp_dir("prepared-overrides");
    fs::create_dir_all(&temp_dir).expect("create temp dir");
    fs::write(
        temp_dir.join("shared.json"),
        indoc! {r#"
            {
                "definitions": {
                    "cloud": {
                        "enum": [null, "azure", "minikube"]
                    }
                }
            }"#},
    )
    .expect("write shared schema");
    let override_path = temp_dir.join("override.json");
    fs::write(
        &override_path,
        indoc! {r#"
            {
                "properties": {
                    "cloud": {
                        "$ref": "./shared.json#/definitions/cloud"
                    }
                }
            }"#},
    )
    .expect("write override schema");

    let policy_options = policy_options(ReferenceMode::SelfContained);
    let output_options = output_options(ReferenceMode::SelfContained);
    let policy_inputs =
        load_policy_inputs(&[override_path], &policy_options).expect("load overrides");
    let schema = serde_json::json!({
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
            "cloud": {
                "type": ["boolean", "string"]
            }
        },
        "type": "object"
    });

    let output =
        apply_schema_output_pipeline(schema, policy_inputs, &[], &temp_dir, output_options)
            .expect("apply output pipeline");

    let cloud = output.pointer("/properties/cloud").expect("cloud schema");
    sim_assert_eq!(
        have: cloud,
        want: &serde_json::json!({
            "$ref": "#/$defs/schema1"
        }),
        "prepared override refs should replace inferred constraints with bundled refs"
    );
    sim_assert_eq!(
        have: output.pointer("/$defs/schema1"),
        want: Some(&serde_json::json!({
            "enum": [null, "azure", "minikube"]
        })),
        "prepared override refs should carry resolved content under $defs"
    );

    fs::remove_dir_all(&temp_dir).expect("remove temp dir");
}

#[test]
fn fully_inlined_export_override_refs_resolve_before_merge() {
    let temp_dir = test_temp_dir("prepared-overrides-inline");
    fs::create_dir_all(&temp_dir).expect("create temp dir");
    fs::write(
        temp_dir.join("shared.json"),
        indoc! {r#"
            {
                "definitions": {
                    "cloud": {
                        "enum": [null, "azure", "minikube"]
                    }
                }
            }"#},
    )
    .expect("write shared schema");
    let override_path = temp_dir.join("override.json");
    fs::write(
        &override_path,
        indoc! {r#"
            {
                "properties": {
                    "cloud": {
                        "$ref": "./shared.json#/definitions/cloud"
                    }
                }
            }"#},
    )
    .expect("write override schema");

    let policy_options = policy_options(ReferenceMode::FullyInlinedExport);
    let output_options = output_options(ReferenceMode::FullyInlinedExport);
    let policy_inputs =
        load_policy_inputs(&[override_path], &policy_options).expect("load overrides");
    let schema = serde_json::json!({
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
            "cloud": {
                "type": ["boolean", "string"]
            }
        },
        "type": "object"
    });

    let output =
        apply_schema_output_pipeline(schema, policy_inputs, &[], &temp_dir, output_options)
            .expect("apply output pipeline");

    let cloud = output.pointer("/properties/cloud").expect("cloud schema");
    sim_assert_eq!(
        have: cloud,
        want: &serde_json::json!({
            "enum": [null, "azure", "minikube"]
        }),
        "fully inlined export refs should replace inferred constraints after dereferencing"
    );

    fs::remove_dir_all(&temp_dir).expect("remove temp dir");
}

#[test]
fn override_refs_are_preserved_when_reference_mode_preserves_refs() {
    let temp_dir = test_temp_dir("prepared-overrides-keep-refs");
    fs::create_dir_all(&temp_dir).expect("create temp dir");
    let override_path = temp_dir.join("override.json");
    fs::write(
        &override_path,
        indoc! {r#"
            {
                "properties": {
                    "cloud": {
                        "$ref": "./shared.json#/definitions/cloud"
                    }
                }
            }"#},
    )
    .expect("write override schema");

    let policy_options = policy_options(ReferenceMode::PreserveRefs);
    let output_options = output_options(ReferenceMode::PreserveRefs);
    let policy_inputs =
        load_policy_inputs(&[override_path], &policy_options).expect("load overrides");
    let schema = serde_json::json!({
        "properties": {
            "cloud": {
                "type": "string"
            }
        },
        "type": "object"
    });

    let output =
        apply_schema_output_pipeline(schema, policy_inputs, &[], &temp_dir, output_options)
            .expect("apply output pipeline");

    sim_assert_eq!(
        have: output
            .pointer("/properties/cloud/$ref")
            .and_then(Value::as_str),
        want: Some("./shared.json#/definitions/cloud"),
    );

    fs::remove_dir_all(&temp_dir).expect("remove temp dir");
}