wsb-daemon 0.275.7622

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the machine
//! Where the workbench layout is kept, and what survives being written.
//!
//! The layout lived in a `workbench.json`, written with a bare `fs::write`
//! beside the database that holds everything else about the worktree. These
//! are about the two things that made that wrong: a write that could destroy
//! the layout it was saving, and a second place for a worktree's state to
//! live.

use super::*;
use std::sync::Arc;

fn database() -> (tempfile::TempDir, Arc<wsb::persist::global::service::DatabaseService>) {
    let dir = tempfile::tempdir().expect("a scratch directory");
    let config = wsb::persist::global::config::DatabaseConfig {
        db_max_connections: 2,
        ..wsb::persist::global::config::DatabaseConfig::new(&dir.path().join("wsb.db"))
    };
    let service = Arc::new(
        wsb::persist::global::service::DatabaseService::new(&config).expect("a database"),
    );
    service.run_migrations().expect("the schema is current");
    (dir, service)
}

/// Save a layout the way the handler does, so the test exercises the statement
/// that actually runs rather than a second spelling of it.
fn save(service: &Arc<wsb::persist::global::service::DatabaseService>, worktree: &str, json: &str) {
    let conn = service.get_connection().expect("a connection");
    conn.execute(UPSERT_LAYOUT, rusqlite::params![worktree, json]).expect("the layout is saved");
}

fn read(service: &Arc<wsb::persist::global::service::DatabaseService>, worktree: &str) -> Option<String> {
    use rusqlite::OptionalExtension;
    let conn = service.get_connection().expect("a connection");
    conn.query_row(SELECT_LAYOUT, [worktree], |row| row.get::<_, String>(0))
        .optional()
        .expect("the read succeeds")
}

/// TEST12026: the layout round-trips through the database.
///
/// The table exists, the upsert replaces rather than duplicates, and what
/// comes back is what went in. A `PRIMARY KEY` that did not conflict-update
/// would make every save fail after the first, and the layout would freeze at
/// whatever was open the first time the daemon ran.
#[test]
fn test12026_a_layout_is_saved_and_read_back() {
    let (_dir, service) = database();

    let fleet = "machinefabric/main";
    assert_eq!(read(&service, fleet), None, "a fresh worktree has no layout");

    save(&service, fleet, r#"{"groups":[],"active_group_id":"group:1"}"#);
    assert_eq!(read(&service, fleet).as_deref(), Some(r#"{"groups":[],"active_group_id":"group:1"}"#));

    // Saved again: replaced, not a second row.
    save(&service, fleet, r#"{"groups":[],"active_group_id":"group:2"}"#);
    assert_eq!(
        read(&service, fleet).as_deref(),
        Some(r#"{"groups":[],"active_group_id":"group:2"}"#),
        "a second save did not replace the first"
    );

    let conn = service.get_connection().expect("a connection");
    let rows: i64 = conn
        .query_row("SELECT count(*) FROM workbench_state", [], |row| row.get(0))
        .expect("the count reads");
    assert_eq!(rows, 1, "saving twice left {rows} rows");
}

/// TEST12027: two worktrees keep two layouts in the one database.
///
/// One daemon serves several, and the tabs open in one are about its files. A
/// key that was not the worktree would have the second worktree's layout
/// overwrite the first's, so opening one would silently rearrange another.
#[test]
fn test12027_each_worktree_keeps_its_own_layout() {
    let (_dir, service) = database();

    save(&service, "fleet/main", r#"{"which":"one"}"#);
    save(&service, "fleet/fix", r#"{"which":"two"}"#);

    assert_eq!(read(&service, "fleet/main").as_deref(), Some(r#"{"which":"one"}"#));
    assert_eq!(read(&service, "fleet/fix").as_deref(), Some(r#"{"which":"two"}"#));
}

/// TEST12028: what a worktree keeps of its own is the folders its explorer has
/// open — and nothing of the machine's layout, which a worktree's state no
/// longer carries (anything else in it is refused, not silently dropped).
#[test]
fn test12028_a_worktree_keeps_only_its_open_folders() {
    let (_dir, service) = database();

    let sent = serde_json::json!({ "expanded_paths": ["src", "src/web"] });
    let parsed: WorktreeState = serde_json::from_value(sent).expect("the wire type parses what the frontend sends");
    save(&service, "fleet/main", &serde_json::to_string(&parsed).expect("it serializes"));
    let back: WorktreeState =
        serde_json::from_str(&read(&service, "fleet/main").expect("it was saved")).expect("it reads back");
    assert_eq!(back.expanded_paths, vec!["src".to_string(), "src/web".to_string()]);

    let old = serde_json::json!({ "groups": [], "active_group_id": "group:1", "expanded_paths": [] });
    assert!(
        serde_json::from_value::<WorktreeState>(old).is_err(),
        "a worktree's state carried the machine's layout"
    );
}