harn-vm 0.10.162

Async bytecode virtual machine for the Harn programming language
Documentation
use super::util::parse_rfc3339;
use super::*;
use crate::redact::REDACTED_HEADER_VALUE;
use serde_json::Value as JsonValue;
use std::collections::BTreeMap;
fn provider_metadata(provider: &str, schema_name: &str) -> ProviderMetadata {
    ProviderMetadata {
        provider: provider.to_string(),
        kinds: vec!["webhook".to_string()],
        schema_name: schema_name.to_string(),
        runtime: ProviderRuntimeMetadata::Placeholder,
        ..ProviderMetadata::default()
    }
}

fn sample_headers() -> BTreeMap<String, String> {
    BTreeMap::from([
        ("Authorization".to_string(), "Bearer secret".to_string()),
        ("Cookie".to_string(), "session=abc".to_string()),
        ("User-Agent".to_string(), "GitHub-Hookshot/123".to_string()),
        ("X-GitHub-Delivery".to_string(), "delivery-123".to_string()),
        ("X-GitHub-Event".to_string(), "issues".to_string()),
        ("X-Webhook-Token".to_string(), "token".to_string()),
    ])
}

#[test]
fn default_redaction_policy_keeps_safe_headers() {
    let redacted = redact_headers(&sample_headers(), &HeaderRedactionPolicy::default());
    assert_eq!(redacted.get("User-Agent").unwrap(), "GitHub-Hookshot/123");
    assert_eq!(redacted.get("X-GitHub-Delivery").unwrap(), "delivery-123");
    assert_eq!(
        redacted.get("Authorization").unwrap(),
        REDACTED_HEADER_VALUE
    );
    assert_eq!(redacted.get("Cookie").unwrap(), REDACTED_HEADER_VALUE);
    assert_eq!(
        redacted.get("X-Webhook-Token").unwrap(),
        REDACTED_HEADER_VALUE
    );
}

#[test]
fn provider_catalog_rejects_duplicates() {
    let mut catalog = ProviderCatalog::default();
    catalog
        .register(provider_metadata("github", "GitHubEventPayload"))
        .unwrap();
    let error = catalog
        .register(provider_metadata("github", "GitHubEventPayload"))
        .unwrap_err();
    assert_eq!(
        error,
        ProviderCatalogError::DuplicateProvider("github".to_string())
    );
}

#[test]
fn merging_contributions_preserves_each_package() {
    let mut catalog = ProviderCatalog::with_defaults();
    catalog
        .merge(vec![provider_metadata("runtime-a", "RuntimeAPayload")])
        .unwrap();
    catalog
        .merge(vec![provider_metadata("runtime-b", "RuntimeBPayload")])
        .unwrap();

    assert!(catalog.metadata_for("runtime-a").is_some());
    assert!(catalog.metadata_for("runtime-b").is_some());
    assert!(catalog.metadata_for("github").is_none());
}

#[test]
fn reloading_the_same_package_is_idempotent() {
    let mut catalog = ProviderCatalog::with_defaults();
    let providers = || vec![provider_metadata("runtime-a", "RuntimeAPayload")];
    catalog.merge(providers()).unwrap();
    catalog.merge(providers()).expect("reloading is idempotent");
}

#[test]
fn same_schema_name_with_different_metadata_is_a_conflict() {
    let mut catalog = ProviderCatalog::with_defaults();
    catalog
        .merge(vec![provider_metadata("runtime-a", "RuntimeAPayload")])
        .unwrap();
    let mut changed = provider_metadata("runtime-a", "RuntimeAPayload");
    changed.kinds = vec!["poll".to_string()];

    assert_eq!(
        catalog.merge(vec![changed]).unwrap_err(),
        ProviderCatalogError::DuplicateProvider("runtime-a".to_string())
    );
    assert_eq!(
        catalog.metadata_for("runtime-a").unwrap().kinds,
        vec!["webhook".to_string()]
    );
}

#[test]
fn failed_merge_keeps_the_original_catalog() {
    let mut catalog = ProviderCatalog::with_defaults();
    let original = catalog.entries();
    let error = catalog
        .merge(vec![
            provider_metadata("runtime-a", "RuntimeAPayload"),
            provider_metadata("webhook", "PackageWebhookPayload"),
        ])
        .unwrap_err();

    assert_eq!(
        error,
        ProviderCatalogError::DuplicateProvider("webhook".to_string())
    );
    assert_eq!(catalog.entries(), original);
    assert!(catalog.metadata_for("runtime-a").is_none());
}

#[test]
fn conflicting_package_schema_does_not_displace_owner() {
    let mut catalog = ProviderCatalog::with_defaults();
    catalog
        .merge(vec![provider_metadata("runtime-a", "RuntimeAPayload")])
        .unwrap();
    let error = catalog
        .merge(vec![provider_metadata("runtime-a", "OtherPayload")])
        .unwrap_err();

    assert_eq!(
        error,
        ProviderCatalogError::DuplicateProvider("runtime-a".to_string())
    );
    assert_eq!(
        catalog.metadata_for("runtime-a").unwrap().schema_name,
        "RuntimeAPayload"
    );
}

