feat: Implement SSH client with authentication support (Task 5)
- Add SSH client with Connect/Execute/Close/IsConnected - Implement password, key, and both authentication methods - Support default key discovery (~/.ssh/id_ed25519, id_rsa, etc.) - Support passphrase-protected keys via ParsePrivateKeyWithPassphrase - Path expansion for ~ in KeyID - Integrate with HandleSSHError for user-friendly errors - All 4 SSH tests passing + 5 error tests still passing (9 total) Progress: Tasks 1-5 complete (~40%)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package ssh_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.tukangketik.id/swanadiva/hostkeeper/internal/models"
|
||||
"git.tukangketik.id/swanadiva/hostkeeper/pkg/ssh"
|
||||
)
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
host := &models.Host{
|
||||
ID: "test-host",
|
||||
Name: "Test Server",
|
||||
Hostname: "localhost",
|
||||
Port: 22,
|
||||
Username: "testuser",
|
||||
Auth: models.AuthConfig{
|
||||
Type: "password",
|
||||
Password: "testpass",
|
||||
},
|
||||
}
|
||||
|
||||
client := ssh.NewClient(host, 30*time.Second)
|
||||
if client == nil {
|
||||
t.Fatal("Failed to create SSH client")
|
||||
}
|
||||
|
||||
if client.IsConnected() {
|
||||
t.Error("Expected client to not be connected initially")
|
||||
}
|
||||
|
||||
// Close should be safe even when not connected
|
||||
if err := client.Close(); err != nil {
|
||||
t.Errorf("Expected nil error on close when not connected, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectFailure(t *testing.T) {
|
||||
// Test connecting to a non-existent server
|
||||
host := &models.Host{
|
||||
ID: "test-host",
|
||||
Name: "Non-existent Server",
|
||||
Hostname: "127.0.0.1",
|
||||
Port: 9999, // Port that's likely not running SSH
|
||||
Username: "testuser",
|
||||
Auth: models.AuthConfig{
|
||||
Type: "password",
|
||||
Password: "testpass",
|
||||
},
|
||||
}
|
||||
|
||||
client := ssh.NewClient(host, 2*time.Second)
|
||||
ctx := context.Background()
|
||||
err := client.Connect(ctx)
|
||||
|
||||
// We expect connection to fail
|
||||
if err == nil {
|
||||
t.Log("Connection succeeded (unexpected - SSH server may be running on port 9999)")
|
||||
_ = client.Close()
|
||||
} else {
|
||||
t.Logf("Connection failed as expected: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteWithoutConnection(t *testing.T) {
|
||||
host := &models.Host{
|
||||
ID: "test-host",
|
||||
Name: "Test Server",
|
||||
Hostname: "localhost",
|
||||
Port: 22,
|
||||
Username: "testuser",
|
||||
Auth: models.AuthConfig{
|
||||
Type: "password",
|
||||
Password: "testpass",
|
||||
},
|
||||
}
|
||||
|
||||
client := ssh.NewClient(host, 30*time.Second)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := client.Execute(ctx, "echo hello")
|
||||
if err == nil {
|
||||
t.Error("Expected error when executing command without connection")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClient(t *testing.T) {
|
||||
host := &models.Host{
|
||||
ID: "test-host",
|
||||
Name: "Test Server",
|
||||
Hostname: "localhost",
|
||||
Port: 22,
|
||||
Username: "testuser",
|
||||
Auth: models.AuthConfig{
|
||||
Type: "password",
|
||||
Password: "testpass",
|
||||
},
|
||||
}
|
||||
|
||||
client := ssh.NewClient(host, 30*time.Second)
|
||||
if client.GetClient() != nil {
|
||||
t.Error("Expected nil underlying client before connecting")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user