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 }