embers-test-support 0.1.0

Shared integration-test harnesses and helpers for Embers crates.
use crate::support::integration_test_lock;
use embers_core::{ErrorCode, new_request_id};
use embers_protocol::{
    BufferRecord, BufferRequest, ClientMessage, ServerResponse, SessionRequest,
    SessionSnapshotResponse, SessionsResponse, TabsRecord,
};
use embers_test_support::{TestConnection, TestServer};

async fn create_session(connection: &mut TestConnection, name: &str) -> SessionSnapshotResponse {
    let response = connection
        .request(&ClientMessage::Session(SessionRequest::Create {
            request_id: new_request_id(),
            name: name.to_owned(),
        }))
        .await
        .expect("create session request succeeds");

    match response {
        ServerResponse::SessionSnapshot(snapshot) => snapshot,
        other => panic!("expected session snapshot response, got {other:?}"),
    }
}

async fn get_session(
    connection: &mut TestConnection,
    session_id: embers_core::SessionId,
) -> ServerResponse {
    connection
        .request(&ClientMessage::Session(SessionRequest::Get {
            request_id: new_request_id(),
            session_id,
        }))
        .await
        .expect("get session request succeeds")
}

async fn create_buffer(connection: &mut TestConnection, title: &str) -> BufferRecord {
    let response = connection
        .request(&ClientMessage::Buffer(BufferRequest::Create {
            request_id: new_request_id(),
            title: Some(title.to_owned()),
            command: vec!["/bin/sh".to_owned(), "-lc".to_owned(), "cat".to_owned()],
            cwd: None,
            env: Default::default(),
        }))
        .await
        .expect("create buffer request succeeds");

    match response {
        ServerResponse::Buffer(buffer) => buffer.buffer,
        other => panic!("expected buffer response, got {other:?}"),
    }
}

async fn get_buffer(
    connection: &mut TestConnection,
    buffer_id: embers_core::BufferId,
) -> BufferRecord {
    let response = connection
        .request(&ClientMessage::Buffer(BufferRequest::Get {
            request_id: new_request_id(),
            buffer_id,
        }))
        .await
        .expect("get buffer request succeeds");

    match response {
        ServerResponse::Buffer(buffer) => buffer.buffer,
        other => panic!("expected buffer response, got {other:?}"),
    }
}

fn root_node(snapshot: &embers_protocol::SessionSnapshot) -> &embers_protocol::NodeRecord {
    snapshot
        .nodes
        .iter()
        .find(|node| node.id == snapshot.session.root_node_id)
        .expect("session root snapshot includes root node")
}

