robson-core 0.1.0

Rust async agent orchestrator for automated development workflows
Documentation
use robson_core::connect_and_migrate;
use robson_core::formatter::markdown_to_mrkdwn;
use robson_core::processor::{Processor, SanitizedInput};
use tracing_test::traced_test;

// ---- logging tests ----

#[traced_test]
#[test]
fn test_scheduler_log_format_is_capturable() {
    // Verify that the exact log messages emitted by Scheduler::run are capturable.
    // This tests the log format contract: if these assertions pass, any subscriber
    // configured the same way will capture the scheduler events.
    tracing::info!(interval_secs = 999u64, "scheduler started");
    tracing::info!("scheduler received shutdown signal — stopping");
    assert!(logs_contain("scheduler started"));
    assert!(logs_contain("scheduler received shutdown signal"));
}

#[traced_test]
#[test]
fn test_command_log_format_is_capturable() {
    // Verify that the exact log message emitted by the run-task no-state path is capturable.
    tracing::debug!("run-task PROJ-99: no state available — returning guidance");
    assert!(logs_contain("run-task PROJ-99"));
    assert!(logs_contain("no state available"));
}

// ---- formatter tests ----

#[test]
fn test_markdown_to_mrkdwn_bold() {
    assert_eq!(markdown_to_mrkdwn("**hello**"), "*hello*");
    assert_eq!(markdown_to_mrkdwn("**hello** world"), "*hello* world");
}

#[test]
fn test_markdown_to_mrkdwn_code_block() {
    let input = "```rust\nlet x = 1;\n```";
    let out = markdown_to_mrkdwn(input);
    assert!(out.contains("```"));
    assert!(out.contains("let x = 1;"));
}

#[test]
fn test_markdown_to_mrkdwn_italic() {
    assert_eq!(markdown_to_mrkdwn("__italic__"), "_italic_");
    // single underscore italic
    assert_eq!(markdown_to_mrkdwn("_italic_"), "_italic_");
}

#[test]
fn test_markdown_to_mrkdwn_unicode_preserved() {
    let input = "**こんにちは** مرحبا";
    let out = markdown_to_mrkdwn(input);
    assert!(out.contains("*こんにちは*"));
    assert!(out.contains("مرحبا"));
}

#[test]
fn test_markdown_to_mrkdwn_heading() {
    assert_eq!(markdown_to_mrkdwn("### Heading"), "*Heading*");
    assert_eq!(markdown_to_mrkdwn("## Heading"), "*Heading*");
    assert_eq!(markdown_to_mrkdwn("# Heading"), "*Heading*");
}

// ---- processor tests ----

#[tokio::test]
async fn test_processor_dispatch_message() {
    let db = connect_and_migrate(":memory:").await.unwrap();
    let processor = Processor::new(db);

    let input = SanitizedInput::Message {
        text: "hello, how are you?".to_string(),
        channel_id: "C123".to_string(),
        user_id: "U456".to_string(),
        thread_ts: Some("ts1".to_string()),
        ts: "ts2".to_string(),
    };

    let output = processor.dispatch(input).await.unwrap();
    assert!(!output.response_text.is_empty());
}

#[tokio::test]
async fn test_processor_dispatch_slash_text_as_message() {
    let db = connect_and_migrate(":memory:").await.unwrap();
    let processor = Processor::new(db);

    let input = SanitizedInput::Message {
        text: "/help".to_string(),
        channel_id: "C123".to_string(),
        user_id: "U456".to_string(),
        thread_ts: None,
        ts: "ts1".to_string(),
    };

    let output = processor.dispatch(input).await.unwrap();
    assert!(!output.response_text.is_empty());
}

#[tokio::test]
async fn test_processor_build_context_returns_last_n() {
    let db = connect_and_migrate(":memory:").await.unwrap();
    use robson_core::entities::conversation::{self, ConversationRole};

    for i in 0..5 {
        conversation::Model::insert(
            &db,
            None,
            "C123",
            "ts_thread",
            "U1",
            ConversationRole::User,
            &format!("message {}", i),
        )
        .await
        .unwrap();
        tokio::time::sleep(std::time::Duration::from_millis(5)).await;
    }

    let processor = Processor::new(db);
    let ctx = processor
        .build_context("C123", "ts_thread", 3)
        .await
        .unwrap();
    assert_eq!(ctx.len(), 3);
    assert!(ctx[2].content.contains("message 4"));
}