Skip to content

Commit

Permalink
BUGFIX-11: Refactor and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
grafviktor committed Nov 23, 2023
1 parent c9b3453 commit 18e326b
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 0 deletions.
57 changes: 57 additions & 0 deletions internal/utils/ssh/connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package ssh

import (
"fmt"
"strings"
)

type CommandLineOption interface{}

type (
OptionPrivateKey struct{ Value string }
OptionRemotePort struct{ Value string }
OptionLoginName struct{ Value string }
OptionAddress struct{ Value string }
)

func constructKeyValueOption(optionFlag, optionValue string) string {
optionValue = strings.TrimSpace(optionValue)
if optionValue != "" {
return fmt.Sprintf(" %s %s", optionFlag, optionValue)
}
return ""
}

func addOption(sb *strings.Builder, rawParameter CommandLineOption) {
var option string
switch p := rawParameter.(type) {
case OptionPrivateKey:
option = constructKeyValueOption("-i", p.Value)
case OptionRemotePort:
option = constructKeyValueOption("-p", p.Value)
case OptionLoginName:
option = constructKeyValueOption("-l", p.Value)
case OptionAddress:
if p.Value != "" {
option = fmt.Sprintf(" %s", p.Value)
}
default:
return
}

sb.WriteString(option)
}

// ConstructCMD - build connect command from main app and its arguments
// cmd - main executable
// options - set of command line options. See Option... public variables.
func ConstructCMD(cmd string, options ...CommandLineOption) string {
sb := strings.Builder{}
sb.WriteString(cmd)

for _, argument := range options {
addOption(&sb, argument)
}

return sb.String()
}
123 changes: 123 additions & 0 deletions internal/utils/ssh/connect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package ssh

import (
"strings"
"testing"
)

func Test_ConstructKeyValueOption(t *testing.T) {
tests := []struct {
name string
optionFlag string
optionValue string
expectedResult string
}{
{
name: "Option with value",
optionFlag: "-i",
optionValue: "private_key",
expectedResult: " -i private_key",
},
{
name: "Option with empty value",
optionFlag: "-p",
optionValue: "",
expectedResult: "",
},
{
name: "Option with space-padded value",
optionFlag: "-l",
optionValue: " login_name ",
expectedResult: " -l login_name",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := constructKeyValueOption(tt.optionFlag, tt.optionValue)

if result != tt.expectedResult {
t.Errorf("Expected result %s, but got %s", tt.expectedResult, result)
}
})
}
}

func Test_AddOption(t *testing.T) {
tests := []struct {
name string
rawParameter CommandLineOption
expectedResult string
}{
{
name: "OptionPrivateKey with value",
rawParameter: OptionPrivateKey{Value: "private_key"},
expectedResult: " -i private_key",
},
{
name: "OptionRemotePort with empty value",
rawParameter: OptionRemotePort{Value: ""},
expectedResult: "",
},
{
name: "OptionLoginName with value",
rawParameter: OptionLoginName{Value: "login_name"},
expectedResult: " -l login_name",
},
{
name: "OptionAddress with empty value",
rawParameter: OptionAddress{Value: ""},
expectedResult: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var sb strings.Builder
addOption(&sb, tt.rawParameter)

result := sb.String()
if result != tt.expectedResult {
t.Errorf("Expected result %s, but got %s", tt.expectedResult, result)
}
})
}
}

func Test_ConstructCMD(t *testing.T) {
tests := []struct {
name string
cmd string
options []CommandLineOption
expectedResult string
}{
{
name: "Command with Options",
cmd: "ssh",
options: []CommandLineOption{OptionPrivateKey{Value: "private_key"}, OptionRemotePort{Value: "22"}},
expectedResult: "ssh -i private_key -p 22",
},
{
name: "Command without Options",
cmd: "ls",
options: []CommandLineOption{},
expectedResult: "ls",
},
{
name: "Command with Address Option",
cmd: "ping",
options: []CommandLineOption{OptionAddress{Value: "example.com"}},
expectedResult: "ping example.com",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ConstructCMD(tt.cmd, tt.options...)

if result != tt.expectedResult {
t.Errorf("Expected result %s, but got %s", tt.expectedResult, result)
}
})
}
}
37 changes: 37 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ package utils
import (
"errors"
"os"
"os/exec"
"os/user"
"path"
"path/filepath"
"strings"

"github.com/grafviktor/goto/internal/model"
"github.com/grafviktor/goto/internal/utils/ssh"
)