fn root_tabs(snapshot: &embers_protocol::SessionSnapshot) -> Option<TabsRecord> {
    root_node(snapshot).tabs.clone()
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn create_list_get_and_close_sessions_via_socket() {
    let _guard = integration_test_lock().lock().await;
    let server = TestServer::start().await.expect("start server");
    let mut connection = TestConnection::connect(server.socket_path())
        .await
        .expect("connect protocol client");

    let alpha = create_session(&mut connection, "alpha").await;
    let beta = create_session(&mut connection, "beta").await;

    let list = connection
        .request(&ClientMessage::Session(SessionRequest::List {
            request_id: new_request_id(),
        }))
        .await
        .expect("list sessions request succeeds");
    let sessions = match list {
        ServerResponse::Sessions(SessionsResponse { sessions, .. }) => sessions,
        other => panic!("expected sessions response, got {other:?}"),
    };
    assert_eq!(sessions.len(), 2);
    assert!(
        sessions
            .iter()
            .any(|session| session.id == alpha.snapshot.session.id)
    );
    assert!(
        sessions
            .iter()
            .any(|session| session.id == beta.snapshot.session.id)
    );

    let fetched = get_session(&mut connection, alpha.snapshot.session.id).await;
    let fetched = match fetched {
        ServerResponse::SessionSnapshot(snapshot) => snapshot,
        other => panic!("expected session snapshot response, got {other:?}"),
    };
    assert_eq!(fetched.snapshot.session.name, "alpha");
    assert!(
        root_tabs(&fetched.snapshot)
            .expect("new sessions start with empty root tabs")
            .tabs
            .is_empty()
    );

    let close = connection
        .request(&ClientMessage::Session(SessionRequest::Close {
            request_id: new_request_id(),
            session_id: alpha.snapshot.session.id,
            force: false,
        }))
        .await
        .expect("close session request succeeds");
    assert!(matches!(close, ServerResponse::Ok(_)));

    let missing = get_session(&mut connection, alpha.snapshot.session.id).await;
    match missing {
        ServerResponse::Error(error) => assert_eq!(error.error.code, ErrorCode::NotFound),
        other => panic!("expected not found error, got {other:?}"),
    }

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

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn create_select_rename_and_close_root_tabs_via_socket() {
    let _guard = integration_test_lock().lock().await;
    let server = TestServer::start().await.expect("start server");
    let mut connection = TestConnection::connect(server.socket_path())
        .await
        .expect("connect protocol client");

    let session = create_session(&mut connection, "main").await;
    let session_id = session.snapshot.session.id;
    let root_node_id = session.snapshot.session.root_node_id;

    let first_buffer = create_buffer(&mut connection, "shell").await;
    let first_added = connection
        .request(&ClientMessage::Session(SessionRequest::AddRootTab {
            request_id: new_request_id(),
            session_id,
            title: "shell".to_owned(),
            buffer_id: Some(first_buffer.id),
            child_node_id: None,
        }))
        .await
        .expect("add first root tab request succeeds");
    let first_added = match first_added {
        ServerResponse::SessionSnapshot(snapshot) => snapshot,
        other => panic!("expected session snapshot response, got {other:?}"),
    };
    let first_tabs = root_tabs(&first_added.snapshot).expect("first root tab keeps tabs root");
    let first_leaf = first_tabs.tabs[0].child_id;
    assert_eq!(first_added.snapshot.session.root_node_id, root_node_id);
    assert_eq!(first_tabs.active, 0);
    assert_eq!(first_tabs.tabs.len(), 1);

    let second_buffer = create_buffer(&mut connection, "logs").await;
    let second_added = connection
        .request(&ClientMessage::Session(SessionRequest::AddRootTab {
            request_id: new_request_id(),
            session_id,
            title: "logs".to_owned(),
            buffer_id: Some(second_buffer.id),
            child_node_id: None,
        }))
        .await
        .expect("add second root tab request succeeds");
    let second_added = match second_added {
        ServerResponse::SessionSnapshot(snapshot) => snapshot,
        other => panic!("expected session snapshot response, got {other:?}"),
    };
    let second_tabs = root_tabs(&second_added.snapshot).expect("second root tab keeps tabs root");
    assert_eq!(second_added.snapshot.session.root_node_id, root_node_id);
    assert_eq!(second_tabs.active, 1);
    assert_eq!(second_tabs.tabs.len(), 2);

    let selected = connection
        .request(&ClientMessage::Session(SessionRequest::SelectRootTab {
            request_id: new_request_id(),
            session_id,
            index: 0,
        }))
        .await
        .expect("select root tab request succeeds");
    let selected = match selected {
        ServerResponse::SessionSnapshot(snapshot) => snapshot,
        other => panic!("expected session snapshot response, got {other:?}"),
    };
    assert_eq!(
        root_tabs(&selected.snapshot)
            .expect("selecting between root windows keeps tabs root")
            .active,
        0
    );
    assert_eq!(selected.snapshot.session.focused_leaf_id, Some(first_leaf));

    let renamed = connection
        .request(&ClientMessage::Session(SessionRequest::RenameRootTab {
            request_id: new_request_id(),
            session_id,
            index: 0,
            title: "editor".to_owned(),
        }))
        .await
        .expect("rename root tab request succeeds");
    let renamed = match renamed {
        ServerResponse::SessionSnapshot(snapshot) => snapshot,
        other => panic!("expected session snapshot response, got {other:?}"),
    };
    assert_eq!(
        root_tabs(&renamed.snapshot)
            .expect("renaming root windows keeps tabs root")
            .tabs[0]
            .title,
        "editor"
    );

    let closed_second = connection
        .request(&ClientMessage::Session(SessionRequest::CloseRootTab {
            request_id: new_request_id(),
            session_id,
            index: 1,
        }))
        .await
        .expect("close second root tab request succeeds");
    let closed_second = match closed_second {
        ServerResponse::SessionSnapshot(snapshot) => snapshot,
        other => panic!("expected session snapshot response, got {other:?}"),
    };
    assert!(root_tabs(&closed_second.snapshot).is_none());
    let root = root_node(&closed_second.snapshot);
    let root_buffer = root
        .buffer_view
        .as_ref()
        .expect("single remaining root window collapses to a buffer view");
    assert_eq!(root.id, first_leaf);
    assert_eq!(root_buffer.buffer_id, first_buffer.id);
    assert_eq!(
        get_buffer(&mut connection, second_buffer.id)
            .await
            .attachment_node_id,
        None
    );

    let closed_last = connection
        .request(&ClientMessage::Session(SessionRequest::CloseRootTab {
            request_id: new_request_id(),
            session_id,
            index: 0,
        }))
        .await
        .expect("close last root tab request succeeds");
    let closed_last = match closed_last {
        ServerResponse::SessionSnapshot(snapshot) => snapshot,
        other => panic!("expected session snapshot response, got {other:?}"),
    };
    let final_tabs = root_tabs(&closed_last.snapshot)
        .expect("closing the last implicit root window resets the empty root tabs");
    assert!(final_tabs.tabs.is_empty());
    assert_eq!(closed_last.snapshot.session.focused_leaf_id, None);
    assert_eq!(
        get_buffer(&mut connection, first_buffer.id)
            .await
            .attachment_node_id,
        None
    );

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