harn-cli 0.10.55

CLI for the Harn programming language — run, test, REPL, format, and lint
Documentation
use super::*;
use crate::commands::check::host_capabilities::parse_host_capability_value;

#[test]
fn parse_host_capability_value_accepts_top_level_object_schema() {
    let value = serde_json::json!({
        "workspace": ["project_root", "file_exists"],
        "runtime": { "operations": ["task", "pipeline_input"] },
        "harn_cloud": {
            "operations": {
                "agent_api": {
                    "param_discriminators": {
                        "operation": ["agents.get", "agents.list"],
                        "forwarded_operation": {
                            "values": ["agents.get", "agents.list"],
                            "allow_dynamic": true
                        }
                    }
                }
            }
        }
    });
    let parsed = parse_host_capability_value(&value);
    assert!(parsed.contains_operation("workspace", "project_root"));
    assert!(parsed.contains_operation("workspace", "file_exists"));
    assert!(parsed.contains_operation("runtime", "task"));
    assert!(parsed.contains_operation("runtime", "pipeline_input"));
    assert!(parsed.contains_operation("harn_cloud", "agent_api"));
    assert_eq!(
        parsed
            .param_discriminators("harn_cloud", "agent_api")
            .unwrap()["operation"]
            .allowed_values,
        ["agents.get".to_string(), "agents.list".to_string()]
            .into_iter()
            .collect()
    );
    let forwarded = &parsed
        .param_discriminators("harn_cloud", "agent_api")
        .unwrap()["forwarded_operation"];
    assert_eq!(forwarded.allowed_values.len(), 2);
    assert!(forwarded.allow_dynamic);
    let projected = parsed.into_manifest_entries();
    assert_eq!(
        projected["harn_cloud"]["operations"]["agent_api"]["param_discriminators"]
            ["forwarded_operation"]["allow_dynamic"],
        true
    );
    assert_eq!(
        projected["harn_cloud"]["operations"]["agent_api"]["param_discriminators"]["operation"]
            ["values"][0],
        "agents.get"
    );
}

#[test]
fn preflight_validates_host_param_discriminator_literals() {
    let dir = unique_temp_dir("harn-check-host-param-discriminator");
    std::fs::create_dir_all(&dir).unwrap();
    let manifest = dir.join("host-capabilities.json");
    std::fs::write(
        &manifest,
        r#"{
          "capabilities": {
            "harn_cloud": {
              "operations": {
                "agent_api": {
                  "param_discriminators": {
                    "operation": ["agents.get", "agents.list"]
                  }
                }
              }
            }
          }
        }"#,
    )
    .unwrap();
    let config = CheckConfig {
        host_capabilities_path: Some(manifest.display().to_string()),
        ..CheckConfig::default()
    };

    let accepted = r#"
pipeline main() {
  host_call("harn_cloud.agent_api", {operation: "agents.get", params: {}})
}
"#;
    let file = dir.join("accepted.harn");
    let diagnostics =
        collect_preflight_diagnostics(&file, accepted, &parse_program(accepted), &config);
    assert!(
        diagnostics.is_empty(),
        "declared discriminator should pass: {:?}",
        diagnostics.iter().map(|d| &d.message).collect::<Vec<_>>()
    );

    let misspelled = r#"
pipeline main() {
  host_call("harn_cloud.agent_api", {operation: "agents.geet", params: {}})
}
"#;
    let diagnostics = collect_preflight_diagnostics(
        &dir.join("misspelled.harn"),
        misspelled,
        &parse_program(misspelled),
        &config,
    );
    assert!(diagnostics.iter().any(|diagnostic| {
        diagnostic.code == harn_parser::DiagnosticCode::CapabilityUnknownOperation
            && diagnostic.message.contains("agents.geet")
            && diagnostic.message.contains("`operation` discriminator")
    }));

    let dynamic = r#"
pipeline main(op) {
  host_call("harn_cloud.agent_api", {operation: op, params: {}})
}
"#;
    let diagnostics = collect_preflight_diagnostics(
        &dir.join("dynamic.harn"),
        dynamic,
        &parse_program(dynamic),
        &config,
    );
    assert!(diagnostics.iter().any(|diagnostic| {
        diagnostic.code == harn_parser::DiagnosticCode::CapabilityCallStaticNameRequired
            && diagnostic
                .message
                .contains("requires a literal `operation` params field")
    }));

    let forwarding_manifest = dir.join("forwarding-host-capabilities.json");
    std::fs::write(
        &forwarding_manifest,
        r#"{
          "capabilities": {
            "harn_cloud": {
              "operations": {
                "agent_api": {
                  "param_discriminators": {
                    "operation": {
                      "values": ["agents.get", "agents.list"],
                      "allow_dynamic": true
                    }
                  }
                }
              }
            }
          }
        }"#,
    )
    .unwrap();
    let forwarding_config = CheckConfig {
        host_capabilities_path: Some(forwarding_manifest.display().to_string()),
        ..CheckConfig::default()
    };
    let diagnostics = collect_preflight_diagnostics(
        &dir.join("forwarding.harn"),
        dynamic,
        &parse_program(dynamic),
        &forwarding_config,
    );
    assert!(
        diagnostics.is_empty(),
        "manifest opted into a separately validated forwarding boundary: {:?}",
        diagnostics.iter().map(|d| &d.message).collect::<Vec<_>>()
    );
    let _ = std::fs::remove_dir_all(&dir);
}