forge-guardrails 0.1.2

Foundation types for an LLM-agent workflow framework
Documentation
//! Integration tests for message tests.

use forge_guardrails::{Message, MessageMeta, MessageRole, MessageType, ToolCallInfo};
use indexmap::IndexMap;
use serde_json::Value;

#[test]
fn role_values() {
    assert_eq!(MessageRole::System.as_str(), "system");
    assert_eq!(MessageRole::User.as_str(), "user");
    assert_eq!(MessageRole::Assistant.as_str(), "assistant");
    assert_eq!(MessageRole::Tool.as_str(), "tool");
}

#[test]
fn role_display() {
    assert_eq!(format!("{}", MessageRole::System), "system");
    assert_eq!(format!("{}", MessageRole::User), "user");
}

#[test]
fn role_serde() {
    let json = serde_json::to_string(&MessageRole::Assistant).expect("serialize");
    assert_eq!(json, "\"assistant\"");
}

#[test]
fn type_values() {
    assert_eq!(MessageType::SystemPrompt.as_str(), "system_prompt");
    assert_eq!(MessageType::UserInput.as_str(), "user_input");
    assert_eq!(MessageType::ToolCall.as_str(), "tool_call");
    assert_eq!(MessageType::ToolResult.as_str(), "tool_result");
    assert_eq!(MessageType::Reasoning.as_str(), "reasoning");
    assert_eq!(MessageType::TextResponse.as_str(), "text_response");
    assert_eq!(MessageType::StepNudge.as_str(), "step_nudge");
    assert_eq!(
        MessageType::PrerequisiteNudge.as_str(),
        "prerequisite_nudge"
    );
    assert_eq!(MessageType::RetryNudge.as_str(), "retry_nudge");
    assert_eq!(MessageType::ContextWarning.as_str(), "context_warning");
    assert_eq!(MessageType::Summary.as_str(), "summary");
}

#[test]
fn type_display() {
    assert_eq!(format!("{}", MessageType::ToolCall), "tool_call");
}

#[test]
fn meta_defaults() {
    let meta = MessageMeta::new(MessageType::UserInput);
    assert_eq!(meta.msg_type, MessageType::UserInput);
    assert!(meta.step_index.is_none());
    assert!(meta.original_type.is_none());
    assert!(meta.token_estimate.is_none());
}

#[test]
fn meta_with_all_fields() {
    let meta = MessageMeta::new(MessageType::UserInput)
        .with_step_index(3)
        .with_original_type(MessageType::StepNudge)
        .with_token_estimate(100);
    assert_eq!(meta.step_index, Some(3));
    assert_eq!(meta.original_type, Some(MessageType::StepNudge));
    assert_eq!(meta.token_estimate, Some(100));
}

#[test]
fn plain_message_ollama() {
    let msg = Message::new(
        MessageRole::User,
        "hello",
        MessageMeta::new(MessageType::UserInput),
    );
    let out = msg.serialize("ollama");
    let obj = out.as_object().expect("expected object");
    assert_eq!(obj.len(), 2);
    assert_eq!(obj["role"], "user");
    assert_eq!(obj["content"], "hello");
}

#[test]
fn plain_message_openai() {
    let msg = Message::new(
        MessageRole::User,
        "hello",
        MessageMeta::new(MessageType::UserInput),
    );
    let out = msg.serialize("openai");
    let obj = out.as_object().expect("expected object");
    assert_eq!(obj.len(), 2);
    assert_eq!(obj["role"], "user");
    assert_eq!(obj["content"], "hello");
}

#[test]
fn tool_call_ollama_format() {
    let mut args = IndexMap::new();
    args.insert("q".to_string(), Value::String("test".to_string()));
    let tc = ToolCallInfo::new("search", Some(args), "call-123");
    let msg = Message::new(
        MessageRole::Assistant,
        "",
        MessageMeta::new(MessageType::ToolCall),
    )
    .with_tool_calls(vec![tc]);
    let out = msg.serialize("ollama");
    let obj = out.as_object().expect("expected object");
    let tool_calls = obj["tool_calls"].as_array().expect("expected array");
    assert_eq!(tool_calls.len(), 1);
    let entry = tool_calls[0].as_object().expect("expected object");
    assert!(!entry.contains_key("id"));
    assert!(!entry.contains_key("type"));
    let func = entry["function"].as_object().expect("expected object");
    assert_eq!(func["name"], "search");
    assert!(func["arguments"].is_object());
}

