mobius-gateway 0.9.21

Headless authenticated gateway for möbius frontends
Documentation
use mobius::backend::checkpoint::Checkpoint;
use uuid::Uuid;

use super::*;

fn gateway(root: &tempfile::TempDir) -> GatewayHost {
    let listen = "127.0.0.1:8741".parse().expect("listen address");
    let (store, config) =
        ConfigStore::initialize(root.path().join("state"), listen, None).expect("config");
    let credentials =
        Arc::new(CredentialStore::open(store.credentials_path()).expect("credentials"));
    let cron = Arc::new(CronStore::open(store.state_dir()).expect("cron"));
    GatewayHost::start(store, config, credentials, cron).expect("gateway")
}

async fn save_session(gateway: &GatewayHost, session_id: &str, workspace_id: &str) {
    let checkpoints = Arc::clone(&gateway.state.lock().await.checkpoints);
    let mut checkpoint = Checkpoint::empty(session_id);
    checkpoint.session_context.workspace_id = Some(workspace_id.into());
    checkpoints
        .save(&checkpoint, &[], None)
        .await
        .expect("save session");
}

#[tokio::test]
async fn gateway_manages_existing_sessions_and_broadcasts_the_swarm_catalog() {
    let root = tempfile::tempdir().expect("root");
    let gateway = gateway(&root);
    let session_ids = (0..3)
        .map(|_| Uuid::new_v4().to_string())
        .collect::<Vec<_>>();
    for session_id in &session_ids {
        save_session(&gateway, session_id, "workspace-a").await;
    }
    let mut events = gateway.subscribe();

    let created = gateway
        .create_swarm(session_ids[0].clone(), session_ids[..2].to_vec())
        .await
        .expect("create swarm");
    let swarm_id = created[0].id.clone();
    let original_handles = created[0].members.clone();
    assert!(created[0].title.starts_with("Swarm "));
    assert_ne!(original_handles[0].handle, original_handles[1].handle);
    assert!(matches!(
        events.recv().await.expect("swarm broadcast").message,
        ServerMessage::Swarms {
            request_id: None,
            ..
        }
    ));

    let joined = gateway
        .add_swarm_member(&swarm_id, session_ids[2].clone())
        .await
        .expect("add member");
    assert!(
        original_handles
            .iter()
            .all(|member| joined[0].members.contains(member))
    );
    let left = gateway
        .leave_swarm(&swarm_id, &session_ids[2])
        .await
        .expect("leave swarm");
    assert_eq!(left[0].members.len(), 2);
    let disbanded = gateway
        .disband_swarm(&swarm_id)
        .await
        .expect("disband swarm");
    assert!(disbanded.is_empty());

    let missing_session = Uuid::new_v4().to_string();
    let rejection = gateway
        .create_swarm(
            session_ids[0].clone(),
            vec![session_ids[0].clone(), missing_session],
        )
        .await
        .expect_err("members must be existing chats");
    assert_eq!(rejection.code, "unknown_session");

    let rejection = gateway
        .create_swarm(session_ids[0].clone(), vec![session_ids[1].clone()])
        .await
        .expect_err("leader must participate");
    assert_eq!(rejection.code, "invalid_swarm");

    let rejection = gateway
        .create_swarm(session_ids[0].clone(), vec![session_ids[0].clone(); 101])
        .await
        .expect_err("membership must be bounded before catalog lookup");
    assert_eq!(rejection.code, "invalid_swarm");

    let foreign_session_id = Uuid::new_v4().to_string();
    save_session(&gateway, &foreign_session_id, "workspace-b").await;
    let rejection = gateway
        .create_swarm(
            session_ids[0].clone(),
            vec![session_ids[0].clone(), foreign_session_id],
        )
        .await
        .expect_err("members must share one workspace");
    assert_eq!(rejection.code, "invalid_swarm");

    let child_session_id = Uuid::new_v4().to_string();
    let checkpoints = Arc::clone(&gateway.state.lock().await.checkpoints);
    let mut child = Checkpoint::empty(&child_session_id);
    child.session_context.workspace_id = Some("workspace-a".into());
    checkpoints
        .fork(&session_ids[0], 0, &child)
        .await
        .expect("fork child session");
    let rejection = gateway
        .create_swarm(
            session_ids[0].clone(),
            vec![session_ids[0].clone(), child_session_id],
        )
        .await
        .expect_err("members must be top-level chats");
    assert_eq!(rejection.code, "invalid_swarm");
}