wsb-daemon 0.291.8591

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the host
//! What the daemon keeps current is what somebody is using, for as long as
//! they are.

use super::*;

fn board(owner: &str, number: i64) -> Demand {
    Demand { boards: [BoardRef { owner: owner.to_string(), number }].into(), ..Demand::default() }
}

/// Whether the keeper was woken, without waiting for long.
async fn woken(wake: &Notify) -> bool {
    tokio::time::timeout(Duration::from_millis(20), wake.notified()).await.is_ok()
}

/// TEST20254: a page's word holds for its lease and lapses after; a page
/// that says it is hidden holds nothing at once.
#[test]
fn test20254_a_pages_lease_lapses_and_a_hidden_page_holds_nothing() {
    let attention = Attention::new();
    let start = Instant::now();
    attention.page("a", true, board("o", 1), start);
    attention.page("b", true, board("o", 2), start);

    let both = attention.demand(start + PAGE_LEASE - Duration::from_secs(1));
    assert_eq!(both.boards.len(), 2, "every live page's view counts");
    assert!(both.notifications, "a person at a page sees its notifications");

    attention.page("b", false, Demand::default(), start + Duration::from_secs(1));
    assert_eq!(attention.demand(start + Duration::from_secs(2)), Demand { notifications: true, ..board("o", 1) });

    assert!(
        attention.demand(start + PAGE_LEASE + Duration::from_secs(1)).is_empty(),
        "a page that stopped saying anything holds nothing"
    );
}

/// TEST20255: a command holds its worktree for a while, then lets go.
#[test]
fn test20255_a_command_holds_its_worktree_for_a_while() {
    let attention = Attention::new();
    let start = Instant::now();
    attention.command(PathBuf::from("/w"), start);
    let held = attention.demand(start + COMMAND_LEASE - Duration::from_secs(1));
    assert_eq!(held.checkouts, [PathBuf::from("/w")].into());
    assert!(!held.notifications, "a command is not a person at a page");
    assert!(attention.demand(start + COMMAND_LEASE + Duration::from_secs(1)).is_empty());
}

/// TEST20256: the keeper is woken when what is in view changes — and not by
/// a heartbeat that says the same again.
#[tokio::test]
async fn test20256_the_keeper_wakes_on_a_change_not_on_a_heartbeat() {
    let attention = Attention::new();
    let wake = attention.wake();
    let now = Instant::now();

    attention.page("a", true, board("o", 1), now);
    assert!(woken(&wake).await, "something came into view");
    attention.page("a", true, board("o", 1), now + Duration::from_secs(60));
    assert!(!woken(&wake).await, "the same view said again is not news");
    attention.page("a", true, board("o", 2), now + Duration::from_secs(61));
    assert!(woken(&wake).await, "the view changed");
    attention.page("a", false, Demand::default(), now + Duration::from_secs(62));
    assert!(woken(&wake).await, "the page went away");

    attention.command(PathBuf::from("/w"), now);
    assert!(woken(&wake).await, "a command in a worktree not held");
    attention.command(PathBuf::from("/w"), now + Duration::from_secs(5));
    assert!(!woken(&wake).await, "a worktree already held is not news");
    // Renewed at five seconds: it lapses a lease after that.
    attention.command(PathBuf::from("/w"), now + Duration::from_secs(5) + COMMAND_LEASE + Duration::from_secs(1));
    assert!(woken(&wake).await, "a worktree whose lease had lapsed is news again");
}

/// TEST20257: a page refreshes at most once per interval.
#[test]
fn test20257_a_refresh_pressed_again_at_once_asks_nothing() {
    let attention = Attention::new();
    let now = Instant::now();
    assert!(attention.may_refresh("a", now));
    assert!(!attention.may_refresh("a", now + REFRESH_AT_MOST - Duration::from_secs(1)));
    assert!(attention.may_refresh("b", now), "another page's refresh is its own");
    assert!(attention.may_refresh("a", now + REFRESH_AT_MOST));
}

/// TEST20258: a page's subjects become what to keep current, and one that
/// names something this daemon cannot find is refused by name.
#[test]
fn test20258_subjects_become_demand_and_unknown_ones_are_refused() {
    let found: BTreeMap<String, PathBuf> = [("wsb:main".to_string(), PathBuf::from("/w/main"))].into();
    let subjects = vec![
        Subject::Worktree { id: "wsb:main".to_string() },
        Subject::Repo { repo: "o/r".to_string(), traffic: true },
        Subject::Repo { repo: "o/s".to_string(), traffic: false },
        Subject::Boards,
        Subject::Board { owner: "o".to_string(), number: 3 },
        Subject::Issue { repo: "o/r".to_string(), number: 9 },
        Subject::Gists,
    ];
    let demand = demand_of_subjects(&subjects, &found).expect("all known");
    assert_eq!(demand.checkouts, [PathBuf::from("/w/main")].into());
    assert_eq!(demand.repositories, ["o/r".to_string(), "o/s".to_string()].into());
    assert_eq!(demand.traffic, ["o/r".to_string()].into(), "traffic only where it is shown");
    assert!(demand.boards_home && demand.gists);
    assert_eq!(demand.boards, [BoardRef { owner: "o".to_string(), number: 3 }].into());
    assert_eq!(demand.threads, [("o/r".to_string(), 9)].into());

    let unknown = demand_of_subjects(&[Subject::Worktree { id: "wsb:gone".to_string() }], &found);
    assert_eq!(unknown, Err("no worktree wsb:gone is open here".to_string()));
    let malformed = demand_of_subjects(&[Subject::Repo { repo: "o/".to_string(), traffic: false }], &found);
    assert_eq!(malformed, Err("\"o/\" is not owner/repo".to_string()));
}

/// What a page sends is read strictly: a subject this daemon does not know
/// is an error, not something quietly ignored.
#[test]
fn a_presence_with_an_unknown_subject_is_refused() {
    let known: Presence = serde_json::from_value(serde_json::json!({
        "client": "a", "visible": true,
        "subjects": [{ "kind": "board", "owner": "o", "number": 1 }, { "kind": "boards" }]
    }))
    .expect("known");
    assert_eq!(known.subjects.len(), 2);
    let unknown = serde_json::from_value::<Presence>(serde_json::json!({
        "client": "a", "visible": true, "subjects": [{ "kind": "everything" }]
    }));
    assert!(unknown.is_err());
}