wsb-daemon 0.275.7622

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the machine
//! What the machine database keeps about services, modes and dismissals.
//!
//! Every one of these is a fact that decides whether files move. A default
//! that silently read the wrong way, or a column an upgrade never added, would
//! not fail anything visibly — it would carry a working tree somebody had
//! switched off, or refuse to carry one they had switched on.

use super::*;
use wsb::peer::identity::{Fingerprint, PairedPeer};

fn store() -> (tempfile::TempDir, MachineStore) {
    let dir = tempfile::tempdir().expect("tempdir");
    let store = MachineStore::open(&dir.path().join("wsb.db")).expect("a machine store");
    (dir, store)
}

fn peer_id() -> PeerId {
    PeerId::parse("aaaa1111bbbb2222").expect("a peer id")
}

fn shared_worktree() -> SharedWorktreeId {
    SharedWorktreeId::parse("cccc3333@main").expect("a worktree id")
}

fn pair(store: &MachineStore) -> PeerId {
    let id = peer_id();
    store
        .add_peer(&PairedPeer {
            id: id.clone(),
            name: "laptop".to_string(),
            public_key: "key".to_string(),
            fingerprint: Fingerprint::from_stored("ff".to_string()),
            address: "127.0.0.1:7420".to_string(),
            paired_at: 1,
        })
        .expect("the peer is paired");
    id
}

/// TEST10147: a machine somebody paired can do everything until they say
/// otherwise, and a switch moves both ways.
#[test]
fn test10147_services_default_on_and_switch_both_ways() {
    let (_dir, store) = store();
    let peer = pair(&store);

    // No rows at all — which is what pairing writes — reads as on.
    for service in Service::BUILT_IN {
        assert_eq!(
            store.service(&peer, service.clone()).expect("a switch"),
            Switch::On,
            "{service} must be available to a machine somebody paired"
        );
    }

    store
        .set_service(&peer, Service::Files, Switch::Off, 10)
        .expect("files off");
    assert_eq!(store.service(&peer, Service::Files).expect("files"), Switch::Off);

    // And back, which is the half that a one-way switch would fail at.
    store
        .set_service(&peer, Service::Files, Switch::On, 11)
        .expect("files on");
    assert_eq!(store.service(&peer, Service::Files).expect("files"), Switch::On);
}

/// TEST10148: a relationship carries nothing by itself until somebody asks it
/// to, and the mode survives a restart.
///
/// The default is the whole point of the mode. It is read from a row that may
/// not exist, from a column that may not have existed when the row was
/// written, and both must come back manual.
#[test]
fn test10148_the_mode_defaults_to_manual_and_persists() {
    let (dir, store) = store();
    let peer = pair(&store);
    let worktree = shared_worktree();

    assert_eq!(
        store.mode(&peer, &worktree).expect("a mode"),
        SyncMode::Manual,
        "a relationship nobody has configured must not carry files by itself"
    );

    // Enabling the worktree does not change how it is carried: the two are
    // separate decisions, and this is the one that was never asked.
    store
        .set_desired(&peer, &worktree, Desired::Enabled, 10)
        .expect("enabled");
    assert_eq!(store.mode(&peer, &worktree).expect("a mode"), SyncMode::Manual);

    store
        .set_mode(&peer, &worktree, SyncMode::Auto, 11)
        .expect("auto");
    assert_eq!(store.mode(&peer, &worktree).expect("a mode"), SyncMode::Auto);
    assert_eq!(
        store.desired(&peer, &worktree).expect("desired"),
        Desired::Enabled,
        "setting the mode must not disturb whether the worktree is carried"
    );

    // Reopened: a mode kept in memory would pass everything above.
    drop(store);
    let store = MachineStore::open(&dir.path().join("wsb.db")).expect("reopened");
    assert_eq!(store.mode(&peer, &worktree).expect("a mode"), SyncMode::Auto);
}

/// TEST10149: choosing a mode before choosing to carry the worktree works.
///
/// The workbench has two controls and no imposed order. Refusing the mode
/// until the relationship existed would make one of them fail depending on
/// which was touched first — and the failure would be a silent no-op.
#[test]
fn test10149_a_mode_can_be_set_before_the_workspace_is_carried() {
    let (_dir, store) = store();
    let peer = pair(&store);
    let worktree = shared_worktree();

    store
        .set_mode(&peer, &worktree, SyncMode::Auto, 10)
        .expect("auto before enabled");
    assert_eq!(store.mode(&peer, &worktree).expect("a mode"), SyncMode::Auto);
    assert_eq!(
        store.desired(&peer, &worktree).expect("desired"),
        Desired::Disabled,
        "choosing how it would be carried must not start carrying it"
    );

    store
        .set_desired(&peer, &worktree, Desired::Enabled, 11)
        .expect("enabled");
    assert_eq!(
        store.mode(&peer, &worktree).expect("a mode"),
        SyncMode::Auto,
        "enabling the worktree must keep the mode already chosen"
    );
}

/// TEST10150: a dismissal is about two versions, not about a path.
///
/// It is stored with the hashes it was made at so the coordinator can tell
/// "this is the difference I was shown" from "this path differs again". The
/// store's job is to hand back exactly what was recorded, including the absent
/// side of a file only one machine has.
#[test]
fn test10150_a_dismissal_records_the_versions_it_was_about() {
    let (_dir, store) = store();
    let peer = pair(&store);
    let worktree = shared_worktree();
    let repository = RepositoryId::root();

    store
        .dismiss(&peer, &worktree, &repository, "notes.md", Some("aaa"), None, 10)
        .expect("dismissed");

    let held = store
        .dismissals(&peer, &worktree, &repository)
        .expect("dismissals");
    assert_eq!(
        held.get("notes.md"),
        Some(&(Some("aaa".to_string()), None)),
        "the absent side must come back absent rather than as an empty string"
    );

    // Dismissing again at different versions replaces the first: it is one
    // statement about one path, made afresh each time.
    store
        .dismiss(&peer, &worktree, &repository, "notes.md", Some("bbb"), Some("ccc"), 11)
        .expect("dismissed again");
    let held = store
        .dismissals(&peer, &worktree, &repository)
        .expect("dismissals");
    assert_eq!(
        held.get("notes.md"),
        Some(&(Some("bbb".to_string()), Some("ccc".to_string())))
    );

    store
        .clear_dismissal(&peer, &worktree, &repository, "notes.md")
        .expect("cleared");
    assert!(store
        .dismissals(&peer, &worktree, &repository)
        .expect("dismissals")
        .is_empty());
}

/// TEST10151: forgetting a peer takes its services with it.
///
/// A re-pairing under the same name must start from the defaults. Leaving a
/// switched-off service behind would mean a machine somebody re-paired
/// deliberately sat there doing nothing, with nothing on screen to say why.
#[test]
fn test10151_forgetting_a_peer_forgets_what_it_was_allowed_to_do() {
    let (_dir, store) = store();
    let peer = pair(&store);

    store
        .set_service(&peer, Service::Files, Switch::Off, 10)
        .expect("files off");
    assert!(store.remove_peer(&peer).expect("removed"));

    let peer = pair(&store);
    assert_eq!(
        store.service(&peer, Service::Files).expect("files"),
        Switch::On,
        "a re-paired machine must start from the defaults"
    );
}