#[test]
fn tool_call_openai_format() {
    let mut args = IndexMap::new();
    args.insert("q".to_string(), Value::String("test".to_string()));
    let tc = ToolCallInfo::new("search", Some(args), "call-456");
    let msg = Message::new(
        MessageRole::Assistant,
        "",
        MessageMeta::new(MessageType::ToolCall),
    )
    .with_tool_calls(vec![tc]);
    let out = msg.serialize("openai");
    let obj = out.as_object().expect("expected object");
    let tool_calls = obj["tool_calls"].as_array().expect("expected array");
    let entry = tool_calls[0].as_object().expect("expected object");
    assert_eq!(entry["id"], "call-456");
    assert_eq!(entry["type"], "function");
    let func = entry["function"].as_object().expect("expected object");
    assert_eq!(func["name"], "search");
    assert!(func["arguments"].is_string());
    let args_str = func["arguments"].as_str().expect("expected string");
    let parsed: Value = serde_json::from_str(args_str).expect("valid json");
    assert_eq!(parsed["q"], "test");
}

#[test]
fn tool_result_ollama() {
    let msg = Message::new(
        MessageRole::Tool,
        "result data",
        MessageMeta::new(MessageType::ToolResult),
    )
    .with_tool_name("search")
    .with_tool_call_id("call-789");
    let out = msg.serialize("ollama");
    let obj = out.as_object().expect("expected object");
    assert_eq!(obj["role"], "tool");
    assert!(obj.contains_key("tool_name"));
    assert_eq!(obj["tool_name"], "search");
    assert!(!obj.contains_key("name"));
    assert!(!obj.contains_key("tool_call_id"));
}

#[test]
fn tool_result_openai_with_call_id() {
    let msg = Message::new(
        MessageRole::Tool,
        "result data",
        MessageMeta::new(MessageType::ToolResult),
    )
    .with_tool_name("search")
    .with_tool_call_id("call-789");
    let out = msg.serialize("openai");
    let obj = out.as_object().expect("expected object");
    assert_eq!(obj["role"], "tool");
    assert!(obj.contains_key("name"));
    assert_eq!(obj["name"], "search");
    assert!(!obj.contains_key("tool_name"));
    assert!(obj.contains_key("tool_call_id"));
    assert_eq!(obj["tool_call_id"], "call-789");
}

#[test]
fn tool_result_openai_no_call_id() {
    let msg = Message::new(
        MessageRole::Tool,
        "result",
        MessageMeta::new(MessageType::ToolResult),
    )
    .with_tool_name("search");
    let out = msg.serialize("openai");
    let obj = out.as_object().expect("expected object");
    assert!(obj.contains_key("name"));
    assert!(!obj.contains_key("tool_call_id"));
}

#[test]
fn multiple_tool_calls_preserve_order() {
    let tc1 = ToolCallInfo::new("alpha", None, "c1");
    let tc2 = ToolCallInfo::new("beta", None, "c2");
    let msg = Message::new(
        MessageRole::Assistant,
        "",
        MessageMeta::new(MessageType::ToolCall),
    )
    .with_tool_calls(vec![tc1, tc2]);

    for fmt in &["ollama", "openai"] {
        let out = msg.serialize(fmt);
        let tool_calls = out["tool_calls"].as_array().expect("expected array");
        assert_eq!(tool_calls.len(), 2);
    }
}

#[test]
fn null_args_default_to_empty_map() {
    let tc = ToolCallInfo::new("tool", None, "call-1");
    let msg = Message::new(
        MessageRole::Assistant,
        "",
        MessageMeta::new(MessageType::ToolCall),
    )
    .with_tool_calls(vec![tc]);

    let ollama = msg.serialize("ollama");
    let args = &ollama["tool_calls"][0]["function"]["arguments"];
    assert!(args.is_object());
    assert_eq!(args.as_object().map(|o| o.len()), Some(0));

    let openai = msg.serialize("openai");
    let args_str = openai["tool_calls"][0]["function"]["arguments"]
        .as_str()
        .expect("expected string");
    let parsed: Value = serde_json::from_str(args_str).expect("valid json");
    assert!(parsed.is_object());
    assert_eq!(parsed.as_object().map(|o| o.len()), Some(0));
}

#[test]
fn all_roles_round_trip() {
    for (role, expected) in [
        (MessageRole::System, "system"),
        (MessageRole::User, "user"),
        (MessageRole::Assistant, "assistant"),
        (MessageRole::Tool, "tool"),
    ] {
        let msg = Message::new(role, "test", MessageMeta::new(MessageType::UserInput));
        for fmt in &["ollama", "openai"] {
            let out = msg.serialize(fmt);
            assert_eq!(out["role"], expected);
        }
    }
}

#[test]
fn no_metadata_in_output() {
    let msg = Message::new(
        MessageRole::User,
        "test",
        MessageMeta::new(MessageType::UserInput).with_step_index(5),
    );
    for fmt in &["ollama", "openai"] {
        let out = msg.serialize(fmt);
        let obj = out.as_object().expect("expected object");
        assert!(!obj.contains_key("metadata"));
        assert!(!obj.contains_key("step_index"));
    }
}

#[test]
fn serialize_default_format_is_ollama() {
    let msg = Message::new(
        MessageRole::User,
        "test",
        MessageMeta::new(MessageType::UserInput),
    );
    let out = msg.serialize("ollama");
    assert_eq!(out["role"], "user");
    assert_eq!(out["content"], "test");
}