aven 0.1.13

Local-first task manager CLI and sync server
Documentation
mod common;

use common::{TestEnv, contains_all, contains_none, extract_ref, fail, ok};

#[test]
fn dependency_list_json_returns_structured_summary() {
    let env = TestEnv::new();
    let db = env.db("dep-list-json.sqlite");

    let root = extract_ref(&ok(env.aven(&db, ["add", "root task", "--project", "app"])));
    let blocked = extract_ref(&ok(
        env.aven(&db, ["add", "blocked task", "--project", "app"])
    ));
    ok(env.aven(&db, ["dep", "add", &blocked, &root]));

    let output = ok(env.aven(&db, ["dep", "list", &blocked, "--json"]));
    let summary: serde_json::Value = serde_json::from_str(&output).unwrap();
    assert_eq!(summary["depends_on_open"], 1);
    assert_eq!(summary["depends_on_total"], 1);
    assert_eq!(summary["blocks_open"], 0);
    assert_eq!(summary["blocks_total"], 0);
    assert_eq!(summary["depends_on"].as_array().unwrap().len(), 1);
    assert_eq!(summary["depends_on"][0]["ref"], root);
    assert_eq!(summary["depends_on"][0]["title"], "root task");
    assert_eq!(summary["blocks"].as_array().unwrap().len(), 0);
}

#[test]
fn dependency_add_list_ready_blocked_and_remove_workflow() {
    let env = TestEnv::new();
    let db = env.db("dependency-workflow.sqlite");

    let root = extract_ref(&ok(env.aven(&db, ["add", "root task", "--project", "app"])));
    let blocked = extract_ref(&ok(
        env.aven(&db, ["add", "blocked task", "--project", "app"])
    ));
    let blocked_by_blocked = extract_ref(&ok(
        env.aven(&db, ["add", "blocked by blocked", "--project", "app"])
    ));
    let ready_task = extract_ref(&ok(env.aven(&db, ["add", "free task", "--project", "app"])));

    ok(env.aven(&db, ["dep", "add", &blocked, &root]));
    ok(env.aven(&db, ["dep", "add", &blocked_by_blocked, &blocked]));

    let dep_list = ok(env.aven(&db, ["dep", "list", &blocked]));
    contains_all(
        &dep_list,
        &["depends_on open=1 total=1", "blocks open=1 total=1", &root],
    );

    let ready = ok(env.aven(&db, ["list", "--ready"]));
    contains_all(&ready, &[&root, &ready_task]);
    contains_none(&ready, &[&blocked, &blocked_by_blocked]);

    let blocked_list = ok(env.aven(&db, ["list", "--blocked"]));
    contains_all(&blocked_list, &[&blocked, &blocked_by_blocked]);
    contains_none(&blocked_list, &[&root, &ready_task]);

    let removed = ok(env.aven(&db, ["dep", "remove", &blocked_by_blocked, &blocked]));
    contains_all(&removed, &["dependency-removed", "changed=yes"]);

    let blocked_list_after_remove = ok(env.aven(&db, ["list", "--blocked"]));
    contains_none(&blocked_list_after_remove, &[&blocked_by_blocked]);
    contains_all(&blocked_list_after_remove, &[&blocked]);

    let ready_after_remove = ok(env.aven(&db, ["list", "--ready"]));
    contains_all(
        &ready_after_remove,
        &[&blocked_by_blocked, &root, &ready_task],
    );
}

#[test]
fn dependency_self_and_cycle_rejections() {
    let env = TestEnv::new();
    let db = env.db("dependency-cycles.sqlite");

    let task_a = extract_ref(&ok(env.aven(&db, ["add", "task A", "--project", "app"])));
    let task_b = extract_ref(&ok(env.aven(&db, ["add", "task B", "--project", "app"])));

    let self_error = fail(env.aven(&db, ["dep", "add", &task_a, &task_a]));
    contains_all(&self_error, &["error dependency-self", "task_id="]);

    ok(env.aven(&db, ["dep", "add", &task_b, &task_a]));
    let cycle_error = fail(env.aven(&db, ["dep", "add", &task_a, &task_b]));
    contains_all(&cycle_error, &["error dependency-cycle"]);
}

