//! Convex workspace / control-plane / compat transport bridge. //! //! local-first 主链下,本地 Markdown 正文、附件和资源文件不以这里作为真源。 //! 这个 crate 只保留用于 Convex workspace、历史 documents.* 兼容命令、 //! control-plane 查询和迁移期执行计划映射。 pub mod context; pub mod mapping; pub mod read_path; pub mod types; pub mod validation; pub mod write_path; pub use context::BridgeContext; pub use mapping::{ map_command_name_to_convex, map_query_name_to_convex, ConvexMutationRequest, ConvexQueryRequest, }; pub use read_path::{build_query_request, QueryReadResult}; pub use types::{BridgeError, BridgeErrorKind, BridgeResult}; pub use validation::{validate_command_envelope, validate_query_envelope}; pub use write_path::{ build_command_log_record, build_domain_event_record, build_write_pipeline, build_write_request, WritePipelineResult, }; #[cfg(test)] mod tests { use super::*; use core_protocol::{ command::{ CopyTreeDocumentPages, CreateDocumentPage, CreatePage, CreateWorkspace, DeleteDocumentPage, DuplicateDocumentPage, MoveDocumentPage, PatchPageBlock, PutMindmap, ReplaceMediaAssetStorage, RestoreDocumentPage, SavePageContent, UpdatePageOptions, UpdatePageStats, UpdatePageTitle, }, query::{ GetBlock, GetBridgeCommand, GetBridgeRequest, GetBridgeTrace, GetMindmap, GetPage, GetPageContent, GetPageMeta, ListBridgeWorkspaceOverview, ListSidebarDataset, SearchDocuments, SearchRecent, }, ActorPayload, CommandEnvelope, QueryEnvelope, SourcePayload, TargetRef, }; fn demo_context() -> BridgeContext { BridgeContext { deployment_id: Some("dep_1".into()), project_id: Some("proj_1".into()), request_id: "req_1".into(), trace_id: "trace_1".into(), actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), workspace_id: Some("ws_1".into()), tenant_id: Some("tenant_1".into()), auth_token: Some("token_1".into()), source_channel: "cli".into(), source_client: "mnote-cli".into(), idempotency_key: Some("idem_ctx".into()), validate_only: false, dry_run: false, } } #[test] fn build_write_request_keeps_workspace_scope() { let command = CommandEnvelope { name: "create_page".into(), command_id: "cmd_1".into(), idempotency_key: Some("idem_cmd".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "cli".into(), client: "mnote-cli".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: None, block_id: None, }), payload: CreatePage { title: "第一页".into(), parent_page_id: None, position: None, }, reason: Some("初始化页面".into()), refs: vec!["spec:phase1-f".into()], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "pages:create"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert_eq!(request.deployment_id.as_deref(), Some("dep_1")); assert_eq!(request.project_id.as_deref(), Some("proj_1")); assert_eq!(request.idempotency_key.as_deref(), Some("idem_cmd")); assert!(request.payload_json.contains("\"kind\":\"command\"")); assert!(request.payload_json.contains("\"name\":\"create_page\"")); } #[test] fn build_query_request_includes_trace_and_workspace() { let query = QueryEnvelope { name: "get_page".into(), payload: GetPage { page_id: "page_1".into(), }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "pages:get"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert_eq!(request.request_id, "req_1"); assert_eq!(request.trace_id, "trace_1"); assert!(request.payload_json.contains("\"kind\":\"query\"")); assert!(request.payload_json.contains("\"name\":\"get_page\"")); } #[test] fn build_write_pipeline_sets_trace_fields() { let command = CommandEnvelope { name: "create_workspace".into(), command_id: "cmd_2".into(), idempotency_key: None, actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "cli".into(), client: "mnote-cli".into(), }, target: None, payload: CreateWorkspace { name: "demo".into(), slug: Some("demo".into()), }, reason: Some("初始化工作区".into()), refs: vec![], dry_run: false, validate_only: false, }; let pipeline = build_write_pipeline(&demo_context(), &command).expect("pipeline should build"); assert_eq!(pipeline.command_log.command_name, "create_workspace"); assert!(pipeline .command_log .payload_summary .contains("request_id=req_1")); assert_eq!(pipeline.command_log.trace_id, "trace_1"); assert_eq!(pipeline.command_log.request_id, "req_1"); assert!(pipeline .domain_event .payload_json .contains("\"trace_id\":\"trace_1\"")); assert_eq!(pipeline.result.event_ids, vec!["evt_cmd_2".to_string()]); } #[test] fn document_title_command_maps_to_documents_update_title() { let command = CommandEnvelope { name: "documents.title.update".into(), command_id: "cmd_title_1".into(), idempotency_key: Some("idem_title".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_1".into()), block_id: None, }), payload: UpdatePageTitle { page_id: "page_1".into(), title: "新标题".into(), }, reason: None, refs: vec![], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "documents:updateTitle"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"documents.title.update\"")); } #[test] fn page_head_update_title_command_maps_to_documents_update_title() { let command = CommandEnvelope { name: "page.head.updateTitle".into(), command_id: "cmd_page_title_1".into(), idempotency_key: Some("idem_page_title".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_1".into()), block_id: None, }), payload: UpdatePageTitle { page_id: "page_1".into(), title: "新标题".into(), }, reason: None, refs: vec![], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "documents:updateTitle"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"page.head.updateTitle\"")); } #[test] fn tree_resource_lifecycle_commands_have_convex_mapping() { let cases = [ ("tree.resource.archive", "mediaAssets:patchById"), ("tree.resource.restore", "mediaAssets:patchById"), ("tree.resource.purge", "mediaAssets:purgeById"), ("tree.resource.rename", "mediaAssets:patchById"), ]; for (command_name, function_name) in cases { let command = CommandEnvelope { name: command_name.into(), command_id: format!("cmd_{command_name}"), idempotency_key: Some(format!("idem_{command_name}")), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("doc_1".into()), block_id: None, }), payload: 1, reason: None, refs: vec![], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, function_name); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains(&format!("\"name\":\"{command_name}\""))); } } #[test] fn document_save_command_maps_to_documents_update_content() { let command = CommandEnvelope { name: "documents.save".into(), command_id: "cmd_save_1".into(), idempotency_key: Some("idem_save".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_1".into()), block_id: None, }), payload: SavePageContent { page_id: "page_1".into(), workspace_id: Some("ws_1".into()), revision: Some(42), content_json: "{\"type\":\"doc\"}".into(), conflict_detection_key: Some("rev:42".into()), }, reason: Some("保存正文".into()), refs: vec!["checklist:c3".into()], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "documents:updateContent"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request.payload_json.contains("\"name\":\"documents.save\"")); } #[test] fn page_body_save_command_maps_to_documents_update_content() { let command = CommandEnvelope { name: "page.body.save".into(), command_id: "cmd_page_save_1".into(), idempotency_key: Some("idem_page_save".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_1".into()), block_id: None, }), payload: SavePageContent { page_id: "page_1".into(), workspace_id: Some("ws_1".into()), revision: Some(42), content_json: "{\"type\":\"doc\"}".into(), conflict_detection_key: Some("rev:42".into()), }, reason: Some("保存正文".into()), refs: vec!["checklist:c3".into()], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "documents:updateContent"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request.payload_json.contains("\"name\":\"page.body.save\"")); } #[test] fn document_embed_command_maps_to_documents_update_content() { let command = CommandEnvelope { name: "documents.embed".into(), command_id: "cmd_embed_1".into(), idempotency_key: Some("idem_embed".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_2".into()), block_id: None, }), payload: SavePageContent { page_id: "page_2".into(), workspace_id: Some("ws_1".into()), revision: Some(7), content_json: "{\"blocks\":[{\"id\":\"block_1\",\"type\":\"pageReference\"}]}" .into(), conflict_detection_key: Some("rev:7".into()), }, reason: Some("嵌入页面".into()), refs: vec!["checklist:p1-3".into()], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "documents:updateContent"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"documents.embed\"")); } #[test] fn page_lifecycle_commands_map_to_document_mutations() { let base_actor = ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }; let base_source = SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }; let create_request = build_write_request( &demo_context(), &CommandEnvelope { name: "documents.create".into(), command_id: "cmd_create_1".into(), idempotency_key: Some("idem_create".into()), actor: base_actor.clone(), source: base_source.clone(), target: Some(TargetRef { workspace_id: None, page_id: Some("page_1".into()), block_id: None, }), payload: CreateDocumentPage { page_id: "page_1".into(), parent_page_id: Some("parent_1".into()), title: "无标题".into(), workspace_seed_id: "ws_seed_1".into(), }, reason: Some("创建页面".into()), refs: vec![], dry_run: false, validate_only: false, }, ) .expect("create request should build"); assert_eq!( create_request.function_name, "documents:createWithParentReference" ); let move_request = build_write_request( &demo_context(), &CommandEnvelope { name: "documents.move".into(), command_id: "cmd_move_1".into(), idempotency_key: Some("idem_move".into()), actor: base_actor.clone(), source: base_source.clone(), target: Some(TargetRef { workspace_id: None, page_id: Some("page_1".into()), block_id: None, }), payload: MoveDocumentPage { page_id: "page_1".into(), parent_page_id: Some("parent_2".into()), sort_order: 3, }, reason: Some("移动页面".into()), refs: vec![], dry_run: false, validate_only: false, }, ) .expect("move request should build"); assert_eq!(move_request.function_name, "documents:move"); let delete_request = build_write_request( &demo_context(), &CommandEnvelope { name: "documents.delete".into(), command_id: "cmd_delete_1".into(), idempotency_key: Some("idem_delete".into()), actor: base_actor.clone(), source: base_source.clone(), target: Some(TargetRef { workspace_id: None, page_id: Some("page_1".into()), block_id: None, }), payload: DeleteDocumentPage { page_id: "page_1".into(), }, reason: Some("删除页面".into()), refs: vec![], dry_run: false, validate_only: false, }, ) .expect("delete request should build"); assert_eq!(delete_request.function_name, "documents:softDelete"); let restore_request = build_write_request( &demo_context(), &CommandEnvelope { name: "documents.restore".into(), command_id: "cmd_restore_1".into(), idempotency_key: Some("idem_restore".into()), actor: base_actor.clone(), source: base_source.clone(), target: Some(TargetRef { workspace_id: None, page_id: Some("page_1".into()), block_id: None, }), payload: RestoreDocumentPage { page_id: "page_1".into(), }, reason: Some("恢复页面".into()), refs: vec![], dry_run: false, validate_only: false, }, ) .expect("restore request should build"); assert_eq!(restore_request.function_name, "documents:restore"); let duplicate_request = build_write_request( &demo_context(), &CommandEnvelope { name: "documents.duplicate".into(), command_id: "cmd_duplicate_1".into(), idempotency_key: Some("idem_duplicate".into()), actor: base_actor.clone(), source: base_source.clone(), target: Some(TargetRef { workspace_id: None, page_id: Some("page_copy_1".into()), block_id: None, }), payload: DuplicateDocumentPage { source_page_id: "page_1".into(), new_page_id: "page_copy_1".into(), title: Some("无标题 副本".into()), }, reason: Some("复制页面".into()), refs: vec![], dry_run: false, validate_only: false, }, ) .expect("duplicate request should build"); assert_eq!( duplicate_request.function_name, "documents:duplicateWithMindmaps" ); let copy_tree_request = build_write_request( &demo_context(), &CommandEnvelope { name: "documents.copy_tree".into(), command_id: "cmd_copy_tree_1".into(), idempotency_key: Some("idem_copy_tree".into()), actor: base_actor, source: base_source, target: Some(TargetRef { workspace_id: None, page_id: Some("target_parent_1".into()), block_id: None, }), payload: CopyTreeDocumentPages { items_json: "[{\"documentId\":\"page_1\",\"recursive\":true}]".into(), target_parent_page_id: Some("target_parent_1".into()), }, reason: Some("复制页面树".into()), refs: vec![], dry_run: false, validate_only: false, }, ) .expect("copy tree request should build"); assert_eq!(copy_tree_request.function_name, "documents:copyTree"); } #[test] fn block_patch_command_maps_to_documents_update_content() { let command = CommandEnvelope { name: "blocks.patch".into(), command_id: "cmd_patch_1".into(), idempotency_key: Some("idem_patch".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_1".into()), block_id: Some("block_1".into()), }), payload: PatchPageBlock { page_id: "page_1".into(), block_id: "block_1".into(), workspace_id: Some("ws_1".into()), revision: Some(7), block_snapshot_json: "{\"id\":\"block_1\",\"type\":\"paragraph\"}".into(), conflict_detection_key: Some("page:1:block:1".into()), }, reason: Some("替换块快照".into()), refs: vec!["checklist:c3".into()], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "documents:updateContent"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request.payload_json.contains("\"name\":\"blocks.patch\"")); } #[test] fn blocks_get_query_maps_to_blocks_get_by_id() { let query = QueryEnvelope { name: "blocks.get".into(), payload: GetBlock { block_id: "block_1".into(), }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "blocks:getById"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request.payload_json.contains("\"name\":\"blocks.get\"")); } #[test] fn document_stats_command_maps_to_documents_update_stats() { let command = CommandEnvelope { name: "documents.stats.update".into(), command_id: "cmd_stats_1".into(), idempotency_key: Some("idem_stats".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_1".into()), block_id: None, }), payload: UpdatePageStats { page_id: "page_1".into(), word_count: 10, character_count: 20, block_count: 3, todo_total: 4, todo_done: 1, }, reason: None, refs: vec![], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "documents:updateStats"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"documents.stats.update\"")); } #[test] fn document_options_command_maps_to_documents_update_options() { let command = CommandEnvelope { name: "documents.options.update".into(), command_id: "cmd_options_1".into(), idempotency_key: Some("idem_options".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_1".into()), block_id: None, }), payload: UpdatePageOptions { page_id: "page_1".into(), wide_layout: None, small_text: None, show_heading_numbers: None, show_toc: Some(true), show_structure: None, protect_editing: None, show_word_count: None, collapse_backlinks: None, page_font: None, layout_density: Some("compact".into()), hide_child_pages: None, show_block_ref_count: None, embed_default_block_id: None, }, reason: None, refs: vec![], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "documents:updateOptions"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"documents.options.update\"")); } #[test] fn page_layout_update_options_command_maps_to_documents_update_options() { let command = CommandEnvelope { name: "page.layout.updateOptions".into(), command_id: "cmd_page_options_1".into(), idempotency_key: Some("idem_page_options".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "next-route".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_1".into()), block_id: None, }), payload: UpdatePageOptions { page_id: "page_1".into(), wide_layout: None, small_text: None, show_heading_numbers: None, show_toc: Some(true), show_structure: None, protect_editing: None, show_word_count: None, collapse_backlinks: None, page_font: None, layout_density: Some("compact".into()), hide_child_pages: None, show_block_ref_count: None, embed_default_block_id: None, }, reason: None, refs: vec![], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "documents:updateOptions"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"page.layout.updateOptions\"")); } #[test] fn media_asset_writeback_command_maps_to_replace_storage_from_upload() { let command = CommandEnvelope { name: "media.assets.replace_storage".into(), command_id: "cmd_asset_1".into(), idempotency_key: Some("idem_asset".into()), actor: ActorPayload { actor_type: "service".into(), actor_id: "onlyoffice-callback".into(), session_id: Some("onlyoffice".into()), }, source: SourcePayload { channel: "onlyoffice-callback".into(), client: "mnote-web".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_1".into()), block_id: None, }), payload: ReplaceMediaAssetStorage { asset_id: "asset_1".into(), storage_id: "storage_1".into(), }, reason: Some("ONLYOFFICE 回调写回附件".into()), refs: vec!["checklist:d".into()], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!( request.function_name, "mediaAssets:replaceStorageFromUpload" ); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"media.assets.replace_storage\"")); } #[test] fn document_meta_query_maps_to_documents_get_meta() { let query = QueryEnvelope { name: "documents.meta.get".into(), payload: GetPageMeta { page_id: "page_1".into(), workspace_id: Some("ws_1".into()), }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "documents:getMeta"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"documents.meta.get\"")); } #[test] fn document_content_query_maps_to_documents_get_content() { let query = QueryEnvelope { name: "documents.content.get".into(), payload: GetPageContent { page_id: "page_1".into(), workspace_id: Some("ws_1".into()), }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "documents:getContent"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"documents.content.get\"")); } #[test] fn mindmap_get_query_maps_to_mindmaps_get() { let query = QueryEnvelope { name: "mindmaps.get".into(), payload: GetMindmap { document_id: "page_1".into(), mindmap_id: "mind_1".into(), workspace_id: Some("ws_1".into()), }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "mindmaps:get"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request.payload_json.contains("\"name\":\"mindmaps.get\"")); } #[test] fn mindmap_editor_scene_query_maps_to_mindmaps_get_editor_scene() { let query = QueryEnvelope { name: "mindmap.editor_scene.get".into(), payload: GetMindmap { document_id: "page_1".into(), mindmap_id: "mind_1".into(), workspace_id: Some("ws_1".into()), }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "mindmaps:getEditorScene"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"mindmap.editor_scene.get\"")); } #[test] fn sidebar_dataset_query_maps_to_sidebar_dataset_list() { let query = QueryEnvelope { name: "sidebar.dataset.list".into(), payload: ListSidebarDataset { workspace_id: "ws_1".into(), }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "sidebar:datasetList"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"sidebar.dataset.list\"")); } #[test] fn search_documents_query_maps_to_search_documents() { let query = QueryEnvelope { name: "search.documents".into(), payload: SearchDocuments { query: "rust".into(), workspace_id: "ws_1".into(), page_id: Some("page_1".into()), pagination: core_protocol::query::Pagination { limit: 20, cursor: None, }, title_only: false, exact: false, include_ocr: true, time_range: "any".into(), time_field: "updated".into(), custom_range_from: None, custom_range_to: None, }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "search:documents"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"search.documents\"")); } #[test] fn search_recent_query_maps_to_search_recent() { let query = QueryEnvelope { name: "search.recent".into(), payload: SearchRecent { workspace_id: "ws_1".into(), pagination: core_protocol::query::Pagination { limit: 10, cursor: None, }, }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "search:recent"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request.payload_json.contains("\"name\":\"search.recent\"")); } #[test] fn bridge_request_query_maps_to_bridge_logs_request() { let query = QueryEnvelope { name: "bridge.request.get".into(), payload: GetBridgeRequest { workspace_id: "ws_1".into(), request_id: "req_1".into(), command_id: Some("cmd_1".into()), }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "bridgeLogs:listByRequest"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"bridge.request.get\"")); } #[test] fn bridge_trace_query_maps_to_bridge_logs_trace() { let query = QueryEnvelope { name: "bridge.trace.get".into(), payload: GetBridgeTrace { workspace_id: "ws_1".into(), trace_id: "trace_1".into(), command_id: None, }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "bridgeLogs:listByTrace"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"bridge.trace.get\"")); } #[test] fn bridge_command_query_maps_to_bridge_logs_command() { let query = QueryEnvelope { name: "bridge.command.get".into(), payload: GetBridgeCommand { workspace_id: "ws_1".into(), command_id: "cmd_1".into(), }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "bridgeLogs:listByCommand"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"bridge.command.get\"")); } #[test] fn bridge_workspace_overview_query_maps_to_bridge_logs_workspace_overview() { let query = QueryEnvelope { name: "bridge.workspace.overview".into(), payload: ListBridgeWorkspaceOverview { workspace_id: "ws_1".into(), pagination: core_protocol::query::Pagination { limit: 20, cursor: Some("cursor_1".into()), }, command_status: Some("failed".into()), event_status: Some("failed".into()), target_page_id: Some("page_1".into()), target_block_id: None, aggregate_type: Some("page".into()), aggregate_id: Some("page_1".into()), }, }; let request = build_query_request(&demo_context(), &query).expect("query request should build"); assert_eq!(request.function_name, "bridgeLogs:listWorkspaceOverview"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request .payload_json .contains("\"name\":\"bridge.workspace.overview\"")); } #[test] fn mindmap_put_command_maps_to_mindmaps_put() { let command = CommandEnvelope { name: "mindmaps.put".into(), command_id: "cmd_mindmap_put_1".into(), idempotency_key: Some("idem_mindmap_put".into()), actor: ActorPayload { actor_type: "human".into(), actor_id: "user_1".into(), session_id: Some("session_1".into()), }, source: SourcePayload { channel: "cli".into(), client: "mnote-cli".into(), }, target: Some(TargetRef { workspace_id: Some("ws_1".into()), page_id: Some("page_1".into()), block_id: Some("mind_1".into()), }), payload: PutMindmap { document_id: "page_1".into(), mindmap_id: "mind_1".into(), data_json: r#"{"data":{"text":"中心主题"},"children":[]}"#.into(), create_only: false, }, reason: Some("保存导图".into()), refs: vec!["task-032".into()], dry_run: false, validate_only: false, }; let request = build_write_request(&demo_context(), &command).expect("write request should build"); assert_eq!(request.function_name, "mindmaps:put"); assert_eq!(request.workspace_id.as_deref(), Some("ws_1")); assert!(request.payload_json.contains("\"name\":\"mindmaps.put\"")); } }