type Logger interface {
Expand Down Expand Up @@ -65,3 +70,35 @@ func GetCurrentOSUser() string {

return user.Username
}

// CheckAppInstalled - checks if application is installed and can be found in executable path
// appName - name of the application to be looked for in $PATH
func CheckAppInstalled(appName string) error {
_, err := exec.LookPath(appName)

return err
}

// HostModelToOptionsAdaptor - extract values from model.Host into a set of ssh.CommandLineOption
// host - model.Host to be adapted
// returns []ssh.CommandLineOption
func HostModelToOptionsAdaptor(host model.Host) []ssh.CommandLineOption {
return []ssh.CommandLineOption{
ssh.OptionAddress{Value: host.Address},
ssh.OptionLoginName{Value: host.LoginName},
ssh.OptionRemotePort{Value: host.RemotePort},
ssh.OptionPrivateKey{Value: host.PrivateKeyPath},
}
}

func BuildProcess(cmd string) *exec.Cmd {
if strings.TrimSpace(cmd) == "" {
return nil
}

commandWithArguments := strings.Split(cmd, " ")
command := commandWithArguments[0]
arguments := commandWithArguments[1:]

return exec.Command(command, arguments...)
}
131 changes: 131 additions & 0 deletions internal/utils/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package utils

import (
"os/exec"
"testing"

"github.com/grafviktor/goto/internal/model"
"github.com/grafviktor/goto/internal/utils/ssh"
)

func TestCheckAppInstalled(t *testing.T) {
tests := []struct {
name string
appName string
expectedError bool
}{
{
name: "Installed App",
appName: "cd", // Assuming 'ls' is always installed
expectedError: false,
},
{
name: "Uninstalled App",
appName: "nonexistentapp",
expectedError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := CheckAppInstalled(tt.appName)

if tt.expectedError && err == nil {
t.Errorf("Expected an error, but got nil")
}

if !tt.expectedError && err != nil {
t.Errorf("Expected no error, but got: %v", err)
}
})
}
}

func TestHostModelToOptionsAdaptor(t *testing.T) {
tests := []struct {
name string
host model.Host
expectedOptions []ssh.CommandLineOption
}{
{
name: "Valid Host",
host: model.Host{
Address: "example.com",
LoginName: "user",
RemotePort: "22",
PrivateKeyPath: "/path/to/private_key",
},
expectedOptions: []ssh.CommandLineOption{
ssh.OptionAddress{Value: "example.com"},
ssh.OptionLoginName{Value: "user"},
ssh.OptionRemotePort{Value: "22"},
ssh.OptionPrivateKey{Value: "/path/to/private_key"},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := HostModelToOptionsAdaptor(tt.host)

if len(result) != len(tt.expectedOptions) {
t.Errorf("Expected %d options, but got %d", len(tt.expectedOptions), len(result))
}

for i := range result {
if result[i] != tt.expectedOptions[i] {
t.Errorf("Expected option %v, but got %v", tt.expectedOptions[i], result[i])
}
}
})
}
}

func TestBuildProcess(t *testing.T) {
tests := []struct {
name string
cmd string
expectedCmd *exec.Cmd
}{
{
name: "Simple Command",
cmd: "cd",
expectedCmd: exec.Command("cd"),
},
{
name: "Command with Arguments",
cmd: "echo hello",
expectedCmd: exec.Command("echo", "hello"),
},
{
name: "Empty Command",
cmd: "",
expectedCmd: nil, // Expecting nil as there is no valid command
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := BuildProcess(tt.cmd)

if tt.expectedCmd == nil && result != nil {

Check failure on line 111 in internal/utils/utils_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ifElseChain: rewrite if-else to switch statement (gocritic)
t.Errorf("Expected nil, but got %+v", result)
} else if tt.expectedCmd != nil && result == nil {
t.Errorf("Expected %+v, but got nil", tt.expectedCmd)
} else if tt.expectedCmd != nil && result != nil {
// Compare relevant fields of the Cmd struct
if tt.expectedCmd.Path != result.Path {
t.Errorf("Expected Path %s, but got %s", tt.expectedCmd.Path, result.Path)
}
if len(tt.expectedCmd.Args) != len(result.Args) {
t.Errorf("Expected %d arguments, but got %d", len(tt.expectedCmd.Args), len(result.Args))
}
for i := range tt.expectedCmd.Args {
if tt.expectedCmd.Args[i] != result.Args[i] {
t.Errorf("Expected argument %s, but got %s", tt.expectedCmd.Args[i], result.Args[i])
}
}
}
})
}
}

0 comments on commit 18e326b

Please sign in to comment.