package main import ( "crypto/ed25519" "os" "path/filepath" "testing" "github.com/henrygd/beszel/agent" "github.com/spf13/pflag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" ) func TestGetAddress(t *testing.T) { tests := []struct { name string opts cmdOptions envVars map[string]string expected string }{ { name: "default port when no config", opts: cmdOptions{}, expected: ":45876", }, { name: "use address from flag", opts: cmdOptions{ listen: "8080", }, expected: ":8080", }, { name: "use unix socket from flag", opts: cmdOptions{ listen: "/tmp/beszel.sock", }, expected: "/tmp/beszel.sock", }, { name: "use LISTEN env var", opts: cmdOptions{}, envVars: map[string]string{ "LISTEN": "1.2.3.4:9090", }, expected: "1.2.3.4:9090", }, { name: "use legacy PORT env var", opts: cmdOptions{}, envVars: map[string]string{ "PORT": "7070", }, expected: ":7070", }, { name: "use unix socket from env var", opts: cmdOptions{ listen: "", }, envVars: map[string]string{ "LISTEN": "/tmp/beszel.sock", }, expected: "/tmp/beszel.sock", }, { name: "flag takes precedence over env vars", opts: cmdOptions{ listen: ":8080", }, envVars: map[string]string{ "LISTEN": ":9090", "PORT": "7070", }, expected: ":8080", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Setup environment for k, v := range tt.envVars { t.Setenv(k, v) } addr := tt.opts.getAddress() assert.Equal(t, tt.expected, addr) }) } } func TestLoadPublicKeys(t *testing.T) { // Generate a test key _, priv, err := ed25519.GenerateKey(nil) require.NoError(t, err) signer, err := ssh.NewSignerFromKey(priv) require.NoError(t, err) pubKey := ssh.MarshalAuthorizedKey(signer.PublicKey()) tests := []struct { name string opts cmdOptions envVars map[string]string setupFiles map[string][]byte wantErr bool errContains string }{ { name: "load key from flag", opts: cmdOptions{ key: string(pubKey), }, }, { name: "load key from env var", envVars: map[string]string{ "KEY": string(pubKey), }, }, { name: "load key from file", envVars: map[string]string{ "KEY_FILE": "testkey.pub", }, setupFiles: map[string][]byte{ "testkey.pub": pubKey, }, }, { name: "error when no key provided", wantErr: true, errContains: "no key provided", }, { name: "error on invalid key file", envVars: map[string]string{ "KEY_FILE": "nonexistent.pub", }, wantErr: true, errContains: "failed to read key file", }, { name: "error on invalid key data", opts: cmdOptions{ key: "invalid-key-data", }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create a temporary directory for test files if len(tt.setupFiles) > 0 { tmpDir := t.TempDir() for name, content := range tt.setupFiles { path := filepath.Join(tmpDir, name) err := os.WriteFile(path, content, 0600) require.NoError(t, err) if tt.envVars != nil { tt.envVars["KEY_FILE"] = path } } } // Set up environment for k, v := range tt.envVars { t.Setenv(k, v) } keys, err := tt.opts.loadPublicKeys() if tt.wantErr { assert.Error(t, err) if tt.errContains != "" { assert.Contains(t, err.Error(), tt.errContains) } return } require.NoError(t, err) assert.Len(t, keys, 1) assert.Equal(t, signer.PublicKey().Type(), keys[0].Type()) }) } } func TestGetNetwork(t *testing.T) { tests := []struct { name string opts cmdOptions envVars map[string]string expected string }{ { name: "NETWORK env var", envVars: map[string]string{ "NETWORK": "tcp4", }, expected: "tcp4", }, { name: "only port", opts: cmdOptions{listen: "8080"}, expected: "tcp", }, { name: "ipv4 address", opts: cmdOptions{listen: "1.2.3.4:8080"}, expected: "tcp", }, { name: "ipv6 address", opts: cmdOptions{listen: "[2001:db8::1]:8080"}, expected: "tcp", }, { name: "unix network", opts: cmdOptions{listen: "/tmp/beszel.sock"}, expected: "unix", }, { name: "env var network", opts: cmdOptions{listen: ":8080"}, envVars: map[string]string{"NETWORK": "tcp4"}, expected: "tcp4", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Setup environment for k, v := range tt.envVars { t.Setenv(k, v) } network := agent.GetNetwork(tt.opts.listen) assert.Equal(t, tt.expected, network) }) } } func TestParseFlags(t *testing.T) { // Save original command line arguments and restore after test oldArgs := os.Args defer func() { os.Args = oldArgs pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) }() tests := []struct { name string args []string expected cmdOptions }{ { name: "no flags", args: []string{"cmd"}, expected: cmdOptions{ key: "", listen: "", }, }, { name: "key flag only", args: []string{"cmd", "-key", "testkey"}, expected: cmdOptions{ key: "testkey", listen: "", }, }, { name: "key flag double dash", args: []string{"cmd", "--key", "testkey"}, expected: cmdOptions{ key: "testkey", listen: "", }, }, { name: "key flag short", args: []string{"cmd", "-k", "testkey"}, expected: cmdOptions{ key: "testkey", listen: "", }, }, { name: "addr flag only", args: []string{"cmd", "-listen", ":8080"}, expected: cmdOptions{ key: "", listen: ":8080", }, }, { name: "addr flag double dash", args: []string{"cmd", "--listen", ":8080"}, expected: cmdOptions{ key: "", listen: ":8080", }, }, { name: "addr flag short", args: []string{"cmd", "-l", ":8080"}, expected: cmdOptions{ key: "", listen: ":8080", }, }, { name: "both flags", args: []string{"cmd", "-key", "testkey", "-listen", ":8080"}, expected: cmdOptions{ key: "testkey", listen: ":8080", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Reset flags for each test pflag.CommandLine = pflag.NewFlagSet(tt.args[0], pflag.ExitOnError) os.Args = tt.args var opts cmdOptions opts.parse() pflag.Parse() assert.Equal(t, tt.expected, opts) }) } }