qq-cli 0.1.0

A modular Rust QQ task interface for coding agents
#![cfg(windows)]
#[path = "common/login_fixture.rs"]
mod fixture;
#[path = "common/login_service.rs"]
mod service;
use fixture::{Fixture, deadline};
use qq_cli::storage::credentials::Credentials;

#[test]
fn public_remove_requires_stop_preserves_secrets_and_repeats_without_the_installation() {
    if service::child() {
        return;
    }
    let fixture = Fixture::new(
        "public_remove_requires_stop_preserves_secrets_and_repeats_without_the_installation",
    );
    fixture.mode("");
    assert_eq!(fixture.status().0, 0);
    let counts = fixture.counts();
    let (code, value) = fixture.run("napcat.remove", &["original-instance"]);
    assert_eq!(code, 4, "{value}");
    assert_eq!(value["error"]["code"], "STOP_PROOF_REQUIRED");
    fixture
        .process
        .as_ref()
        .unwrap()
        .verify(deadline())
        .unwrap();
    assert_eq!(fixture.run("napcat.stop", &[]).0, 0);
    let secrets = fixture
        .keys
        .iter()
        .map(|key| fixture.credentials.get(key).unwrap())
        .collect::<Vec<_>>();
    let marker = fixture
        .store
        .root()
        .join("original-unresolved-request.json");
    std::fs::write(&marker, b"preserve original intent").unwrap();
    let (code, value) = fixture.run("napcat.remove", &["original-instance"]);
    assert_eq!(code, 0, "{value}");
    assert_eq!(value["data"]["state"], "removed");
    assert_eq!(value["data"]["reused"], false);
    assert!(value.get("meta").is_none());
    assert!(fixture.store.read().unwrap().instances.is_empty());
    assert_eq!(fixture.store.read().unwrap().installations.len(), 1);
    assert_eq!(std::fs::read(marker).unwrap(), b"preserve original intent");
    for (key, secret) in fixture.keys.iter().zip(secrets) {
        assert_eq!(fixture.credentials.get(key).unwrap(), secret);
    }
    assert_eq!(fixture.counts(), counts);
    assert!(fixture.store.root().join("runtimes").exists());
    let mut tx = fixture.store.transaction(deadline()).unwrap();
    tx.registry.installations.clear();
    tx.commit().unwrap();
    let (code, value) = fixture.run("napcat.remove", &["original-instance"]);
    assert_eq!(code, 0, "{value}");
    assert_eq!(value["data"]["reused"], true);
}

#[test]
fn concurrent_public_removals_reuse_one_intent_and_unsigned_stopped_text_is_rejected() {
    if service::child() {
        return;
    }
    let fixture = Fixture::new(
        "concurrent_public_removals_reuse_one_intent_and_unsigned_stopped_text_is_rejected",
    );
    let mut tx = fixture.store.transaction(deadline()).unwrap();
    tx.registry.instances[0].state = "stopped".into();
    tx.commit().unwrap();
    let (code, value) = fixture.run("napcat.remove", &["original-instance"]);
    assert_eq!(code, 4, "{value}");
    assert_eq!(value["error"]["code"], "STOP_PROOF_REQUIRED");
    fixture
        .process
        .as_ref()
        .unwrap()
        .verify(deadline())
        .unwrap();
    assert!(!fixture.store.root().join("instance-removals").exists());
    assert_eq!(fixture.run("napcat.stop", &[]).0, 0);
    let first = fixture.spawn("napcat.remove", &["original-instance"]);
    let second = fixture.spawn("napcat.remove", &["original-instance"]);
    let (first_code, first) = fixture.finish("napcat.remove", first);
    let (second_code, second) = fixture.finish("napcat.remove", second);
    assert_eq!(first_code, 0, "{first}");
    assert_eq!(second_code, 0, "{second}");
    assert_ne!(first["data"]["reused"], second["data"]["reused"]);
    assert!(fixture.store.read().unwrap().instances.is_empty());
}