qq-cli 0.1.0

A modular Rust QQ task interface for coding agents
use qq_cli::messaging::attachments::{self, Kind};
use qq_cli::storage::state::Store;
use std::time::{Duration, Instant};

fn deadline() -> Instant {
    Instant::now() + Duration::from_secs(5)
}

#[test]
fn snapshots_preserve_bytes_and_stable_fingerprints_across_staging_paths() {
    let temp = tempfile::tempdir().unwrap();
    let source = temp.path().join("original.bin");
    std::fs::write(&source, b"synthetic attachment").unwrap();
    let store = Store::new(temp.path().join("state"));
    let first = attachments::prepare(&source, Kind::File, &store, deadline()).unwrap();
    let second = attachments::prepare(&source, Kind::File, &store, deadline()).unwrap();
    assert_ne!(first.path(), second.path());
    assert_eq!(first.fingerprint(), second.fingerprint());
    assert!(
        !serde_json::to_string(&first.fingerprint())
            .unwrap()
            .contains("state")
    );
    std::fs::write(&source, b"changed input").unwrap();
    assert_eq!(
        std::fs::read(first.path()).unwrap(),
        b"synthetic attachment"
    );
    let prepared = second.path().to_owned();
    drop(second);
    assert!(!prepared.exists());
    let refs =
        qq_cli::storage::references::References::with_key(temp.path().join("refs"), [85; 32]);
    let scope = qq_cli::core::domain::Scope {
        instance_id: "instance-example".into(),
        account: "10001".into(),
    };
    let retained = first
        .retain(&store, &refs, "attachment-intent", &scope, deadline())
        .unwrap();
    assert_eq!(
        std::fs::read(retained.path()).unwrap(),
        b"synthetic attachment"
    );
    let path = retained.path().to_owned();
    drop(retained);
    assert!(path.exists());
    let changed = attachments::prepare(&source, Kind::File, &store, deadline()).unwrap();
    assert_ne!(
        changed.fingerprint().sha256,
        format!("{:x}", sha2::Sha256::digest(b"synthetic attachment"))
    );
}

use sha2::Digest;
#[test]
fn invalid_attachment_inputs_fail_before_creating_staging_state() {
    let temp = tempfile::tempdir().unwrap();
    let store = Store::new(temp.path().join("state"));
    let empty = temp.path().join("empty.bin");
    std::fs::write(&empty, []).unwrap();
    for path in [
        empty,
        temp.path().join("missing.bin"),
        temp.path().to_owned(),
    ] {
        assert_eq!(
            attachments::prepare(&path, Kind::Image, &store, deadline())
                .err()
                .unwrap()
                .code,
            "ATTACHMENT_INPUT_INVALID"
        );
    }
    let source = temp.path().join("fixture.bin");
    std::fs::write(&source, b"fixture").unwrap();
    assert_eq!(
        attachments::prepare(&source, Kind::File, &store, Instant::now())
            .err()
            .unwrap()
            .code,
        "DEADLINE"
    );
    assert!(!store.root().exists());
}

#[test]
fn retained_attachments_recover_from_disk_only_with_matching_request_scope_and_bytes() {
    use qq_cli::core::domain::Scope;
    use qq_cli::storage::references::References;
    let temp = tempfile::tempdir().unwrap();
    let source = temp.path().join("fixture.bin");
    std::fs::write(&source, b"synthetic retained bytes").unwrap();
    let store = Store::new(temp.path().join("state"));
    let refs = References::with_key(temp.path().join("refs"), [86; 32]);
    let scope = Scope {
        instance_id: "instance-example".into(),
        account: "10001".into(),
    };
    let retained = attachments::prepare(&source, Kind::File, &store, deadline())
        .unwrap()
        .retain(&store, &refs, "retained-request", &scope, deadline())
        .unwrap();
    let directory = retained.directory().to_owned();
    let fingerprint = retained.fingerprint().clone();
    drop(retained);
    drop(refs);
    let refs = References::with_key(temp.path().join("refs"), [86; 32]);
    let restored = attachments::load(
        &directory,
        &store,
        &refs,
        "retained-request",
        &scope,
        deadline(),
    )
    .unwrap();
    assert_eq!(restored.fingerprint(), &fingerprint);
    assert_eq!(
        std::fs::read(restored.path()).unwrap(),
        b"synthetic retained bytes"
    );
    assert!(
        attachments::load(
            &directory,
            &store,
            &refs,
            "another-request",
            &scope,
            deadline()
        )
        .is_err()
    );
    let other = Scope {
        account: "10002".into(),
        ..scope.clone()
    };
    assert!(
        attachments::load(
            &directory,
            &store,
            &refs,
            "retained-request",
            &other,
            deadline()
        )
        .is_err()
    );
    std::fs::write(restored.path(), b"synthetic modified bytes").unwrap();
    assert!(
        attachments::load(
            &directory,
            &store,
            &refs,
            "retained-request",
            &scope,
            deadline()
        )
        .is_err()
    );
}

#[test]
fn unsigned_foreign_or_extra_attachment_files_are_never_adopted() {
    use qq_cli::core::domain::Scope;
    use qq_cli::storage::references::References;
    let temp = tempfile::tempdir().unwrap();
    let source = temp.path().join("fixture.bin");
    std::fs::write(&source, b"synthetic bytes").unwrap();
    let store = Store::new(temp.path().join("state"));
    let refs = References::with_key(temp.path().join("refs"), [87; 32]);
    let scope = Scope {
        instance_id: "instance-example".into(),
        account: "10001".into(),
    };
    let retained = attachments::prepare(&source, Kind::Image, &store, deadline())
        .unwrap()
        .retain(&store, &refs, "request", &scope, deadline())
        .unwrap();
    std::fs::write(retained.directory().join("unowned.bin"), b"foreign").unwrap();
    assert!(
        attachments::load(
            retained.directory(),
            &store,
            &refs,
            "request",
            &scope,
            deadline()
        )
        .is_err()
    );
    assert!(attachments::load(temp.path(), &store, &refs, "request", &scope, deadline()).is_err());
    std::fs::remove_file(retained.directory().join("unowned.bin")).unwrap();
    let marker = retained.directory().join(".qq-cli-attachment.json");
    let mut pointer: serde_json::Value =
        serde_json::from_slice(&std::fs::read(&marker).unwrap()).unwrap();
    pointer["reference"] = serde_json::json!("a1.invalid.invalid");
    std::fs::write(&marker, serde_json::to_vec(&pointer).unwrap()).unwrap();
    assert!(
        attachments::load(
            retained.directory(),
            &store,
            &refs,
            "request",
            &scope,
            deadline()
        )
        .is_err()
    );
    assert!(retained.path().exists());
}