AppFlowy-Cloud/tests/workspace/import_test.rs

288 lines
9.2 KiB
Rust

use anyhow::Error;
use client_api_test::TestClient;
use collab_document::importer::define::{BlockType, URL_FIELD};
use collab_folder::ViewLayout;
use std::path::PathBuf;
use std::time::Duration;
// #[tokio::test]
// async fn import_blog_post_four_times_test() {
// let mut handles = vec![];
// // Simulate 4 clients, each uploading 3 files concurrently.
// for _ in 0..4 {
// let handle = tokio::spawn(async {
// let client = TestClient::new_user().await;
// for _ in 0..3 {
// let _ = upload_file(&client, "blog_post.zip", None).await.unwrap();
// }
//
// // the default concurrency limit is 3, so the fourth import should fail
// upload_file(&client, "blog_post.zip", None).await.unwrap();
// wait_until_num_import_task_complete(&client, 3).await;
// });
// handles.push(handle);
// }
//
// for result in join_all(handles).await {
// result.unwrap();
// }
// }
#[tokio::test]
async fn import_blog_post_test() {
// Step 1: Import the blog post zip
let (client, imported_workspace_id) = import_notion_zip_until_complete("blog_post.zip").await;
// Step 2: Fetch the folder and views
let folder = client.get_folder(&imported_workspace_id).await;
let mut space_views = folder.get_views_belong_to(&imported_workspace_id);
assert_eq!(
space_views.len(),
1,
"Expected 1 view, found {:?}",
space_views
);
// Step 3: Validate the space view name
let space_view = space_views.pop().unwrap();
assert_eq!(space_view.name, "Imported Space");
// Step 4: Fetch the imported view and document
let imported_view = folder.get_views_belong_to(&space_view.id).pop().unwrap();
let document = client
.get_document(&imported_workspace_id, &imported_view.id)
.await;
// Step 5: Generate the expected blob URLs
let host = client.api_client.base_url.clone();
let object_id = imported_view.id.clone();
let blob_names = vec![
"PGTRCFsf2duc7iP3KjE62Xs8LE7B96a0aQtLtGtfIcw=.jpg",
"fFWPgqwdqbaxPe7Q_vUO143Sa2FypnRcWVibuZYdkRI=.jpg",
"EIj9Z3yj8Gw8UW60U8CLXx7ulckEs5Eu84LCFddCXII=.jpg",
];
let mut expected_urls = blob_names
.iter()
.map(|s| format!("{host}/api/file_storage/{imported_workspace_id}/v1/blob/{object_id}/{s}"))
.collect::<Vec<String>>();
// Step 6: Concurrently fetch blobs
let fetch_blob_futures = blob_names.into_iter().map(|blob_name| {
client
.api_client
.get_blob_v1(&imported_workspace_id, &object_id, blob_name)
});
let blob_results = futures::future::join_all(fetch_blob_futures).await;
// Ensure all blobs are fetched successfully
for result in blob_results {
result.unwrap();
}
// Step 7: Extract block URLs from the document and filter expected URLs
let page_block_id = document.get_page_id().unwrap();
let block_ids = document.get_block_children_ids(&page_block_id);
for block_id in block_ids.iter() {
if let Some((block_type, block_data)) = document.get_block_data(block_id) {
if matches!(block_type, BlockType::Image) {
let url = block_data.get(URL_FIELD).unwrap().as_str().unwrap();
expected_urls.retain(|allowed_url| !url.contains(allowed_url));
}
}
}
// Step 8: Ensure no expected URLs remain
assert!(
expected_urls.is_empty(),
"expected URLs to be empty: {:?}",
expected_urls
);
}
#[tokio::test]
async fn import_project_and_task_zip_test() {
let (client, imported_workspace_id) = import_notion_zip_until_complete("project&task.zip").await;
let folder = client.get_folder(&imported_workspace_id).await;
let workspace_database = client.get_workspace_database(&imported_workspace_id).await;
let space_views = folder.get_views_belong_to(&imported_workspace_id);
assert_eq!(
space_views.len(),
1,
"Expected 1 view, found {:?}",
space_views
);
assert_eq!(space_views[0].name, "Imported Space");
assert!(space_views[0].space_info().is_some());
let mut sub_views = folder.get_views_belong_to(&space_views[0].id);
let imported_view = sub_views.pop().unwrap();
assert_eq!(imported_view.name, "Projects & Tasks");
assert_eq!(
imported_view.children.len(),
2,
"Expected 2 views, found {:?}",
imported_view.children
);
assert_eq!(imported_view.layout, ViewLayout::Document);
let sub_views = folder.get_views_belong_to(&imported_view.id);
for (index, view) in sub_views.iter().enumerate() {
if index == 0 {
assert_eq!(view.name, "Projects");
assert_eq!(view.layout, ViewLayout::Grid);
let database_id = workspace_database
.get_database_meta_with_view_id(&view.id)
.unwrap()
.database_id
.clone();
let database = client
.get_database(&imported_workspace_id, &database_id)
.await;
let inline_views = database.get_inline_view_id();
let fields = database.get_fields_in_view(&inline_views, None);
let rows = database.collect_all_rows().await;
assert_eq!(rows.len(), 4);
assert_eq!(fields.len(), 13);
continue;
}
if index == 1 {
assert_eq!(view.name, "Tasks");
assert_eq!(view.layout, ViewLayout::Grid);
let database_id = workspace_database
.get_database_meta_with_view_id(&view.id)
.unwrap()
.database_id
.clone();
let database = client
.get_database(&imported_workspace_id, &database_id)
.await;
let inline_views = database.get_inline_view_id();
let fields = database.get_fields_in_view(&inline_views, None);
let rows = database.collect_all_rows().await;
assert_eq!(rows.len(), 17);
assert_eq!(fields.len(), 13);
continue;
}
panic!("Unexpected view found: {:?}", view);
}
}
#[tokio::test]
async fn imported_workspace_do_not_become_latest_visit_workspace_test() {
let client = TestClient::new_user().await;
let file_path = PathBuf::from("tests/workspace/asset/blog_post.zip".to_string());
client.api_client.import_file(&file_path).await.unwrap();
// When importing a Notion file, a new task is spawned to create a workspace for the imported data.
// However, the workspace should remain hidden until the import is completed successfully.
let user_workspace = client.get_user_workspace_info().await;
let visiting_workspace_id = user_workspace.visiting_workspace.workspace_id;
assert_eq!(user_workspace.workspaces.len(), 1);
assert_eq!(
user_workspace.visiting_workspace.workspace_id,
user_workspace.workspaces[0].workspace_id
);
wait_until_num_import_task_complete(&client, 1).await;
// after the workspace was imported, then the workspace should be visible
let user_workspace = client.get_user_workspace_info().await;
assert_eq!(user_workspace.workspaces.len(), 2);
assert_eq!(
user_workspace.visiting_workspace.workspace_id,
visiting_workspace_id,
);
}
#[allow(dead_code)]
async fn upload_file(
client: &TestClient,
name: &str,
upload_after_secs: Option<u64>,
) -> Result<(), Error> {
let file_path = PathBuf::from(format!("tests/workspace/asset/{name}"));
let mut url = client
.api_client
.create_import(&file_path)
.await?
.presigned_url;
if url.contains("http://minio:9000") {
url = url.replace("http://minio:9000", "http://localhost/minio");
}
if let Some(secs) = upload_after_secs {
tokio::time::sleep(Duration::from_secs(secs)).await;
}
client
.api_client
.upload_import_file(&file_path, &url)
.await?;
Ok(())
}
// upload_after_secs: simulate the delay of uploading the file
async fn import_notion_zip_until_complete(name: &str) -> (TestClient, String) {
let client = TestClient::new_user().await;
let file_path = PathBuf::from(format!("tests/workspace/asset/{name}"));
client.api_client.import_file(&file_path).await.unwrap();
let default_workspace_id = client.workspace_id().await;
// when importing a file, the workspace for the file should be created and it's
// not visible until the import task is completed
let workspaces = client.api_client.get_workspaces().await.unwrap();
assert_eq!(workspaces.len(), 1);
let tasks = client.api_client.get_import_list().await.unwrap().tasks;
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].status, 0);
wait_until_num_import_task_complete(&client, 1).await;
// after the import task is completed, the new workspace should be visible
let workspaces = client.api_client.get_workspaces().await.unwrap();
assert_eq!(workspaces.len(), 2);
let imported_workspace = workspaces
.into_iter()
.find(|workspace| workspace.workspace_id.to_string() != default_workspace_id)
.expect("Failed to find imported workspace");
let imported_workspace_id = imported_workspace.workspace_id.to_string();
(client, imported_workspace_id)
}
async fn wait_until_num_import_task_complete(client: &TestClient, num: usize) {
let mut task_completed = false;
let max_retries = 12;
let mut retries = 0;
while !task_completed {
tokio::time::sleep(Duration::from_secs(10)).await;
let tasks = client.api_client.get_import_list().await.unwrap().tasks;
assert_eq!(tasks.len(), num);
if tasks[0].status == 1 {
task_completed = true;
}
retries += 1;
if retries > max_retries {
eprintln!("{:?}", tasks);
break;
}
}
assert!(
task_completed,
"The import task was not completed within the expected time."
);
}