2024-11-03 17:18:24 +01:00
|
|
|
use super::{
|
|
|
|
assert_or_err,
|
|
|
|
fixtures::{ClientFixture, ScopedFixture, WorkspaceFixture},
|
|
|
|
};
|
2024-10-30 13:21:09 +01:00
|
|
|
use crate::api::{AsyncReceiver, AsyncSender};
|
|
|
|
|
|
|
|
#[tokio::test]
|
2024-11-03 17:18:24 +01:00
|
|
|
async fn test_workspace_creation_and_lookup() {
|
|
|
|
ClientFixture::of("alice")
|
|
|
|
.with(|client| {
|
|
|
|
let client = client.clone();
|
2024-10-30 13:21:09 +01:00
|
|
|
|
|
|
|
async move {
|
2024-11-03 17:18:24 +01:00
|
|
|
let workspace_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
let wrong_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
|
|
|
client.create_workspace(&workspace_name).await?;
|
|
|
|
let ws = client.get_workspace(&workspace_name);
|
|
|
|
assert_or_err!(ws.is_some());
|
|
|
|
assert_or_err!(ws.unwrap().id() == workspace_name);
|
|
|
|
|
|
|
|
assert_or_err!(client.get_workspace(&wrong_name).is_none());
|
|
|
|
|
|
|
|
let wslist = client.fetch_owned_workspaces().await?;
|
|
|
|
assert_or_err!(wslist.len() == 1);
|
|
|
|
assert_or_err!(wslist.contains(&workspace_name));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
}
|
2024-11-03 17:20:09 +01:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_cant_create_same_workspace_more_than_once() {
|
|
|
|
ClientFixture::of("alice")
|
|
|
|
.with(|client| {
|
|
|
|
let client = client.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
let workspace_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
|
|
|
client.create_workspace(&workspace_name).await?;
|
|
|
|
assert_or_err!(client.create_workspace(workspace_name).await.is_err());
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
}
|
2024-11-03 17:20:30 +01:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_workspace_attach_and_active_workspaces() {
|
|
|
|
ClientFixture::of("alice")
|
|
|
|
.with(|client| {
|
|
|
|
let client = client.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
let workspace_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
|
|
|
client.create_workspace(&workspace_name).await?;
|
|
|
|
let ws = client.attach_workspace(&workspace_name).await?;
|
|
|
|
|
|
|
|
assert_or_err!(ws.id() == workspace_name);
|
|
|
|
assert_or_err!(client.active_workspaces().contains(&workspace_name));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
}
|
2024-11-03 17:21:06 +01:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_attaching_to_non_existing_is_error() {
|
|
|
|
ClientFixture::of("alice")
|
|
|
|
.with(|client| {
|
|
|
|
let client = client.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
let workspace_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
|
|
|
// we don't create any workspace.
|
|
|
|
// client.create_workspace(workspace_name).await?;
|
|
|
|
assert_or_err!(client.attach_workspace(&workspace_name).await.is_err());
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
}
|
2024-11-03 17:22:13 +01:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_attach_and_leave_workspace_interactions() {
|
|
|
|
ClientFixture::of("alice")
|
|
|
|
.with(|client| {
|
|
|
|
let client = client.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
let workspace_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
|
|
|
client.create_workspace(&workspace_name).await?;
|
|
|
|
// leaving a workspace you are not attached to, returns true
|
|
|
|
assert_or_err!(
|
|
|
|
client.leave_workspace(&workspace_name),
|
|
|
|
"leaving a workspace you are not attached to returned false, should return true."
|
|
|
|
);
|
|
|
|
|
|
|
|
// leaving a workspace you are attached to, returns true
|
|
|
|
// when there is only one reference to it.
|
|
|
|
client.attach_workspace(&workspace_name).await?;
|
|
|
|
assert_or_err!(
|
|
|
|
client.leave_workspace(&workspace_name),
|
|
|
|
"leaving a workspace with a single reference returned false."
|
|
|
|
);
|
|
|
|
|
|
|
|
// we are also implicitly testing repeated leaving and attaching
|
|
|
|
// to the same workspace.
|
|
|
|
let res = client.attach_workspace(&workspace_name).await;
|
|
|
|
assert_or_err!(res.is_ok(), "could not attach to the same workspace immediately after successfully leaving it.");
|
|
|
|
|
|
|
|
// we should have an extra reference here, which should make the
|
|
|
|
// consume return false.
|
|
|
|
let ws = res.unwrap();
|
|
|
|
assert_or_err!(
|
|
|
|
!client.leave_workspace(&workspace_name),
|
|
|
|
"leaving a workspace while some reference still exist returned true."
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
}
|
2024-11-03 17:22:41 +01:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_delete_empty_workspace() {
|
|
|
|
ClientFixture::of("alice")
|
|
|
|
.with(|client| {
|
|
|
|
let client = client.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
let workspace_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
|
|
|
client.create_workspace(&workspace_name).await?;
|
|
|
|
client.delete_workspace(&workspace_name).await?;
|
|
|
|
|
|
|
|
assert_or_err!(client.get_workspace(&workspace_name).is_none());
|
|
|
|
assert_or_err!(client.fetch_owned_workspaces().await?.is_empty());
|
|
|
|
|
2024-10-30 13:21:09 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
2024-11-03 17:22:41 +01:00
|
|
|
.await
|
|
|
|
}
|
2024-11-03 17:23:00 +01:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_deleting_twice_or_non_existing_is_an_error() {
|
|
|
|
ClientFixture::of("alice")
|
|
|
|
.with(|client| {
|
|
|
|
let client = client.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
let workspace_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
|
|
|
client.create_workspace(&workspace_name).await?;
|
|
|
|
client.delete_workspace(&workspace_name).await?;
|
|
|
|
|
|
|
|
assert_or_err!(client.delete_workspace(&workspace_name).await.is_err());
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
}
|
2024-11-03 17:24:08 +01:00
|
|
|
|
|
|
|
// Now we can begin using WorkspaceFixtures for with a single user.
|
|
|
|
|
|
|
|
// #[tokio::test]
|
|
|
|
// async fn test_delete_workspace_with_users_attached() {
|
|
|
|
// WorkspaceFixture::one("bob", "to-be-deleted")
|
|
|
|
// .with(|(client, workspace): &mut (crate::Client, crate::Workspace)| {
|
|
|
|
|
|
|
|
// async move {
|
|
|
|
// client.delete_workspace(workspace.id()).await?;
|
|
|
|
|
|
|
|
// // TODO: I Don't know what should happen here.
|
|
|
|
// Ok(())
|
|
|
|
// }
|
|
|
|
// })
|
|
|
|
// .await
|
|
|
|
// }
|
2024-11-03 17:24:47 +01:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_invite_user_to_workspace_and_invited_lookup() {
|
|
|
|
WorkspaceFixture::one("bob", "workspace-di-bob")
|
|
|
|
.with(
|
|
|
|
|(client_bob, workspace_bob): &mut (crate::Client, crate::Workspace)| {
|
|
|
|
let client_bob = client_bob.clone();
|
|
|
|
let workspace_bob = workspace_bob.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
let client_alice = ClientFixture::of("alice").setup().await?;
|
|
|
|
|
|
|
|
let wrong_workspace_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
// inviting to a non existing workspace is an error
|
|
|
|
assert_or_err!(client_bob
|
|
|
|
.invite_to_workspace(
|
|
|
|
wrong_workspace_name,
|
|
|
|
client_alice.current_user().name.clone(),
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.is_err());
|
|
|
|
|
|
|
|
client_bob
|
|
|
|
.invite_to_workspace(
|
|
|
|
workspace_bob.id(),
|
|
|
|
client_alice.current_user().name.clone(),
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
// there are two users now in the workspace of bob
|
|
|
|
// alice is one of the users
|
|
|
|
// bob is one of the users
|
|
|
|
// the workspace appears in the joined workspaces for alice
|
|
|
|
// the workspace does not appear in the owned workspaces for alice
|
|
|
|
|
|
|
|
let user_list = workspace_bob.fetch_users().await?;
|
|
|
|
assert_or_err!(user_list.len() == 2);
|
|
|
|
assert_or_err!(user_list
|
|
|
|
.iter()
|
|
|
|
.any(|u| u.name == client_alice.current_user().name));
|
|
|
|
assert_or_err!(user_list
|
|
|
|
.iter()
|
|
|
|
.any(|u| u.name == client_bob.current_user().name));
|
|
|
|
|
|
|
|
let alice_owned_workspaces = client_alice.fetch_owned_workspaces().await?;
|
|
|
|
let alice_invited_workspaces = client_alice.fetch_joined_workspaces().await?;
|
|
|
|
|
|
|
|
assert_or_err!(alice_owned_workspaces.is_empty());
|
|
|
|
assert_or_err!(alice_invited_workspaces.contains(&workspace_bob.id()));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now we can use workspace fixtures with invite.
|
2024-11-03 17:26:14 +01:00
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn cannot_delete_others_workspaces() {
|
|
|
|
WorkspaceFixture::two("alice", "bob", "test-cannot-delete-others-workspaces")
|
|
|
|
.with(|((_, ws_alice), (client_bob, _))| {
|
|
|
|
let ws_alice = ws_alice.clone();
|
|
|
|
let client_bob = client_bob.clone();
|
|
|
|
async move {
|
|
|
|
assert_or_err!(
|
|
|
|
client_bob.delete_workspace(&ws_alice.id()).await.is_err(),
|
|
|
|
"bob was allowed to delete a workspace he didn't own!"
|
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_buffer_search() {
|
|
|
|
WorkspaceFixture::one("alice", "test-buffer-search")
|
|
|
|
.with(
|
|
|
|
|(_, workspace_alice): &mut (crate::Client, crate::Workspace)| {
|
|
|
|
let buffer_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
let workspace_alice = workspace_alice.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
workspace_alice.create_buffer(&buffer_name).await?;
|
|
|
|
assert_or_err!(!workspace_alice
|
|
|
|
.search_buffers(Some(&buffer_name[0..4]))
|
|
|
|
.is_empty());
|
|
|
|
assert_or_err!(workspace_alice.search_buffers(Some("_")).is_empty());
|
|
|
|
workspace_alice.delete_buffer(&buffer_name).await?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
2024-10-30 13:21:09 +01:00
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_send_operation() {
|
2024-10-30 17:33:51 +01:00
|
|
|
WorkspaceFixture::two("alice", "bob", "test-send-operation")
|
2024-10-30 13:21:09 +01:00
|
|
|
.with(|((_, workspace_alice), (_, workspace_bob))| {
|
|
|
|
let buffer_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
let workspace_alice = workspace_alice.clone();
|
|
|
|
let workspace_bob = workspace_bob.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
workspace_alice.create_buffer(&buffer_name).await?;
|
|
|
|
let alice = workspace_alice.attach_buffer(&buffer_name).await?;
|
|
|
|
let bob = workspace_bob.attach_buffer(&buffer_name).await?;
|
|
|
|
|
|
|
|
alice.send(crate::api::TextChange {
|
|
|
|
start_idx: 0,
|
|
|
|
end_idx: 0,
|
|
|
|
content: "hello world".to_string(),
|
|
|
|
})?;
|
|
|
|
|
|
|
|
let result = bob.recv().await?;
|
|
|
|
assert_or_err!(result.change.start_idx == 0);
|
|
|
|
assert_or_err!(result.change.end_idx == 0);
|
|
|
|
assert_or_err!(result.change.content == "hello world");
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_content_converges() {
|
2024-10-30 17:33:51 +01:00
|
|
|
WorkspaceFixture::two("alice", "bob", "test-content-converges")
|
2024-10-30 13:21:09 +01:00
|
|
|
.with(|((_, workspace_alice), (_, workspace_bob))| {
|
|
|
|
let buffer_name = uuid::Uuid::new_v4().to_string();
|
|
|
|
let workspace_alice = workspace_alice.clone();
|
|
|
|
let workspace_bob = workspace_bob.clone();
|
|
|
|
|
|
|
|
async move {
|
|
|
|
workspace_alice.create_buffer(&buffer_name).await?;
|
|
|
|
let alice = workspace_alice.attach_buffer(&buffer_name).await?;
|
|
|
|
let bob = workspace_bob.attach_buffer(&buffer_name).await?;
|
|
|
|
|
|
|
|
let mut join_set = tokio::task::JoinSet::new();
|
|
|
|
|
|
|
|
let _alice = alice.clone();
|
|
|
|
join_set.spawn(async move {
|
|
|
|
for i in 0..10 {
|
|
|
|
_alice.content().await?;
|
|
|
|
_alice.send(crate::api::TextChange {
|
|
|
|
start_idx: 7 * i,
|
|
|
|
end_idx: 7 * i,
|
|
|
|
content: format!("alice{i} "), // TODO generate a random string instead!!
|
|
|
|
})?;
|
|
|
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
|
|
|
}
|
|
|
|
Ok::<(), crate::errors::ControllerError>(())
|
|
|
|
});
|
|
|
|
|
|
|
|
let _bob = bob.clone();
|
|
|
|
join_set.spawn(async move {
|
|
|
|
for i in 0..10 {
|
|
|
|
_bob.content().await?;
|
|
|
|
_bob.send(crate::api::TextChange {
|
|
|
|
start_idx: 5 * i,
|
|
|
|
end_idx: 5 * i,
|
|
|
|
content: format!("bob{i} "), // TODO generate a random string instead!!
|
|
|
|
})?;
|
|
|
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
|
|
|
}
|
|
|
|
Ok::<(), crate::errors::ControllerError>(())
|
|
|
|
});
|
|
|
|
|
|
|
|
while let Some(x) = join_set.join_next().await {
|
|
|
|
x??;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO is there a nicer way to make sure we received all changes?
|
|
|
|
|
2024-10-30 14:22:51 +01:00
|
|
|
for i in 0..20 {
|
|
|
|
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
|
2024-10-30 13:21:09 +01:00
|
|
|
match bob.try_recv().await? {
|
|
|
|
Some(change) => bob.ack(change.version),
|
|
|
|
None => break,
|
|
|
|
}
|
|
|
|
eprintln!("bob more to recv at attempt #{i}");
|
|
|
|
}
|
|
|
|
|
2024-10-30 14:22:51 +01:00
|
|
|
for i in 0..20 {
|
|
|
|
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
|
2024-10-30 13:21:09 +01:00
|
|
|
match alice.try_recv().await? {
|
|
|
|
Some(change) => alice.ack(change.version),
|
|
|
|
None => break,
|
|
|
|
}
|
|
|
|
eprintln!("alice more to recv at attempt #{i}");
|
|
|
|
}
|
|
|
|
|
|
|
|
let alice_content = alice.content().await?;
|
|
|
|
let bob_content = bob.content().await?;
|
|
|
|
|
|
|
|
eprintln!("alice: {alice_content}");
|
|
|
|
eprintln!("bob : {bob_content}");
|
|
|
|
|
|
|
|
assert_or_err!(alice_content == bob_content);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|