use spool::desktop::tauri::{
TauriContextCommand, TauriDaemonCommand, TauriImportSessionCommand, TauriMemoryActionCommand,
TauriMemoryDraftCommand, TauriPromptOptimizeCommand, TauriRecordCommand,
TauriSessionBrowserCommand, TauriWakeupCommand, TauriWorkbenchCommand,
desktop_apply_memory_action, desktop_browse_sessions, desktop_build_wakeup,
desktop_get_history, desktop_get_record, desktop_import_session, desktop_load_workbench,
desktop_optimize_prompt, desktop_propose_memory, desktop_read_wiki_index, desktop_run_context,
desktop_wiki_lint,
};
use spool::desktop::{
DesktopErrorKind, DesktopLifecycleActionDto, DesktopMetadataDto, DesktopWikiIndexRequest,
DesktopWikiLintRequest,
};
use spool::domain::{MemoryLifecycleState, MemoryScope, OutputFormat, TargetTool, WakeupProfile};
use std::fs;
use tempfile::tempdir;
fn setup_workspace() -> (tempfile::TempDir, std::path::PathBuf, std::path::PathBuf) {
let temp = tempdir().unwrap();
let vault_root = temp.path().join("vault");
let repo_root = temp.path().join("repo");
fs::create_dir_all(vault_root.join("10-Projects")).unwrap();
fs::create_dir_all(&repo_root).unwrap();
fs::write(
vault_root.join("10-Projects/spool.md"),
"---\ntitle: spool\nmemory_type: project\n---\n\nDesktop tauri command integration note.\n",
)
.unwrap();
let config_path = temp.path().join("spool.toml");
fs::write(
&config_path,
format!(
"[vault]\nroot = \"{}\"\n\n[output]\ndefault_format = \"markdown\"\nmax_chars = 4000\nmax_notes = 8\n\n[[projects]]\nid = \"spool\"\nname = \"spool\"\nrepo_paths = [\"{}\"]\nnote_roots = [\"10-Projects\"]\n",
vault_root.display(),
repo_root.display(),
),
)
.unwrap();
(temp, config_path, repo_root)
}
#[test]
fn tauri_smoke_should_run_context_and_wakeup_commands() {
let (_temp, config_path, repo_root) = setup_workspace();
let context = desktop_run_context(TauriContextCommand {
config_path: config_path.clone(),
vault_root_override: None,
cwd: repo_root.clone(),
task: "summarize current project context".to_string(),
files: vec!["src/lib.rs".to_string(), "src/main.rs".to_string()],
target: TargetTool::Claude,
format: OutputFormat::Markdown,
})
.unwrap();
assert_eq!(context.bundle.route.debug.note_count, 1);
let wakeup = desktop_build_wakeup(TauriWakeupCommand {
config_path,
vault_root_override: None,
cwd: repo_root,
task: "prepare restart packet".to_string(),
files: Vec::new(),
target: TargetTool::Claude,
profile: WakeupProfile::Project,
})
.unwrap();
assert_eq!(wakeup.packet.profile, WakeupProfile::Project);
}
#[test]
fn tauri_smoke_should_optimize_prompt_through_shared_orchestrator() {
let (_temp, config_path, repo_root) = setup_workspace();
let response = desktop_optimize_prompt(TauriPromptOptimizeCommand {
config_path,
vault_root_override: None,
cwd: repo_root,
task: "continue desktop integration".to_string(),
files: vec![
"src/desktop.rs".to_string(),
"frontend/desktop.js".to_string(),
],
target: TargetTool::Codex,
profile: WakeupProfile::Project,
provider: Some("codex".to_string()),
session_id: Some("codex:session-1".to_string()),
})
.unwrap();
assert!(response.combined_prompt.contains("Codex"));
assert!(response.context_prompt.contains("Codex"));
assert_eq!(response.profile, WakeupProfile::Project);
assert_eq!(response.provider.as_deref(), Some("codex"));
assert!(response.runtime_trace.is_none());
}
#[test]
fn tauri_smoke_should_cover_lifecycle_read_and_write_commands() {
let (_temp, config_path, _repo_root) = setup_workspace();
let created = desktop_propose_memory(TauriMemoryDraftCommand {
config_path: config_path.clone(),
title: "测试偏好".to_string(),
summary: "先 smoke 再收口".to_string(),
memory_type: "workflow".to_string(),
scope: MemoryScope::User,
source_ref: "session:1".to_string(),
project_id: None,
user_id: Some("long".to_string()),
sensitivity: None,
metadata: DesktopMetadataDto {
actor: Some("desktop".to_string()),
reason: Some("captured from tauri command".to_string()),
evidence_refs: vec!["session:1".to_string(), "obsidian://workflow".to_string()],
},
entities: Vec::new(),
tags: Vec::new(),
triggers: Vec::new(),
related_files: Vec::new(),
related_records: Vec::new(),
supersedes: None,
applies_to: Vec::new(),
valid_until: None,
})
.unwrap();
assert_eq!(created.entry.record.state, MemoryLifecycleState::Candidate);
let workbench = desktop_load_workbench(TauriWorkbenchCommand {
config_path: config_path.clone(),
daemon: Some(TauriDaemonCommand {
enabled: false,
daemon_bin: None,
}),
})
.unwrap();
assert_eq!(workbench.snapshot.pending_review.len(), 1);
let record = desktop_get_record(TauriRecordCommand {
config_path: config_path.clone(),
record_id: created.entry.record_id.clone(),
daemon: None,
})
.unwrap()
.unwrap();
assert_eq!(record.record.record.title, "测试偏好");
let action = desktop_apply_memory_action(TauriMemoryActionCommand {
config_path: config_path.clone(),
record_id: created.entry.record_id.clone(),
action: DesktopLifecycleActionDto::Accept,
metadata: DesktopMetadataDto::default(),
})
.unwrap();
assert_eq!(action.entry.record.state, MemoryLifecycleState::Accepted);
let history = desktop_get_history(TauriRecordCommand {
config_path,
record_id: created.entry.record_id,
daemon: None,
})
.unwrap();
assert_eq!(history.history.len(), 2);
}
#[test]
fn tauri_smoke_should_browse_sessions_from_memory_refs() {
let (_temp, config_path, _repo_root) = setup_workspace();
let _created = desktop_propose_memory(TauriMemoryDraftCommand {
config_path: config_path.clone(),
title: "会话偏好".to_string(),
summary: "来自一次桌面会话".to_string(),
memory_type: "workflow".to_string(),
scope: MemoryScope::User,
source_ref: "session:demo-1".to_string(),
project_id: None,
user_id: Some("long".to_string()),
sensitivity: None,
metadata: DesktopMetadataDto {
actor: Some("desktop".to_string()),
reason: Some("captured from session browser smoke".to_string()),
evidence_refs: vec![
"session:demo-1".to_string(),
"obsidian://workflow".to_string(),
],
},
entities: Vec::new(),
tags: Vec::new(),
triggers: Vec::new(),
related_files: Vec::new(),
related_records: Vec::new(),
supersedes: None,
applies_to: Vec::new(),
valid_until: None,
})
.unwrap();
let sessions = desktop_browse_sessions(TauriSessionBrowserCommand {
config_path: config_path.clone(),
page: None,
per_page: None,
provider: None,
})
.unwrap();
let spool_items: Vec<_> = sessions
.sessions
.iter()
.filter(|session| session.provider == "spool")
.collect();
assert_eq!(spool_items.len(), 0);
}
#[test]
fn tauri_smoke_should_paginate_session_browser_without_duplicates() {
let (_temp, config_path, _repo_root) = setup_workspace();
for index in 0..12 {
desktop_propose_memory(TauriMemoryDraftCommand {
config_path: config_path.clone(),
title: format!("会话 {index}"),
summary: format!("分页 smoke {index}"),
memory_type: "workflow".to_string(),
scope: MemoryScope::User,
source_ref: format!("session:page-{index:02}"),
project_id: None,
user_id: Some("long".to_string()),
sensitivity: None,
metadata: DesktopMetadataDto {
actor: Some("desktop".to_string()),
reason: Some("pagination smoke".to_string()),
evidence_refs: vec![format!("session:page-{index:02}")],
},
entities: Vec::new(),
tags: Vec::new(),
triggers: Vec::new(),
related_files: Vec::new(),
related_records: Vec::new(),
supersedes: None,
applies_to: Vec::new(),
valid_until: None,
})
.unwrap();
}
let page_one = desktop_browse_sessions(TauriSessionBrowserCommand {
config_path: config_path.clone(),
page: Some(1),
per_page: Some(10),
provider: Some("spool".to_string()),
})
.unwrap();
assert_eq!(page_one.total, 0);
assert_eq!(page_one.sessions.len(), 0);
assert!(!page_one.has_more);
let all = desktop_browse_sessions(TauriSessionBrowserCommand {
config_path,
page: Some(1),
per_page: Some(50),
provider: None,
})
.unwrap();
let spool_count = all
.sessions
.iter()
.filter(|s| s.provider == "spool")
.count();
assert_eq!(spool_count, 0);
}
#[test]
fn tauri_smoke_should_validate_import_session_inputs() {
let (_temp, config_path, _repo_root) = setup_workspace();
let missing_session_id = desktop_import_session(TauriImportSessionCommand {
config_path: config_path.clone(),
provider: "claude".to_string(),
session_id: " ".to_string(),
apply: false,
actor: None,
})
.expect_err("blank session_id should return input error");
assert!(matches!(missing_session_id.kind, DesktopErrorKind::Input));
let bad_provider = desktop_import_session(TauriImportSessionCommand {
config_path,
provider: "opencode".to_string(),
session_id: "abc".to_string(),
apply: false,
actor: None,
})
.expect_err("unknown provider should return input error");
assert!(matches!(bad_provider.kind, DesktopErrorKind::Input));
}
#[test]
fn tauri_smoke_should_return_wiki_lint_clean_report_for_empty_ledger() {
let (_temp, config_path, _repo_root) = setup_workspace();
let response = desktop_wiki_lint(DesktopWikiLintRequest { config_path })
.expect("wiki_lint should succeed on a fresh config");
assert_eq!(response.report.total_active_records, 0);
assert!(response.report.prune_suggestions.is_empty());
assert!(response.report.broken_cross_refs.is_empty());
assert!(response.report.orphan_notes.is_empty());
assert!(response.markdown.contains("知识库干净"));
assert!(!response.used_vault_root.as_os_str().is_empty());
}
#[test]
fn tauri_smoke_should_detect_broken_cross_refs_via_wiki_lint() {
let (_temp, config_path, _repo_root) = setup_workspace();
desktop_propose_memory(TauriMemoryDraftCommand {
config_path: config_path.clone(),
title: "依赖缺失".to_string(),
summary: "引用了一个不存在的记录".to_string(),
memory_type: "workflow".to_string(),
scope: MemoryScope::User,
source_ref: "session:wiki-lint".to_string(),
project_id: None,
user_id: Some("long".to_string()),
sensitivity: None,
metadata: DesktopMetadataDto {
actor: Some("desktop".to_string()),
reason: Some("broken cross-ref smoke".to_string()),
evidence_refs: vec!["session:wiki-lint".to_string()],
},
entities: Vec::new(),
tags: Vec::new(),
triggers: Vec::new(),
related_files: Vec::new(),
related_records: vec!["missing-id".to_string()],
supersedes: None,
applies_to: Vec::new(),
valid_until: None,
})
.unwrap();
let created = desktop_propose_memory(TauriMemoryDraftCommand {
config_path: config_path.clone(),
title: "有断链的记忆".to_string(),
summary: "accepted 之后应暴露 broken cross-ref".to_string(),
memory_type: "workflow".to_string(),
scope: MemoryScope::User,
source_ref: "session:wiki-lint-accepted".to_string(),
project_id: None,
user_id: Some("long".to_string()),
sensitivity: None,
metadata: DesktopMetadataDto {
actor: Some("desktop".to_string()),
reason: Some("broken cross-ref accepted smoke".to_string()),
evidence_refs: vec!["session:wiki-lint-accepted".to_string()],
},
entities: Vec::new(),
tags: Vec::new(),
triggers: Vec::new(),
related_files: Vec::new(),
related_records: vec!["missing-id".to_string()],
supersedes: None,
applies_to: Vec::new(),
valid_until: None,
})
.unwrap();
desktop_apply_memory_action(TauriMemoryActionCommand {
config_path: config_path.clone(),
record_id: created.entry.record_id.clone(),
action: DesktopLifecycleActionDto::Accept,
metadata: DesktopMetadataDto::default(),
})
.unwrap();
let response = desktop_wiki_lint(DesktopWikiLintRequest { config_path })
.expect("wiki_lint should succeed with broken cross-ref present");
assert!(
!response.report.broken_cross_refs.is_empty(),
"expected at least one broken cross-ref, got {:?}",
response.report.broken_cross_refs
);
assert!(
response
.report
.broken_cross_refs
.iter()
.any(|broken| broken.missing_target == "missing-id"),
"expected broken cross-ref to mention missing-id"
);
assert!(response.markdown.contains("断链"));
}
#[test]
fn tauri_smoke_should_return_none_markdown_when_index_missing() {
let (_temp, config_path, _repo_root) = setup_workspace();
let response = desktop_read_wiki_index(DesktopWikiIndexRequest {
config_path,
project_id: None,
})
.expect("read_wiki_index should succeed even when INDEX.md is missing");
assert!(response.markdown.is_none());
assert!(!response.used_vault_root.as_os_str().is_empty());
}