Files
gopeed/cmd/web/flags_test.go
T

699 lines
17 KiB
Go

package main
import (
"os"
"path/filepath"
"reflect"
"testing"
"github.com/GopeedLab/gopeed/pkg/base"
)
func TestSetDefaults(t *testing.T) {
tests := []struct {
name string
input *args
expected *args
}{
{
name: "empty config should get defaults",
input: &args{},
expected: &args{
Address: stringPtr("0.0.0.0"),
Port: intPtr(9999),
Username: stringPtr("gopeed"),
},
},
{
name: "partial config should only fill missing defaults",
input: &args{
Address: stringPtr("127.0.0.1"),
},
expected: &args{
Address: stringPtr("127.0.0.1"),
Port: intPtr(9999),
Username: stringPtr("gopeed"),
},
},
{
name: "full config should remain unchanged",
input: &args{
Address: stringPtr("192.168.1.1"),
Port: intPtr(8080),
Username: stringPtr("admin"),
},
expected: &args{
Address: stringPtr("192.168.1.1"),
Port: intPtr(8080),
Username: stringPtr("admin"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setDefaults(tt.input)
if !reflect.DeepEqual(tt.input, tt.expected) {
t.Errorf("setDefaults() = %+v, want %+v", tt.input, tt.expected)
}
})
}
}
func TestOverrideWithCliArgs(t *testing.T) {
tests := []struct {
name string
config *args
cliConfig *args
expected *args
}{
{
name: "empty cli should not override",
config: &args{Address: stringPtr("192.168.1.1")},
cliConfig: &args{
Address: stringPtr(""),
Port: intPtr(0),
},
expected: &args{Address: stringPtr("192.168.1.1")},
},
{
name: "non-empty cli should override",
config: &args{Address: stringPtr("192.168.1.1")},
cliConfig: &args{
Address: stringPtr("127.0.0.1"),
Port: intPtr(8080),
},
expected: &args{
Address: stringPtr("127.0.0.1"),
Port: intPtr(8080),
},
},
{
name: "all fields override test",
config: &args{
Address: stringPtr("old_address"),
Port: intPtr(1111),
Username: stringPtr("old_user"),
Password: stringPtr("old_pass"),
ApiToken: stringPtr("old_token"),
StorageDir: stringPtr("old_dir"),
},
cliConfig: &args{
Address: stringPtr("new_address"),
Port: intPtr(2222),
Username: stringPtr("new_user"),
Password: stringPtr("new_pass"),
ApiToken: stringPtr("new_token"),
StorageDir: stringPtr("new_dir"),
},
expected: &args{
Address: stringPtr("new_address"),
Port: intPtr(2222),
Username: stringPtr("new_user"),
Password: stringPtr("new_pass"),
ApiToken: stringPtr("new_token"),
StorageDir: stringPtr("new_dir"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
overrideWithCliArgs(tt.config, tt.cliConfig)
if !reflect.DeepEqual(tt.config, tt.expected) {
t.Errorf("overrideWithCliArgs() = %+v, want %+v", tt.config, tt.expected)
}
})
}
}
func TestLoadConfigFile(t *testing.T) {
// Create temporary directory for test files
tempDir, err := os.MkdirTemp("", "flags_test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
tests := []struct {
name string
configData string
fileName string
expected *args
}{
{
name: "valid config file",
configData: `{
"address": "192.168.1.100",
"port": 8080,
"username": "testuser",
"password": "testpass",
"apiToken": "testtoken",
"storageDir": "/test/storage",
"downloadConfig": {
"downloadDir": "/test/downloads",
"maxRunning": 10
}
}`,
fileName: "valid_config.json",
expected: &args{
Address: stringPtr("192.168.1.100"),
Port: intPtr(8080),
Username: stringPtr("testuser"),
Password: stringPtr("testpass"),
ApiToken: stringPtr("testtoken"),
StorageDir: stringPtr("/test/storage"),
DownloadConfig: &base.DownloaderStoreConfig{
DownloadDir: "/test/downloads",
MaxRunning: 10,
},
},
},
{
name: "partial config file",
configData: `{
"address": "10.0.0.1",
"port": 3000
}`,
fileName: "partial_config.json",
expected: &args{
Address: stringPtr("10.0.0.1"),
Port: intPtr(3000),
},
},
{
name: "invalid json should not panic",
configData: `{invalid json}`,
fileName: "invalid_config.json",
expected: &args{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create test config file
configPath := filepath.Join(tempDir, tt.fileName)
err := os.WriteFile(configPath, []byte(tt.configData), 0644)
if err != nil {
t.Fatal(err)
}
cfg := &args{}
loadConfigFile(cfg, configPath)
if !reflect.DeepEqual(cfg, tt.expected) {
t.Errorf("loadConfigFile() = %+v, want %+v", cfg, tt.expected)
}
})
}
// Test non-existent file
t.Run("non-existent file", func(t *testing.T) {
cfg := &args{}
loadConfigFile(cfg, "/non/existent/file.json")
expected := &args{}
if !reflect.DeepEqual(cfg, expected) {
t.Errorf("loadConfigFile() with non-existent file = %+v, want %+v", cfg, expected)
}
})
}
func TestLoadEnvVars(t *testing.T) {
// Save original environment
originalEnv := make(map[string]string)
envKeys := []string{
"GOPEED_ADDRESS", "GOPEED_PORT", "GOPEED_USERNAME",
"GOPEED_PASSWORD", "GOPEED_APITOKEN", "GOPEED_STORAGEDIR",
"GOPEED_DOWNLOADCONFIG", "GOPEED_WHITEDOWNLOADDIRS",
}
for _, key := range envKeys {
originalEnv[key] = os.Getenv(key)
}
// Clean up function
cleanup := func() {
for _, key := range envKeys {
if val, exists := originalEnv[key]; exists {
os.Setenv(key, val)
} else {
os.Unsetenv(key)
}
}
}
defer cleanup()
tests := []struct {
name string
envVars map[string]string
expected *args
}{
{
name: "all environment variables set",
envVars: map[string]string{
"GOPEED_ADDRESS": "env.example.com",
"GOPEED_PORT": "7777",
"GOPEED_USERNAME": "envuser",
"GOPEED_PASSWORD": "envpass",
"GOPEED_APITOKEN": "envtoken",
"GOPEED_STORAGEDIR": "/env/storage",
},
expected: &args{
Address: stringPtr("env.example.com"),
Port: intPtr(7777),
Username: stringPtr("envuser"),
Password: stringPtr("envpass"),
ApiToken: stringPtr("envtoken"),
StorageDir: stringPtr("/env/storage"),
},
},
{
name: "partial environment variables",
envVars: map[string]string{
"GOPEED_ADDRESS": "partial.example.com",
"GOPEED_PORT": "5555",
},
expected: &args{
Address: stringPtr("partial.example.com"),
Port: intPtr(5555),
},
},
{
name: "downloadConfig from environment",
envVars: map[string]string{
"GOPEED_DOWNLOADCONFIG": `{"downloadDir": "/env/downloads", "maxRunning": 15}`,
},
expected: &args{
DownloadConfig: &base.DownloaderStoreConfig{
DownloadDir: "/env/downloads",
MaxRunning: 15,
},
},
},
{
name: "invalid port should be ignored",
envVars: map[string]string{
"GOPEED_PORT": "invalid_port",
},
expected: &args{
Port: intPtr(0), // Invalid port creates pointer with 0 value
},
},
{
name: "invalid json for downloadConfig should be ignored",
envVars: map[string]string{
"GOPEED_DOWNLOADCONFIG": `{invalid json}`,
},
expected: &args{
DownloadConfig: &base.DownloaderStoreConfig{}, // Invalid JSON creates empty config
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear all environment variables first
for _, key := range envKeys {
os.Unsetenv(key)
}
// Set test environment variables
for key, value := range tt.envVars {
os.Setenv(key, value)
}
cfg := &args{}
loadEnvVars(cfg)
if !reflect.DeepEqual(cfg, tt.expected) {
t.Errorf("loadEnvVars() = %+v, want %+v", cfg, tt.expected)
}
})
}
}
func TestConfigPriority(t *testing.T) {
// Create temporary directory for test files
tempDir, err := os.MkdirTemp("", "flags_test_priority")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
// Create test config file
configData := `{
"address": "config.example.com",
"port": 6666,
"username": "configuser",
"password": "configpass"
}`
configPath := filepath.Join(tempDir, "test_config.json")
err = os.WriteFile(configPath, []byte(configData), 0644)
if err != nil {
t.Fatal(err)
}
// Save original environment
originalEnv := make(map[string]string)
envKeys := []string{"GOPEED_ADDRESS", "GOPEED_PORT", "GOPEED_USERNAME"}
for _, key := range envKeys {
originalEnv[key] = os.Getenv(key)
}
defer func() {
for _, key := range envKeys {
if val, exists := originalEnv[key]; exists {
os.Setenv(key, val)
} else {
os.Unsetenv(key)
}
}
}()
// Set environment variables
os.Setenv("GOPEED_ADDRESS", "env.example.com")
os.Setenv("GOPEED_PORT", "7777")
// Test configuration priority: CLI > ENV > Config File > Defaults
cfg := &args{}
// Load config file
loadConfigFile(cfg, configPath)
// Load environment variables (should override config file)
loadEnvVars(cfg)
// Simulate CLI args (should override env vars)
cliConfig := &args{
Address: stringPtr("cli.example.com"),
// Port not set in CLI, should remain from env
// Username not set in CLI or env, should remain from config file
}
overrideWithCliArgs(cfg, cliConfig)
// Set defaults for missing fields
setDefaults(cfg)
expected := &args{
Address: stringPtr("cli.example.com"), // From CLI (highest priority)
Port: intPtr(7777), // From ENV (overrides config file)
Username: stringPtr("configuser"), // From config file (env not set)
Password: stringPtr("configpass"), // From config file only
}
if !reflect.DeepEqual(cfg, expected) {
t.Errorf("Configuration priority test failed.\nGot: %+v\nWant: %+v", cfg, expected)
}
}
func TestCompleteConfigurationFlow(t *testing.T) {
// This test simulates a complete configuration loading scenario
// with all sources: defaults, config file, environment variables, and CLI args
tempDir, err := os.MkdirTemp("", "flags_test_complete")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
// Create comprehensive config file
configData := `{
"address": "config.host.com",
"port": 5000,
"username": "configuser",
"password": "configpass",
"apiToken": "configtoken",
"storageDir": "/config/storage",
"downloadConfig": {
"downloadDir": "/config/downloads",
"maxRunning": 8,
"protocolConfig": {"http": {"maxConnections": 16}},
"proxy": {
"enable": true,
"system": false,
"scheme": "http",
"host": "proxy.example.com:8080"
}
}
}`
configPath := filepath.Join(tempDir, "complete_config.json")
err = os.WriteFile(configPath, []byte(configData), 0644)
if err != nil {
t.Fatal(err)
}
// Save and set environment variables
originalEnv := make(map[string]string)
envKeys := []string{"GOPEED_ADDRESS", "GOPEED_USERNAME", "GOPEED_APITOKEN", "GOPEED_WHITEDOWNLOADDIRS"}
for _, key := range envKeys {
originalEnv[key] = os.Getenv(key)
}
defer func() {
for _, key := range envKeys {
if val, exists := originalEnv[key]; exists {
os.Setenv(key, val)
} else {
os.Unsetenv(key)
}
}
}()
os.Setenv("GOPEED_ADDRESS", "env.host.com")
os.Setenv("GOPEED_USERNAME", "envuser")
// Simulate complete configuration loading
cfg := &args{}
// Load from config file
loadConfigFile(cfg, configPath)
// Override with environment variables
loadEnvVars(cfg)
// Override with CLI arguments
cliConfig := &args{
Port: intPtr(9090),
StorageDir: stringPtr("/cli/storage"),
}
overrideWithCliArgs(cfg, cliConfig)
// Set defaults
setDefaults(cfg)
// Verify the final configuration follows priority rules
if *cfg.Address != "env.host.com" {
t.Errorf("Address should be from environment, got %s", *cfg.Address)
}
if *cfg.Port != 9090 {
t.Errorf("Port should be from CLI, got %d", *cfg.Port)
}
if *cfg.Username != "envuser" {
t.Errorf("Username should be from environment, got %s", *cfg.Username)
}
if *cfg.Password != "configpass" {
t.Errorf("Password should be from config file, got %s", *cfg.Password)
}
if *cfg.ApiToken != "configtoken" {
t.Errorf("ApiToken should be from config file, got %s", *cfg.ApiToken)
}
if *cfg.StorageDir != "/cli/storage" {
t.Errorf("StorageDir should be from CLI, got %s", *cfg.StorageDir)
}
if cfg.DownloadConfig == nil {
t.Error("DownloadConfig should be loaded from config file")
} else {
if cfg.DownloadConfig.DownloadDir != "/config/downloads" {
t.Errorf("DownloadConfig.DownloadDir should be from config file, got %s", cfg.DownloadConfig.DownloadDir)
}
if cfg.DownloadConfig.MaxRunning != 8 {
t.Errorf("DownloadConfig.MaxRunning should be from config file, got %d", cfg.DownloadConfig.MaxRunning)
}
}
}
// Helper functions for creating pointers
func stringPtr(s string) *string {
return &s
}
func intPtr(i int) *int {
return &i
}
func TestWhiteDownloadDirs(t *testing.T) {
t.Run("overrideWithCliArgs should handle WhiteDownloadDirs", func(t *testing.T) {
tests := []struct {
name string
config *args
cliConfig *args
expected *args
}{
{
name: "nil cli WhiteDownloadDirs should not override",
config: &args{WhiteDownloadDirs: []string{"/old/dir1", "/old/dir2"}},
cliConfig: &args{
WhiteDownloadDirs: nil,
},
expected: &args{WhiteDownloadDirs: []string{"/old/dir1", "/old/dir2"}},
},
{
name: "non-nil cli WhiteDownloadDirs should override",
config: &args{WhiteDownloadDirs: []string{"/old/dir1"}},
cliConfig: &args{
WhiteDownloadDirs: []string{"/new/dir1", "/new/dir2"},
},
expected: &args{WhiteDownloadDirs: []string{"/new/dir1", "/new/dir2"}},
},
{
name: "empty cli WhiteDownloadDirs should override with empty slice",
config: &args{WhiteDownloadDirs: []string{"/old/dir1"}},
cliConfig: &args{
WhiteDownloadDirs: []string{},
},
expected: &args{WhiteDownloadDirs: []string{}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
overrideWithCliArgs(tt.config, tt.cliConfig)
if !reflect.DeepEqual(tt.config, tt.expected) {
t.Errorf("overrideWithCliArgs() = %+v, want %+v", tt.config, tt.expected)
}
})
}
})
t.Run("loadConfigFile should handle WhiteDownloadDirs", func(t *testing.T) {
// Create temporary directory for test files
tempDir, err := os.MkdirTemp("", "flags_test_whitedirs")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
tests := []struct {
name string
configData string
fileName string
expected *args
}{
{
name: "config with WhiteDownloadDirs array",
configData: `{
"address": "test.example.com",
"whiteDownloadDirs": ["/path/to/dir1", "/path/to/dir2", "/path/to/dir3"]
}`,
fileName: "whitedirs_config.json",
expected: &args{
Address: stringPtr("test.example.com"),
WhiteDownloadDirs: []string{"/path/to/dir1", "/path/to/dir2", "/path/to/dir3"},
},
},
{
name: "config with empty WhiteDownloadDirs array",
configData: `{
"address": "test.example.com",
"whiteDownloadDirs": []
}`,
fileName: "empty_whitedirs_config.json",
expected: &args{
Address: stringPtr("test.example.com"),
WhiteDownloadDirs: []string{},
},
},
{
name: "config without WhiteDownloadDirs",
configData: `{
"address": "test.example.com",
"port": 8080
}`,
fileName: "no_whitedirs_config.json",
expected: &args{
Address: stringPtr("test.example.com"),
Port: intPtr(8080),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create test config file
configPath := filepath.Join(tempDir, tt.fileName)
err := os.WriteFile(configPath, []byte(tt.configData), 0644)
if err != nil {
t.Fatal(err)
}
cfg := &args{}
loadConfigFile(cfg, configPath)
if !reflect.DeepEqual(cfg, tt.expected) {
t.Errorf("loadConfigFile() = %+v, want %+v", cfg, tt.expected)
}
})
}
})
t.Run("loadEnvVars should handle WhiteDownloadDirs", func(t *testing.T) {
// Save original environment
originalEnv := os.Getenv("GOPEED_WHITEDOWNLOADDIRS")
defer func() {
if originalEnv != "" {
os.Setenv("GOPEED_WHITEDOWNLOADDIRS", originalEnv)
} else {
os.Unsetenv("GOPEED_WHITEDOWNLOADDIRS")
}
}()
tests := []struct {
name string
envValue string
expected *args
}{
{
name: "comma-separated directories",
envValue: "/env/dir1,/env/dir2,/env/dir3",
expected: &args{
WhiteDownloadDirs: []string{"/env/dir1", "/env/dir2", "/env/dir3"},
},
},
{
name: "comma-separated with spaces",
envValue: " /env/dir1 , /env/dir2 , /env/dir3 ",
expected: &args{
WhiteDownloadDirs: []string{"/env/dir1", "/env/dir2", "/env/dir3"},
},
},
{
name: "single directory",
envValue: "/single/dir",
expected: &args{
WhiteDownloadDirs: []string{"/single/dir"},
},
},
{
name: "empty string should create empty slice",
envValue: "",
expected: &args{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear environment variable first
os.Unsetenv("GOPEED_WHITEDOWNLOADDIRS")
if tt.envValue != "" {
os.Setenv("GOPEED_WHITEDOWNLOADDIRS", tt.envValue)
}
cfg := &args{}
loadEnvVars(cfg)
if !reflect.DeepEqual(cfg, tt.expected) {
t.Errorf("loadEnvVars() = %+v, want %+v", cfg, tt.expected)
}
})
}
})
}