qq-cli 0.1.0

A modular Rust QQ task interface for coding agents
use super::*;
use crate::storage::state::{Installation, Instance};
use std::time::Duration;
fn end() -> Instant {
    Instant::now() + Duration::from_secs(3)
}
fn fixture() -> (tempfile::TempDir, Store, References, Instance) {
    let temp = tempfile::tempdir().unwrap();
    let store = Store::new(temp.path().join("state"));
    let refs = References::with_key(store.root().join("references"), [83; 32]);
    let instance = Instance {
        id: "test-instance".into(),
        run_id: "test-run".into(),
        installation_id: "test-installation".into(),
        endpoint: "http://127.0.0.1:19001".into(),
        ws_endpoint: None,
        credential_key: "test-credential".into(),
        account: None,
        state: "prepared".into(),
        process_id: None,
        process_started: None,
    };
    let mut tx = store.transaction(end()).unwrap();
    tx.registry.instances.push(instance.clone());
    tx.registry.installations.push(Installation {
        id: instance.installation_id.clone(),
        version: "test".into(),
        digest: "a".repeat(64),
        directory: temp.path().join("installation"),
    });
    tx.commit().unwrap();
    (temp, store, refs, instance)
}
#[test]
fn removal_requires_signed_stop_and_preserves_material_and_history() {
    let (_temp, store, refs, instance) = fixture();
    assert_eq!(
        execute(&store, &refs, &instance.id, end())
            .err()
            .unwrap()
            .code,
        "STOP_PROOF_REQUIRED"
    );
    let mut tx = store.transaction(end()).unwrap();
    tx.registry.instances[0].state = "stopped".into();
    tx.commit().unwrap();
    assert_eq!(
        execute(&store, &refs, &instance.id, end())
            .err()
            .unwrap()
            .code,
        "STOP_PROOF_REQUIRED"
    );
    let mut tx = store.transaction(end()).unwrap();
    tx.registry.instances[0] = instance.clone();
    tx.commit().unwrap();
    super::super::stop::execute(&store, &refs, &instance, end()).unwrap();
    let marker = store.root().join("original-request.json");
    std::fs::write(&marker, b"preserve unknown receipt").unwrap();
    assert!(!execute(&store, &refs, &instance.id, end()).unwrap().reused);
    assert!(store.read().unwrap().instances.is_empty());
    assert_eq!(store.read().unwrap().installations.len(), 1);
    assert_eq!(std::fs::read(marker).unwrap(), b"preserve unknown receipt");
    assert!(store.root().join("instance-stops").exists());
    assert!(execute(&store, &refs, &instance.id, end()).unwrap().reused);
    assert_eq!(
        execute(&store, &refs, "missing-instance", end())
            .err()
            .unwrap()
            .code,
        "INSTANCE_NOT_FOUND"
    );
}
#[test]
fn committed_removal_with_pending_receipt_recovers_after_installation_is_gone() {
    let (_temp, store, refs, instance) = fixture();
    super::super::stop::execute(&store, &refs, &instance, end()).unwrap();
    let instance = store.read().unwrap().instances.remove(0);
    journal::save(
        &store,
        &refs,
        &Record {
            schema_version: 1,
            instance: instance.clone(),
            installation_digest: "a".repeat(64),
            outcome: Outcome::Pending,
        },
    )
    .unwrap();
    let mut tx = store.transaction(end()).unwrap();
    tx.registry.instances.clear();
    tx.registry.installations.clear();
    tx.commit().unwrap();
    execute(&store, &refs, &instance.id, end()).unwrap();
    assert!(execute(&store, &refs, &instance.id, end()).unwrap().reused);
    assert!(store.read().unwrap().installations.is_empty());
}
#[test]
fn removal_never_adopts_a_recreated_registration_with_the_old_id() {
    let (_temp, store, refs, instance) = fixture();
    super::super::stop::execute(&store, &refs, &instance, end()).unwrap();
    execute(&store, &refs, &instance.id, end()).unwrap();
    let mut replacement = instance.clone();
    replacement.run_id = "replacement-run".into();
    let mut tx = store.transaction(end()).unwrap();
    tx.registry.instances.push(replacement.clone());
    tx.commit().unwrap();
    assert_eq!(
        execute(&store, &refs, &instance.id, end())
            .err()
            .unwrap()
            .code,
        "REMOVE_INPUT_CHANGED"
    );
    assert_eq!(store.read().unwrap().instances[0], replacement);
}

