rust_expect/session/
builder.rs

1//! Session builder for constructing sessions with custom configuration.
2//!
3//! This module provides a builder pattern for creating sessions with
4//! customized configuration options.
5
6use std::path::PathBuf;
7use std::time::Duration;
8
9use crate::config::{
10    BufferConfig, EncodingConfig, LineEnding, LoggingConfig, SessionConfig, TimeoutConfig,
11};
12
13/// Builder for creating session configurations.
14#[derive(Debug, Clone)]
15pub struct SessionBuilder {
16    config: SessionConfig,
17}
18
19impl SessionBuilder {
20    /// Create a new session builder with default configuration.
21    #[must_use]
22    pub fn new() -> Self {
23        Self {
24            config: SessionConfig::default(),
25        }
26    }
27
28    /// Set the command to execute.
29    #[must_use]
30    pub fn command(mut self, command: impl Into<String>) -> Self {
31        self.config.command = command.into();
32        self
33    }
34
35    /// Set the command arguments.
36    #[must_use]
37    pub fn args<I, S>(mut self, args: I) -> Self
38    where
39        I: IntoIterator<Item = S>,
40        S: Into<String>,
41    {
42        self.config.args = args.into_iter().map(Into::into).collect();
43        self
44    }
45
46    /// Add a single argument.
47    #[must_use]
48    pub fn arg(mut self, arg: impl Into<String>) -> Self {
49        self.config.args.push(arg.into());
50        self
51    }
52
53    /// Set environment variables.
54    #[must_use]
55    pub fn envs<I, K, V>(mut self, envs: I) -> Self
56    where
57        I: IntoIterator<Item = (K, V)>,
58        K: Into<String>,
59        V: Into<String>,
60    {
61        self.config.env = envs
62            .into_iter()
63            .map(|(k, v)| (k.into(), v.into()))
64            .collect();
65        self
66    }
67
68    /// Set a single environment variable.
69    #[must_use]
70    pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
71        self.config.env.insert(key.into(), value.into());
72        self
73    }
74
75    /// Set the working directory.
76    #[must_use]
77    pub fn working_directory(mut self, path: impl Into<PathBuf>) -> Self {
78        self.config.working_dir = Some(path.into());
79        self
80    }
81
82    /// Set the terminal dimensions (width, height).
83    #[must_use]
84    pub const fn dimensions(mut self, cols: u16, rows: u16) -> Self {
85        self.config.dimensions = (cols, rows);
86        self
87    }
88
89    /// Set the default timeout.
90    #[must_use]
91    pub const fn timeout(mut self, timeout: Duration) -> Self {
92        self.config.timeout.default = timeout;
93        self
94    }
95
96    /// Set the timeout configuration.
97    #[must_use]
98    pub const fn timeout_config(mut self, config: TimeoutConfig) -> Self {
99        self.config.timeout = config;
100        self
101    }
102
103    /// Set the buffer max size.
104    #[must_use]
105    pub const fn buffer_max_size(mut self, max_size: usize) -> Self {
106        self.config.buffer.max_size = max_size;
107        self
108    }
109
110    /// Set the buffer configuration.
111    #[must_use]
112    pub const fn buffer_config(mut self, config: BufferConfig) -> Self {
113        self.config.buffer = config;
114        self
115    }
116
117    /// Set the line ending style.
118    #[must_use]
119    pub const fn line_ending(mut self, line_ending: LineEnding) -> Self {
120        self.config.line_ending = line_ending;
121        self
122    }
123
124    /// Use Unix line endings (LF).
125    #[must_use]
126    pub const fn unix_line_endings(self) -> Self {
127        self.line_ending(LineEnding::Lf)
128    }
129
130    /// Use Windows line endings (CRLF).
131    #[must_use]
132    pub const fn windows_line_endings(self) -> Self {
133        self.line_ending(LineEnding::CrLf)
134    }
135
136    /// Set the encoding configuration.
137    #[must_use]
138    pub const fn encoding(mut self, config: EncodingConfig) -> Self {
139        self.config.encoding = config;
140        self
141    }
142
143    /// Set the logging configuration.
144    #[must_use]
145    pub fn logging(mut self, config: LoggingConfig) -> Self {
146        self.config.logging = config;
147        self
148    }
149
150    /// Enable logging to a file.
151    #[must_use]
152    pub fn log_to_file(mut self, path: impl Into<PathBuf>) -> Self {
153        self.config.logging.log_file = Some(path.into());
154        self
155    }
156
157    /// Build the session configuration.
158    #[must_use]
159    pub fn build(self) -> SessionConfig {
160        self.config
161    }
162}
163
164impl Default for SessionBuilder {
165    fn default() -> Self {
166        Self::new()
167    }
168}
169
170impl From<SessionBuilder> for SessionConfig {
171    fn from(builder: SessionBuilder) -> Self {
172        builder.build()
173    }
174}
175
176/// Quick session configuration for common use cases.
177pub struct QuickSession;
178
179impl QuickSession {
180    /// Create a session config for a shell command.
181    #[must_use]
182    pub fn shell() -> SessionConfig {
183        SessionBuilder::new().command(Self::default_shell()).build()
184    }
185
186    /// Create a session config for bash.
187    #[must_use]
188    pub fn bash() -> SessionConfig {
189        SessionBuilder::new()
190            .command("/bin/bash")
191            .arg("--norc")
192            .arg("--noprofile")
193            .build()
194    }
195
196    /// Create a session config for a custom command.
197    #[must_use]
198    pub fn command(cmd: impl Into<String>) -> SessionConfig {
199        SessionBuilder::new().command(cmd).build()
200    }
201
202    /// Create a session config for SSH.
203    #[must_use]
204    pub fn ssh(host: &str) -> SessionConfig {
205        SessionBuilder::new()
206            .command("ssh")
207            .arg(host)
208            .timeout(Duration::from_secs(30))
209            .build()
210    }
211
212    /// Create a session config for SSH with user.
213    #[must_use]
214    pub fn ssh_user(user: &str, host: &str) -> SessionConfig {
215        SessionBuilder::new()
216            .command("ssh")
217            .arg(format!("{user}@{host}"))
218            .timeout(Duration::from_secs(30))
219            .build()
220    }
221
222    /// Create a session config for telnet.
223    #[must_use]
224    pub fn telnet(host: &str, port: u16) -> SessionConfig {
225        SessionBuilder::new()
226            .command("telnet")
227            .arg(host)
228            .arg(port.to_string())
229            .timeout(Duration::from_secs(30))
230            .build()
231    }
232
233    /// Create a session config for Python.
234    #[must_use]
235    pub fn python() -> SessionConfig {
236        SessionBuilder::new()
237            .command(if cfg!(windows) { "python" } else { "python3" })
238            .arg("-i")
239            .build()
240    }
241
242    /// Create a session config for Windows Command Prompt.
243    ///
244    /// This configures a cmd.exe session with Windows-style line endings.
245    #[must_use]
246    pub fn cmd() -> SessionConfig {
247        SessionBuilder::new()
248            .command("cmd.exe")
249            .windows_line_endings()
250            .build()
251    }
252
253    /// Create a session config for `PowerShell`.
254    ///
255    /// Works with both Windows `PowerShell` (`powershell.exe`) and
256    /// `PowerShell` Core (`pwsh.exe`). Defaults to `powershell.exe` on Windows,
257    /// `pwsh` on other platforms.
258    #[must_use]
259    pub fn powershell() -> SessionConfig {
260        let command = if cfg!(windows) {
261            "powershell.exe"
262        } else {
263            "pwsh"
264        };
265        SessionBuilder::new()
266            .command(command)
267            .arg("-NoLogo")
268            .arg("-NoProfile")
269            .build()
270    }
271
272    /// Create a session config for zsh.
273    #[must_use]
274    pub fn zsh() -> SessionConfig {
275        SessionBuilder::new()
276            .command("/bin/zsh")
277            .arg("--no-rcs")
278            .build()
279    }
280
281    /// Create a session config for fish shell.
282    #[must_use]
283    pub fn fish() -> SessionConfig {
284        SessionBuilder::new()
285            .command("fish")
286            .arg("--no-config")
287            .build()
288    }
289
290    /// Create a session config for a REPL.
291    #[must_use]
292    pub fn repl(cmd: impl Into<String>) -> SessionConfig {
293        SessionBuilder::new().command(cmd).build()
294    }
295
296    /// Create a session config for Node.js REPL.
297    #[must_use]
298    pub fn node() -> SessionConfig {
299        SessionBuilder::new().command("node").build()
300    }
301
302    /// Create a session config for Ruby IRB.
303    #[must_use]
304    pub fn ruby() -> SessionConfig {
305        SessionBuilder::new()
306            .command("irb")
307            .arg("--simple-prompt")
308            .build()
309    }
310
311    /// Create a session config for `MySQL` client.
312    #[must_use]
313    pub fn mysql(host: &str, user: &str, database: &str) -> SessionConfig {
314        SessionBuilder::new()
315            .command("mysql")
316            .arg("-h")
317            .arg(host)
318            .arg("-u")
319            .arg(user)
320            .arg(database)
321            .timeout(Duration::from_secs(30))
322            .build()
323    }
324
325    /// Create a session config for `MySQL` client with password prompt.
326    #[must_use]
327    pub fn mysql_password(host: &str, user: &str, database: &str) -> SessionConfig {
328        SessionBuilder::new()
329            .command("mysql")
330            .arg("-h")
331            .arg(host)
332            .arg("-u")
333            .arg(user)
334            .arg("-p")
335            .arg(database)
336            .timeout(Duration::from_secs(30))
337            .build()
338    }
339
340    /// Create a session config for `PostgreSQL` client.
341    #[must_use]
342    pub fn psql(host: &str, user: &str, database: &str) -> SessionConfig {
343        SessionBuilder::new()
344            .command("psql")
345            .arg("-h")
346            .arg(host)
347            .arg("-U")
348            .arg(user)
349            .arg(database)
350            .timeout(Duration::from_secs(30))
351            .build()
352    }
353
354    /// Create a session config for Docker exec into a container.
355    #[must_use]
356    pub fn docker_exec(container: &str) -> SessionConfig {
357        SessionBuilder::new()
358            .command("docker")
359            .arg("exec")
360            .arg("-it")
361            .arg(container)
362            .arg("/bin/sh")
363            .build()
364    }
365
366    /// Create a session config for Docker exec with a specific shell.
367    #[must_use]
368    pub fn docker_exec_shell(container: &str, shell: &str) -> SessionConfig {
369        SessionBuilder::new()
370            .command("docker")
371            .arg("exec")
372            .arg("-it")
373            .arg(container)
374            .arg(shell)
375            .build()
376    }
377
378    /// Create a session config for Docker run with interactive shell.
379    #[must_use]
380    pub fn docker_run(image: &str) -> SessionConfig {
381        SessionBuilder::new()
382            .command("docker")
383            .arg("run")
384            .arg("-it")
385            .arg("--rm")
386            .arg(image)
387            .build()
388    }
389
390    /// Create a session config for Redis CLI.
391    #[must_use]
392    pub fn redis_cli(host: &str) -> SessionConfig {
393        SessionBuilder::new()
394            .command("redis-cli")
395            .arg("-h")
396            .arg(host)
397            .build()
398    }
399
400    /// Create a session config for `MongoDB` shell.
401    #[must_use]
402    pub fn mongosh(uri: &str) -> SessionConfig {
403        SessionBuilder::new()
404            .command("mongosh")
405            .arg(uri)
406            .timeout(Duration::from_secs(30))
407            .build()
408    }
409
410    /// Create a session config for `SQLite`.
411    #[must_use]
412    pub fn sqlite(database: &str) -> SessionConfig {
413        SessionBuilder::new()
414            .command("sqlite3")
415            .arg(database)
416            .build()
417    }
418
419    /// Create a session config for GDB debugger.
420    #[must_use]
421    pub fn gdb(program: &str) -> SessionConfig {
422        SessionBuilder::new().command("gdb").arg(program).build()
423    }
424
425    /// Create a session config for LLDB debugger.
426    #[must_use]
427    pub fn lldb(program: &str) -> SessionConfig {
428        SessionBuilder::new().command("lldb").arg(program).build()
429    }
430
431    /// Create a session config for Lua REPL.
432    #[must_use]
433    pub fn lua() -> SessionConfig {
434        SessionBuilder::new().command("lua").arg("-i").build()
435    }
436
437    /// Create a session config for Perl debugger.
438    #[must_use]
439    pub fn perl() -> SessionConfig {
440        SessionBuilder::new().command("perl").arg("-de0").build()
441    }
442
443    /// Create a session config for R REPL.
444    #[must_use]
445    pub fn r() -> SessionConfig {
446        SessionBuilder::new()
447            .command("R")
448            .arg("--no-save")
449            .arg("--no-restore")
450            .build()
451    }
452
453    /// Create a session config for Julia REPL.
454    #[must_use]
455    pub fn julia() -> SessionConfig {
456        SessionBuilder::new().command("julia").build()
457    }
458
459    /// Create a session config for Scala REPL.
460    #[must_use]
461    pub fn scala() -> SessionConfig {
462        SessionBuilder::new().command("scala").build()
463    }
464
465    /// Create a session config for Elixir `IEx`.
466    #[must_use]
467    pub fn iex() -> SessionConfig {
468        SessionBuilder::new().command("iex").build()
469    }
470
471    /// Create a session config for Clojure REPL.
472    #[must_use]
473    pub fn clojure() -> SessionConfig {
474        SessionBuilder::new().command("clj").build()
475    }
476
477    /// Create a session config for Haskell `GHCi`.
478    #[must_use]
479    pub fn ghci() -> SessionConfig {
480        SessionBuilder::new().command("ghci").build()
481    }
482
483    /// Create a session config for OCaml REPL.
484    #[must_use]
485    pub fn ocaml() -> SessionConfig {
486        SessionBuilder::new().command("ocaml").build()
487    }
488
489    /// Create a session config for kubectl exec into a pod.
490    #[must_use]
491    pub fn kubectl_exec(pod: &str) -> SessionConfig {
492        SessionBuilder::new()
493            .command("kubectl")
494            .arg("exec")
495            .arg("-it")
496            .arg(pod)
497            .arg("--")
498            .arg("/bin/sh")
499            .build()
500    }
501
502    /// Create a session config for kubectl exec with namespace.
503    #[must_use]
504    pub fn kubectl_exec_ns(namespace: &str, pod: &str, shell: &str) -> SessionConfig {
505        SessionBuilder::new()
506            .command("kubectl")
507            .arg("exec")
508            .arg("-it")
509            .arg("-n")
510            .arg(namespace)
511            .arg(pod)
512            .arg("--")
513            .arg(shell)
514            .build()
515    }
516
517    /// Create a session config for screen attach.
518    #[must_use]
519    pub fn screen_attach(session_name: &str) -> SessionConfig {
520        SessionBuilder::new()
521            .command("screen")
522            .arg("-r")
523            .arg(session_name)
524            .build()
525    }
526
527    /// Create a session config for tmux attach.
528    #[must_use]
529    pub fn tmux_attach(session_name: &str) -> SessionConfig {
530        SessionBuilder::new()
531            .command("tmux")
532            .arg("attach")
533            .arg("-t")
534            .arg(session_name)
535            .build()
536    }
537
538    /// Create a session config for SSH with a specific port.
539    #[must_use]
540    pub fn ssh_port(host: &str, port: u16) -> SessionConfig {
541        SessionBuilder::new()
542            .command("ssh")
543            .arg("-p")
544            .arg(port.to_string())
545            .arg(host)
546            .timeout(Duration::from_secs(30))
547            .build()
548    }
549
550    /// Create a session config for SSH with user and port.
551    #[must_use]
552    pub fn ssh_full(user: &str, host: &str, port: u16) -> SessionConfig {
553        SessionBuilder::new()
554            .command("ssh")
555            .arg("-p")
556            .arg(port.to_string())
557            .arg(format!("{user}@{host}"))
558            .timeout(Duration::from_secs(30))
559            .build()
560    }
561
562    /// Create a session config for SSH with a specific identity file.
563    #[must_use]
564    pub fn ssh_key(user: &str, host: &str, key_file: &str) -> SessionConfig {
565        SessionBuilder::new()
566            .command("ssh")
567            .arg("-i")
568            .arg(key_file)
569            .arg(format!("{user}@{host}"))
570            .timeout(Duration::from_secs(30))
571            .build()
572    }
573
574    /// Create a session config for Vagrant SSH.
575    #[must_use]
576    pub fn vagrant_ssh() -> SessionConfig {
577        SessionBuilder::new()
578            .command("vagrant")
579            .arg("ssh")
580            .timeout(Duration::from_secs(30))
581            .build()
582    }
583
584    /// Create a session config for Vagrant SSH to a specific machine.
585    #[must_use]
586    pub fn vagrant_ssh_machine(machine: &str) -> SessionConfig {
587        SessionBuilder::new()
588            .command("vagrant")
589            .arg("ssh")
590            .arg(machine)
591            .timeout(Duration::from_secs(30))
592            .build()
593    }
594
595    /// Create a session config for SFTP.
596    #[must_use]
597    pub fn sftp(host: &str) -> SessionConfig {
598        SessionBuilder::new()
599            .command("sftp")
600            .arg(host)
601            .timeout(Duration::from_secs(30))
602            .build()
603    }
604
605    /// Create a session config for SFTP with user.
606    #[must_use]
607    pub fn sftp_user(user: &str, host: &str) -> SessionConfig {
608        SessionBuilder::new()
609            .command("sftp")
610            .arg(format!("{user}@{host}"))
611            .timeout(Duration::from_secs(30))
612            .build()
613    }
614
615    /// Create a session config for FTP.
616    #[must_use]
617    pub fn ftp(host: &str) -> SessionConfig {
618        SessionBuilder::new()
619            .command("ftp")
620            .arg(host)
621            .timeout(Duration::from_secs(30))
622            .build()
623    }
624
625    /// Create a session config for netcat interactive mode.
626    #[must_use]
627    pub fn netcat(host: &str, port: u16) -> SessionConfig {
628        SessionBuilder::new()
629            .command("nc")
630            .arg(host)
631            .arg(port.to_string())
632            .build()
633    }
634
635    /// Create a session config for socat interactive mode.
636    #[must_use]
637    pub fn socat(address: &str) -> SessionConfig {
638        SessionBuilder::new()
639            .command("socat")
640            .arg("-")
641            .arg(address)
642            .build()
643    }
644
645    /// Create a session config for minicom serial terminal.
646    #[must_use]
647    pub fn minicom(device: &str) -> SessionConfig {
648        SessionBuilder::new()
649            .command("minicom")
650            .arg("-D")
651            .arg(device)
652            .build()
653    }
654
655    /// Create a session config for screen serial terminal.
656    #[must_use]
657    pub fn screen_serial(device: &str, baud_rate: u32) -> SessionConfig {
658        SessionBuilder::new()
659            .command("screen")
660            .arg(device)
661            .arg(baud_rate.to_string())
662            .build()
663    }
664
665    /// Create a session config for picocom serial terminal.
666    #[must_use]
667    pub fn picocom(device: &str, baud_rate: u32) -> SessionConfig {
668        SessionBuilder::new()
669            .command("picocom")
670            .arg("-b")
671            .arg(baud_rate.to_string())
672            .arg(device)
673            .build()
674    }
675
676    /// Create a session config for AWS SSM session.
677    #[must_use]
678    pub fn aws_ssm(instance_id: &str) -> SessionConfig {
679        SessionBuilder::new()
680            .command("aws")
681            .arg("ssm")
682            .arg("start-session")
683            .arg("--target")
684            .arg(instance_id)
685            .timeout(Duration::from_secs(60))
686            .build()
687    }
688
689    /// Create a session config for Azure VM serial console.
690    #[must_use]
691    pub fn az_serial_console(resource_group: &str, vm_name: &str) -> SessionConfig {
692        SessionBuilder::new()
693            .command("az")
694            .arg("serial-console")
695            .arg("connect")
696            .arg("--resource-group")
697            .arg(resource_group)
698            .arg("--name")
699            .arg(vm_name)
700            .timeout(Duration::from_secs(60))
701            .build()
702    }
703
704    /// Create a session config for GCP SSH.
705    #[must_use]
706    pub fn gcloud_ssh(instance: &str, zone: &str) -> SessionConfig {
707        SessionBuilder::new()
708            .command("gcloud")
709            .arg("compute")
710            .arg("ssh")
711            .arg(instance)
712            .arg("--zone")
713            .arg(zone)
714            .timeout(Duration::from_secs(60))
715            .build()
716    }
717
718    /// Create a session config for Rust REPL (evcxr).
719    #[must_use]
720    pub fn evcxr() -> SessionConfig {
721        SessionBuilder::new().command("evcxr").build()
722    }
723
724    /// Create a session config for Go playground.
725    #[must_use]
726    pub fn gore() -> SessionConfig {
727        SessionBuilder::new().command("gore").build()
728    }
729
730    /// Create a session config for PHP interactive mode.
731    #[must_use]
732    pub fn php() -> SessionConfig {
733        SessionBuilder::new().command("php").arg("-a").build()
734    }
735
736    /// Create a session config for Swift REPL.
737    #[must_use]
738    pub fn swift() -> SessionConfig {
739        SessionBuilder::new().command("swift").build()
740    }
741
742    /// Create a session config for Kotlin REPL.
743    #[must_use]
744    pub fn kotlin() -> SessionConfig {
745        SessionBuilder::new().command("kotlin").build()
746    }
747
748    /// Create a session config for Groovy console.
749    #[must_use]
750    pub fn groovysh() -> SessionConfig {
751        SessionBuilder::new().command("groovysh").build()
752    }
753
754    /// Create a session config for TypeScript REPL (ts-node).
755    #[must_use]
756    pub fn ts_node() -> SessionConfig {
757        SessionBuilder::new().command("ts-node").build()
758    }
759
760    /// Create a session config for Deno REPL.
761    #[must_use]
762    pub fn deno() -> SessionConfig {
763        SessionBuilder::new().command("deno").build()
764    }
765
766    /// Create a session config for Bun REPL.
767    #[must_use]
768    pub fn bun() -> SessionConfig {
769        SessionBuilder::new().command("bun").arg("repl").build()
770    }
771
772    /// Get the default shell for the current platform.
773    #[must_use]
774    pub fn default_shell() -> String {
775        std::env::var("SHELL").unwrap_or_else(|_| {
776            if cfg!(windows) {
777                "cmd.exe".to_string()
778            } else {
779                "/bin/sh".to_string()
780            }
781        })
782    }
783}
784
785#[cfg(test)]
786mod tests {
787    use super::*;
788
789    #[test]
790    fn builder_basic() {
791        let config = SessionBuilder::new()
792            .command("/bin/bash")
793            .arg("-c")
794            .arg("echo hello")
795            .build();
796
797        assert_eq!(config.command, "/bin/bash");
798        assert_eq!(config.args, vec!["-c", "echo hello"]);
799    }
800
801    #[test]
802    fn builder_env() {
803        let config = SessionBuilder::new()
804            .command("test")
805            .env("FOO", "bar")
806            .env("BAZ", "qux")
807            .build();
808
809        assert_eq!(config.env.get("FOO"), Some(&"bar".to_string()));
810        assert_eq!(config.env.get("BAZ"), Some(&"qux".to_string()));
811    }
812
813    #[test]
814    fn builder_timeout() {
815        let config = SessionBuilder::new()
816            .command("test")
817            .timeout(Duration::from_secs(60))
818            .build();
819
820        assert_eq!(config.timeout.default, Duration::from_secs(60));
821    }
822
823    #[test]
824    fn quick_session_bash() {
825        let config = QuickSession::bash();
826        assert_eq!(config.command, "/bin/bash");
827        assert!(config.args.contains(&"--norc".to_string()));
828    }
829
830    #[test]
831    fn quick_session_ssh() {
832        let config = QuickSession::ssh_user("admin", "example.com");
833        assert_eq!(config.command, "ssh");
834        assert!(config.args.contains(&"admin@example.com".to_string()));
835    }
836
837    #[test]
838    fn quick_session_cmd() {
839        let config = QuickSession::cmd();
840        assert_eq!(config.command, "cmd.exe");
841        assert_eq!(config.line_ending, LineEnding::CrLf);
842    }
843
844    #[test]
845    fn quick_session_powershell() {
846        let config = QuickSession::powershell();
847        #[cfg(windows)]
848        assert_eq!(config.command, "powershell.exe");
849        #[cfg(not(windows))]
850        assert_eq!(config.command, "pwsh");
851        assert!(config.args.contains(&"-NoLogo".to_string()));
852        assert!(config.args.contains(&"-NoProfile".to_string()));
853    }
854
855    #[test]
856    fn quick_session_zsh() {
857        let config = QuickSession::zsh();
858        assert_eq!(config.command, "/bin/zsh");
859        assert!(config.args.contains(&"--no-rcs".to_string()));
860    }
861
862    #[test]
863    fn quick_session_fish() {
864        let config = QuickSession::fish();
865        assert_eq!(config.command, "fish");
866        assert!(config.args.contains(&"--no-config".to_string()));
867    }
868
869    #[test]
870    fn quick_session_python() {
871        let config = QuickSession::python();
872        #[cfg(windows)]
873        assert_eq!(config.command, "python");
874        #[cfg(not(windows))]
875        assert_eq!(config.command, "python3");
876        assert!(config.args.contains(&"-i".to_string()));
877    }
878
879    #[test]
880    fn quick_session_node() {
881        let config = QuickSession::node();
882        assert_eq!(config.command, "node");
883    }
884
885    #[test]
886    fn quick_session_ruby() {
887        let config = QuickSession::ruby();
888        assert_eq!(config.command, "irb");
889        assert!(config.args.contains(&"--simple-prompt".to_string()));
890    }
891
892    #[test]
893    fn quick_session_mysql() {
894        let config = QuickSession::mysql("localhost", "root", "testdb");
895        assert_eq!(config.command, "mysql");
896        assert!(config.args.contains(&"-h".to_string()));
897        assert!(config.args.contains(&"localhost".to_string()));
898        assert!(config.args.contains(&"-u".to_string()));
899        assert!(config.args.contains(&"root".to_string()));
900        assert!(config.args.contains(&"testdb".to_string()));
901    }
902
903    #[test]
904    fn quick_session_psql() {
905        let config = QuickSession::psql("localhost", "postgres", "mydb");
906        assert_eq!(config.command, "psql");
907        assert!(config.args.contains(&"-h".to_string()));
908        assert!(config.args.contains(&"-U".to_string()));
909        assert!(config.args.contains(&"postgres".to_string()));
910    }
911
912    #[test]
913    fn quick_session_docker_exec() {
914        let config = QuickSession::docker_exec("my-container");
915        assert_eq!(config.command, "docker");
916        assert!(config.args.contains(&"exec".to_string()));
917        assert!(config.args.contains(&"-it".to_string()));
918        assert!(config.args.contains(&"my-container".to_string()));
919        assert!(config.args.contains(&"/bin/sh".to_string()));
920    }
921
922    #[test]
923    fn quick_session_docker_run() {
924        let config = QuickSession::docker_run("ubuntu:latest");
925        assert_eq!(config.command, "docker");
926        assert!(config.args.contains(&"run".to_string()));
927        assert!(config.args.contains(&"-it".to_string()));
928        assert!(config.args.contains(&"--rm".to_string()));
929        assert!(config.args.contains(&"ubuntu:latest".to_string()));
930    }
931
932    #[test]
933    fn quick_session_redis() {
934        let config = QuickSession::redis_cli("redis.example.com");
935        assert_eq!(config.command, "redis-cli");
936        assert!(config.args.contains(&"-h".to_string()));
937        assert!(config.args.contains(&"redis.example.com".to_string()));
938    }
939
940    #[test]
941    fn quick_session_sqlite() {
942        let config = QuickSession::sqlite("test.db");
943        assert_eq!(config.command, "sqlite3");
944        assert!(config.args.contains(&"test.db".to_string()));
945    }
946
947    #[test]
948    fn quick_session_gdb() {
949        let config = QuickSession::gdb("./my_program");
950        assert_eq!(config.command, "gdb");
951        assert!(config.args.contains(&"./my_program".to_string()));
952    }
953
954    #[test]
955    fn quick_session_kubectl() {
956        let config = QuickSession::kubectl_exec("my-pod");
957        assert_eq!(config.command, "kubectl");
958        assert!(config.args.contains(&"exec".to_string()));
959        assert!(config.args.contains(&"-it".to_string()));
960        assert!(config.args.contains(&"my-pod".to_string()));
961        assert!(config.args.contains(&"--".to_string()));
962        assert!(config.args.contains(&"/bin/sh".to_string()));
963    }
964
965    #[test]
966    fn quick_session_kubectl_ns() {
967        let config = QuickSession::kubectl_exec_ns("production", "api-pod", "/bin/bash");
968        assert_eq!(config.command, "kubectl");
969        assert!(config.args.contains(&"-n".to_string()));
970        assert!(config.args.contains(&"production".to_string()));
971        assert!(config.args.contains(&"api-pod".to_string()));
972        assert!(config.args.contains(&"/bin/bash".to_string()));
973    }
974
975    #[test]
976    fn quick_session_repls() {
977        // Test various REPL helpers
978        assert_eq!(QuickSession::lua().command, "lua");
979        assert_eq!(QuickSession::julia().command, "julia");
980        assert_eq!(QuickSession::scala().command, "scala");
981        assert_eq!(QuickSession::iex().command, "iex");
982        assert_eq!(QuickSession::clojure().command, "clj");
983        assert_eq!(QuickSession::ghci().command, "ghci");
984        assert_eq!(QuickSession::ocaml().command, "ocaml");
985        assert_eq!(QuickSession::r().command, "R");
986    }
987
988    #[test]
989    fn quick_session_tmux_screen() {
990        let config = QuickSession::tmux_attach("mysession");
991        assert_eq!(config.command, "tmux");
992        assert!(config.args.contains(&"attach".to_string()));
993        assert!(config.args.contains(&"-t".to_string()));
994        assert!(config.args.contains(&"mysession".to_string()));
995
996        let config = QuickSession::screen_attach("myscreen");
997        assert_eq!(config.command, "screen");
998        assert!(config.args.contains(&"-r".to_string()));
999        assert!(config.args.contains(&"myscreen".to_string()));
1000    }
1001
1002    #[test]
1003    fn quick_session_ssh_variants() {
1004        // ssh_port
1005        let config = QuickSession::ssh_port("example.com", 2222);
1006        assert_eq!(config.command, "ssh");
1007        assert!(config.args.contains(&"-p".to_string()));
1008        assert!(config.args.contains(&"2222".to_string()));
1009        assert!(config.args.contains(&"example.com".to_string()));
1010
1011        // ssh_full
1012        let config = QuickSession::ssh_full("admin", "server.com", 2222);
1013        assert_eq!(config.command, "ssh");
1014        assert!(config.args.contains(&"-p".to_string()));
1015        assert!(config.args.contains(&"2222".to_string()));
1016        assert!(config.args.contains(&"admin@server.com".to_string()));
1017
1018        // ssh_key
1019        let config = QuickSession::ssh_key("root", "host.com", "/path/to/key");
1020        assert_eq!(config.command, "ssh");
1021        assert!(config.args.contains(&"-i".to_string()));
1022        assert!(config.args.contains(&"/path/to/key".to_string()));
1023        assert!(config.args.contains(&"root@host.com".to_string()));
1024    }
1025
1026    #[test]
1027    fn quick_session_vagrant() {
1028        let config = QuickSession::vagrant_ssh();
1029        assert_eq!(config.command, "vagrant");
1030        assert!(config.args.contains(&"ssh".to_string()));
1031
1032        let config = QuickSession::vagrant_ssh_machine("web");
1033        assert_eq!(config.command, "vagrant");
1034        assert!(config.args.contains(&"ssh".to_string()));
1035        assert!(config.args.contains(&"web".to_string()));
1036    }
1037
1038    #[test]
1039    fn quick_session_file_transfer() {
1040        let config = QuickSession::sftp("server.com");
1041        assert_eq!(config.command, "sftp");
1042        assert!(config.args.contains(&"server.com".to_string()));
1043
1044        let config = QuickSession::sftp_user("admin", "server.com");
1045        assert_eq!(config.command, "sftp");
1046        assert!(config.args.contains(&"admin@server.com".to_string()));
1047
1048        let config = QuickSession::ftp("ftp.example.com");
1049        assert_eq!(config.command, "ftp");
1050        assert!(config.args.contains(&"ftp.example.com".to_string()));
1051    }
1052
1053    #[test]
1054    fn quick_session_network_tools() {
1055        let config = QuickSession::netcat("localhost", 8080);
1056        assert_eq!(config.command, "nc");
1057        assert!(config.args.contains(&"localhost".to_string()));
1058        assert!(config.args.contains(&"8080".to_string()));
1059
1060        let config = QuickSession::socat("TCP:server:1234");
1061        assert_eq!(config.command, "socat");
1062        assert!(config.args.contains(&"-".to_string()));
1063        assert!(config.args.contains(&"TCP:server:1234".to_string()));
1064    }
1065
1066    #[test]
1067    fn quick_session_serial_terminals() {
1068        let config = QuickSession::minicom("/dev/ttyUSB0");
1069        assert_eq!(config.command, "minicom");
1070        assert!(config.args.contains(&"-D".to_string()));
1071        assert!(config.args.contains(&"/dev/ttyUSB0".to_string()));
1072
1073        let config = QuickSession::screen_serial("/dev/ttyACM0", 115_200);
1074        assert_eq!(config.command, "screen");
1075        assert!(config.args.contains(&"/dev/ttyACM0".to_string()));
1076        assert!(config.args.contains(&"115200".to_string()));
1077
1078        let config = QuickSession::picocom("/dev/ttyS0", 9600);
1079        assert_eq!(config.command, "picocom");
1080        assert!(config.args.contains(&"-b".to_string()));
1081        assert!(config.args.contains(&"9600".to_string()));
1082        assert!(config.args.contains(&"/dev/ttyS0".to_string()));
1083    }
1084
1085    #[test]
1086    fn quick_session_cloud_providers() {
1087        let config = QuickSession::aws_ssm("i-1234567890abcdef0");
1088        assert_eq!(config.command, "aws");
1089        assert!(config.args.contains(&"ssm".to_string()));
1090        assert!(config.args.contains(&"start-session".to_string()));
1091        assert!(config.args.contains(&"--target".to_string()));
1092        assert!(config.args.contains(&"i-1234567890abcdef0".to_string()));
1093
1094        let config = QuickSession::az_serial_console("my-rg", "my-vm");
1095        assert_eq!(config.command, "az");
1096        assert!(config.args.contains(&"serial-console".to_string()));
1097        assert!(config.args.contains(&"connect".to_string()));
1098        assert!(config.args.contains(&"--resource-group".to_string()));
1099        assert!(config.args.contains(&"my-rg".to_string()));
1100        assert!(config.args.contains(&"--name".to_string()));
1101        assert!(config.args.contains(&"my-vm".to_string()));
1102
1103        let config = QuickSession::gcloud_ssh("instance-1", "us-central1-a");
1104        assert_eq!(config.command, "gcloud");
1105        assert!(config.args.contains(&"compute".to_string()));
1106        assert!(config.args.contains(&"ssh".to_string()));
1107        assert!(config.args.contains(&"instance-1".to_string()));
1108        assert!(config.args.contains(&"--zone".to_string()));
1109        assert!(config.args.contains(&"us-central1-a".to_string()));
1110    }
1111
1112    #[test]
1113    fn quick_session_additional_repls() {
1114        // Rust REPL
1115        assert_eq!(QuickSession::evcxr().command, "evcxr");
1116
1117        // Go REPL
1118        assert_eq!(QuickSession::gore().command, "gore");
1119
1120        // PHP
1121        let config = QuickSession::php();
1122        assert_eq!(config.command, "php");
1123        assert!(config.args.contains(&"-a".to_string()));
1124
1125        // Swift
1126        assert_eq!(QuickSession::swift().command, "swift");
1127
1128        // Kotlin
1129        assert_eq!(QuickSession::kotlin().command, "kotlin");
1130
1131        // Groovy
1132        assert_eq!(QuickSession::groovysh().command, "groovysh");
1133
1134        // TypeScript
1135        assert_eq!(QuickSession::ts_node().command, "ts-node");
1136
1137        // Deno
1138        assert_eq!(QuickSession::deno().command, "deno");
1139
1140        // Bun
1141        let config = QuickSession::bun();
1142        assert_eq!(config.command, "bun");
1143        assert!(config.args.contains(&"repl".to_string()));
1144    }
1145}