a73x

tests/lease_cli_test.rs

Ref:   Size: 6.8 KiB   History

mod common;

use std::path::Path;
use std::process::Output;

use common::ServerHarness;

/// Run `git-collab issue …` in the harness work repo against the harness SSH
/// server, as the default client key.
fn issue_cmd(harness: &ServerHarness, args: &[&str]) -> Output {
    issue_cmd_as(harness, &harness.ssh_client_key(), args)
}

/// Same, as a specific key — a lease conflict needs two principals.
fn issue_cmd_as(harness: &ServerHarness, key: &Path, args: &[&str]) -> Output {
    let mut cmd = harness.work_repo().cli_command();
    cmd.env(
        "GIT_COLLAB_SSH_COMMAND",
        harness.ssh_command_string_for(key),
    );
    cmd.args(["issue"]).args(args);
    cmd.output().expect("failed to run git-collab issue")
}

fn out(output: &Output) -> String {
    String::from_utf8_lossy(&output.stdout).to_string()
}

fn err(output: &Output) -> String {
    String::from_utf8_lossy(&output.stderr).to_string()
}

/// A harness whose work repo has an `srv` SSH remote and one open issue,
/// already pushed. Returns the issue id.
fn setup(name: &str) -> (ServerHarness, String) {
    let harness = ServerHarness::new(name);
    harness.push_head();
    let url = harness.repo_ssh_url();
    harness.work_repo().git(&["remote", "add", "srv", &url]);
    let (_ref_name, id) = common::open_issue(
        &harness.work_repo_git2(),
        &common::alice(),
        "parser chokes on empty input",
    );
    harness.push_collab_refs();
    (harness, id)
}

#[test]
fn claim_then_claims_lists_it() {
    let (harness, id) = setup("cli-lease-claim");

    // Use the same abbreviated id that `issue list` prints.
    let claim = issue_cmd(&harness, &["claim", &id[..8], "--remote", "srv"]);
    assert!(
        claim.status.success(),
        "claim failed: {}{}",
        out(&claim),
        err(&claim)
    );
    assert!(out(&claim).contains("Claimed issue"), "{}", out(&claim));

    let claims = issue_cmd(&harness, &["claims", "--remote", "srv"]);
    assert!(claims.status.success());
    assert!(
        out(&claims).contains(&id[..8]),
        "claims should list the issue: {}",
        out(&claims)
    );
    // No --ttl, so the claim is an assignment rather than a lease.
    assert!(out(&claims).contains("assigned"), "{}", out(&claims));

    // Prose abbreviates like every other command, even though the server
    // answers with the full id — which `--json` still carries. This is the
    // house convention, not an accident of formatting.
    assert!(
        !out(&claim).contains(&id),
        "prose should abbreviate the id, not print all 40 characters: {}",
        out(&claim)
    );
    assert!(
        !out(&claims).contains(&id),
        "prose should abbreviate the id in the claims list: {}",
        out(&claims)
    );
}

#[test]
fn claims_with_no_claims_says_so() {
    let (harness, _id) = setup("cli-lease-empty");

    let claims = issue_cmd(&harness, &["claims", "--remote", "srv"]);
    assert!(claims.status.success());
    assert!(out(&claims).contains("No claims."), "{}", out(&claims));
}

#[test]
fn claim_conflict_exits_4_and_names_the_holder() {
    let (harness, id) = setup("cli-lease-conflict");
    let first = harness.ssh_client_key();
    let second = harness.second_authorized_key();

    let mine = issue_cmd_as(
        &harness,
        &first,
        &["claim", &id, "--ttl", "300", "--remote", "srv"],
    );
    assert!(mine.status.success(), "{}{}", out(&mine), err(&mine));

    let theirs = issue_cmd_as(
        &harness,
        &second,
        &["claim", &id, "--ttl", "300", "--remote", "srv"],
    );
    // 4, not 1: losing a race is an answer a script can branch on.
    assert_eq!(
        theirs.status.code(),
        Some(4),
        "stdout {} stderr {}",
        out(&theirs),
        err(&theirs)
    );
    assert!(
        err(&theirs).contains("is claimed by"),
        "stderr should name the holder: {}",
        err(&theirs)
    );
}

#[test]
fn unclaim_frees_the_issue() {
    let (harness, id) = setup("cli-lease-unclaim");
    let second = harness.second_authorized_key();

    issue_cmd(&harness, &["claim", &id, "--ttl", "300", "--remote", "srv"]);
    // The CLI prints an abbreviation, so the other side of the pair must
    // accept one rather than silently leaving the full-id lease held.
    let unclaim = issue_cmd(&harness, &["unclaim", &id[..8], "--remote", "srv"]);
    assert!(
        unclaim.status.success(),
        "{}{}",
        out(&unclaim),
        err(&unclaim)
    );

    let theirs = issue_cmd_as(
        &harness,
        &second,
        &["claim", &id, "--ttl", "300", "--remote", "srv"],
    );
    assert!(
        theirs.status.success(),
        "a released issue must be claimable: {}{}",
        out(&theirs),
        err(&theirs)
    );
}

#[test]
fn renew_updates_the_expiry() {
    let (harness, id) = setup("cli-lease-renew");

    issue_cmd(&harness, &["claim", &id, "--ttl", "60", "--remote", "srv"]);
    let renew = issue_cmd(
        &harness,
        &["renew", &id[..8], "--ttl", "600", "--remote", "srv"],
    );
    assert!(renew.status.success(), "{}{}", out(&renew), err(&renew));
    assert!(out(&renew).contains("Renewed"), "{}", out(&renew));
}

#[test]
fn renew_without_a_claim_exits_4() {
    let (harness, id) = setup("cli-lease-renew-none");

    let renew = issue_cmd(&harness, &["renew", &id, "--ttl", "600", "--remote", "srv"]);
    assert_eq!(
        renew.status.code(),
        Some(4),
        "{}{}",
        out(&renew),
        err(&renew)
    );
    assert!(
        err(&renew).contains("claim it first"),
        "stderr should say how to fix it: {}",
        err(&renew)
    );
}

#[test]
fn claim_json_is_the_servers_own_reply() {
    let (harness, id) = setup("cli-lease-json");

    let claim = issue_cmd(&harness, &["claim", &id, "--remote", "srv", "--json"]);
    assert!(claim.status.success(), "{}{}", out(&claim), err(&claim));
    let json: serde_json::Value = serde_json::from_str(out(&claim).trim()).unwrap();
    assert_eq!(json["status"], "acquired");
    assert_eq!(json["issue"], id);
    assert_eq!(json["token"], 1);
}

#[test]
fn claim_rejects_a_non_hex_id_locally() {
    let (harness, _id) = setup("cli-lease-badid");

    // Client-side: a quote must never reach the single-quoted remote command.
    let claim = issue_cmd(&harness, &["claim", "nope'; true", "--remote", "srv"]);
    assert!(!claim.status.success());
    assert!(
        err(&claim).contains("not an issue id"),
        "stderr: {}",
        err(&claim)
    );
}

#[test]
fn claim_against_a_non_ssh_remote_fails_clearly() {
    let (harness, id) = setup("cli-lease-localremote");

    // `origin` is the local bare path the harness set up, not an SSH remote.
    let claim = issue_cmd(&harness, &["claim", &id, "--remote", "origin"]);
    assert!(!claim.status.success());
    assert!(
        err(&claim).contains("not an SSH remote"),
        "stderr: {}",
        err(&claim)
    );
}