#[test]
fn removal_retires_only_its_exact_default_identity_and_keeps_signed_history() {
    use serde_json::json;
    use sha2::{Digest, Sha256};
    for (id, run, matches) in [
        ("test-instance", "test-run", true),
        ("test-instance", "other-run", false),
        ("other-instance", "test-run", false),
    ] {
        let (_temp, store, refs, instance) = fixture();
        let creation = store.root().join("instance-creations");
        crate::storage::state::private_dir(&creation).unwrap();
        let installation = store.read().unwrap().installations.remove(0);
        let reference=refs.issue("n1", &json!({"schemaVersion":1,"requestId":"old-default","installation":installation,"plan":{"instanceId":id,"runId":run,"version":"test","ports":{"http":19001,"events":19002,"control":19003}},"phase":"registered"})).unwrap();
        let creation_path = creation.join(format!("{:x}.json", Sha256::digest(b"old-default")));
        crate::storage::state::atomic_json(
            &creation_path,
            &json!({"schemaVersion":1,"reference":reference}),
        )
        .unwrap();
        let directory = store.root().join("automatic-start");
        crate::storage::state::private_dir(&directory).unwrap();
        let reference = refs
            .issue(
                "d1",
                &json!({"schemaVersion":1,"requestId":"old-default","version":"test"}),
            )
            .unwrap();
        let path = directory.join("active.json");
        crate::storage::state::atomic_json(
            &path,
            &json!({"schemaVersion":1,"reference":reference}),
        )
        .unwrap();
        let original = std::fs::read(&path).unwrap();
        let creation_bytes = std::fs::read(&creation_path).unwrap();
        super::super::stop::execute(&store, &refs, &instance, end()).unwrap();
        execute(&store, &refs, &instance.id, end()).unwrap();
        assert_eq!(path.exists(), !matches);
        assert_eq!(std::fs::read(&creation_path).unwrap(), creation_bytes);
        if matches {
            let super::super::default_start::Selection::Create { request_id } =
                super::super::default_start::select(&store, &refs, "test", end()).unwrap()
            else {
                panic!("new default expected")
            };
            assert_ne!(request_id, "old-default");
            let active = std::fs::read(&path).unwrap();
            assert!(execute(&store, &refs, &instance.id, end()).unwrap().reused);
            assert_eq!(std::fs::read(&path).unwrap(), active);
        } else {
            assert_eq!(std::fs::read(&path).unwrap(), original);
        }
    }
}

#[test]
fn missing_default_creation_proof_preserves_registration_and_active_pointer() {
    let (_temp, store, refs, instance) = fixture();
    super::super::stop::execute(&store, &refs, &instance, end()).unwrap();
    let directory = store.root().join("automatic-start");
    crate::storage::state::private_dir(&directory).unwrap();
    let reference = refs.issue("d1", &serde_json::json!({"schemaVersion":1,"requestId":"missing-default-creation","version":"test"})).unwrap();
    let path = directory.join("active.json");
    crate::storage::state::atomic_json(
        &path,
        &serde_json::json!({"schemaVersion":1,"reference":reference}),
    )
    .unwrap();
    let original = std::fs::read(&path).unwrap();
    assert_eq!(
        execute(&store, &refs, &instance.id, end())
            .err()
            .unwrap()
            .code,
        "AUTOMATIC_START_CONFLICT"
    );
    assert_eq!(store.read().unwrap().instances.len(), 1);
    assert_eq!(std::fs::read(path).unwrap(), original);
}