pi_agent_rust 0.6.0

Native AI coding agent CLI - Rust port of Pi Agent
use super::*;

const TEXT: &str = "let 😀 = call(value, flag)\r\n";

fn range() -> Range {
    hint_range(TEXT, None).unwrap()
}

fn hint() -> Value {
    json!({"position":{"line":0,"character":6},"label":[
        {"value":": ","tooltip":"prefix"},
        {"value":"bool","location":{"uri":"https://invalid.example/type","range":{
            "start":{"line":1,"character":0},"end":{"line":1,"character":4}
        }},"command":{"title":"must not execute","command":"unsafe.run","arguments":["private"]}}
    ],"kind":1,"paddingLeft":true,"tooltip":{"kind":"markdown","value":"Inferred type"},
    "data":{"opaque":[1,"x"]},"textEdits":[{"arbitrary":"never apply"}]})
}

#[test]
fn compound_labels_kinds_padding_tooltips_and_locations_are_retained() {
    let result = normalize(&hint(), TEXT, range(), 4, false).unwrap();
    assert_eq!(result["index"], 4);
    assert_eq!(result["kind"], "type");
    assert_eq!(result["label"], ": bool");
    assert_eq!(result["paddingLeft"], true);
    assert_eq!(result["paddingRight"], false);
    assert_eq!(result["resolved"], false);
    assert_eq!(
        result["labelParts"][1]["location"]["uri"],
        "https://invalid.example/type"
    );
    assert_eq!(result["tooltip"]["value"], "Inferred type");
}

#[test]
fn accepting_edits_and_commands_is_not_part_of_inspection_output() {
    let result = normalize(&hint(), TEXT, range(), 0, false).unwrap();
    assert_eq!(result["textEditsPresent"], true);
    assert_eq!(result["labelParts"][1]["commandPresent"], true);
    assert!(result.get("textEdits").is_none());
    assert!(result.get("data").is_none());
    assert!(result["labelParts"][1].get("command").is_none());
    assert!(!result.to_string().contains("private"));
}

#[test]
fn hint_positions_reject_surrogate_interiors_and_document_overflow() {
    for position in [
        json!({"line":0,"character":5}),
        json!({"line":0,"character":999}),
        json!({"line":2,"character":0}),
        json!({"line":0,"character":-1}),
    ] {
        let mut raw = hint();
        raw["position"] = position;
        assert!(normalize(&raw, TEXT, range(), 0, false).is_err());
    }
}

#[test]
fn zero_width_anchors_can_touch_either_selected_endpoint() {
    let mut raw = hint();
    let selection = Range {
        start: Position {
            line: 0,
            character: 6,
        },
        end: Position {
            line: 0,
            character: 10,
        },
    };
    for col in [6, 10] {
        raw["position"]["character"] = json!(col);
        normalize(&raw, TEXT, selection, 0, false).unwrap();
    }
    raw["position"]["character"] = json!(11);
    assert!(normalize(&raw, TEXT, selection, 0, false).is_err());
    raw["position"]["character"] = json!(6);
    normalize(
        &raw,
        TEXT,
        Range {
            start: selection.start,
            end: selection.start,
        },
        0,
        false,
    )
    .unwrap();
}

#[test]
fn whole_file_ranges_share_exact_cr_lf_crlf_and_unicode_mapping() {
    for (text, end) in [
        (
            "a\rb\r\n😀",
            Position {
                line: 2,
                character: 2,
            },
        ),
        (
            "",
            Position {
                line: 0,
                character: 0,
            },
        ),
        (
            "a\n",
            Position {
                line: 1,
                character: 0,
            },
        ),
    ] {
        assert_eq!(hint_range(text, None).unwrap().end, end);
    }
    assert!(
        hint_range(
            TEXT,
            Some(Range {
                start: Position {
                    line: 0,
                    character: 5
                },
                end: range().end
            })
        )
        .is_err()
    );
    assert!(
        hint_range(
            TEXT,
            Some(Range {
                start: range().end,
                end: range().start
            })
        )
        .is_err()
    );
}

