rho-coding-agent 1.19.2

A lightweight agent harness inspired by Pi
Documentation
use super::*;

#[test]
fn parse_request_trims_optional_fields() {
    let request = parse_request(json!({
        "title": "  Edit target  ",
        "reason": "  I need a target  ",
        "questions": [
            {
                "id": " file ",
                "question": "  Which file?  ",
                "header": "  File  ",
                "help": "  Use a repo-relative path  ",
                "type": "choice",
                "choices": ["src/main.rs", "src/lib.rs"],
                "default": "  src/main.rs  "
            }
        ]
    }))
    .unwrap();

    assert_eq!(
        request,
        QuestionnaireRequest {
            title: Some("Edit target".into()),
            reason: Some("I need a target".into()),
            questions: vec![QuestionnaireQuestion {
                id: "file".into(),
                question: "Which file?".into(),
                header: Some("File".into()),
                help: Some("Use a repo-relative path".into()),
                default: Some(json!("src/main.rs")),
                default_selection: QuestionnaireDefaultSelection::Selected,
                kind: QuestionnaireQuestionKind::Choice,
                required: true,
                choices: vec!["src/main.rs".into(), "src/lib.rs".into()],
                allow_other: false,
            }],
        }
    );
}

#[test]
fn parse_request_accepts_choice_descriptions() {
    let request = parse_request(json!({
        "questions": [{
            "id": "mode",
            "question": "Which mode?",
            "type": "choice",
            "choices": [
                { "label": "Fast", "description": "Finish sooner with fewer checks" },
                "Safe"
            ],
            "default": "fast"
        }]
    }))
    .unwrap();

    assert_eq!(
        request.questions[0].choices,
        vec![
            QuestionnaireChoice {
                label: "Fast".into(),
                description: Some("Finish sooner with fewer checks".into()),
            },
            QuestionnaireChoice::from("Safe"),
        ]
    );
    assert_eq!(request.questions[0].default, Some(json!("Fast")));
}

#[test]
fn parse_request_trims_choice_descriptions() {
    let request = parse_request(json!({
        "questions": [{
            "question": "Which mode?",
            "type": "choice",
            "choices": [{ "label": " Fast ", "description": "  Quicker  " }]
        }]
    }))
    .unwrap();

    assert_eq!(request.questions[0].choices[0].label, "Fast");
    assert_eq!(
        request.questions[0].choices[0].description.as_deref(),
        Some("Quicker")
    );
}

#[test]
fn parse_request_accepts_legacy_single_question() {
    let request = parse_request(json!({
        "question": "  Which file?  ",
        "reason": "  I need a target  ",
        "default": "  src/main.rs  "
    }))
    .unwrap();

    assert_eq!(request.questions.len(), 1);
    assert_eq!(request.questions[0].id, "q1");
    assert_eq!(request.questions[0].question, "Which file?");
    assert_eq!(request.questions[0].default, Some(json!("src/main.rs")));
}

#[test]
fn parse_request_normalizes_multi_select_defaults_and_other() {
    let request = parse_request(json!({
        "questions": [
            {
                "id": "suites",
                "question": "Which suites?",
                "type": "multi_select",
                "choices": ["unit", "e2e"],
                "allow_other": true,
                "default": ["Unit", "smoke"]
            }
        ]
    }))
    .unwrap();

    assert_eq!(
        request.questions[0].kind,
        QuestionnaireQuestionKind::MultiSelect
    );
    assert!(request.questions[0].allow_other);
    assert_eq!(request.questions[0].default, Some(json!(["unit", "smoke"])));
}

#[test]
fn parse_request_rejects_empty_questions() {
    let err = parse_request(json!({ "questions": [] })).unwrap_err();

    assert_eq!(err, "questions must include at least one question");
}

#[test]
fn parse_request_rejects_text_questions_in_forms() {
    let err = parse_request(json!({
        "questions": [
            {
                "id": "freeform",
                "question": "What should I do?"
            }
        ]
    }))
    .unwrap_err();

    assert_eq!(
        err,
        "questions[0] must use choice, multi_select, or confirm"
    );
}

