Rework tests, and move all source files into main package

This commit is contained in:
Pim van Pelt
2025-07-06 17:38:47 +02:00
parent db91d59481
commit 2a1ed2dd35
5 changed files with 512 additions and 478 deletions

View File

@ -1,4 +1,4 @@
package config
package main
import (
"fmt"

319
src/config_test.go Normal file
View File

@ -0,0 +1,319 @@
package main
import (
"os"
"path/filepath"
"testing"
)
func TestConfigRead(t *testing.T) {
tempDir := t.TempDir()
// Create a single config file with types and devices
configPath := filepath.Join(tempDir, "test-config.yaml")
configContent := `types:
test-type:
commands:
- show version
- show status
devices:
test-device:
user: testuser
type: test-type
direct-device:
user: directuser
commands:
- direct command
`
err := os.WriteFile(configPath, []byte(configContent), 0644)
if err != nil {
t.Fatalf("Failed to create test config file: %v", err)
}
cfg, err := ConfigRead([]string{configPath})
if err != nil {
t.Fatalf("Failed to load config: %v", err)
}
// Test types section
if len(cfg.Types) != 1 {
t.Errorf("Expected 1 type, got %d", len(cfg.Types))
}
testType, exists := cfg.Types["test-type"]
if !exists {
t.Error("Expected 'test-type' to exist in types")
}
if len(testType.Commands) != 2 {
t.Errorf("Expected 2 commands in test-type, got %d", len(testType.Commands))
}
// Test devices section
if len(cfg.Devices) != 2 {
t.Errorf("Expected 2 devices, got %d", len(cfg.Devices))
}
testDevice, exists := cfg.Devices["test-device"]
if !exists {
t.Error("Expected 'test-device' to exist in devices")
}
if testDevice.User != "testuser" {
t.Errorf("Expected user 'testuser', got '%s'", testDevice.User)
}
if testDevice.Type != "test-type" {
t.Errorf("Expected type 'test-type', got '%s'", testDevice.Type)
}
}
func TestConfigReadMerging(t *testing.T) {
tempDir := t.TempDir()
// Create first config file with device types
typesPath := filepath.Join(tempDir, "types.yaml")
typesContent := `types:
test-type:
commands:
- show version
- show status`
err := os.WriteFile(typesPath, []byte(typesContent), 0644)
if err != nil {
t.Fatalf("Failed to create types file: %v", err)
}
// Create second config file with devices
devicesPath := filepath.Join(tempDir, "devices.yaml")
devicesContent := `devices:
test-device:
user: testuser
type: test-type`
err = os.WriteFile(devicesPath, []byte(devicesContent), 0644)
if err != nil {
t.Fatalf("Failed to create devices file: %v", err)
}
// Load and merge configs
cfg, err := ConfigRead([]string{typesPath, devicesPath})
if err != nil {
t.Fatalf("Failed to merge configs: %v", err)
}
// Check that merging worked
if len(cfg.Types) != 1 {
t.Errorf("Expected 1 type, got %d", len(cfg.Types))
}
testType, exists := cfg.Types["test-type"]
if !exists {
t.Error("Expected 'test-type' to exist in merged config")
}
if len(testType.Commands) != 2 {
t.Errorf("Expected 2 commands in test-type, got %d", len(testType.Commands))
}
if len(cfg.Devices) != 1 {
t.Errorf("Expected 1 device, got %d", len(cfg.Devices))
}
testDevice, exists := cfg.Devices["test-device"]
if !exists {
t.Error("Expected 'test-device' to exist in merged config")
}
if testDevice.Type != "test-type" {
t.Errorf("Expected device type 'test-type', got '%s'", testDevice.Type)
}
}
func TestConfigReadOverrides(t *testing.T) {
tempDir := t.TempDir()
// Create base config
basePath := filepath.Join(tempDir, "base.yaml")
baseContent := `devices:
test-device:
user: baseuser
type: base-type`
err := os.WriteFile(basePath, []byte(baseContent), 0644)
if err != nil {
t.Fatalf("Failed to create base file: %v", err)
}
// Create override config
overridePath := filepath.Join(tempDir, "override.yaml")
overrideContent := `devices:
test-device:
user: overrideuser`
err = os.WriteFile(overridePath, []byte(overrideContent), 0644)
if err != nil {
t.Fatalf("Failed to create override file: %v", err)
}
// Load with override (later file should override earlier file)
cfg, err := ConfigRead([]string{basePath, overridePath})
if err != nil {
t.Fatalf("Failed to merge configs: %v", err)
}
testDevice := cfg.Devices["test-device"]
if testDevice.User != "overrideuser" {
t.Errorf("Expected overridden user 'overrideuser', got '%s'", testDevice.User)
}
// Type should be preserved from base config
if testDevice.Type != "base-type" {
t.Errorf("Expected type 'base-type' to be preserved, got '%s'", testDevice.Type)
}
}
func TestConfigReadInvalidFile(t *testing.T) {
_, err := ConfigRead([]string{"/nonexistent/config.yaml"})
if err == nil {
t.Error("Expected error when loading nonexistent config file")
}
}
func TestConfigReadInvalidYAML(t *testing.T) {
tempDir := t.TempDir()
configPath := filepath.Join(tempDir, "invalid-config.yaml")
// Create a file with invalid YAML syntax
invalidContent := `types:
test-type:
commands
- invalid yaml`
err := os.WriteFile(configPath, []byte(invalidContent), 0644)
if err != nil {
t.Fatalf("Failed to create invalid config file: %v", err)
}
_, err = ConfigRead([]string{configPath})
if err == nil {
t.Error("Expected error when loading invalid YAML")
}
}
func TestConfigReadEmptyFile(t *testing.T) {
tempDir := t.TempDir()
configPath := filepath.Join(tempDir, "empty-config.yaml")
// Create empty file
err := os.WriteFile(configPath, []byte(""), 0644)
if err != nil {
t.Fatalf("Failed to create empty config file: %v", err)
}
cfg, err := ConfigRead([]string{configPath})
if err != nil {
t.Fatalf("Failed to load empty config: %v", err)
}
// Should have empty maps
if len(cfg.Types) != 0 {
t.Errorf("Expected 0 types in empty config, got %d", len(cfg.Types))
}
if len(cfg.Devices) != 0 {
t.Errorf("Expected 0 devices in empty config, got %d", len(cfg.Devices))
}
}
func TestConfigReadComplexMerge(t *testing.T) {
tempDir := t.TempDir()
// Create device types file
typesPath := filepath.Join(tempDir, "types.yaml")
typesContent := `types:
srlinux:
commands:
- show version
- show platform linecard
eos:
commands:
- show version
- show inventory`
err := os.WriteFile(typesPath, []byte(typesContent), 0644)
if err != nil {
t.Fatalf("Failed to create types file: %v", err)
}
// Create production devices file
prodPath := filepath.Join(tempDir, "production.yaml")
prodContent := `devices:
prod-asw100:
user: netops
type: srlinux
prod-core-01:
user: netops
type: eos`
err = os.WriteFile(prodPath, []byte(prodContent), 0644)
if err != nil {
t.Fatalf("Failed to create production file: %v", err)
}
// Create lab devices file
labPath := filepath.Join(tempDir, "lab.yaml")
labContent := `devices:
lab-switch:
user: admin
type: srlinux
commands:
- show version only`
err = os.WriteFile(labPath, []byte(labContent), 0644)
if err != nil {
t.Fatalf("Failed to create lab file: %v", err)
}
// Load merged configuration
cfg, err := ConfigRead([]string{typesPath, prodPath, labPath})
if err != nil {
t.Fatalf("Failed to load merged config: %v", err)
}
// Verify types were loaded correctly
if len(cfg.Types) != 2 {
t.Errorf("Expected 2 types, got %d", len(cfg.Types))
}
srlinuxType, exists := cfg.Types["srlinux"]
if !exists {
t.Error("Expected 'srlinux' type to exist")
}
if len(srlinuxType.Commands) != 2 {
t.Errorf("Expected 2 commands for srlinux type, got %d", len(srlinuxType.Commands))
}
// Verify devices reference the correct types
if len(cfg.Devices) != 3 {
t.Errorf("Expected 3 devices, got %d", len(cfg.Devices))
}
prodDevice, exists := cfg.Devices["prod-asw100"]
if !exists {
t.Error("Expected 'prod-asw100' device to exist")
}
if prodDevice.Type != "srlinux" {
t.Errorf("Expected prod-asw100 type 'srlinux', got '%s'", prodDevice.Type)
}
labDevice, exists := cfg.Devices["lab-switch"]
if !exists {
t.Error("Expected 'lab-switch' device to exist")
}
if len(labDevice.Commands) != 1 {
t.Errorf("Expected 1 custom command for lab-switch, got %d", len(labDevice.Commands))
}
}

View File

@ -6,7 +6,6 @@ import (
"fmt"
"log"
"os"
"router_backup/config"
"github.com/spf13/cobra"
)
@ -38,7 +37,7 @@ func main() {
fmt.Printf("IPng Networks Router Backup v%s\n", Version)
// Load configuration
cfg, err := config.ConfigRead(yamlFiles)
cfg, err := ConfigRead(yamlFiles)
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
@ -63,7 +62,7 @@ func main() {
// Filter devices if --host flags are provided
devicesToProcess := cfg.Devices
if len(hostFilter) > 0 {
devicesToProcess = make(map[string]config.Device)
devicesToProcess = make(map[string]Device)
for _, hostname := range hostFilter {
if deviceConfig, exists := cfg.Devices[hostname]; exists {
devicesToProcess[hostname] = deviceConfig

View File

@ -1,474 +0,0 @@
// Copyright 2025, IPng Networks GmbH, Pim van Pelt <pim@ipng.ch>
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestNewRouterBackup(t *testing.T) {
rb := NewRouterBackup("test-host", "test-user", "test-pass", "/test/key", 2222)
if rb.hostname != "test-host" {
t.Errorf("Expected hostname 'test-host', got '%s'", rb.hostname)
}
if rb.username != "test-user" {
t.Errorf("Expected username 'test-user', got '%s'", rb.username)
}
if rb.password != "test-pass" {
t.Errorf("Expected password 'test-pass', got '%s'", rb.password)
}
if rb.keyFile != "/test/key" {
t.Errorf("Expected keyFile '/test/key', got '%s'", rb.keyFile)
}
if rb.port != 2222 {
t.Errorf("Expected port 2222, got %d", rb.port)
}
}
func TestFindDefaultSSHKey(t *testing.T) {
// Create a temporary directory to simulate home directory
tempDir := t.TempDir()
sshDir := filepath.Join(tempDir, ".ssh")
err := os.MkdirAll(sshDir, 0755)
if err != nil {
t.Fatalf("Failed to create .ssh directory: %v", err)
}
// Create a fake SSH key
keyPath := filepath.Join(sshDir, "id_rsa")
err = os.WriteFile(keyPath, []byte("fake-key"), 0600)
if err != nil {
t.Fatalf("Failed to create fake SSH key: %v", err)
}
// Temporarily change HOME environment variable
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)
os.Setenv("HOME", tempDir)
result := findDefaultSSHKey()
if result != keyPath {
t.Errorf("Expected SSH key path '%s', got '%s'", keyPath, result)
}
}
func TestFindDefaultSSHKeyNotFound(t *testing.T) {
// Create a temporary directory with no SSH keys
tempDir := t.TempDir()
// Temporarily change HOME environment variable
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)
os.Setenv("HOME", tempDir)
result := findDefaultSSHKey()
if result != "" {
t.Errorf("Expected empty string when no SSH key found, got '%s'", result)
}
}
func TestLoadConfig(t *testing.T) {
// Create a temporary directory and files
tempDir := t.TempDir()
// Create device-types.yaml file
deviceTypesPath := filepath.Join(tempDir, "device-types.yaml")
deviceTypesContent := `test-type:
commands:
- show version
- show status`
err := os.WriteFile(deviceTypesPath, []byte(deviceTypesContent), 0644)
if err != nil {
t.Fatalf("Failed to create device-types file: %v", err)
}
// Create main config file with !include
configPath := filepath.Join(tempDir, "test-config.yaml")
configContent := `types:
!include device-types.yaml
devices:
test-device:
user: testuser
type: test-type
direct-device:
user: directuser
commands:
- direct command
`
err = os.WriteFile(configPath, []byte(configContent), 0644)
if err != nil {
t.Fatalf("Failed to create test config file: %v", err)
}
config, err := loadConfig(configPath)
if err != nil {
t.Fatalf("Failed to load config: %v", err)
}
// Test types section
if len(config.Types) != 1 {
t.Errorf("Expected 1 type, got %d", len(config.Types))
}
testType, exists := config.Types["test-type"]
if !exists {
t.Error("Expected 'test-type' to exist in types")
}
if len(testType.Commands) != 2 {
t.Errorf("Expected 2 commands in test-type, got %d", len(testType.Commands))
}
// Test devices section
if len(config.Devices) != 2 {
t.Errorf("Expected 2 devices, got %d", len(config.Devices))
}
testDevice, exists := config.Devices["test-device"]
if !exists {
t.Error("Expected 'test-device' to exist in devices")
}
if testDevice.User != "testuser" {
t.Errorf("Expected user 'testuser', got '%s'", testDevice.User)
}
if testDevice.Type != "test-type" {
t.Errorf("Expected type 'test-type', got '%s'", testDevice.Type)
}
}
func TestLoadConfigInvalidFile(t *testing.T) {
_, err := loadConfig("/nonexistent/config.yaml")
if err == nil {
t.Error("Expected error when loading nonexistent config file")
}
}
func TestLoadConfigInvalidYAML(t *testing.T) {
tempDir := t.TempDir()
configPath := filepath.Join(tempDir, "invalid-config.yaml")
// Create invalid YAML content
invalidYAML := `types:
test-type:
commands:
- show version
invalid: [unclosed
`
err := os.WriteFile(configPath, []byte(invalidYAML), 0644)
if err != nil {
t.Fatalf("Failed to create invalid config file: %v", err)
}
_, err = loadConfig(configPath)
if err == nil {
t.Error("Expected error when loading invalid YAML")
}
}
func TestBackupCommandsDirectoryCreation(t *testing.T) {
rb := NewRouterBackup("test-host", "test-user", "", "", 22)
tempDir := t.TempDir()
outputDir := filepath.Join(tempDir, "new-directory")
// Test with empty commands to avoid SSH connection
err := rb.BackupCommands([]string{}, outputDir)
if err != nil {
t.Fatalf("BackupCommands failed: %v", err)
}
// Check if directory was created
if _, err := os.Stat(outputDir); os.IsNotExist(err) {
t.Error("Expected output directory to be created")
}
}
func TestBackupCommandsFileCreation(t *testing.T) {
rb := NewRouterBackup("test-host", "test-user", "", "", 22)
tempDir := t.TempDir()
expectedFilePath := filepath.Join(tempDir, "test-host")
// Test with empty commands to avoid SSH connection
err := rb.BackupCommands([]string{}, tempDir)
if err != nil {
t.Fatalf("BackupCommands failed: %v", err)
}
// Check if file was created
if _, err := os.Stat(expectedFilePath); os.IsNotExist(err) {
t.Error("Expected output file to be created")
}
}
// Benchmark tests
func BenchmarkNewRouterBackup(b *testing.B) {
for i := 0; i < b.N; i++ {
NewRouterBackup("bench-host", "bench-user", "bench-pass", "/bench/key", 22)
}
}
func BenchmarkLoadConfig(b *testing.B) {
// Create a temporary config file
tempDir := b.TempDir()
configPath := filepath.Join(tempDir, "bench-config.yaml")
configContent := `types:
srlinux:
commands:
- show version
- show platform linecard
devices:
device1:
user: user1
type: srlinux
device2:
user: user2
type: srlinux
`
err := os.WriteFile(configPath, []byte(configContent), 0644)
if err != nil {
b.Fatalf("Failed to create benchmark config file: %v", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := loadConfig(configPath)
if err != nil {
b.Fatalf("Failed to load config: %v", err)
}
}
}
// Example test to demonstrate usage
func ExampleNewRouterBackup() {
rb := NewRouterBackup("example-host", "admin", "", "/home/user/.ssh/id_rsa", 22)
_ = rb // Use the router backup instance
}
// Table-driven test for multiple scenarios
func TestRouterBackupCreation(t *testing.T) {
tests := []struct {
name string
hostname string
username string
password string
keyFile string
port int
}{
{"Basic", "host1", "user1", "pass1", "/key1", 22},
{"Custom Port", "host2", "user2", "pass2", "/key2", 2222},
{"No Password", "host3", "user3", "", "/key3", 22},
{"No Key", "host4", "user4", "pass4", "", 22},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rb := NewRouterBackup(tt.hostname, tt.username, tt.password, tt.keyFile, tt.port)
if rb.hostname != tt.hostname {
t.Errorf("Expected hostname '%s', got '%s'", tt.hostname, rb.hostname)
}
if rb.username != tt.username {
t.Errorf("Expected username '%s', got '%s'", tt.username, rb.username)
}
if rb.password != tt.password {
t.Errorf("Expected password '%s', got '%s'", tt.password, rb.password)
}
if rb.keyFile != tt.keyFile {
t.Errorf("Expected keyFile '%s', got '%s'", tt.keyFile, rb.keyFile)
}
if rb.port != tt.port {
t.Errorf("Expected port %d, got %d", tt.port, rb.port)
}
})
}
}
// Test !include functionality
func TestProcessIncludes(t *testing.T) {
tempDir := t.TempDir()
// Create included file
includedPath := filepath.Join(tempDir, "included.yaml")
includedContent := `test-type:
commands:
- show version
- show status`
err := os.WriteFile(includedPath, []byte(includedContent), 0644)
if err != nil {
t.Fatalf("Failed to create included file: %v", err)
}
// Create main file with !include
mainPath := filepath.Join(tempDir, "main.yaml")
mainContent := `types:
!include included.yaml
devices:
test-device:
user: testuser
type: test-type`
err = os.WriteFile(mainPath, []byte(mainContent), 0644)
if err != nil {
t.Fatalf("Failed to create main file: %v", err)
}
// Process includes
result, err := processIncludes(mainPath)
if err != nil {
t.Fatalf("Failed to process includes: %v", err)
}
// Check that include was processed
if !strings.Contains(result, "show version") {
t.Error("Expected included content to be present in result")
}
if !strings.Contains(result, "show status") {
t.Error("Expected included content to be present in result")
}
if strings.Contains(result, "!include") {
t.Error("Expected !include directive to be replaced")
}
}
func TestProcessIncludesWithQuotes(t *testing.T) {
tempDir := t.TempDir()
// Create included file with spaces in name
includedPath := filepath.Join(tempDir, "file with spaces.yaml")
includedContent := `production-srlinux:
commands:
- show version`
err := os.WriteFile(includedPath, []byte(includedContent), 0644)
if err != nil {
t.Fatalf("Failed to create included file: %v", err)
}
// Create main file with quoted !include
mainPath := filepath.Join(tempDir, "main.yaml")
mainContent := `types:
!include "file with spaces.yaml"`
err = os.WriteFile(mainPath, []byte(mainContent), 0644)
if err != nil {
t.Fatalf("Failed to create main file: %v", err)
}
// Process includes
result, err := processIncludes(mainPath)
if err != nil {
t.Fatalf("Failed to process includes: %v", err)
}
// Check that include was processed
if !strings.Contains(result, "production-srlinux") {
t.Error("Expected included content to be present in result")
}
}
func TestProcessIncludesNonexistentFile(t *testing.T) {
tempDir := t.TempDir()
// Create main file with include to nonexistent file
mainPath := filepath.Join(tempDir, "main.yaml")
mainContent := `types:
!include nonexistent.yaml`
err := os.WriteFile(mainPath, []byte(mainContent), 0644)
if err != nil {
t.Fatalf("Failed to create main file: %v", err)
}
// Process includes should fail
_, err = processIncludes(mainPath)
if err == nil {
t.Error("Expected error for nonexistent include file")
}
}
func TestLoadConfigWithIncludes(t *testing.T) {
tempDir := t.TempDir()
// Create device types file
typesPath := filepath.Join(tempDir, "types.yaml")
typesContent := `srlinux:
commands:
- show version
- show platform linecard
eos:
commands:
- show version
- show inventory`
err := os.WriteFile(typesPath, []byte(typesContent), 0644)
if err != nil {
t.Fatalf("Failed to create types file: %v", err)
}
// Create main config file with includes
mainPath := filepath.Join(tempDir, "config.yaml")
mainContent := `types:
!include types.yaml
devices:
asw100:
user: admin
type: srlinux
edge-01:
user: operator
type: eos`
err = os.WriteFile(mainPath, []byte(mainContent), 0644)
if err != nil {
t.Fatalf("Failed to create main config file: %v", err)
}
// Load configuration
config, err := loadConfig(mainPath)
if err != nil {
t.Fatalf("Failed to load config with includes: %v", err)
}
// Verify types were loaded correctly
if len(config.Types) != 2 {
t.Errorf("Expected 2 types, got %d", len(config.Types))
}
srlinuxType, exists := config.Types["srlinux"]
if !exists {
t.Error("Expected 'srlinux' type to exist")
}
if len(srlinuxType.Commands) != 2 {
t.Errorf("Expected 2 commands for srlinux type, got %d", len(srlinuxType.Commands))
}
// Verify devices were loaded correctly
if len(config.Devices) != 2 {
t.Errorf("Expected 2 devices, got %d", len(config.Devices))
}
asw100, exists := config.Devices["asw100"]
if !exists {
t.Error("Expected 'asw100' device to exist")
}
if asw100.User != "admin" {
t.Errorf("Expected user 'admin', got '%s'", asw100.User)
}
if asw100.Type != "srlinux" {
t.Errorf("Expected type 'srlinux', got '%s'", asw100.Type)
}
}

190
src/ssh_test.go Normal file
View File

@ -0,0 +1,190 @@
package main
import (
"os"
"path/filepath"
"testing"
)
func TestNewRouterBackup(t *testing.T) {
rb := NewRouterBackup("testhost", "testuser", "testpass", "/path/to/key", 2222)
if rb.hostname != "testhost" {
t.Errorf("Expected hostname 'testhost', got '%s'", rb.hostname)
}
if rb.username != "testuser" {
t.Errorf("Expected username 'testuser', got '%s'", rb.username)
}
if rb.password != "testpass" {
t.Errorf("Expected password 'testpass', got '%s'", rb.password)
}
if rb.keyFile != "/path/to/key" {
t.Errorf("Expected keyFile '/path/to/key', got '%s'", rb.keyFile)
}
if rb.port != 2222 {
t.Errorf("Expected port 2222, got %d", rb.port)
}
if rb.client != nil {
t.Error("Expected client to be nil initially")
}
}
func TestRunCommandWithoutConnection(t *testing.T) {
rb := NewRouterBackup("testhost", "testuser", "testpass", "", 22)
_, err := rb.RunCommand("show version")
if err == nil {
t.Error("Expected error when running command without connection")
}
if err.Error() != "no active connection" {
t.Errorf("Expected 'no active connection' error, got '%s'", err.Error())
}
}
func TestBackupCommandsDirectoryCreation(t *testing.T) {
tempDir := t.TempDir()
outputDir := filepath.Join(tempDir, "nonexistent", "backup")
rb := NewRouterBackup("testhost", "testuser", "testpass", "", 22)
// This should create the directory even without a connection
// and fail gracefully when trying to run commands
_ = rb.BackupCommands([]string{"show version"}, outputDir)
// Should not error on directory creation
if _, statErr := os.Stat(outputDir); os.IsNotExist(statErr) {
t.Error("Expected output directory to be created")
}
// Should create the output file even if commands fail
expectedFile := filepath.Join(outputDir, "testhost")
if _, statErr := os.Stat(expectedFile); os.IsNotExist(statErr) {
t.Error("Expected output file to be created")
}
}
func TestBackupCommandsEmptyCommands(t *testing.T) {
tempDir := t.TempDir()
rb := NewRouterBackup("testhost", "testuser", "testpass", "", 22)
err := rb.BackupCommands([]string{}, tempDir)
if err != nil {
t.Errorf("Expected no error for empty commands list, got %v", err)
}
// Should still create the output file
expectedFile := filepath.Join(tempDir, "testhost")
if _, statErr := os.Stat(expectedFile); os.IsNotExist(statErr) {
t.Error("Expected output file to be created even for empty commands")
}
}
func TestDisconnectWithoutConnection(t *testing.T) {
rb := NewRouterBackup("testhost", "testuser", "testpass", "", 22)
// Should not panic or error when disconnecting without connection
rb.Disconnect()
}
func TestFindDefaultSSHKey(t *testing.T) {
// Test when no SSH keys exist
originalHome := os.Getenv("HOME")
tempDir := t.TempDir()
os.Setenv("HOME", tempDir)
defer os.Setenv("HOME", originalHome)
keyPath := findDefaultSSHKey()
if keyPath != "" {
t.Errorf("Expected empty string when no SSH keys exist, got '%s'", keyPath)
}
// Create .ssh directory and a test key
sshDir := filepath.Join(tempDir, ".ssh")
err := os.MkdirAll(sshDir, 0700)
if err != nil {
t.Fatalf("Failed to create .ssh directory: %v", err)
}
// Create id_rsa key (should be found first)
rsaKeyPath := filepath.Join(sshDir, "id_rsa")
err = os.WriteFile(rsaKeyPath, []byte("fake rsa key"), 0600)
if err != nil {
t.Fatalf("Failed to create RSA key: %v", err)
}
keyPath = findDefaultSSHKey()
if keyPath != rsaKeyPath {
t.Errorf("Expected to find RSA key at '%s', got '%s'", rsaKeyPath, keyPath)
}
// Remove RSA key and create ed25519 key
os.Remove(rsaKeyPath)
ed25519KeyPath := filepath.Join(sshDir, "id_ed25519")
err = os.WriteFile(ed25519KeyPath, []byte("fake ed25519 key"), 0600)
if err != nil {
t.Fatalf("Failed to create ed25519 key: %v", err)
}
keyPath = findDefaultSSHKey()
if keyPath != ed25519KeyPath {
t.Errorf("Expected to find ed25519 key at '%s', got '%s'", ed25519KeyPath, keyPath)
}
}
func TestFindDefaultSSHKeyHomeError(t *testing.T) {
// Test behavior when HOME environment is invalid
originalHome := os.Getenv("HOME")
os.Unsetenv("HOME")
defer os.Setenv("HOME", originalHome)
keyPath := findDefaultSSHKey()
if keyPath != "" {
t.Errorf("Expected empty string when HOME is not set, got '%s'", keyPath)
}
}
func TestBackupCommandsFileOperations(t *testing.T) {
tempDir := t.TempDir()
rb := NewRouterBackup("testhost", "testuser", "testpass", "", 22)
// Create some fake commands (they will fail but we can test file operations)
commands := []string{"show version", "show interfaces"}
err := rb.BackupCommands(commands, tempDir)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
// Check that output file was created
outputFile := filepath.Join(tempDir, "testhost")
_, err = os.ReadFile(outputFile)
if err != nil {
t.Fatalf("Failed to read output file: %v", err)
}
// File should be created (it will be empty if all commands fail)
// This test just verifies the file creation works
}
func TestRouterBackupConnectionState(t *testing.T) {
rb := NewRouterBackup("testhost", "testuser", "testpass", "", 22)
// Initially no client
if rb.client != nil {
t.Error("Expected client to be nil initially")
}
// After disconnect, should still be nil (safe to call multiple times)
rb.Disconnect()
if rb.client != nil {
t.Error("Expected client to remain nil after disconnect")
}
}