embers-cli 0.1.0

Interactive terminal UI and automation CLI for the Embers terminal multiplexer.
use std::process::Stdio;
use std::time::Duration;

use embers_test_support::{TestServer, acquire_test_lock, cargo_bin_path};
use serde_json::Value;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::{ChildStdout, Command};
use tokio::time::timeout;

async fn read_record(lines: &mut tokio::io::Lines<BufReader<ChildStdout>>) -> Value {
    let line = timeout(Duration::from_secs(2), lines.next_line())
        .await
        .expect("automation output arrives before timeout")
        .expect("automation stdout read succeeds")
        .expect("automation output line");
    serde_json::from_str(&line).expect("automation output is valid JSON")
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn automation_mode_emits_hello_response_and_event_records() {
    let _guard = acquire_test_lock().await.expect("acquire test lock");
    let server = TestServer::start().await.expect("start server");

    let mut child = Command::new(cargo_bin_path("embers"))
        .arg("--socket")
        .arg(server.socket_path())
        .arg("automation")
        .arg("--all-sessions")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("spawn automation mode");

    let mut stdin = child.stdin.take().expect("automation stdin");
    let stdout = child.stdout.take().expect("automation stdout");
    let mut lines = BufReader::new(stdout).lines();

    let hello = read_record(&mut lines).await;
    assert_eq!(hello["kind"], "hello");
    assert_eq!(hello["mode"], "automation");
    assert_eq!(hello["subscription"]["all_sessions"], true);
    assert!(hello["subscription"]["subscription_id"].as_u64().is_some());

    stdin
        .write_all(b"new-session alpha\n")
        .await
        .expect("write automation command");
    stdin.flush().await.expect("flush automation stdin");

    let mut saw_response = false;
    let mut saw_event = false;
    loop {
        let record = read_record(&mut lines).await;
        match record["kind"].as_str() {
            Some("response") => {
                assert_eq!(record["seq"], 1);
                assert_eq!(record["command"], "new-session alpha");
                assert_eq!(record["ok"], true);
                assert!(
                    record["stdout"]
                        .as_str()
                        .is_some_and(|stdout| stdout.contains("alpha")),
                    "response stdout should mention the created session: {record:?}"
                );
                saw_response = true;
            }
            Some("event") => {
                if record["event"]["type"] == "session_created" {
                    assert_eq!(record["event"]["session"]["name"], "alpha");
                    saw_event = true;
                }
            }
            other => panic!("unexpected automation record kind: {other:?}"),
        }

        if saw_response && saw_event {
            break;
        }
    }

    assert!(
        saw_response,
        "automation mode did not emit a response record"
    );
    assert!(
        saw_event,
        "automation mode did not emit a session_created event"
    );

    drop(stdin);
    let status = timeout(Duration::from_secs(2), child.wait())
        .await
        .expect("automation process exits before timeout")
        .expect("automation process wait succeeds");
    assert!(
        status.success(),
        "automation mode exited unsuccessfully: {status}"
    );

    server.shutdown().await.expect("shutdown server");
}