use super::*; struct EnvVarGuard { key: &'static str, prev: Option, } impl EnvVarGuard { fn set_path(key: &'static str, value: &std::path::Path) -> Self { let prev = std::env::var_os(key); crate::env::set_var(key, value); Self { key, prev } } } impl Drop for EnvVarGuard { fn drop(&mut self) { if let Some(prev) = self.prev.take() { crate::env::set_var(self.key, prev); } else { crate::env::remove_var(self.key); } } } #[test] fn test_truncate_title() { assert_eq!(truncate_title("short"), "short"); assert_eq!(truncate_title("line1\nline2"), "line1"); let long = "a".repeat(100); let truncated = truncate_title(&long); assert!(truncated.ends_with("...")); assert!(truncated.len() <= 80); } #[test] fn test_convert_text_content() { let content = ClaudeCodeContent::Text("hello".to_string()); let blocks = convert_content_blocks(&content); assert_eq!(blocks.len(), 1); match &blocks[0] { ContentBlock::Text { text, .. } => assert_eq!(text, "hello"), _ => panic!("Expected text block"), } } #[test] fn test_convert_empty_content() { let content = ClaudeCodeContent::Empty; let blocks = convert_content_blocks(&content); assert!(blocks.is_empty()); } #[test] fn test_convert_blocks_content() { let content = ClaudeCodeContent::Blocks(vec![ ClaudeCodeContentBlock::Text { text: "hello".to_string(), }, ClaudeCodeContentBlock::Thinking { thinking: "let me think".to_string(), _signature: None, }, ClaudeCodeContentBlock::ToolUse { id: "tool1".to_string(), name: "bash".to_string(), input: serde_json::json!({"cmd": "ls"}), }, ]); let blocks = convert_content_blocks(&content); assert_eq!(blocks.len(), 3); match &blocks[0] { ContentBlock::Text { text, .. } => assert_eq!(text, "hello"), _ => panic!("Expected text"), } match &blocks[1] { ContentBlock::Reasoning { text } => assert_eq!(text, "let me think"), _ => panic!("Expected reasoning"), } match &blocks[2] { ContentBlock::ToolUse { name, .. } => assert_eq!(name, "bash"), _ => panic!("Expected tool use"), } } #[test] fn test_discover_projects_uses_sandboxed_external_home() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); let project_dir = temp.path().join("external/.claude/projects/demo"); std::fs::create_dir_all(&project_dir).unwrap(); std::fs::write( project_dir.join("sessions-index.json"), r#"{"version":1,"entries":[]}"#, ) .unwrap(); let projects = discover_projects().unwrap(); assert_eq!(projects, vec![project_dir.join("sessions-index.json")]); } #[test] fn test_list_claude_code_sessions_uses_live_transcripts_when_index_is_stale() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); let project_dir = temp.path().join("external/.claude/projects/demo-project"); std::fs::create_dir_all(&project_dir).unwrap(); let indexed_session_path = project_dir.join("live-session-1.jsonl"); std::fs::write( &indexed_session_path, concat!( "{\"type\":\"user\",\"uuid\":\"u1\",\"sessionId\":\"live-session-1\",\"cwd\":\"/tmp/demo-project\",\"message\":{\"role\":\"user\",\"content\":\"Investigate the login bug\"},\"timestamp\":\"2026-04-04T12:00:00Z\"}\n", "{\"type\":\"assistant\",\"uuid\":\"a1\",\"parentUuid\":\"u1\",\"sessionId\":\"live-session-1\",\"cwd\":\"/tmp/demo-project\",\"message\":{\"role\":\"assistant\",\"model\":\"claude-sonnet-4-6\",\"content\":\"I can help with that.\"},\"timestamp\":\"2026-04-04T12:05:00Z\"}\n" ), ) .unwrap(); let orphan_session_path = project_dir.join("orphan-session-2.jsonl"); std::fs::write( &orphan_session_path, concat!( "{\"type\":\"user\",\"uuid\":\"u2\",\"sessionId\":\"orphan-session-2\",\"cwd\":\"/tmp/demo-project\",\"message\":{\"role\":\"user\",\"content\":\"Summarize the deployment issue\"},\"timestamp\":\"2026-04-05T09:00:00Z\"}\n", "{\"type\":\"assistant\",\"uuid\":\"a2\",\"parentUuid\":\"u2\",\"sessionId\":\"orphan-session-2\",\"cwd\":\"/tmp/demo-project\",\"message\":{\"role\":\"assistant\",\"model\":\"claude-sonnet-4-6\",\"content\":\"Here is the deployment summary.\"},\"timestamp\":\"2026-04-05T09:01:00Z\"}\n" ), ) .unwrap(); std::fs::write( project_dir.join("sessions-index.json"), concat!( "{\"version\":1,\"entries\":[", "{\"sessionId\":\"live-session-1\",", "\"fullPath\":\"/missing/live-session-1.jsonl\",", "\"firstPrompt\":\"Investigate the login bug\",", "\"summary\":\"Investigate the login bug\",", "\"messageCount\":2,", "\"created\":\"2026-04-04T12:00:00Z\",", "\"modified\":\"2026-04-04T12:05:00Z\",", "\"projectPath\":\"/tmp/demo-project\"", "}] }" ), ) .unwrap(); let sessions = list_claude_code_sessions().unwrap(); let indexed = sessions .iter() .find(|session| session.session_id == "live-session-1") .expect("indexed live transcript should be discovered"); assert_eq!(indexed.full_path, indexed_session_path.to_string_lossy()); assert_eq!( indexed.summary.as_deref(), Some("Investigate the login bug") ); assert_eq!(indexed.project_path.as_deref(), Some("/tmp/demo-project")); let orphan = sessions .iter() .find(|session| session.session_id == "orphan-session-2") .expect("orphan live transcript should be discovered"); assert_eq!(orphan.full_path, orphan_session_path.to_string_lossy()); assert_eq!(orphan.first_prompt, "Summarize the deployment issue"); assert_eq!(orphan.message_count, 2); } #[test] fn test_list_claude_code_sessions_uses_index_metadata_without_parsing_transcript() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); let project_dir = temp.path().join("external/.claude/projects/demo-project"); std::fs::create_dir_all(&project_dir).unwrap(); let transcript_path = project_dir.join("indexed-session.jsonl"); std::fs::write(&transcript_path, "{this is not valid jsonl}\n").unwrap(); std::fs::write( project_dir.join("sessions-index.json"), format!( concat!( "{{\"version\":1,\"entries\":[", "{{\"sessionId\":\"indexed-session\",", "\"fullPath\":\"{}\",", "\"firstPrompt\":\"Investigate the login bug\",", "\"summary\":\"Investigate the login bug\",", "\"messageCount\":2,", "\"created\":\"2026-04-04T12:00:00Z\",", "\"modified\":\"2026-04-04T12:05:00Z\",", "\"projectPath\":\"/tmp/demo-project\"", "}}]}}" ), transcript_path.display() ), ) .unwrap(); let sessions = list_claude_code_sessions().unwrap(); let session = sessions .iter() .find(|session| session.session_id == "indexed-session") .expect("indexed session should be listed from index metadata"); assert_eq!(session.message_count, 2); assert_eq!( session.summary.as_deref(), Some("Investigate the login bug") ); assert_eq!(session.first_prompt, "Investigate the login bug"); } #[test] fn test_list_claude_code_sessions_skips_empty_index_entries_without_messages() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); let project_dir = temp.path().join("external/.claude/projects/demo-project"); std::fs::create_dir_all(&project_dir).unwrap(); let transcript_path = project_dir.join("empty-session.jsonl"); std::fs::write( &transcript_path, "{\"type\":\"system\",\"sessionId\":\"empty-session\"}\n", ) .unwrap(); std::fs::write( project_dir.join("sessions-index.json"), format!( concat!( "{{\"version\":1,\"entries\":[", "{{\"sessionId\":\"empty-session\",", "\"fullPath\":\"{}\",", "\"firstPrompt\":\"\",", "\"summary\":\"\",", "\"messageCount\":0", "}}]}}" ), transcript_path.display() ), ) .unwrap(); let sessions = list_claude_code_sessions().unwrap(); assert!( sessions.is_empty(), "empty placeholder sessions should be hidden" ); } #[test] fn test_import_claude_session_uses_recovered_live_transcript() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); let project_dir = temp.path().join("external/.claude/projects/demo-project"); std::fs::create_dir_all(&project_dir).unwrap(); let transcript_path = project_dir.join("live-session-1.jsonl"); std::fs::write( &transcript_path, concat!( "{\"type\":\"user\",\"uuid\":\"u1\",\"sessionId\":\"live-session-1\",\"cwd\":\"/tmp/demo-project\",\"message\":{\"role\":\"user\",\"content\":\"Investigate the login bug\"},\"timestamp\":\"2026-04-04T12:00:00Z\"}\n", "{\"type\":\"assistant\",\"uuid\":\"a1\",\"parentUuid\":\"u1\",\"sessionId\":\"live-session-1\",\"cwd\":\"/tmp/demo-project\",\"message\":{\"role\":\"assistant\",\"model\":\"claude-sonnet-4-6\",\"content\":\"I can help with that.\"},\"timestamp\":\"2026-04-04T12:05:00Z\"}\n" ), ) .unwrap(); std::fs::write( project_dir.join("sessions-index.json"), concat!( "{\"version\":1,\"entries\":[", "{\"sessionId\":\"live-session-1\",", "\"fullPath\":\"/missing/live-session-1.jsonl\",", "\"firstPrompt\":\"Investigate the login bug\",", "\"summary\":\"Investigate the login bug\",", "\"messageCount\":2,", "\"created\":\"2026-04-04T12:00:00Z\",", "\"modified\":\"2026-04-04T12:05:00Z\",", "\"projectPath\":\"/tmp/demo-project\"", "}] }" ), ) .unwrap(); let imported = import_session("live-session-1").unwrap(); assert_eq!( imported.id, imported_claude_code_session_id("live-session-1") ); assert_eq!(imported.provider_key.as_deref(), Some("claude-code")); assert_eq!(imported.working_dir.as_deref(), Some("/tmp/demo-project")); assert_eq!(imported.model.as_deref(), Some("claude-sonnet-4-6")); assert_eq!(imported.messages.len(), 2); } #[test] fn test_import_pi_session_creates_jcode_snapshot() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); let pi_dir = temp.path().join("external/.pi/agent/sessions/project"); std::fs::create_dir_all(&pi_dir).unwrap(); let session_path = pi_dir.join("session.jsonl"); std::fs::write( &session_path, concat!( "{\"type\":\"session\",\"id\":\"pi-session-1\",\"timestamp\":\"2026-04-05T19:00:00Z\",\"cwd\":\"/tmp/pi-demo\"}\n", "{\"type\":\"model_change\",\"timestamp\":\"2026-04-05T19:00:01Z\",\"provider\":\"pi\",\"modelId\":\"pi-model\"}\n", "{\"type\":\"message\",\"timestamp\":\"2026-04-05T19:00:02Z\",\"message\":{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"hello pi\"}]}}\n", "{\"type\":\"message\",\"timestamp\":\"2026-04-05T19:00:03Z\",\"message\":{\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"hi back\"}]}}\n" ), ) .unwrap(); let imported = import_pi_session(&session_path.to_string_lossy()).unwrap(); assert_eq!( imported.id, imported_pi_session_id(&session_path.to_string_lossy()) ); assert_eq!(imported.provider_key.as_deref(), Some("pi")); assert_eq!(imported.model.as_deref(), Some("pi-model")); assert_eq!(imported.working_dir.as_deref(), Some("/tmp/pi-demo")); assert_eq!(imported.messages.len(), 2); } #[test] fn test_import_opencode_session_creates_jcode_snapshot() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); let session_dir = temp .path() .join("external/.local/share/opencode/storage/session/global"); let message_dir = temp .path() .join("external/.local/share/opencode/storage/message/ses_test_opencode"); let user_part_dir = temp .path() .join("external/.local/share/opencode/storage/part/msg-user"); let assistant_part_dir = temp .path() .join("external/.local/share/opencode/storage/part/msg-assistant"); std::fs::create_dir_all(&session_dir).unwrap(); std::fs::create_dir_all(&message_dir).unwrap(); std::fs::create_dir_all(&user_part_dir).unwrap(); std::fs::create_dir_all(&assistant_part_dir).unwrap(); std::fs::write( session_dir.join("ses_test_opencode.json"), concat!( "{", "\"id\":\"ses_test_opencode\",", "\"directory\":\"/tmp/opencode-demo\",", "\"title\":\"OpenCode imported\",", "\"time\":{\"created\":1775415600000,\"updated\":1775415605000}", "}" ), ) .unwrap(); std::fs::write( message_dir.join("msg-user.json"), concat!( "{", "\"id\":\"msg-user\",", "\"role\":\"user\",", "\"time\":{\"created\":1775415601000},", "\"model\":{\"providerID\":\"opencode\",\"modelID\":\"big-pickle\"}", "}" ), ) .unwrap(); std::fs::write( message_dir.join("msg-assistant.json"), concat!( "{", "\"id\":\"msg-assistant\",", "\"role\":\"assistant\",", "\"time\":{\"created\":1775415602000},", "\"providerID\":\"opencode\",", "\"modelID\":\"big-pickle\"", "}" ), ) .unwrap(); // Modern OpenCode (Go storage) keeps message body text in part files. std::fs::write( user_part_dir.join("prt-user.json"), concat!( "{", "\"id\":\"prt-user\",", "\"messageID\":\"msg-user\",", "\"type\":\"text\",", "\"text\":\"Investigate provider routing\"", "}" ), ) .unwrap(); std::fs::write( assistant_part_dir.join("prt-assistant.json"), concat!( "{", "\"id\":\"prt-assistant\",", "\"messageID\":\"msg-assistant\",", "\"type\":\"text\",", "\"text\":\"Found the bad provider switch\"", "}" ), ) .unwrap(); let imported = import_opencode_session("ses_test_opencode").unwrap(); assert_eq!( imported.id, imported_opencode_session_id("ses_test_opencode") ); assert_eq!(imported.provider_key.as_deref(), Some("opencode")); assert_eq!(imported.model.as_deref(), Some("big-pickle")); assert_eq!(imported.working_dir.as_deref(), Some("/tmp/opencode-demo")); assert_eq!(imported.messages.len(), 2); let all_text: String = imported .messages .iter() .flat_map(|m| m.content.iter()) .filter_map(|block| match block { ContentBlock::Text { text, .. } => Some(text.as_str()), _ => None, }) .collect::>() .join("\n"); assert!( all_text.contains("Investigate provider routing"), "expected user part text to be imported: {all_text:?}" ); assert!( all_text.contains("Found the bad provider switch"), "expected assistant part text to be imported: {all_text:?}" ); } #[test] fn test_resolve_resume_target_to_jcode_imports_codex_session() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); let codex_dir = temp.path().join("external/.codex/sessions/2026/04/05"); std::fs::create_dir_all(&codex_dir).unwrap(); std::fs::write( codex_dir.join("rollout.jsonl"), concat!( "{\"timestamp\":\"2026-04-05T19:00:00Z\",\"type\":\"session_meta\",\"payload\":{\"id\":\"codex-resolve-test\",\"timestamp\":\"2026-04-05T18:59:00Z\",\"cwd\":\"/tmp/codex-resolve\"}}\n", "{\"timestamp\":\"2026-04-05T19:00:01Z\",\"type\":\"response_item\",\"payload\":{\"type\":\"message\",\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"Fix codex resume\"}]}}\n", "{\"timestamp\":\"2026-04-05T19:00:02Z\",\"type\":\"response_item\",\"payload\":{\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"Done\"}]}}\n" ), ) .unwrap(); let resolved = resolve_resume_target_to_jcode(&jcode_session_types::ResumeTarget::CodexSession { session_id: "codex-resolve-test".to_string(), session_path: codex_dir .join("rollout.jsonl") .to_string_lossy() .to_string(), }) .unwrap(); assert_eq!( resolved, jcode_session_types::ResumeTarget::JcodeSession { session_id: imported_codex_session_id("codex-resolve-test"), } ); let loaded = Session::load(&imported_codex_session_id("codex-resolve-test")).unwrap(); assert_eq!(loaded.messages.len(), 2); } /// The resume picker builds a `ClaudeCodeSession` target with id `claude:` /// and a transcript path; selecting it routes through /// `resolve_resume_target_to_jcode`, which must import the transcript and hand /// back a resumable `imported_cc_` jcode session. This guards the full /// detect -> import -> resume round-trip for Claude Code (previously only Codex /// had coverage here). #[test] fn test_resolve_resume_target_to_jcode_imports_claude_code_session() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); let project_dir = temp.path().join("external/.claude/projects/demo-project"); std::fs::create_dir_all(&project_dir).unwrap(); let transcript_path = project_dir.join("claude-resolve-test.jsonl"); std::fs::write( &transcript_path, concat!( "{\"type\":\"user\",\"uuid\":\"u1\",\"sessionId\":\"claude-resolve-test\",\"cwd\":\"/tmp/demo-project\",\"message\":{\"role\":\"user\",\"content\":\"Fix the resume round-trip\"},\"timestamp\":\"2026-04-04T12:00:00Z\"}\n", "{\"type\":\"assistant\",\"uuid\":\"a1\",\"parentUuid\":\"u1\",\"sessionId\":\"claude-resolve-test\",\"cwd\":\"/tmp/demo-project\",\"message\":{\"role\":\"assistant\",\"model\":\"claude-sonnet-4-6\",\"content\":\"On it.\"},\"timestamp\":\"2026-04-04T12:01:00Z\"}\n" ), ) .unwrap(); let resolved = resolve_resume_target_to_jcode(&jcode_session_types::ResumeTarget::ClaudeCodeSession { session_id: "claude-resolve-test".to_string(), session_path: transcript_path.to_string_lossy().to_string(), }) .unwrap(); let imported_id = imported_claude_code_session_id("claude-resolve-test"); assert_eq!( resolved, jcode_session_types::ResumeTarget::JcodeSession { session_id: imported_id.clone(), } ); // The id the picker would also derive via `imported_session_id_for_target` // must match the snapshot actually written to disk. assert_eq!( imported_session_id_for_target(&jcode_session_types::ResumeTarget::ClaudeCodeSession { session_id: "claude-resolve-test".to_string(), session_path: transcript_path.to_string_lossy().to_string(), }), Some(imported_id.clone()) ); let loaded = Session::load(&imported_id).unwrap(); assert_eq!(loaded.messages.len(), 2); assert_eq!( loaded.provider_session_id.as_deref(), Some("claude-resolve-test") ); assert_eq!(loaded.provider_key.as_deref(), Some("claude-code")); } /// Regression for silent data loss: the picker hides the imported jcode session /// (any `imported_*` stem) and only shows the external `claude:` entry, so /// re-selecting a Claude session re-enters `import_session_from_file`. If the /// user already resumed and continued that imported session inside jcode, a /// blind re-import previously overwrote the snapshot and dropped the jcode-side /// messages. The continuation must be preserved instead. #[test] fn test_reimporting_claude_session_preserves_jcode_continuation() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); let project_dir = temp.path().join("external/.claude/projects/demo-project"); std::fs::create_dir_all(&project_dir).unwrap(); let transcript_path = project_dir.join("claude-continued.jsonl"); std::fs::write( &transcript_path, concat!( "{\"type\":\"user\",\"uuid\":\"u1\",\"sessionId\":\"claude-continued\",\"cwd\":\"/tmp/demo-project\",\"message\":{\"role\":\"user\",\"content\":\"original prompt\"},\"timestamp\":\"2026-04-04T12:00:00Z\"}\n", "{\"type\":\"assistant\",\"uuid\":\"a1\",\"parentUuid\":\"u1\",\"sessionId\":\"claude-continued\",\"cwd\":\"/tmp/demo-project\",\"message\":{\"role\":\"assistant\",\"content\":\"original reply\"},\"timestamp\":\"2026-04-04T12:01:00Z\"}\n" ), ) .unwrap(); // First selection imports the transcript. let imported = import_session_from_file(&transcript_path, "claude-continued").unwrap(); assert_eq!(imported.messages.len(), 2); let imported_id = imported_claude_code_session_id("claude-continued"); // User resumes inside jcode and appends a jcode-only follow-up message. let mut session = Session::load(&imported_id).unwrap(); session.append_stored_message(StoredMessage { id: "jcode-continuation".to_string(), role: Role::User, content: vec![ContentBlock::Text { text: "jcode-only follow up".to_string(), cache_control: None, }], display_role: None, timestamp: None, tool_duration_ms: None, token_usage: None, }); session.save().unwrap(); assert_eq!(Session::load(&imported_id).unwrap().messages.len(), 3); // Re-selecting the external entry re-enters import; the continuation must survive. let resumed = resolve_resume_target_to_jcode(&jcode_session_types::ResumeTarget::ClaudeCodeSession { session_id: "claude-continued".to_string(), session_path: transcript_path.to_string_lossy().to_string(), }) .unwrap(); assert_eq!( resumed, jcode_session_types::ResumeTarget::JcodeSession { session_id: imported_id.clone(), } ); let after = Session::load(&imported_id).unwrap(); assert_eq!( after.messages.len(), 3, "jcode-side continuation must not be clobbered by re-import" ); let preserved = after.messages.iter().flat_map(|m| m.content.iter()).any( |block| matches!(block, ContentBlock::Text { text, .. } if text == "jcode-only follow up"), ); assert!( preserved, "the jcode-only follow up message must be preserved" ); } #[test] fn test_import_cursor_session_creates_jcode_snapshot() { let _guard = crate::storage::lock_test_env(); let temp = tempfile::TempDir::new().unwrap(); let _home = EnvVarGuard::set_path("JCODE_HOME", temp.path()); // Cursor stores transcripts at // ~/.cursor/projects//agent-transcripts//.jsonl where the // project dir encodes the cwd with `-` separators. let session_id = "11111111-2222-3333-4444-555555555555"; let transcript_dir = temp.path().join(format!( "external/.cursor/projects/tmp-cursor-demo/agent-transcripts/{session_id}" )); std::fs::create_dir_all(&transcript_dir).unwrap(); let transcript_path = transcript_dir.join(format!("{session_id}.jsonl")); std::fs::write( &transcript_path, concat!( "{\"role\":\"user\",\"message\":{\"content\":[{\"type\":\"text\",\"text\":\"CURSOR_USER_MARKER refactor router\"}]}}\n", "{\"role\":\"assistant\",\"message\":{\"content\":[{\"type\":\"text\",\"text\":\"CURSOR_ASSISTANT_MARKER done\"}]}}\n", ), ) .unwrap(); let imported = import_cursor_session(session_id).unwrap(); assert_eq!(imported.id, imported_cursor_session_id(session_id)); assert_eq!(imported.provider_key.as_deref(), Some("cursor")); assert_eq!(imported.messages.len(), 2); let all_text: String = imported .messages .iter() .flat_map(|m| m.content.iter()) .filter_map(|block| match block { ContentBlock::Text { text, .. } => Some(text.as_str()), _ => None, }) .collect::>() .join("\n"); assert!( all_text.contains("CURSOR_USER_MARKER refactor router"), "expected user text to import: {all_text:?}" ); assert!( all_text.contains("CURSOR_ASSISTANT_MARKER done"), "expected assistant text to import: {all_text:?}" ); // Resolving the resume target should import and remap to the jcode snapshot. let resumed = crate::import::resolve_resume_target_to_jcode( &jcode_session_types::ResumeTarget::CursorSession { session_id: session_id.to_string(), session_path: transcript_path.to_string_lossy().to_string(), }, ) .unwrap(); assert_eq!( resumed, jcode_session_types::ResumeTarget::JcodeSession { session_id: imported_cursor_session_id(session_id), } ); }