#[test]
fn package_cannot_displace_a_core_provider() {
    let mut catalog = ProviderCatalog::with_defaults();
    let error = catalog
        .merge(vec![provider_metadata("webhook", "PackageWebhookPayload")])
        .unwrap_err();

    assert_eq!(
        error,
        ProviderCatalogError::DuplicateProvider("webhook".to_string())
    );
    assert_eq!(
        catalog.metadata_for("webhook").unwrap().schema_name,
        "GenericWebhookPayload"
    );
}

#[test]
fn invalid_registration_is_rejected_before_install() {
    let mut catalog = ProviderCatalog::default();
    let error = catalog
        .register(provider_metadata(" ", "RuntimeAPayload"))
        .unwrap_err();
    assert!(matches!(error, ProviderCatalogError::InvalidMetadata(_)));

    let mut spoofed = provider_metadata("package-provider", "PackagePayload");
    spoofed.runtime = ProviderRuntimeMetadata::Builtin {
        connector: "webhook".into(),
        default_signature_variant: None,
    };
    let error = catalog.register(spoofed).unwrap_err();
    assert!(matches!(error, ProviderCatalogError::InvalidMetadata(_)));
    assert!(catalog.entries().is_empty());
}

#[test]
fn extension_normalization_uses_the_registered_identity() {
    let mut catalog = ProviderCatalog::default();
    catalog
        .register(provider_metadata("runtime-a", "RuntimeAPayload"))
        .unwrap();

    let raw = serde_json::json!({"id": 1});
    let payload = catalog
        .normalize(
            &ProviderId::from("runtime-a"),
            "webhook",
            &BTreeMap::new(),
            raw.clone(),
        )
        .unwrap();
    assert_eq!(
        payload,
        ProviderPayload::Extension(ExtensionProviderPayload {
            provider: "runtime-a".into(),
            schema_name: "RuntimeAPayload".into(),
            raw,
        })
    );
    assert_eq!(
        catalog
            .normalize(
                &ProviderId::from("runtime-b"),
                "webhook",
                &BTreeMap::new(),
                JsonValue::Null,
            )
            .unwrap_err(),
        ProviderCatalogError::UnknownProvider("runtime-b".into())
    );
}

#[test]
fn default_catalog_contains_only_core_provider_schemas() {
    let catalog = ProviderCatalog::with_defaults();
    let entries = catalog.entries();
    assert_eq!(entries.len(), 9);
    for entry in &entries {
        let payload = catalog
            .normalize(
                &ProviderId::from(entry.provider.as_str()),
                "test",
                &BTreeMap::new(),
                JsonValue::Null,
            )
            .unwrap();
        assert_eq!(payload.provider(), entry.provider);
    }
    for provider in ["github", "linear", "slack"] {
        assert!(
            entries.iter().all(|entry| entry.provider != provider),
            "{provider} must be registered only by its Harn package"
        );
    }
    for provider in ["a2a-push", "cron", "webhook"] {
        assert!(entries.iter().any(|entry| entry.provider == provider));
    }
    let kafka = entries
        .iter()
        .find(|entry| entry.provider == "kafka")
        .expect("kafka stream provider");
    assert_eq!(kafka.kinds, vec!["stream".to_string()]);
    assert_eq!(kafka.schema_name, "StreamEventPayload");
}

#[test]
fn extension_trigger_event_round_trip_is_stable() {
    let provider = ProviderId::from("github");
    let event = TriggerEvent {
        id: TriggerEventId("trigger_evt_fixed".to_string()),
        provider: provider.clone(),
        kind: "issues".to_string(),
        received_at: parse_rfc3339("2026-04-19T07:00:00Z").unwrap(),
        occurred_at: Some(parse_rfc3339("2026-04-19T06:59:59Z").unwrap()),
        dedupe_key: "delivery-123".to_string(),
        trace_id: TraceId("trace_fixed".to_string()),
        tenant_id: Some(TenantId("tenant_1".to_string())),
        headers: redact_headers(&sample_headers(), &HeaderRedactionPolicy::default()),
        provider_payload: ProviderPayload::Extension(ExtensionProviderPayload {
            provider: provider.as_str().to_string(),
            schema_name: "GitHubEventPayload".to_string(),
            raw: serde_json::json!({
                "action": "opened",
                "installation": {"id": 42},
                "issue": {"number": 99}
            }),
        }),
        signature_status: SignatureStatus::Verified,
        dedupe_claimed: false,
        batch: None,
        raw_body: Some(vec![0, 159, 255, 10]),
    };

    let once = serde_json::to_value(&event).unwrap();
    assert_eq!(once["raw_body"], serde_json::json!("AJ//Cg=="));
    let decoded: TriggerEvent = serde_json::from_value(once.clone()).unwrap();
    assert_eq!(serde_json::to_value(&decoded).unwrap(), once);
    assert_eq!(decoded, event);
}