ralph-agent-loop 0.4.0

A Rust CLI for managing AI agent loops with a structured JSON task queue
Documentation
//! Parser-focused queue import tests.

use super::super::parse::{
    parse_csv_tasks, parse_custom_fields, parse_json_tasks, parse_list_field, parse_status,
};
use crate::contracts::{TaskPriority, TaskStatus};

#[test]
fn parse_json_array_succeeds() {
    let json = r#"[{"id": "RQ-0001", "title": "Test task", "status": "todo"}]"#;
    let tasks = parse_json_tasks(json).unwrap();
    assert_eq!(tasks.len(), 1);
    assert_eq!(tasks[0].id, "RQ-0001");
    assert_eq!(tasks[0].title, "Test task");
}

#[test]
fn parse_json_wrapper_succeeds() {
    let json = r#"{"version": 1, "tasks": [{"id": "RQ-0001", "title": "Test"}]}"#;
    let tasks = parse_json_tasks(json).unwrap();
    assert_eq!(tasks.len(), 1);
    assert_eq!(tasks[0].id, "RQ-0001");
}

#[test]
fn parse_json_wrapper_wrong_version_fails() {
    let json = r#"{"version": 2, "tasks": [{"id": "RQ-0001", "title": "Test"}]}"#;
    let result = parse_json_tasks(json);
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("version"));
}

#[test]
fn parse_json_empty_input_returns_empty() {
    assert!(parse_json_tasks("").unwrap().is_empty());
    assert!(parse_json_tasks("   ").unwrap().is_empty());
}

#[test]
fn parse_csv_basic_succeeds() {
    let csv = "id,title,status\nRQ-0001,Test task,todo\nRQ-0002,Another task,done";
    let tasks = parse_csv_tasks(csv, b',').unwrap();
    assert_eq!(tasks.len(), 2);
    assert_eq!(tasks[0].status, TaskStatus::Todo);
    assert_eq!(tasks[1].status, TaskStatus::Done);
}

#[test]
fn parse_csv_missing_title_fails() {
    let csv = "id,status\nRQ-0001,todo";
    let result = parse_csv_tasks(csv, b',');
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("title"));
}

#[test]
fn parse_csv_empty_title_fails() {
    let csv = "id,title\nRQ-0001,";
    assert!(parse_csv_tasks(csv, b',').is_err());
}

#[test]
fn parse_csv_list_fields_parsed() {
    let csv = "title,tags,scope,evidence,plan,notes\nTest,a,b,c,d,e";
    let tasks = parse_csv_tasks(csv, b',').unwrap();
    assert_eq!(tasks[0].tags, vec!["a"]);
    assert_eq!(tasks[0].scope, vec!["b"]);
    assert_eq!(tasks[0].evidence, vec!["c"]);
    assert_eq!(tasks[0].plan, vec!["d"]);
    assert_eq!(tasks[0].notes, vec!["e"]);
}

#[test]
fn parse_csv_list_fields_drop_empty() {
    let csv = "title,evidence\nTest,a;;b;";
    let tasks = parse_csv_tasks(csv, b',').unwrap();
    assert_eq!(tasks[0].evidence, vec!["a", "b"]);
}

#[test]
fn parse_csv_semicolon_fields_parsed() {
    let csv = "title,evidence,plan,notes\nTest,a;b,c;d,e;f;";
    let tasks = parse_csv_tasks(csv, b',').unwrap();
    assert_eq!(tasks[0].evidence, vec!["a", "b"]);
    assert_eq!(tasks[0].plan, vec!["c", "d"]);
    assert_eq!(tasks[0].notes, vec!["e", "f"]);
}

#[test]
fn parse_csv_custom_fields_parsed() {
    let csv = "title,custom_fields\nTest,\"a=1,b=two\"";
    let tasks = parse_csv_tasks(csv, b',').unwrap();
    assert_eq!(tasks[0].custom_fields.get("a"), Some(&"1".to_string()));
    assert_eq!(tasks[0].custom_fields.get("b"), Some(&"two".to_string()));
}

#[test]
fn parse_csv_custom_fields_invalid_fails() {
    let csv = "title,custom_fields\nTest,invalid_no_equals";
    assert!(parse_csv_tasks(csv, b',').is_err());
}

#[test]
fn parse_csv_empty_custom_fields_ok() {
    let csv = "title,custom_fields\nTest,";
    let tasks = parse_csv_tasks(csv, b',').unwrap();
    assert!(tasks[0].custom_fields.is_empty());
}

#[test]
fn parse_csv_unknown_columns_ignored() {
    let csv = "id,title,unknown_col\nRQ-0001,Test,foo";
    let tasks = parse_csv_tasks(csv, b',').unwrap();
    assert_eq!(tasks[0].id, "RQ-0001");
    assert_eq!(tasks[0].title, "Test");
}

#[test]
fn parse_tsv_succeeds() {
    let tsv = "id\ttitle\tstatus\nRQ-0001\tTest\ttodo";
    let tasks = parse_csv_tasks(tsv, b'\t').unwrap();
    assert_eq!(tasks.len(), 1);
    assert_eq!(tasks[0].id, "RQ-0001");
}

#[test]
fn parse_list_field_handles_delimiters() {
    assert_eq!(parse_list_field("a, b, , c", ','), vec!["a", "b", "c"]);
    assert_eq!(parse_list_field("x; y; ; z", ';'), vec!["x", "y", "z"]);
}

#[test]
fn parse_custom_fields_parses_empty_string() {
    assert!(parse_custom_fields("").unwrap().is_empty());
}

#[test]
fn parse_status_case_insensitive() {
    assert_eq!(parse_status("TODO").unwrap(), TaskStatus::Todo);
    assert_eq!(parse_status("Done").unwrap(), TaskStatus::Done);
    assert_eq!(parse_status("Rejected").unwrap(), TaskStatus::Rejected);
}

#[test]
fn parse_csv_invalid_priority_uses_canonical_parser_error() {
    let csv = "title,priority\nTest,nope";
    let err = parse_csv_tasks(csv, b',').unwrap_err();
    let expected = "nope".parse::<TaskPriority>().unwrap_err().to_string();
    assert!(err.to_string().contains(&expected), "err was: {err}");
}