1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//! Integration tests for `ssh supabase.sh`.
//!
//! Requires `ssh` feature. No credentials needed — supabase.sh is a public SSH service.
//! CI decision: the live public endpoint can reset connections, so the live
//! test uses a small bounded retry while still remaining a required gate.
#[cfg(feature = "ssh")]
mod ssh_supabase {
use bashkit::{Bash, SshConfig};
fn bash_with_supabase() -> Bash {
Bash::builder()
.ssh(
SshConfig::new()
.allow("supabase.sh")
.strict_host_key_checking(false),
)
.build()
}
/// Connects to supabase.sh via SSH. Verifies the connection succeeds.
/// supabase.sh is a TUI service — it may not send output without an
/// interactive terminal, so we only assert the connection didn't error.
#[tokio::test]
async fn ssh_supabase_connects() {
let mut last_stderr = String::new();
for attempt in 1..=3 {
let mut bash = bash_with_supabase();
let result = bash.exec("ssh supabase.sh").await.unwrap();
if result.exit_code == 0 {
return;
}
last_stderr = result.stderr;
if attempt < 3 {
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
}
}
panic!("ssh supabase.sh failed after 3 attempts: {last_stderr}");
}
#[tokio::test]
async fn ssh_blocked_host_rejected() {
let mut bash = bash_with_supabase();
let result = bash.exec("ssh evil.com 'id'").await.unwrap();
assert_ne!(result.exit_code, 0);
assert!(
result.stderr.contains("not in allowlist"),
"expected allowlist error, got: {}",
result.stderr
);
}
}