Skip to main content

rec/import/
mod.rs

1//! Import sessions from external sources.
2//!
3//! Supports importing from Bash scripts, Bash/Zsh/Fish history files,
4//! and auto-detects the input format.
5
6pub mod bash_history;
7pub mod bash_script;
8pub mod detect;
9pub mod fish_history;
10pub mod importer;
11pub mod zsh_history;
12
13pub use detect::*;
14pub use importer::*;
15
16#[cfg(test)]
17mod tests {
18    use super::*;
19    use crate::storage::{Paths, SessionStore};
20    use std::path::Path;
21    use tempfile::TempDir;
22
23    // ─── Format detection tests ──────────────────────────────
24
25    #[test]
26    fn test_detect_cast_extension_rejected() {
27        let result = detect_format("recording.cast", "");
28        assert!(result.is_err());
29        let err = result.unwrap_err().to_string();
30        assert!(err.contains("not currently supported"), "Error was: {err}");
31    }
32
33    #[test]
34    fn test_detect_sh_extension() {
35        let result = detect_format("deploy.sh", "echo hello").unwrap();
36        assert_eq!(result, ImportFormat::BashScript);
37    }
38
39    #[test]
40    fn test_detect_bash_extension() {
41        let result = detect_format("setup.bash", "echo hello").unwrap();
42        assert_eq!(result, ImportFormat::BashScript);
43    }
44
45    #[test]
46    fn test_detect_shebang_bash() {
47        let result = detect_format("myscript", "#!/bin/bash\necho hello").unwrap();
48        assert_eq!(result, ImportFormat::BashScript);
49    }
50
51    #[test]
52    fn test_detect_shebang_sh() {
53        let result = detect_format("myscript", "#!/bin/sh\necho hello").unwrap();
54        assert_eq!(result, ImportFormat::BashScript);
55    }
56
57    #[test]
58    fn test_detect_shebang_env_bash() {
59        let result = detect_format("myscript", "#!/usr/bin/env bash\necho hello").unwrap();
60        assert_eq!(result, ImportFormat::BashScript);
61    }
62
63    #[test]
64    fn test_detect_zsh_extended() {
65        let content = ": 1458291931:0;ls -l\n: 1458291945:3;git push";
66        let result = detect_format("history", content).unwrap();
67        assert_eq!(result, ImportFormat::ZshHistory);
68    }
69
70    #[test]
71    fn test_detect_fish_history() {
72        let content = "- cmd: ls -la\n  when: 123";
73        let result = detect_format("fish_history", content).unwrap();
74        assert_eq!(result, ImportFormat::FishHistory);
75    }
76
77    #[test]
78    fn test_detect_fallback_bash_history() {
79        let content = "ls -la\ngit status";
80        let result = detect_format("history", content).unwrap();
81        assert_eq!(result, ImportFormat::BashHistory);
82    }
83
84    // ─── Session name generation tests ───────────────────────
85
86    #[test]
87    fn test_name_from_sh_file() {
88        let name = session_name_from_path(Path::new("deploy.sh"));
89        assert_eq!(name, "deploy-sh");
90    }
91
92    #[test]
93    fn test_name_from_hidden_file() {
94        let name = session_name_from_path(Path::new(".bash_history"));
95        assert_eq!(name, "bash_history");
96    }
97
98    #[test]
99    fn test_name_from_zsh_hidden() {
100        let name = session_name_from_path(Path::new(".zsh_history"));
101        assert_eq!(name, "zsh_history");
102    }
103
104    #[test]
105    fn test_name_with_spaces_and_parens() {
106        let name = session_name_from_path(Path::new("my script (v2).sh"));
107        assert_eq!(name, "my-script-v2-sh");
108    }
109
110    #[test]
111    fn test_name_preserves_underscores() {
112        let name = session_name_from_path(Path::new("my_script.sh"));
113        assert_eq!(name, "my_script-sh");
114    }
115
116    #[test]
117    fn test_name_from_full_path() {
118        let name = session_name_from_path(Path::new("/home/user/.bash_history"));
119        assert_eq!(name, "bash_history");
120    }
121
122    #[test]
123    fn test_name_lowercased() {
124        let name = session_name_from_path(Path::new("DeployScript.SH"));
125        assert_eq!(name, "deployscript-sh");
126    }
127
128    // ─── ImportFormat Display tests ──────────────────────────
129
130    #[test]
131    fn test_format_display() {
132        assert_eq!(ImportFormat::BashScript.to_string(), "bash-script");
133        assert_eq!(ImportFormat::BashHistory.to_string(), "bash-history");
134        assert_eq!(ImportFormat::ZshHistory.to_string(), "zsh-history");
135        assert_eq!(ImportFormat::FishHistory.to_string(), "fish-history");
136    }
137
138    // ─── import_file integration tests ───────────────────────
139
140    fn create_test_paths(temp_dir: &TempDir) -> Paths {
141        Paths {
142            data_dir: temp_dir.path().join("sessions"),
143            config_dir: temp_dir.path().join("config"),
144            config_file: temp_dir.path().join("config").join("config.toml"),
145            state_dir: temp_dir.path().join("state"),
146        }
147    }
148
149    #[test]
150    fn test_import_bash_script_file() {
151        let temp_dir = TempDir::new().unwrap();
152        let paths = create_test_paths(&temp_dir);
153        let store = SessionStore::new(paths);
154
155        let script_path = temp_dir.path().join("deploy.sh");
156        std::fs::write(&script_path, "#!/bin/bash\n# setup\necho hello\ngit push\n").unwrap();
157
158        let result = import_file(&script_path, None, &store).unwrap();
159
160        assert_eq!(result.session_name, "deploy-sh");
161        assert_eq!(result.command_count, 2);
162        assert_eq!(result.format, ImportFormat::BashScript);
163        assert_eq!(result.preview_commands, vec!["echo hello", "git push"]);
164    }
165
166    #[test]
167    fn test_import_with_name_override() {
168        let temp_dir = TempDir::new().unwrap();
169        let paths = create_test_paths(&temp_dir);
170        let store = SessionStore::new(paths);
171
172        let script_path = temp_dir.path().join("script.sh");
173        std::fs::write(&script_path, "echo hello\n").unwrap();
174
175        let result = import_file(&script_path, Some("custom-name"), &store).unwrap();
176        assert_eq!(result.session_name, "custom-name");
177    }
178
179    #[test]
180    fn test_import_empty_file_errors() {
181        let temp_dir = TempDir::new().unwrap();
182        let paths = create_test_paths(&temp_dir);
183        let store = SessionStore::new(paths);
184
185        let script_path = temp_dir.path().join("empty.sh");
186        std::fs::write(&script_path, "#!/bin/bash\n# only comments\n").unwrap();
187
188        let result = import_file(&script_path, None, &store);
189        assert!(result.is_err());
190        let err = result.unwrap_err().to_string();
191        assert!(err.contains("No commands found"), "Error was: {err}");
192    }
193
194    #[test]
195    fn test_import_preview_capped_at_5() {
196        let temp_dir = TempDir::new().unwrap();
197        let paths = create_test_paths(&temp_dir);
198        let store = SessionStore::new(paths);
199
200        let script_path = temp_dir.path().join("many.sh");
201        std::fs::write(
202            &script_path,
203            "cmd1\ncmd2\ncmd3\ncmd4\ncmd5\ncmd6\ncmd7\ncmd8\n",
204        )
205        .unwrap();
206
207        let result = import_file(&script_path, None, &store).unwrap();
208        assert_eq!(result.command_count, 8);
209        assert_eq!(result.preview_commands.len(), 5);
210    }
211
212    #[test]
213    fn test_import_cast_file_rejected() {
214        let temp_dir = TempDir::new().unwrap();
215        let paths = create_test_paths(&temp_dir);
216        let store = SessionStore::new(paths);
217
218        let cast_path = temp_dir.path().join("recording.cast");
219        std::fs::write(&cast_path, "some asciinema content").unwrap();
220
221        let result = import_file(&cast_path, None, &store);
222        assert!(result.is_err());
223        let err = result.unwrap_err().to_string();
224        assert!(err.contains("not currently supported"), "Error was: {err}");
225    }
226
227    #[test]
228    fn test_import_session_persisted() {
229        let temp_dir = TempDir::new().unwrap();
230        let paths = create_test_paths(&temp_dir);
231        let store = SessionStore::new(paths);
232
233        let script_path = temp_dir.path().join("test.sh");
234        std::fs::write(&script_path, "echo hello\nls\n").unwrap();
235
236        let result = import_file(&script_path, None, &store).unwrap();
237
238        // Verify session was actually saved
239        let sessions = store.list().unwrap();
240        assert_eq!(sessions.len(), 1);
241
242        // Load and verify content
243        let loaded = store.load(&sessions[0]).unwrap();
244        assert_eq!(loaded.header.name, result.session_name);
245        assert_eq!(loaded.commands.len(), 2);
246        assert!(loaded.footer.is_some());
247    }
248}