Rework tests, and move all source files into main package
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
package config
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
319
src/config_test.go
Normal file
319
src/config_test.go
Normal 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))
|
||||
}
|
||||
}
|
@ -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
|
||||
|
474
src/main_test.go
474
src/main_test.go
@ -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
190
src/ssh_test.go
Normal 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")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user