1984 lines · 75.1 KB · Rust
RawDownload
use std::sync::Arc;use std::time::Duration; use chrono::Utc;use cloud_object_client::MockObjectClient;use lazy_static::lazy_static;use mockall::Sequence;use rand::Rng;use settings::{RespectUserSyncSetting, SyncToCloud};use warpui::{App, ModelHandle}; use super::*;use crate::auth::auth_manager::AuthManager;use crate::auth::user::TEST_USER_UID;use crate::auth::{AuthStateProvider, UserUid};use crate::cloud_object::model::actions::ObjectActions;use crate::cloud_object::model::generic_string_model::GenericStringModel;use crate::cloud_object::model::view::{    CloudViewModel, EDITOR_TIMEOUT_DURATION_MINUTES, EditorState, UpdateTimestamp,};use crate::cloud_object::{    CloudObjectMetadata, CloudObjectPermissions, CloudObjectStatuses, CloudObjectSyncStatus,    NumInFlightRequests, ObjectIdType, Owner, ServerMetadata, ServerPermissions,};use crate::drive::DriveIndexVariant;use crate::drive::folders::{CloudFolderModel, FolderId};use crate::features::FeatureFlag;use crate::notebooks::{CloudNotebookModel, NotebookId};use crate::server::cloud_objects::listener::ObjectUpdateMessage;use crate::server::cloud_objects::update_manager::InitialLoadResponse;use crate::server::ids::{ServerId, ServerIdAndType};use crate::server::server_api::ServerApiProvider;use crate::server::server_api::object::ObjectClient;use crate::server::server_api::team::MockTeamClient;use crate::server::server_api::workspace::MockWorkspaceClient;use crate::server::sync_queue::SyncQueue;use crate::server::telemetry::context_provider::AppTelemetryContextProvider;use crate::settings::{Preference, init_and_register_user_preferences};use crate::system::SystemStats;use crate::workflows::CloudWorkflowModel;use crate::workspaces::team::Team;use crate::workspaces::team_tester::TeamTesterStatus;use crate::workspaces::user_profiles::UserProfiles;use crate::workspaces::user_workspaces::UserWorkspaces;use crate::workspaces::workspace::{Workspace, WorkspaceUid};use crate::{NetworkStatus, UpdateManager}; fn create_cloud_model(    app: &mut App,    objects: Vec<Box<dyn CloudObject>>,) -> ModelHandle<CloudModel> {    // Make sure to register the CloudModel singleton - some CloudObject methods    // find it and other dependencies via the AppContext.    app.add_singleton_model(|_ctx| CloudModel::new(None, objects, None))} lazy_static! {    /// Mock the user being on _a_ team in tests, so that the team drive is available.    /// Otherwise, any team objects will appear shared.    static ref TEST_TEAM: Team = Team::from_local_cache(        ServerId::from(1),        "Test Team".to_string(),        None,        None,        None,        None,    );     static ref TEST_WORKSPACE: Workspace = Workspace::from_local_cache(        WorkspaceUid::from(ServerId::from(1)),        "Test Workspace".to_string(),        Some(vec![TEST_TEAM.clone()]),        None,    );} fn initialize_app(    app: &mut App,    cached_objects: Vec<Box<dyn CloudObject>>,    cloud_object_server_api_mock: Arc<impl ObjectClient>,) {    let team_client_mock = Arc::new(MockTeamClient::new());    let workspace_client_mock = Arc::new(MockWorkspaceClient::new());     // Add the necessary singleton models to the App    app.add_singleton_model(|_| NetworkStatus::new());    app.add_singleton_model(|_| SystemStats::new());    app.add_singleton_model(|_| ServerApiProvider::new_for_test());    app.add_singleton_model(|_| AuthStateProvider::new_for_test());    app.add_singleton_model(AppTelemetryContextProvider::new_context_provider);    app.add_singleton_model(AuthManager::new_for_test);    app.add_singleton_model(|ctx| {        UserWorkspaces::mock(            team_client_mock.clone(),            workspace_client_mock.clone(),            vec![TEST_WORKSPACE.clone()],            ctx,        )    });    app.add_singleton_model(TeamTesterStatus::new);    app.add_singleton_model(SyncQueue::mock);    app.add_singleton_model(|_ctx| CloudModel::new(None, cached_objects, None));    app.add_singleton_model(|ctx| UpdateManager::new(None, cloud_object_server_api_mock, ctx));    app.add_singleton_model(|_| UserProfiles::new(Vec::new()));    app.add_singleton_model(CloudViewModel::new);    app.add_singleton_model(|_| ObjectActions::new(Vec::new()));     // The start of polling is normally triggered by authentication completion, but    // we need to do it manually for tests.    TeamTesterStatus::handle(app).update(app, |team_tester, ctx| {        team_tester.initiate_data_pollers(false, ctx);    });} fn mock_random_workflows(start_id: i64, owner: Owner) -> Vec<ServerWorkflow> {    let mut rng = rand::thread_rng();    // pick how many workflows to generate at random    let number_of_workflows = rng.gen_range(1..10);    mock_server_workflows(start_id, owner, number_of_workflows)} fn mock_server_metadata() -> ServerMetadata {    ServerMetadata {        uid: ServerId::default(),        revision: Revision::now(),        metadata_last_updated_ts: Utc::now().into(),        trashed_ts: None,        folder_id: None,        is_welcome_object: false,        creator_uid: None,        last_editor_uid: None,        current_editor_uid: None,    }} fn mock_server_permissions(owner: Owner) -> ServerPermissions {    ServerPermissions {        space: owner,        guests: Vec::new(),        permissions_last_updated_ts: Utc::now().into(),        anyone_link_sharing: None,    }} fn mock_permissions() -> CloudObjectPermissions {    CloudObjectPermissions {        owner: Owner::mock_current_user(),        guests: Vec::new(),        permissions_last_updated_ts: None,        anyone_with_link: None,    }} fn mock_server_workflows(    start_id: i64,    owner: Owner,    number_of_workflows: i64,) -> Vec<ServerWorkflow> {    (0..number_of_workflows)        .map(|idx| {            ServerWorkflow::new(                SyncId::ServerId((start_id + idx).into()),                CloudWorkflowModel::new(Workflow::new(                    format!("w{}", start_id + idx),                    format!("c{}", start_id + idx),                )),                mock_server_metadata(),                mock_server_permissions(owner),            )        })        .collect()} fn mock_random_folders(start_id: i64, owner: Owner) -> Vec<ServerFolder> {    let mut rng = rand::thread_rng();    // pick how many folders to generate at random    let number_of_workflows = rng.gen_range(1..10);    mock_server_folders(start_id, owner, number_of_workflows)} fn mock_server_folders(start_id: i64, owner: Owner, number_of_folders: i64) -> Vec<ServerFolder> {    (0..number_of_folders)        .map(|idx| {            ServerFolder::new(                SyncId::ServerId((start_id + idx).into()),                CloudFolderModel::new(&format!("f{}", start_id + idx), false),                mock_server_metadata(),                mock_server_permissions(owner),            )        })        .collect()} fn mock_server_notebooks() -> Vec<ServerNotebook> {    let owner = Owner::mock_current_user();    vec![        ServerNotebook::new(            SyncId::ServerId(1.into()),            CloudNotebookModel {                title: "t1".to_string(),                data: "d1".to_string(),                ai_document_id: None,                conversation_id: None,            },            mock_server_metadata(),            mock_server_permissions(owner),        ),        ServerNotebook::new(            SyncId::ServerId(2.into()),            CloudNotebookModel {                title: "t2".to_string(),                data: "d2".to_string(),                ai_document_id: None,                conversation_id: None,            },            mock_server_metadata(),            mock_server_permissions(owner),        ),        ServerNotebook::new(            SyncId::ServerId(3.into()),            CloudNotebookModel {                title: "t3".to_string(),                data: "d3".to_string(),                ai_document_id: None,                conversation_id: None,            },            mock_server_metadata(),            mock_server_permissions(owner),        ),        ServerNotebook::new(            SyncId::ServerId(4.into()),            CloudNotebookModel {                title: "t4".to_string(),                data: "d4".to_string(),                ai_document_id: None,                conversation_id: None,            },            mock_server_metadata(),            mock_server_permissions(owner),        ),    ]} fn mock_cloud_folder(id: SyncId, name: String, folder_id: Option<SyncId>) -> CloudFolder {    CloudFolder::new(        id,        CloudFolderModel {            name,            is_open: true,            is_warp_pack: false,        },        CloudObjectMetadata {            pending_changes_statuses: CloudObjectStatuses {                content_sync_status: CloudObjectSyncStatus::NoLocalChanges,                has_pending_metadata_change: false,                has_pending_permissions_change: false,                pending_untrash: false,                pending_delete: false,            },            folder_id,            revision: Default::default(),            metadata_last_updated_ts: Default::default(),            current_editor_uid: Default::default(),            trashed_ts: Default::default(),            is_welcome_object: false,            creator_uid: None,            last_editor_uid: None,            last_task_run_ts: None,        },        mock_permissions(),    )} fn mock_cloud_notebook(id: SyncId, title: String, folder_id: Option<SyncId>) -> CloudNotebook {    CloudNotebook::new(        id,        CloudNotebookModel {            title,            data: "test".into(),            ai_document_id: None,            conversation_id: None,        },        CloudObjectMetadata {            pending_changes_statuses: CloudObjectStatuses {                content_sync_status: CloudObjectSyncStatus::NoLocalChanges,                has_pending_metadata_change: false,                has_pending_permissions_change: false,                pending_untrash: false,                pending_delete: false,            },            folder_id,            revision: Default::default(),            metadata_last_updated_ts: Default::default(),            current_editor_uid: Default::default(),            trashed_ts: Default::default(),            is_welcome_object: false,            creator_uid: None,            last_editor_uid: None,            last_task_run_ts: None,        },        mock_permissions(),    )} fn mock_trashed_cloud_folder(id: SyncId, name: String, folder_id: Option<SyncId>) -> CloudFolder {    let mut folder = mock_cloud_folder(id, name, folder_id);    folder.metadata.trashed_ts = Some(ServerTimestamp::from_unix_timestamp_micros(10).unwrap());    folder} fn folder_from_cloud_model(model: &CloudModel, id: SyncId) -> &CloudFolder {    model.get_folder_by_uid(&id.uid()).expect("is a folder")} /// Mock receiving an RTC update. These tests update objects by mocking RTC messages so that they/// don't need to mock the server API for updates. The unit tests for [`UpdateManager`] ensure that/// updates from both RTC and client actions emit the same events.fn receive_rtc_update(message: ObjectUpdateMessage, app: &mut App) {    UpdateManager::handle(app).update(app, |update_manager, ctx| {        update_manager.received_message_from_server(message, ctx)    });} fn move_object(id: ServerId, folder_id: Option<FolderId>, app: &mut App) {    let message = CloudModel::handle(app).read(app, |cloud_model, _| {        let object = cloud_model            .get_by_uid(&id.uid())            .expect("Expected object to exist in cloud model");         let metadata = ServerMetadata {            uid: id,            revision: object.metadata().revision.expect("Revision is required"),            current_editor_uid: object.metadata().current_editor_uid.clone(),            metadata_last_updated_ts: (object                .metadata()                .metadata_last_updated_ts                .expect("Metadata TS is required")                .utc()                + chrono::Duration::seconds(1))            .into(),            trashed_ts: object.metadata().trashed_ts,            folder_id,            is_welcome_object: object.metadata().is_welcome_object,            creator_uid: object.metadata().creator_uid.clone(),            last_editor_uid: object.metadata().last_editor_uid.clone(),        };         ObjectUpdateMessage::ObjectMetadataChanged { metadata }    });     receive_rtc_update(message, app);} #[test]fn test_update_with_deleted_objects() {    let workflows = mock_server_workflows(        5,        Owner::Team {            team_uid: ServerId::from(1),        },        3,    );    let notebooks = mock_server_notebooks();     App::test((), |mut app| async move {        let cloud_model = create_cloud_model(            &mut app,            workflows                .iter()                .map(|workflow| CloudWorkflow::new_from_server(workflow.clone()))                .map(|o| Box::new(o) as Box<dyn CloudObject>)                .collect(),        );        cloud_model.update(&mut app, |model, ctx| {            for notebook in notebooks.clone() {                model.upsert_from_server_notebook(notebook, ctx);            }        });         // Validate there's some notebooks and workflows in memory        cloud_model.read(&app, |cloud_model, _| {            assert_eq!(                3,                cloud_model.get_all_active_and_inactive_workflows().count()            );            assert_eq!(                4,                cloud_model.get_all_active_and_inactive_notebooks().count()            );            assert_eq!(7, cloud_model.as_cloud_objects().count());        });         // Apply the "update from server"        cloud_model.update(&mut app, |cloud_model, ctx| {            // Set 3rd notebook to have pending changes. This should keep it in memory,            // even though it's not returned from the server.            let notebook_id: SyncId = SyncId::ServerId(3.into());            if let Some(object) = cloud_model.get_notebook_mut(&notebook_id) {                object.set_pending_content_changes_status(CloudObjectSyncStatus::InFlight(                    NumInFlightRequests(1),                ));            }            cloud_model.update_objects(notebooks.into_iter().take(2), ctx);            cloud_model.update_objects(workflows.into_iter().take(2), ctx);        });         cloud_model.read(&app, |cloud_model, _| {            // expected: 3rd workflow was removed on the server, and so we don't want it in            // memory            assert_eq!(                2,                cloud_model.get_all_active_and_inactive_workflows().count()            );            // expected: 3rd notebook has local changes, so we want to keep it, but 4th            // doesn't and also wasn't returned from the server, so we want to remove it.            assert_eq!(                3,                cloud_model.get_all_active_and_inactive_notebooks().count()            );            assert_eq!(5, cloud_model.as_cloud_objects().count());        });    })} #[test]fn test_update_object_server_id_for_notebook() {    let client_id = ClientId::new();    let server_id: NotebookId = 1.into();    let notebooks: Vec<Box<dyn CloudObject>> = vec![Box::new(CloudNotebook::new(        SyncId::ClientId(client_id),        CloudNotebookModel {            title: "t1".to_string(),            data: "d1".to_string(),            ai_document_id: None,            conversation_id: None,        },        CloudObjectMetadata {            pending_changes_statuses: CloudObjectStatuses {                content_sync_status: CloudObjectSyncStatus::NoLocalChanges,                has_pending_metadata_change: false,                has_pending_permissions_change: false,                pending_untrash: false,                pending_delete: false,            },            folder_id: Default::default(),            revision: Default::default(),            metadata_last_updated_ts: Default::default(),            current_editor_uid: Default::default(),            trashed_ts: Default::default(),            is_welcome_object: false,            creator_uid: None,            last_editor_uid: None,            last_task_run_ts: None,        },        mock_permissions(),    ))];     App::test((), |mut app| async move {        let cloud_model = create_cloud_model(&mut app, notebooks);        cloud_model.update(&mut app, |model, ctx| {            model.update_object_after_server_creation(                client_id,                ServerCreationInfo {                    creator_uid: None,                    permissions: ServerPermissions::mock_personal(),                    server_id_and_type: ServerIdAndType {                        id: server_id.to_server_id(),                        id_type: ObjectIdType::Notebook,                    },                },                ctx,            )        });         cloud_model.read(&app, |model, _| {            let notebook = model                .get_notebook(&SyncId::ServerId(server_id.into()))                .unwrap();            assert_eq!(notebook.id, SyncId::ServerId(server_id.into()));        });    })} #[test]fn test_create_json_object() {    let client_id = ClientId::default();    let id = SyncId::ClientId(client_id);    let json_object: Box<dyn CloudObject> = Box::new(CloudPreference::new(        id,        GenericStringModel::new(            Preference::new(                "test_storage_key".to_owned(),                "{\"test_key\": \"test_value\"}",                SyncToCloud::Globally(RespectUserSyncSetting::Yes),            )            .expect("error creating preference"),        ),        CloudObjectMetadata {            pending_changes_statuses: CloudObjectStatuses {                content_sync_status: CloudObjectSyncStatus::NoLocalChanges,

Lines 1–500 of 1984.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t