jacs-binding-core 0.11.3

Shared core logic for JACS language bindings (Python, Node.js, etc.)
Documentation
//! Method enumeration parity test for `SimpleAgentWrapper`.
//!
//! Validates that `binding-core/tests/fixtures/method_parity.json` accurately
//! lists all public methods on `SimpleAgentWrapper`. If a method is added or
//! removed from the wrapper without updating the fixture, this test fails.
//!
//! This is a *structural* test (method names), not a *behavioral* test
//! (sign/verify roundtrips). It complements, not duplicates, `parity.rs`.

use serde_json::Value;

/// Hardcoded, sorted list of all public methods on `SimpleAgentWrapper`.
///
/// This list MUST be updated whenever a public method is added to or removed
/// from `binding-core/src/simple_wrapper.rs`. It serves as a compile-time
/// anchor so that the fixture and the implementation stay in sync.
fn known_methods() -> Vec<&'static str> {
    let mut methods = vec![
        // Constructors
        "create",
        "load",
        "load_with_info",
        "ephemeral",
        "create_with_params",
        "from_agent",
        // Identity / Introspection
        "get_agent_id",
        "key_id",
        "is_strict",
        "config_path",
        "export_agent",
        "get_public_key_pem",
        "get_public_key_base64",
        "diagnostics",
        "inner_ref",
        // Verification
        "verify_self",
        "verify_json",
        "verify_with_key_json",
        "verify_by_id_json",
        // Signing
        "sign_message_json",
        "sign_raw_bytes_base64",
        "sign_file_json",
        // Conversion
        "to_yaml",
        "from_yaml",
        "to_html",
        "from_html",
        // Key management
        "rotate_keys",
        // W3C AI Agent Protocol interop
        "export_w3c_did",
        "export_w3c_did_document_json",
        "export_w3c_agent_description_json",
        "generate_w3c_well_known_json",
        "sign_w3c_request_json",
        "verify_w3c_request_json",
        // Inline text + media (Task 05 + 06)
        "sign_text_file_json",
        "verify_text_file_json",
        "sign_image_json",
        "verify_image_json",
        "extract_media_signature_json",
    ];
    methods.sort();
    methods
}

#[cfg(feature = "agreements")]
fn known_agreement_v2_methods() -> Vec<&'static str> {
    let mut methods = vec![
        "create_agreement_v2_json",
        "apply_agreement_v2_json",
        "sign_agreement_v2_json",
        "verify_agreement_v2_json",
        "detect_agreement_v2_branch_conflict_json",
        "merge_agreement_v2_transcript_branches_json",
        "resolve_agreement_v2_branch_conflict_json",
    ];
    methods.sort();
    methods
}

fn load_method_parity_fixture() -> Value {
    let fixture_bytes = include_bytes!("fixtures/method_parity.json");
    serde_json::from_slice(fixture_bytes).expect("method_parity.json should be valid JSON")
}

#[test]
fn test_method_parity_fixture_matches_impl() {
    let fixture = load_method_parity_fixture();

    // Extract the flat sorted list from the fixture
    let fixture_methods: Vec<String> = fixture["all_methods_flat"]
        .as_array()
        .expect("all_methods_flat should be an array")
        .iter()
        .map(|v| {
            v.as_str()
                .expect("each method should be a string")
                .to_string()
        })
        .collect();

    let known = known_methods();
    let known_strings: Vec<String> = known.iter().map(|s| s.to_string()).collect();

    // Both lists should already be sorted
    assert_eq!(
        fixture_methods,
        known_strings,
        "\nFixture method list does not match known SimpleAgentWrapper methods.\n\
         \nFixture has {} methods, known list has {} methods.\n\
         \nIn fixture but not in known list: {:?}\n\
         In known list but not in fixture: {:?}\n\
         \nIf you added a method to SimpleAgentWrapper, update BOTH:\n\
         1. binding-core/tests/fixtures/method_parity.json\n\
         2. The known_methods() list in binding-core/tests/method_parity.rs",
        fixture_methods.len(),
        known_strings.len(),
        fixture_methods
            .iter()
            .filter(|m| !known_strings.contains(m))
            .collect::<Vec<_>>(),
        known_strings
            .iter()
            .filter(|m| !fixture_methods.contains(m))
            .collect::<Vec<_>>(),
    );
}

