qq-cli 0.1.0

A modular Rust QQ task interface for coding agents
use super::*;
use std::time::Duration;
fn end() -> Instant {
    Instant::now() + Duration::from_secs(3)
}
fn fixture() -> (tempfile::TempDir, Store, References, Instance, Installation) {
    let temp = tempfile::tempdir().unwrap();
    let store = Store::new(temp.path().join("state"));
    let refs = References::with_key(store.root().join("references"), [73; 32]);
    let source = Instance {
        id: "original-instance".into(),
        run_id: "original-run".into(),
        installation_id: "original-installation".into(),
        endpoint: "http://127.0.0.1:19001/".into(),
        ws_endpoint: None,
        credential_key: "original-key".into(),
        account: None,
        state: "prepared".into(),
        process_id: None,
        process_started: None,
    };
    let original = Installation {
        id: source.installation_id.clone(),
        version: "v4.18.37".into(),
        digest: "a".repeat(64),
        directory: temp.path().join("original"),
    };
    let target = Installation {
        id: "target-installation".into(),
        version: "v4.18.38".into(),
        digest: "b".repeat(64),
        directory: temp.path().join("target"),
    };
    let mut tx = store.transaction(end()).unwrap();
    tx.registry.instances.push(source.clone());
    tx.registry.installations.extend([original, target.clone()]);
    tx.commit().unwrap();
    (temp, store, refs, source, target)
}
#[test]
fn unknown_source_is_refused_before_any_signed_claim() {
    let (_temp, store, refs, mut source, target) = fixture();
    source.state = "unknown".into();
    let mut tx = store.transaction(end()).unwrap();
    tx.registry.instances[0] = source.clone();
    tx.commit().unwrap();
    assert!(begin(&store, &refs, &source, &target, Operation::Update, end()).is_err());
    assert!(!store.root().join("version-maintenance").exists());
    assert!(store.read().unwrap().maintenance_intents.is_empty());
}

#[test]
fn intent_precedes_stop_and_blocks_old_start_and_target_uninstall() {
    let (_temp, store, refs, source, target) = fixture();
    let first = begin(&store, &refs, &source, &target, Operation::Update, end()).unwrap();
    assert_eq!(store.read().unwrap().instances[0], source);
    assert!(!store.root().join("instance-stops").exists());
    let again = begin(&store, &refs, &source, &target, Operation::Update, end()).unwrap();
    assert_eq!(first.reference, again.reference);
    assert!(begin(&store, &refs, &source, &target, Operation::Rollback, end()).is_err());
    let error = guard_start(&store, &refs, &store.read().unwrap(), &source)
        .err()
        .unwrap();
    assert_eq!(error.code, "VERSION_MAINTENANCE_PENDING");
    assert_eq!(
        serde_json::to_value(error.next_action.unwrap()).unwrap()["args"]["version"],
        target.version
    );
    assert_eq!(
        crate::napcat::install::installation::uninstall(&store, &target.id, &refs, end())
            .err()
            .unwrap()
            .code,
        "INSTALLATION_IN_USE"
    );
    super::super::stop::execute(&store, &refs, &source, end()).unwrap();
    let stopped = store.read().unwrap().instances.remove(0);
    assert!(guard_start(&store, &refs, &store.read().unwrap(), &stopped).is_err());
    assert_eq!(
        begin(&store, &refs, &stopped, &target, Operation::Update, end())
            .unwrap()
            .reference,
        first.reference
    );
    super::super::remove::execute(&store, &refs, &source.id, end()).unwrap();
    let removed = store.read().unwrap();
    assert!(removed.instances.is_empty() && removed.maintenance_intents.is_empty());
    assert!(
        journal::pointer(&store, &source.id, &source.run_id)
            .unwrap()
            .is_some()
    );
    assert!(begin(&store, &refs, &stopped, &target, Operation::Update, end()).is_err());
}
#[test]
fn missing_index_or_marker_after_stop_is_not_repaired() {
    let (_temp, store, refs, source, target) = fixture();
    let intent = begin(&store, &refs, &source, &target, Operation::Update, end()).unwrap();
    super::super::stop::execute(&store, &refs, &source, end()).unwrap();
    let stopped = store.read().unwrap().instances.remove(0);
    let index = std::fs::read_dir(store.root().join("version-maintenance"))
        .unwrap()
        .next()
        .unwrap()
        .unwrap()
        .path();
    let original = std::fs::read(&index).unwrap();
    std::fs::remove_file(&index).unwrap();
    assert!(guard_start(&store, &refs, &store.read().unwrap(), &stopped).is_err());
    assert!(begin(&store, &refs, &stopped, &target, Operation::Update, end()).is_err());
    std::fs::write(index, original).unwrap();
    let mut tx = store.transaction(end()).unwrap();
    tx.registry.maintenance_intents.clear();
    tx.commit().unwrap();
    assert!(begin(&store, &refs, &stopped, &target, Operation::Update, end()).is_err());
    assert!(store.read().unwrap().maintenance_intents.is_empty());
    assert_eq!(
        journal::load(&store, &refs, &intent.reference)
            .unwrap()
            .target(),
        &target
    );
}
#[test]
fn failed_marker_publication_recovers_only_the_unchanged_source() {
    use std::os::windows::fs::OpenOptionsExt;
    let (_temp, store, refs, source, target) = fixture();
    let held = std::fs::OpenOptions::new()
        .read(true)
        .share_mode(1)
        .open(store.root().join("registry.json"))
        .unwrap();
    assert!(begin(&store, &refs, &source, &target, Operation::Update, end()).is_err());
    assert!(store.read().unwrap().maintenance_intents.is_empty());
    let reference = journal::pointer(&store, &source.id, &source.run_id)
        .unwrap()
        .unwrap();
    assert!(!store.root().join("instance-stops").exists());
    drop(held);
    assert_eq!(
        begin(&store, &refs, &source, &target, Operation::Update, end())
            .unwrap()
            .reference,
        reference
    );
}