spikard-cli 0.15.4

Command-line interface for building and validating Spikard applications
Documentation
use spikard_cli::codegen::{CodegenEngine, CodegenRequest, CodegenTargetKind, SchemaKind, TargetLanguage};
use std::path::PathBuf;
use tempfile::tempdir;

fn workspace_root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .and_then(|p| p.parent())
        .expect("workspace root")
        .to_path_buf()
}

#[test]
fn codegen_engine_rejects_unsupported_schema_target_combinations() {
    let tmp = tempdir().unwrap();
    let schema_path = tmp.path().join("schema.json");
    std::fs::write(&schema_path, "{}").unwrap();

    let request = CodegenRequest {
        schema_path,
        schema_kind: SchemaKind::OpenRpc,
        target: CodegenTargetKind::AsyncFixtures {
            output: tmp.path().to_path_buf(),
        },
        dto: None,
    };

    let err = CodegenEngine::execute(request).unwrap_err().to_string();
    assert!(err.contains("Unsupported schema/target combination"));
}

#[test]
fn codegen_engine_generates_php_asyncapi_test_apps() {
    let output_dir = tempdir().unwrap();
    let out_file = output_dir.path().join("chat-service-asyncapi.php");

    let request = CodegenRequest {
        schema_path: workspace_root().join("testing_data/schemas/chat-service.asyncapi.yaml"),
        schema_kind: SchemaKind::AsyncApi,
        target: CodegenTargetKind::AsyncTestApp {
            language: TargetLanguage::Php,
            output: out_file,
        },
        dto: None,
    };

    let outcome = CodegenEngine::execute(request).expect("engine run");
    let asset = match outcome {
        spikard_cli::codegen::CodegenOutcome::Files(mut files) => files.pop().expect("asset"),
        other @ spikard_cli::codegen::CodegenOutcome::InMemory(_) => panic!("expected files, got {other:?}"),
    };

    assert!(asset.path.exists(), "missing asset {}", asset.path.display());
    let contents = std::fs::read_to_string(&asset.path).expect("read generated app");
    assert!(contents.contains("Testing WebSocket endpoints"));
    assert!(contents.contains("loadFixture"));
}

#[test]
fn codegen_engine_generates_rust_asyncapi_test_apps() {
    let output_dir = tempdir().unwrap();
    let out_file = output_dir.path().join("chat-service-asyncapi.rs");

    let request = CodegenRequest {
        schema_path: workspace_root().join("testing_data/schemas/chat-service.asyncapi.yaml"),
        schema_kind: SchemaKind::AsyncApi,
        target: CodegenTargetKind::AsyncTestApp {
            language: TargetLanguage::Rust,
            output: out_file,
        },
        dto: None,
    };

    let outcome = CodegenEngine::execute_validated(request).expect("engine run");
    let asset = match outcome {
        spikard_cli::codegen::CodegenOutcome::Files(mut files) => files.pop().expect("asset"),
        other @ spikard_cli::codegen::CodegenOutcome::InMemory(_) => panic!("expected files, got {other:?}"),
    };

    assert!(asset.path.exists(), "missing asset {}", asset.path.display());
    let contents = std::fs::read_to_string(&asset.path).expect("read generated app");
    assert!(contents.contains("tokio::main"));
    assert!(contents.contains("Connecting to"));
}

#[test]
fn codegen_engine_generates_elixir_asyncapi_test_apps() {
    let output_dir = tempdir().unwrap();
    let out_file = output_dir.path().join("chat-service-asyncapi.ex");

    let request = CodegenRequest {
        schema_path: workspace_root().join("testing_data/schemas/chat-service.asyncapi.yaml"),
        schema_kind: SchemaKind::AsyncApi,
        target: CodegenTargetKind::AsyncTestApp {
            language: TargetLanguage::Elixir,
            output: out_file,
        },
        dto: None,
    };

    let outcome = CodegenEngine::execute_validated(request).expect("engine run");
    let asset = match outcome {
        spikard_cli::codegen::CodegenOutcome::Files(mut files) => files.pop().expect("asset"),
        other @ spikard_cli::codegen::CodegenOutcome::InMemory(_) => panic!("expected files, got {other:?}"),
    };

    assert!(asset.path.exists(), "missing asset {}", asset.path.display());
    let contents = std::fs::read_to_string(&asset.path).expect("read generated app");
    assert!(contents.contains("defmodule AsyncApiTestClient do"));
    assert!(contents.contains("AsyncApiFixtures.websocket_fixtures()"));
}

#[test]
fn codegen_engine_asyncapi_all_writes_fixtures_and_apps() {
    let out_dir = tempdir().unwrap();

    let request = CodegenRequest {
        schema_path: workspace_root().join("testing_data/schemas/chat-service.asyncapi.yaml"),
        schema_kind: SchemaKind::AsyncApi,
        target: CodegenTargetKind::AsyncAll {
            output: out_dir.path().to_path_buf(),
        },
        dto: None,
    };

    let outcome = CodegenEngine::execute(request).expect("engine run");
    let assets = match outcome {
        spikard_cli::codegen::CodegenOutcome::Files(files) => files,
        other @ spikard_cli::codegen::CodegenOutcome::InMemory(_) => panic!("expected files, got {other:?}"),
    };

    assert!(assets.iter().any(|a| a.description.contains("fixture")));
    assert!(assets.iter().any(|a| a.description.contains("AsyncAPI test app")));
    assert!(
        assets
            .iter()
            .any(|a| a.description.contains("AsyncAPI test app") && a.path.extension().is_some_and(|ext| ext == "php"))
    );
    assert!(
        assets
            .iter()
            .any(|a| a.description.contains("AsyncAPI test app") && a.path.extension().is_some_and(|ext| ext == "rs"))
    );
    assert!(
        assets
            .iter()
            .any(|a| a.description.contains("AsyncAPI test app") && a.path.extension().is_some_and(|ext| ext == "ex"))
    );

    for asset in assets {
        assert!(asset.path.exists(), "missing asset {}", asset.path.display());
    }
}