#[test]
fn test_method_parity_fixture_categories_cover_all() {
    let fixture = load_method_parity_fixture();
    let categories = fixture["simple_agent_wrapper_methods"]
        .as_object()
        .expect("simple_agent_wrapper_methods should be an object");

    // Collect all methods from categories
    let mut category_methods: Vec<String> = Vec::new();
    for (_category_name, methods) in categories {
        for method in methods.as_array().expect("category should be an array") {
            category_methods.push(
                method
                    .as_str()
                    .expect("method should be a string")
                    .to_string(),
            );
        }
    }
    category_methods.sort();

    // Compare against flat list
    let flat_methods: Vec<String> = fixture["all_methods_flat"]
        .as_array()
        .expect("all_methods_flat should be an array")
        .iter()
        .map(|v| {
            v.as_str()
                .expect("each method should be a string")
                .to_string()
        })
        .collect();

    assert_eq!(
        category_methods,
        flat_methods,
        "\nCategorized methods do not match all_methods_flat.\n\
         This means the fixture is internally inconsistent.\n\
         \nIn categories but not in flat: {:?}\n\
         In flat but not in categories: {:?}",
        category_methods
            .iter()
            .filter(|m| !flat_methods.contains(m))
            .collect::<Vec<_>>(),
        flat_methods
            .iter()
            .filter(|m| !category_methods.contains(m))
            .collect::<Vec<_>>(),
    );
}

#[test]
fn test_method_parity_fixture_count() {
    let fixture = load_method_parity_fixture();
    let flat_methods = fixture["all_methods_flat"]
        .as_array()
        .expect("all_methods_flat should be an array");

    assert_eq!(
        flat_methods.len(),
        38,
        "SimpleAgentWrapper should have exactly 38 public methods. \
         Found {}. If you added or removed a method, update the fixture.",
        flat_methods.len()
    );
}

#[cfg(feature = "agreements")]
#[test]
fn test_agreement_v2_feature_gated_methods_match_impl() {
    let fixture = load_method_parity_fixture();
    let mut fixture_methods: Vec<String> = fixture["feature_gated_methods"]["agreements"]
        .as_array()
        .expect("feature_gated_methods.agreements should be an array")
        .iter()
        .map(|v| {
            v.as_str()
                .expect("each agreement v2 method should be a string")
                .to_string()
        })
        .collect();
    fixture_methods.sort();

    let known = known_agreement_v2_methods();
    let known_strings: Vec<String> = known.iter().map(|s| s.to_string()).collect();

    assert_eq!(
        fixture_methods, known_strings,
        "\nAgreement v2 feature-gated methods do not match SimpleAgentWrapper.\n\
         If you added an agreement v2 binding-core method, update \
         binding-core/tests/fixtures/method_parity.json feature_gated_methods.agreements."
    );
}

#[cfg(feature = "agreements")]
#[test]
fn wrapper_exposes_create_agreement_v2_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::create_agreement_v2_json;
}

#[cfg(feature = "agreements")]
#[test]
fn wrapper_exposes_apply_agreement_v2_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::apply_agreement_v2_json;
}

#[cfg(feature = "agreements")]
#[test]
fn wrapper_exposes_sign_agreement_v2_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::sign_agreement_v2_json;
}

#[cfg(feature = "agreements")]
#[test]
fn wrapper_exposes_verify_agreement_v2_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::verify_agreement_v2_json;
}

#[cfg(feature = "agreements")]
#[test]
fn wrapper_exposes_detect_agreement_v2_branch_conflict_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
        &str,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::detect_agreement_v2_branch_conflict_json;
}

#[cfg(feature = "agreements")]
#[test]
fn wrapper_exposes_merge_agreement_v2_transcript_branches_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
        &str,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::merge_agreement_v2_transcript_branches_json;
}

#[cfg(feature = "agreements")]
#[test]
fn wrapper_exposes_resolve_agreement_v2_branch_conflict_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
        &str,
        &str,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::resolve_agreement_v2_branch_conflict_json;
}

// =========================================================================
// Reflection sanity checks for Task 05 + Task 06 wrapper methods.
//
// These are *compile-time anchors* that prove the new wrapper methods exist
// with the expected signatures. The flat fixture above and the
// `known_methods()` list MUST be updated by Task 07; until then those three
// snapshot tests above will fail by design — these reflection tests are the
// red-anchor for the implementation work in Task 05/06.
// =========================================================================

#[test]
fn wrapper_exposes_sign_text_file_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::sign_text_file_json;
}

#[test]
fn wrapper_exposes_verify_text_file_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::verify_text_file_json;
}

#[test]
fn wrapper_exposes_sign_image_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
        &str,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::sign_image_json;
}

#[test]
fn wrapper_exposes_verify_image_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::verify_image_json;
}

#[test]
fn wrapper_exposes_extract_media_signature_json() {
    let _: fn(
        &jacs_binding_core::SimpleAgentWrapper,
        &str,
        &str,
    ) -> jacs_binding_core::BindingResult<String> =
        jacs_binding_core::SimpleAgentWrapper::extract_media_signature_json;
}