#[test]
fn all_supported_kinds_and_string_labels_have_explicit_output() {
    for (kind, name) in [
        (Value::Null, "unspecified"),
        (json!(1), "type"),
        (json!(2), "parameter"),
    ] {
        let mut raw = hint();
        raw["label"] = json!("value:");
        raw["kind"] = kind;
        let result = normalize(&raw, TEXT, range(), 0, false).unwrap();
        assert_eq!(result["kind"], name);
        assert_eq!(result["labelParts"], json!([]));
    }
    for kind in [json!(0), json!(3), json!("1"), json!(-1), json!(1.5)] {
        let mut raw = hint();
        raw["kind"] = kind;
        assert!(normalize(&raw, TEXT, range(), 0, false).is_err());
    }
}

#[test]
fn empty_or_malformed_labels_are_rejected_instead_of_silently_omitted() {
    for label in [
        json!(""),
        json!([]),
        json!([{"value":""}]),
        json!(["text"]),
        json!(2),
    ] {
        let mut raw = hint();
        raw["label"] = label;
        assert!(normalize(&raw, TEXT, range(), 0, false).is_err());
    }
}

#[test]
fn label_parts_and_total_utf8_label_bytes_are_bounded() {
    for label in [
        json!("x".repeat(MAX_LABEL_BYTES + 1)),
        json!(vec![json!({"value":"x"}); MAX_LABEL_PARTS + 1]),
        json!([{"value":"😀".repeat(MAX_LABEL_BYTES/4)},{"value":"x"}]),
    ] {
        let mut raw = hint();
        raw["label"] = label;
        assert!(normalize(&raw, TEXT, range(), 0, false).is_err());
    }
    let mut raw = hint();
    raw["data"] = json!("x".repeat(MAX_HINT_BYTES));
    assert!(normalize(&raw, TEXT, range(), 0, false).is_err());
}

#[test]
fn malformed_padding_tooltips_locations_and_edit_containers_are_errors() {
    for (key, value) in [
        ("paddingLeft", json!(1)),
        ("paddingRight", json!("true")),
        ("tooltip", json!({"kind":"html","value":"x"})),
        ("textEdits", json!({})),
    ] {
        let mut raw = hint();
        raw[key] = value;
        assert!(normalize(&raw, TEXT, range(), 0, false).is_err());
    }
    for loc in [
        json!({"uri":"","range":range()}),
        json!({"uri":"x","range":{"start":{"line":2,"character":0},"end":{"line":1,"character":0}}}),
    ] {
        let mut raw = hint();
        raw["label"][1]["location"] = loc;
        assert!(normalize(&raw, TEXT, range(), 0, false).is_err());
    }
}

#[test]
fn resolve_identity_allows_only_negotiated_tooltips_and_locations() {
    let raw = hint();
    let mut resolved = raw.clone();
    resolved["tooltip"] = json!("new details");
    resolved["label"][0]["tooltip"] = json!("part details");
    resolved["label"][1]["location"] = Value::Null;
    assert_eq!(identity(&raw), identity(&resolved));
    assert_eq!(raw["tooltip"]["value"], "Inferred type");
    for (key, value) in [
        ("position", json!({"line":0,"character":7})),
        ("label", json!("substitute")),
        ("kind", json!(2)),
        ("paddingLeft", json!(false)),
        ("data", json!({"new":"identity"})),
        ("textEdits", json!([])),
        ("unknown", json!(1)),
    ] {
        let mut other = resolved.clone();
        other[key] = value;
        assert_ne!(identity(&raw), identity(&other), "{key}");
    }
    resolved["label"][1]["command"] = Value::Null;
    assert_ne!(identity(&raw), identity(&resolved));
}

#[test]
fn static_and_resolvable_provider_capabilities_are_distinguished() {
    for provider in [json!(true), json!({}), json!({"resolveProvider":false})] {
        assert!(!resolve_supported(&json!({"inlayHintProvider":provider})).unwrap());
    }
    assert!(resolve_supported(&json!({"inlayHintProvider":{"resolveProvider":true}})).unwrap());
    for provider in [
        Value::Null,
        json!(false),
        json!(1),
        json!({"resolveProvider":"true"}),
    ] {
        assert!(resolve_supported(&json!({"inlayHintProvider":provider})).is_err());
    }
}