#[test]
fn dependency_filter_ignores_done_canceled_and_deleted_blockers() {
    let env = TestEnv::new();
    let db = env.db("dependency-state-filtering.sqlite");

    let done_blocker = extract_ref(&ok(
        env.aven(&db, ["add", "done blocker", "--project", "app"])
    ));
    let done_blocked = extract_ref(&ok(
        env.aven(&db, ["add", "blocked by done", "--project", "app"])
    ));
    let canceled_blocker = extract_ref(&ok(
        env.aven(&db, ["add", "canceled blocker", "--project", "app"])
    ));
    let canceled_blocked = extract_ref(&ok(
        env.aven(&db, ["add", "blocked by canceled", "--project", "app"])
    ));
    let deleted_blocker = extract_ref(&ok(
        env.aven(&db, ["add", "deleted blocker", "--project", "app"])
    ));
    let deleted_blocked = extract_ref(&ok(
        env.aven(&db, ["add", "blocked by deleted", "--project", "app"])
    ));

    ok(env.aven(&db, ["edit", &done_blocker, "--status", "done"]));
    ok(env.aven(&db, ["edit", &canceled_blocker, "--status", "canceled"]));
    ok(env.aven(&db, ["delete", &deleted_blocker]));

    ok(env.aven(&db, ["dep", "add", &done_blocked, &done_blocker]));
    ok(env.aven(&db, ["dep", "add", &canceled_blocked, &canceled_blocker]));
    ok(env.aven(&db, ["dep", "add", &deleted_blocked, &deleted_blocker]));

    let ready = ok(env.aven(&db, ["list", "--ready"]));
    contains_all(
        &ready,
        &[&done_blocked, &canceled_blocked, &deleted_blocked],
    );

    let blocked = ok(env.aven(&db, ["list", "--blocked"]));
    contains_none(
        &blocked,
        &[&done_blocked, &canceled_blocked, &deleted_blocked],
    );

    let all = ok(env.aven(&db, ["list", "--all"]));
    contains_none(&all, &["blocks=1"]);

    let done_dep_list = ok(env.aven(&db, ["dep", "list", &done_blocker]));
    contains_all(&done_dep_list, &["blocks open=0 total=1"]);
}

#[test]
fn dependency_show_full_prints_dependents_sections() {
    let env = TestEnv::new();
    let db = env.db("dependency-show-full.sqlite");

    let root = extract_ref(&ok(env.aven(&db, ["add", "dep-root", "--project", "app"])));
    let middle = extract_ref(&ok(env.aven(&db, ["add", "dep-middle", "--project", "app"])));
    let leaf = extract_ref(&ok(env.aven(&db, ["add", "dep-leaf", "--project", "app"])));

    ok(env.aven(&db, ["dep", "add", &middle, &root]));
    ok(env.aven(&db, ["dep", "add", &leaf, &middle]));

    let full = ok(env.aven(&db, ["show", &middle, "--full"]));
    contains_all(
        &full,
        &[
            "depends_on open=1 total=1",
            "blocks open=1 total=1",
            &root,
            &leaf,
        ],
    );
}

#[test]
fn dependency_list_filters_enforce_conflicts() {
    let env = TestEnv::new();
    let db = env.db("dependency-filter-errors.sqlite");

    let _ = extract_ref(&ok(
        env.aven(&db, ["add", "filter task", "--project", "app"])
    ));

    let conflict_error = fail(env.aven(&db, ["list", "--ready", "--blocked"]));
    contains_all(&conflict_error, &["error list-dependency-filter-conflict"]);

    let all_error = fail(env.aven(&db, ["list", "--ready", "--all"]));
    contains_all(&all_error, &["error list-dependency-filter-all-conflict"]);
}