#[test]
fn parse_request_normalizes_choice_and_confirm_defaults() {
    let request = parse_request(json!({
        "questions": [
            {
                "id": "style",
                "question": "Style?",
                "type": "choice",
                "choices": ["brief", "detailed"],
                "default": "Detailed"
            },
            {
                "id": "apply",
                "question": "Apply changes?",
                "type": "confirm",
                "default": true
            }
        ]
    }))
    .unwrap();

    assert_eq!(request.questions[0].kind, QuestionnaireQuestionKind::Choice);
    assert_eq!(request.questions[0].default, Some(json!("detailed")));
    assert_eq!(
        request.questions[1].kind,
        QuestionnaireQuestionKind::Confirm
    );
    assert_eq!(request.questions[1].default, Some(json!("yes")));
}

#[test]
fn questionnaire_question_serializes_type_field() {
    let question = QuestionnaireQuestion {
        id: "apply".into(),
        question: "Apply changes?".into(),
        header: None,
        help: None,
        default: Some(json!("no")),
        default_selection: QuestionnaireDefaultSelection::Selected,
        kind: QuestionnaireQuestionKind::Confirm,
        required: true,
        choices: Vec::new(),
        allow_other: false,
    };

    let value = serde_json::to_value(&question).unwrap();

    assert_eq!(value.get("type"), Some(&json!("confirm")));
    assert!(value.get("kind").is_none());
    let round_tripped: QuestionnaireQuestion = serde_json::from_value(value).unwrap();
    assert_eq!(round_tripped.kind, QuestionnaireQuestionKind::Confirm);
}

#[test]
fn questionnaire_question_deserializes_legacy_and_detailed_choices() {
    let question: QuestionnaireQuestion = serde_json::from_value(json!({
        "id": "mode",
        "question": "Which mode?",
        "type": "choice",
        "choices": [
            "Fast",
            { "label": "Safe", "description": "Run every check" }
        ]
    }))
    .unwrap();

    assert_eq!(
        question.choices,
        vec![
            QuestionnaireChoice::from("Fast"),
            QuestionnaireChoice {
                label: "Safe".into(),
                description: Some("Run every check".into()),
            },
        ]
    );
}

#[test]
fn parse_request_accepts_focused_default_selection() {
    let request = parse_request(json!({
        "questions": [{
            "id": "prompt",
            "question": "Prompt mode?",
            "type": "choice",
            "choices": [
                { "label": "extend", "description": "Keep the standard prompt" },
                "replace"
            ],
            "default": "extend",
            "default_selection": "focused"
        }]
    }))
    .unwrap();

    assert_eq!(request.questions[0].default, Some(json!("extend")));
    assert_eq!(
        request.questions[0].default_selection,
        QuestionnaireDefaultSelection::Focused
    );
    assert_eq!(
        request.questions[0].choices,
        vec![
            QuestionnaireChoice {
                label: "extend".into(),
                description: Some("Keep the standard prompt".into()),
            },
            QuestionnaireChoice::from("replace"),
        ]
    );
}

#[test]
fn parse_request_rejects_focused_default_selection_without_default() {
    let err = parse_request(json!({
        "questions": [{
            "id": "prompt",
            "question": "Prompt mode?",
            "type": "choice",
            "choices": ["extend", "replace"],
            "default_selection": "focused"
        }]
    }))
    .unwrap_err();

    assert_eq!(
        err,
        "questions[0].default_selection focused requires default"
    );
}

#[test]
fn parse_request_accepts_kind_alias() {
    let request = parse_request(json!({
        "questions": [
            {
                "id": "apply",
                "question": "Apply changes?",
                "kind": "confirm"
            }
        ]
    }))
    .unwrap();

    assert_eq!(
        request.questions[0].kind,
        QuestionnaireQuestionKind::Confirm
    );
}