rpi-cli 0.21.1

Terminal coding-agent CLI (the `rpi` binary) built on the rpi-* library crates
Documentation
use super::resource_lifecycle::{fixture_model_config, rejected, session_files};
use super::*;

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn corrupt_binding_metadata_is_rejected_without_registration_or_rewrite() {
    let fixture = Fixture::build().await;
    let temp = tempfile::tempdir().unwrap();
    let root = temp.path();
    fixture_model_config(root);
    let selection = json!({"version":1,"extensions":[fixture.library],"tools":{"mode":"none"}});
    put(root.join("exact.json"), selection.to_string());
    let mut host = Host::start(root, "exact.json", "corrupt-binding").await;
    let state = host.state().await;
    host.stop().await;
    let files = session_files(root);
    let (path, original) = files
        .iter()
        .find(|(p, _)| p.extension().is_some_and(|e| e == "jsonl"))
        .unwrap();
    let registration = std::fs::read(root.join("registrations.log")).unwrap();
    put(root.join("transition.json"), json!({"version":1,"operation_id":"test-corruption","expected":{"revision":state["binding"]["revision"],"fingerprint":state["binding"]["fingerprint"]},"target":selection}).to_string());
    fn replace(value: &mut Value, field: &str, damaged: &Value) -> usize {
        let mut count = 0;
        if value["key"] == "rpi.resource-selection.v1" && value.get("value").is_some() {
            value["value"][field] = damaged.clone();
            return 1;
        }
        match value {
            Value::Object(o) => {
                for v in o.values_mut() {
                    count += replace(v, field, damaged);
                }
            }
            Value::Array(a) => {
                for v in a {
                    count += replace(v, field, damaged);
                }
            }
            _ => {}
        }
        count
    }
    for (field, damaged) in [
        ("revision", json!("corrupt")),
        ("revision", json!(null)),
        ("revision", json!(0)),
        ("revision", json!(-1)),
        ("operations", json!([])),
        ("operations", json!(1)),
        ("operations", json!(null)),
        ("operationId", json!(false)),
        ("operations", json!({"test-corruption":[]})),
        (
            "operations",
            json!({"test-corruption":{"target":"bad","expected":[],"revision":2,"fingerprint":"bad"}}),
        ),
    ] {
        let mut count = 0;
        let text = String::from_utf8(original.clone())
            .unwrap()
            .lines()
            .map(|line| {
                let mut entry: Value = serde_json::from_str(line).unwrap();
                count += replace(&mut entry, field, &damaged);
                entry.to_string() + "\n"
            })
            .collect::<String>();
        assert!(count > 0, "binding fixture not located");
        put(path, text);
        for flag in ["--resource-config", "--resource-transition"] {
            let config = if flag == "--resource-config" {
                "exact.json"
            } else {
                "transition.json"
            };
            rejected(
                root,
                &[flag, config, "--session-id", "corrupt-binding"],
                "invalid persisted resource binding metadata",
            )
            .await;
        }
        assert_eq!(
            std::fs::read(root.join("registrations.log")).unwrap(),
            registration
        );
    }
    put(path, original);
    Host::start(root, "exact.json", "corrupt-binding")
        .await
        .stop()
        .await;
}