alef 0.26.0

Opinionated polyglot binding generator for Rust libraries
Documentation
//! Verifies the Elixir e2e codegen emits complete plugin trait-bridge stubs with
//! super-trait methods and generic numeric defaults.

use alef::core::config::TraitBridgeConfig;
use alef::core::ir::{MethodDef, PrimitiveType, ReceiverKind, TypeRef};
use alef::e2e::codegen::elixir::emit_test_backend;
use alef::e2e::fixture::Fixture;
use serde_json::json;

fn make_trait_bridge(trait_name: &str, super_trait: Option<&str>) -> TraitBridgeConfig {
    TraitBridgeConfig {
        trait_name: trait_name.to_string(),
        super_trait: super_trait.map(|s| s.to_string()),
        register_fn: Some(format!("register_{}", trait_name.to_lowercase())),
        ..Default::default()
    }
}

fn make_method(name: &str, return_type: TypeRef, has_default: bool) -> MethodDef {
    MethodDef {
        name: name.to_string(),
        params: vec![],
        return_type,
        is_async: false,
        is_static: false,
        error_type: None,
        doc: String::new(),
        receiver: Some(ReceiverKind::Ref),
        sanitized: false,
        trait_source: None,
        returns_ref: false,
        returns_cow: false,
        return_newtype_wrapper: None,
        has_default_impl: has_default,
        binding_excluded: false,
        binding_exclusion_reason: None,
        version: Default::default(),
    }
}

fn make_fixture(id: &str, input: serde_json::Value) -> Fixture {
    Fixture {
        id: id.to_string(),
        category: None,
        description: "test".to_string(),
        tags: vec![],
        skip: None,
        env: None,
        setup: Vec::new(),
        call: None,
        input,
        mock_response: None,
        source: String::new(),
        http: None,
        assertions: vec![],
        visitor: None,
        args: vec![],
        assertion_recipes: Vec::new(),
    }
}

#[test]
fn elixir_stub_emits_super_trait_name_and_initialize() {
    let bridge = make_trait_bridge("DocumentExtractor", Some("Plugin"));
    let extract_method = make_method("extract_bytes", TypeRef::Named("ProcessingResult".to_string()), false);
    let methods = vec![&extract_method];
    let fixture = make_fixture("my_extractor", json!({ "extractor": { "name": "test-extractor" } }));

    let emission = emit_test_backend(&bridge, &methods, &fixture, "");
    let output = format!("{}\n{}", emission.setup_block, emission.arg_expr);

    // Should emit `name/0` from super-trait
    assert!(
        output.contains("def name, do: \"test-extractor\""),
        "must emit name() from super-trait, got:\n{output}"
    );

    // Should emit `initialize/0` unconditionally when super_trait is set
    assert!(
        output.contains("def initialize, do: :ok"),
        "must emit initialize() from super-trait, got:\n{output}"
    );

    // Should still emit the required method
    assert!(
        output.contains("def extract_bytes"),
        "must emit extract_bytes() required method, got:\n{output}"
    );
}

#[test]
fn elixir_stub_emits_integer_methods_as_one() {
    let bridge = make_trait_bridge("VectorBackend", Some("Plugin"));
    let dimensions_method = make_method("dimensions", TypeRef::Primitive(PrimitiveType::U32), false);
    let embed_method = make_method("embed", TypeRef::Named("Vec<Vec<f32>>".to_string()), false);
    let methods = vec![&dimensions_method, &embed_method];
    let fixture = make_fixture("my_backend", json!({ "backend": { "name": "test-vector-backend" } }));

    let emission = emit_test_backend(&bridge, &methods, &fixture, "");
    let output = format!("{}\n{}", emission.setup_block, emission.arg_expr);

    // Should emit `dimensions` returning 1 (not 0)
    assert!(
        output.contains("def dimensions, do: 1"),
        "must emit dimensions() returning 1, got:\n{output}"
    );

    // Should emit `initialize/0` from super-trait
    assert!(
        output.contains("def initialize, do: :ok"),
        "must emit initialize() from super-trait, got:\n{output}"
    );

    // Should emit other required methods
    assert!(
        output.contains("def embed"),
        "must emit embed() required method, got:\n{output}"
    );
}

#[test]
fn elixir_stub_emits_all_required_trait_methods() {
    let bridge = make_trait_bridge("ImageBackend", Some("Plugin"));
    let process_image = make_method("process_image", TypeRef::Named("OcrResult".to_string()), false);
    let supports_language = make_method("supports_language", TypeRef::Primitive(PrimitiveType::Bool), false);
    let backend_type = make_method("backend_type", TypeRef::Named("BackendType".to_string()), false);
    let methods = vec![&process_image, &supports_language, &backend_type];
    let fixture = make_fixture("my_image_backend", json!({ "backend": { "name": "test-backend" } }));

    let emission = emit_test_backend(&bridge, &methods, &fixture, "");
    let output = format!("{}\n{}", emission.setup_block, emission.arg_expr);

    // All three required methods should be present
    assert!(
        output.contains("def process_image"),
        "must emit process_image(), got:\n{output}"
    );
    assert!(
        output.contains("def supports_language"),
        "must emit supports_language(), got:\n{output}"
    );
    assert!(
        output.contains("def backend_type"),
        "must emit backend_type(), got:\n{output}"
    );

    // Super-trait methods should also be present
    assert!(output.contains("def name, do:"), "must emit name(), got:\n{output}");
    assert!(
        output.contains("def initialize, do:"),
        "must emit initialize(), got:\n{output}"
    );
}