github.com/tomwright/dasel@v1.27.3/internal/command/root_update_test.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"github.com/spf13/cobra"
     8  	"github.com/tomwright/dasel/internal/selfupdate"
     9  	"io"
    10  	"net/http"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"runtime"
    15  	"testing"
    16  )
    17  
    18  func mustAbs(path string) string {
    19  	res, err := filepath.Abs(path)
    20  	if err != nil {
    21  		panic(err)
    22  	}
    23  	return res
    24  }
    25  
    26  func expectedExecutableName() string {
    27  	var ext string
    28  	if runtime.GOOS == "windows" {
    29  		ext = ".exe"
    30  	}
    31  	return fmt.Sprintf("dasel_%s_%s%s", runtime.GOOS, runtime.GOARCH, ext)
    32  }
    33  
    34  func validFetchReleaseFn(httpClient *http.Client, user string, repo string, tag string) (*selfupdate.Release, error) {
    35  	if exp, got := "TomWright", user; exp != got {
    36  		return nil, fmt.Errorf("exp user %s, got %s", exp, got)
    37  	}
    38  	if exp, got := "dasel", repo; exp != got {
    39  		return nil, fmt.Errorf("exp repo %s, got %s", exp, got)
    40  	}
    41  	if exp, got := "latest", tag; exp != got {
    42  		return nil, fmt.Errorf("exp tag %s, got %s", exp, got)
    43  	}
    44  	return &selfupdate.Release{
    45  		Assets: []*selfupdate.ReleaseAsset{
    46  			{
    47  				Name:               expectedExecutableName(),
    48  				BrowserDownloadURL: "asd",
    49  			},
    50  		},
    51  		TagName: "v1.1.0",
    52  	}, nil
    53  }
    54  
    55  func validDownloadFileFn(url string, dest string) error {
    56  	if exp, got := "asd", url; exp != got {
    57  		return fmt.Errorf("exp url %s, got %s", exp, got)
    58  	}
    59  	if exp, got := mustAbs(fmt.Sprintf("./%s", expectedExecutableName())), dest; exp != got {
    60  		return fmt.Errorf("exp dest %s, got %s", exp, got)
    61  	}
    62  	return nil
    63  }
    64  
    65  func validChmodFn(name string, mode os.FileMode) error {
    66  	if exp, got := mustAbs(fmt.Sprintf("./%s", expectedExecutableName())), name; exp != got {
    67  		return fmt.Errorf("exp name %s, got %s", exp, got)
    68  	}
    69  	if exp, got := os.ModePerm, mode; exp != got {
    70  		return fmt.Errorf("exp mode %s, got %s", exp, got)
    71  	}
    72  	return nil
    73  }
    74  
    75  func validExecuteCmdFn(version string) func(name string, arg ...string) ([]byte, error) {
    76  	return func(name string, arg ...string) ([]byte, error) {
    77  		if exp, got := mustAbs(fmt.Sprintf("./%s", expectedExecutableName())), name; exp != got {
    78  			return nil, fmt.Errorf("exp name %s, got %s", exp, got)
    79  		}
    80  		if exp, got := []string{"--version"}, arg; !reflect.DeepEqual(exp, got) {
    81  			return nil, fmt.Errorf("exp args %v, got %s", exp, got)
    82  		}
    83  		return []byte(`dasel version ` + version), nil
    84  	}
    85  }
    86  
    87  func validExecutableFn() (string, error) {
    88  	return "/current", nil
    89  }
    90  
    91  func validRenameFn(src string, dst string) error {
    92  	if exp, got := mustAbs(fmt.Sprintf("./%s", expectedExecutableName())), src; exp != got {
    93  		return fmt.Errorf("exp src %s, got %s", exp, got)
    94  	}
    95  	if exp, got := "/current", dst; exp != got {
    96  		return fmt.Errorf("exp dst %s, got %s", exp, got)
    97  	}
    98  	return nil
    99  }
   100  
   101  func validRemoveFn(removed *bool) func(path string) error {
   102  	return func(path string) error {
   103  		if exp, got := mustAbs(fmt.Sprintf("./%s", expectedExecutableName())), path; exp != got {
   104  			return fmt.Errorf("exp path %s, got %s", exp, got)
   105  		}
   106  		if removed != nil {
   107  			*removed = true
   108  		}
   109  		return nil
   110  	}
   111  }
   112  
   113  func TestRootCMD_Update(t *testing.T) {
   114  	expectedErr := errors.New("some expected error")
   115  
   116  	t.Run("Successful", updateTestOutputEqual("v1.0.0",
   117  		validFetchReleaseFn,
   118  		validDownloadFileFn,
   119  		validChmodFn,
   120  		validExecuteCmdFn("v1.1.0"),
   121  		validExecutableFn,
   122  		validRenameFn,
   123  		validRemoveFn(nil),
   124  		`Updating...
   125  Current version: v1.0.0
   126  Release version: v1.1.0
   127  New version: v1.1.0
   128  Successfully updated
   129  `, nil))
   130  
   131  	t.Run("SuccessfulDevelopment", updateTestOutputEqual("development",
   132  		validFetchReleaseFn,
   133  		validDownloadFileFn,
   134  		validChmodFn,
   135  		validExecuteCmdFn("v1.1.0"),
   136  		validExecutableFn,
   137  		validRenameFn,
   138  		validRemoveFn(nil),
   139  		`Updating...
   140  Current version: development
   141  Release version: v1.1.0
   142  New version: v1.1.0
   143  Successfully updated
   144  `, nil, "--dev"))
   145  
   146  	t.Run("SkipDevelopment", updateTestOutputEqual("development",
   147  		nil, nil, nil, nil, nil, nil, nil,
   148  		``, ErrIgnoredDev))
   149  
   150  	t.Run("AlreadyOnLatestVersion", updateTestOutputEqual("v1.1.0",
   151  		validFetchReleaseFn,
   152  		nil, nil, nil, nil, nil, nil,
   153  		``, ErrHaveLatestVersion))
   154  
   155  	t.Run("AlreadyOnNewerVersion", updateTestOutputEqual("v1.2.0",
   156  		validFetchReleaseFn,
   157  		nil, nil, nil, nil, nil, nil,
   158  		``, ErrNewerVersion))
   159  
   160  	t.Run("ErrorGettingLatestRelease", updateTestOutputEqual("v1.0.0",
   161  		func(httpClient *http.Client, user string, repo string, tag string) (*selfupdate.Release, error) {
   162  			return nil, expectedErr
   163  		},
   164  		nil, nil, nil, nil, nil, nil,
   165  		``, expectedErr))
   166  
   167  	t.Run("MissingAssetForSystem", updateTestOutputEqual("v1.0.0",
   168  		func(httpClient *http.Client, user string, repo string, tag string) (*selfupdate.Release, error) {
   169  			if exp, got := "TomWright", user; exp != got {
   170  				return nil, fmt.Errorf("exp user %s, got %s", exp, got)
   171  			}
   172  			if exp, got := "dasel", repo; exp != got {
   173  				return nil, fmt.Errorf("exp repo %s, got %s", exp, got)
   174  			}
   175  			if exp, got := "latest", tag; exp != got {
   176  				return nil, fmt.Errorf("exp tag %s, got %s", exp, got)
   177  			}
   178  			return &selfupdate.Release{
   179  				Assets:  []*selfupdate.ReleaseAsset{},
   180  				TagName: "v1.1.0",
   181  			}, nil
   182  		},
   183  		nil, nil, nil, nil, nil, nil,
   184  		``, fmt.Errorf("could not find asset for %s %s", runtime.GOOS, runtime.GOARCH)))
   185  
   186  	t.Run("DownloadError", updateTestOutputEqual("v1.0.0",
   187  		validFetchReleaseFn,
   188  		func(url string, dest string) error {
   189  			return expectedErr
   190  		}, nil, nil, nil, nil, nil,
   191  		``, expectedErr))
   192  
   193  	t.Run("FailGettingNewVersion", func(t *testing.T) {
   194  		removed := false
   195  		testFunc := updateTestOutputEqual("v1.0.0",
   196  			validFetchReleaseFn,
   197  			validDownloadFileFn,
   198  			validChmodFn,
   199  			func(name string, arg ...string) ([]byte, error) {
   200  				return nil, expectedErr
   201  			},
   202  			nil, nil,
   203  			validRemoveFn(&removed),
   204  			``, expectedErr)
   205  		testFunc(t)
   206  		if !removed {
   207  			t.Errorf("downloaded file was not removed")
   208  		}
   209  	})
   210  
   211  	t.Run("FailGettingCurrentExecutablePath", func(t *testing.T) {
   212  		removed := false
   213  		testFunc := updateTestOutputEqual("v1.0.0",
   214  			validFetchReleaseFn,
   215  			validDownloadFileFn,
   216  			validChmodFn,
   217  			validExecuteCmdFn("v1.1.0"),
   218  			func() (string, error) {
   219  				return "", expectedErr
   220  			},
   221  			nil,
   222  			validRemoveFn(&removed),
   223  			``, expectedErr)
   224  		testFunc(t)
   225  		if !removed {
   226  			t.Errorf("downloaded file was not removed")
   227  		}
   228  	})
   229  
   230  	t.Run("FailReplacingCurrentExecutable", func(t *testing.T) {
   231  		removed := false
   232  		testFunc := updateTestOutputEqual("v1.0.0",
   233  			validFetchReleaseFn,
   234  			validDownloadFileFn,
   235  			validChmodFn,
   236  			validExecuteCmdFn("v1.1.0"),
   237  			validExecutableFn,
   238  			func(src string, dst string) error {
   239  				return expectedErr
   240  			},
   241  			validRemoveFn(&removed),
   242  			``, expectedErr)
   243  		testFunc(t)
   244  		if !removed {
   245  			t.Errorf("downloaded file was not removed")
   246  		}
   247  	})
   248  
   249  }
   250  
   251  func updateTestOutputEqual(currentVersion string,
   252  	fetchReleaseFn func(httpClient *http.Client, user string, repo string, tag string) (*selfupdate.Release, error),
   253  	downloadFileFn func(url string, dest string) error,
   254  	chmodFn func(name string, mode os.FileMode) error,
   255  	executeCmdFn func(name string, arg ...string) ([]byte, error),
   256  	executableFn func() (string, error),
   257  	renameFn func(src string, dst string) error,
   258  	removeFn func(path string) error,
   259  	exp string, expErr error, additionalArgs ...string) func(t *testing.T) {
   260  
   261  	return updateTestCheck(currentVersion, fetchReleaseFn, downloadFileFn, chmodFn, executeCmdFn, executableFn,
   262  		renameFn, removeFn, func(out string) error {
   263  			if exp != out {
   264  				return fmt.Errorf("expected output %s, got %s", exp, out)
   265  			}
   266  			return nil
   267  		}, expErr, additionalArgs...)
   268  }
   269  
   270  func newUpdateRootCmd(updater *selfupdate.Updater) *cobra.Command {
   271  	root := NewRootCMD()
   272  
   273  	for _, c := range root.Commands() {
   274  		if c.Use == "update" {
   275  			root.RemoveCommand(c)
   276  		}
   277  	}
   278  
   279  	root.AddCommand(updateCommand(updater))
   280  
   281  	return root
   282  }
   283  
   284  func assertError(t *testing.T, err error, expErr error) bool {
   285  	if expErr == nil && err != nil {
   286  		t.Errorf("expected err %v, got %v", expErr, err)
   287  		return false
   288  	}
   289  	if expErr != nil && err == nil {
   290  		t.Errorf("expected err %v, got %v", expErr, err)
   291  		return false
   292  	}
   293  	if expErr != nil && err != nil && !(errors.Is(err, expErr) || err.Error() == expErr.Error()) {
   294  		t.Errorf("expected err %v, got %v", expErr, err)
   295  		return false
   296  	}
   297  	return true
   298  }
   299  
   300  func updateTestCheck(currentVersion string,
   301  	fetchReleaseFn func(httpClient *http.Client, user string, repo string, tag string) (*selfupdate.Release, error),
   302  	downloadFileFn func(url string, dest string) error,
   303  	chmodFn func(name string, mode os.FileMode) error,
   304  	executeCmdFn func(name string, arg ...string) ([]byte, error),
   305  	executableFn func() (string, error),
   306  	renameFn func(src string, dst string) error,
   307  	removeFn func(path string) error,
   308  	checkFn func(out string) error, expErr error, additionalArgs ...string) func(t *testing.T) {
   309  
   310  	updater := selfupdate.NewUpdater(currentVersion)
   311  
   312  	updater.FetchReleaseFn = fetchReleaseFn
   313  	updater.DownloadFileFn = downloadFileFn
   314  	updater.ChmodFn = chmodFn
   315  	updater.ExecuteCmdFn = executeCmdFn
   316  	updater.ExecutableFn = executableFn
   317  	updater.RenameFn = renameFn
   318  	updater.RemoveFn = removeFn
   319  
   320  	return func(t *testing.T) {
   321  		cmd := newUpdateRootCmd(updater)
   322  		outputBuffer := bytes.NewBuffer([]byte{})
   323  
   324  		args := []string{
   325  			"update",
   326  		}
   327  		if additionalArgs != nil {
   328  			args = append(args, additionalArgs...)
   329  		}
   330  
   331  		cmd.SetOut(outputBuffer)
   332  		cmd.SetArgs(args)
   333  
   334  		err := cmd.Execute()
   335  
   336  		if !assertError(t, err, expErr) {
   337  			return
   338  		}
   339  
   340  		if expErr != nil || err != nil {
   341  			return
   342  		}
   343  
   344  		output, err := io.ReadAll(outputBuffer)
   345  		if err != nil {
   346  			t.Errorf("unexpected error reading output buffer: %s", err)
   347  			return
   348  		}
   349  
   350  		if err := checkFn(string(output)); err != nil {
   351  			t.Errorf("unexpected output: %s", err)
   352  		}
   353  	}
   354  }