github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/updater/updater_test.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package updater 5 6 import ( 7 "fmt" 8 "io" 9 "net/http" 10 "time" 11 12 "net/http/httptest" 13 "os" 14 "path/filepath" 15 "runtime" 16 "testing" 17 18 "github.com/keybase/client/go/updater/saltpack" 19 "github.com/keybase/client/go/updater/util" 20 "github.com/keybase/go-logging" 21 "github.com/stretchr/testify/assert" 22 "github.com/stretchr/testify/require" 23 ) 24 25 var testLog = &logging.Logger{Module: "test"} 26 27 var testZipPath string 28 29 var testAppStatePath = filepath.Join(os.TempDir(), "KBTest_app_state.json") 30 31 const ( 32 // shasum -a 256 test/test.zip 33 validDigest = "54970995e4d02da631e0634162ef66e2663e0eee7d018e816ac48ed6f7811c84" 34 // keybase sign -d -i test.zip 35 validSignature = `BEGIN KEYBASE SALTPACK DETACHED SIGNATURE. 36 kXR7VktZdyH7rvq v5weRa8moXPeKBe e2YLT0PnyHzCrVi RbC1J5uJtYgYyLW eGg4qzsWqkb7hcX 37 GTVc0vsEUVwBCly qhPdOL0mE19kfxg A4fMqpNGNTY0jtO iMpjwwuIyLBxkCC jHzMiJFskzluz2S 38 otWUI0nTu2vG2Fx Mgeyqm20Ug8j7Bi N. END KEYBASE SALTPACK DETACHED SIGNATURE.` 39 invalidDigest = "74970995e4d02da631e0634162ef66e2663e0eee7d018e816ac48ed6f7811c84" 40 invalidSignature = `BEGIN KEYBASE SALTPACK DETACHED SIGNATURE. 41 QXR7VktZdyH7rvq v5wcIkPOwDJ1n11 M8RnkLKQGO2f3Bb fzCeMYz4S6oxLAy 42 Cco4N255JFzv2PX E6WWdobANV4guJI iEE8XJb6uudCX4x QWZfnamVAaZpXuW 43 vdz65rE7oZsLSdW oxMsbBgG9NVpSJy x3CD6LaC9GlZ4IS ofzkHe401mHjr7M M. END 44 KEYBASE SALTPACK DETACHED SIGNATURE.` 45 ) 46 47 func makeKeybaseUpdateTempDir(t *testing.T, updater *Updater, testAsset *Asset) (tmpDir string) { 48 // This creates a real KebyaseUpdater.[ID] directory in os.TempDir 49 // Then we download the test zip to this directory from testServer 50 tmpDir, err := util.MakeTempDir("KeybaseUpdater.", 0700) 51 require.NoError(t, err) 52 err = updater.downloadAsset(testAsset, tmpDir, UpdateOptions{}) 53 require.NoError(t, err) 54 return tmpDir 55 } 56 57 func init() { 58 _, filename, _, _ := runtime.Caller(0) 59 testZipPath = filepath.Join(filepath.Dir(filename), "test/test.zip") 60 } 61 62 func newTestUpdater(t *testing.T) (*Updater, error) { 63 return newTestUpdaterWithServer(t, nil, nil, &testConfig{}) 64 } 65 66 func newTestUpdaterWithServer(t *testing.T, testServer *httptest.Server, update *Update, config Config) (*Updater, error) { 67 return NewUpdater(testUpdateSource{testServer: testServer, config: config, update: update}, config, testLog), nil 68 } 69 70 func newTestContext(options UpdateOptions, cfg Config, response *UpdatePromptResponse) *testUpdateUI { 71 return &testUpdateUI{options: options, cfg: cfg, response: response} 72 } 73 74 type testUpdateUI struct { 75 options UpdateOptions 76 cfg Config 77 response *UpdatePromptResponse 78 promptErr error 79 verifyErr error 80 beforeApplyErr error 81 afterApplyErr error 82 errReported error 83 actionReported UpdateAction 84 autoUpdateReported bool 85 updateReported *Update 86 successReported bool 87 isCheckCommand bool 88 } 89 90 func (u testUpdateUI) BeforeUpdatePrompt(_ Update, _ UpdateOptions) error { 91 return nil 92 } 93 94 func (u testUpdateUI) UpdatePrompt(_ Update, _ UpdateOptions, _ UpdatePromptOptions) (*UpdatePromptResponse, error) { 95 if u.promptErr != nil { 96 return nil, u.promptErr 97 } 98 return u.response, nil 99 } 100 101 func (u testUpdateUI) BeforeApply(update Update) error { 102 return u.beforeApplyErr 103 } 104 105 func (u testUpdateUI) Apply(update Update, options UpdateOptions, tmpDir string) error { 106 return nil 107 } 108 109 func (u testUpdateUI) AfterApply(update Update) error { 110 return u.afterApplyErr 111 } 112 113 func (u testUpdateUI) GetUpdateUI() UpdateUI { 114 return u 115 } 116 117 func (u testUpdateUI) Verify(update Update) error { 118 if u.verifyErr != nil { 119 return u.verifyErr 120 } 121 var validCodeSigningKIDs = map[string]bool{ 122 "0120d7539e27e83a9c8caf8701199c6985c0a96801ff7cb69456e9b3a8a8446c66080a": true, // joshblum (saltine) 123 } 124 return saltpack.VerifyDetachedFileAtPath(update.Asset.LocalPath, update.Asset.Signature, validCodeSigningKIDs, testLog) 125 } 126 127 func (u *testUpdateUI) ReportError(err error, update *Update, options UpdateOptions) { 128 u.errReported = err 129 } 130 131 func (u *testUpdateUI) ReportAction(actionResponse UpdatePromptResponse, update *Update, options UpdateOptions) { 132 u.actionReported = actionResponse.Action 133 autoUpdate, _ := u.cfg.GetUpdateAuto() 134 u.autoUpdateReported = autoUpdate 135 u.updateReported = update 136 } 137 138 func (u *testUpdateUI) ReportSuccess(update *Update, options UpdateOptions) { 139 u.successReported = true 140 u.updateReported = update 141 } 142 143 func (u *testUpdateUI) AfterUpdateCheck(update *Update) {} 144 145 func (u testUpdateUI) UpdateOptions() UpdateOptions { 146 return u.options 147 } 148 149 func (u testUpdateUI) GetAppStatePath() string { 150 return testAppStatePath 151 } 152 153 func (u testUpdateUI) IsCheckCommand() bool { 154 return u.isCheckCommand 155 } 156 157 func (u testUpdateUI) DeepClean() {} 158 159 type testUpdateSource struct { 160 testServer *httptest.Server 161 config Config 162 update *Update 163 findErr error 164 } 165 166 func (u testUpdateSource) Description() string { 167 return "Test" 168 } 169 170 func testUpdate(uri string) *Update { 171 return newTestUpdate(uri, true) 172 } 173 174 func newTestUpdate(uri string, needUpdate bool) *Update { 175 update := &Update{ 176 Version: "1.0.1", 177 Name: "Test", 178 Description: "Bug fixes", 179 InstallID: "deadbeef", 180 RequestID: "cafedead", 181 NeedUpdate: needUpdate, 182 } 183 if uri != "" { 184 update.Asset = &Asset{ 185 Name: "test.zip", 186 URL: uri, 187 Digest: validDigest, 188 Signature: validSignature, 189 } 190 } 191 return update 192 } 193 194 func (u testUpdateSource) FindUpdate(options UpdateOptions) (*Update, error) { 195 return u.update, u.findErr 196 } 197 198 type testConfig struct { 199 auto bool 200 autoSet bool 201 autoOverride bool 202 installID string 203 err error 204 } 205 206 func (c testConfig) GetUpdateAuto() (bool, bool) { 207 return c.auto, c.autoSet 208 } 209 210 func (c *testConfig) SetUpdateAuto(b bool) error { 211 c.auto = b 212 c.autoSet = true 213 return c.err 214 } 215 216 func (c *testConfig) IsLastUpdateCheckTimeRecent(d time.Duration) bool { 217 return true 218 } 219 220 func (c *testConfig) SetLastUpdateCheckTime() { 221 222 } 223 224 // For overriding the current Auto setting 225 func (c testConfig) GetUpdateAutoOverride() bool { 226 return c.autoOverride 227 } 228 229 func (c *testConfig) SetUpdateAutoOverride(auto bool) error { 230 c.autoOverride = auto 231 return nil 232 } 233 234 func (c testConfig) GetInstallID() string { 235 return c.installID 236 } 237 238 func (c *testConfig) SetInstallID(s string) error { 239 c.installID = s 240 return c.err 241 } 242 243 func (c testConfig) GetLastAppliedVersion() string { 244 return "" 245 } 246 247 func (c *testConfig) SetLastAppliedVersion(version string) error { 248 return nil 249 } 250 251 func newDefaultTestUpdateOptions() UpdateOptions { 252 return UpdateOptions{ 253 Version: "1.0.0", 254 Platform: runtime.GOOS, 255 DestinationPath: filepath.Join(os.TempDir(), "Test"), 256 } 257 } 258 259 func testServerForUpdateFile(t *testing.T, path string) *httptest.Server { 260 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 261 f, err := os.Open(path) 262 require.NoError(t, err) 263 w.Header().Set("Content-Type", "application/zip") 264 _, err = io.Copy(w, f) 265 require.NoError(t, err) 266 })) 267 } 268 269 func testServerForError(t *testing.T, err error) *httptest.Server { 270 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 271 http.Error(w, err.Error(), 500) 272 })) 273 } 274 275 func testServerNotFound(t *testing.T) *httptest.Server { 276 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 277 http.Error(w, "Not Found", 404) 278 })) 279 } 280 281 func TestUpdaterApply(t *testing.T) { 282 testServer := testServerForUpdateFile(t, testZipPath) 283 defer testServer.Close() 284 285 upr, err := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{}) 286 assert.NoError(t, err) 287 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionApply, AutoUpdate: true}) 288 update, err := upr.Update(ctx) 289 require.NoError(t, err) 290 require.NotNil(t, update) 291 t.Logf("Update: %#v\n", *update) 292 require.NotNil(t, update.Asset) 293 t.Logf("Asset: %#v\n", *update.Asset) 294 295 auto, autoSet := upr.config.GetUpdateAuto() 296 assert.True(t, auto) 297 assert.True(t, autoSet) 298 assert.Equal(t, "deadbeef", upr.config.GetInstallID()) 299 300 assert.Nil(t, ctx.errReported) 301 assert.Equal(t, ctx.actionReported, UpdateActionApply) 302 assert.True(t, ctx.autoUpdateReported) 303 304 require.NotNil(t, ctx.updateReported) 305 assert.Equal(t, "deadbeef", ctx.updateReported.InstallID) 306 assert.Equal(t, "cafedead", ctx.updateReported.RequestID) 307 assert.True(t, ctx.successReported) 308 309 assert.Equal(t, "apply", UpdateActionApply.String()) 310 } 311 312 func TestUpdaterDownloadError(t *testing.T) { 313 testServer := testServerForError(t, fmt.Errorf("bad response")) 314 defer testServer.Close() 315 316 upr, err := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{}) 317 assert.NoError(t, err) 318 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionApply, AutoUpdate: true}) 319 _, err = upr.Update(ctx) 320 assert.EqualError(t, err, "Update Error (download): Responded with 500 Internal Server Error") 321 322 require.NotNil(t, ctx.errReported) 323 assert.Equal(t, ctx.errReported.(Error).errorType, DownloadError) 324 assert.False(t, ctx.successReported) 325 } 326 327 func TestUpdaterCancel(t *testing.T) { 328 testServer := testServerForUpdateFile(t, testZipPath) 329 defer testServer.Close() 330 331 upr, err := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{}) 332 assert.NoError(t, err) 333 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionCancel, AutoUpdate: true}) 334 _, err = upr.Update(ctx) 335 assert.EqualError(t, err, "Update Error (cancel): Canceled") 336 337 // Don't report error on user cancel 338 assert.NoError(t, ctx.errReported) 339 } 340 341 func TestUpdaterSnooze(t *testing.T) { 342 testServer := testServerForUpdateFile(t, testZipPath) 343 defer testServer.Close() 344 345 upr, err := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{}) 346 assert.NoError(t, err) 347 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionSnooze, AutoUpdate: true}) 348 _, err = upr.Update(ctx) 349 assert.EqualError(t, err, "Update Error (cancel): Snoozed update") 350 351 // Don't report error on user snooze 352 assert.NoError(t, ctx.errReported) 353 } 354 355 func TestUpdaterContinue(t *testing.T) { 356 testServer := testServerForUpdateFile(t, testZipPath) 357 defer testServer.Close() 358 359 upr, err := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{}) 360 assert.NoError(t, err) 361 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionContinue}) 362 update, err := upr.Update(ctx) 363 require.NoError(t, err) 364 require.NotNil(t, update) 365 require.NotNil(t, update.Asset) 366 367 auto, autoSet := upr.config.GetUpdateAuto() 368 assert.False(t, auto) 369 assert.False(t, autoSet) 370 assert.Equal(t, "deadbeef", upr.config.GetInstallID()) 371 372 assert.Nil(t, ctx.errReported) 373 assert.Empty(t, string(ctx.actionReported)) 374 assert.False(t, ctx.autoUpdateReported) 375 376 require.NotNil(t, ctx.updateReported) 377 assert.Equal(t, "deadbeef", ctx.updateReported.InstallID) 378 assert.Equal(t, "cafedead", ctx.updateReported.RequestID) 379 assert.True(t, ctx.successReported) 380 } 381 382 func TestUpdateNoResponse(t *testing.T) { 383 testServer := testServerForUpdateFile(t, testZipPath) 384 defer testServer.Close() 385 386 upr, err := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{}) 387 assert.NoError(t, err) 388 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, nil) 389 _, err = upr.Update(ctx) 390 assert.EqualError(t, err, "Update Error (prompt): No response") 391 392 require.NotNil(t, ctx.errReported) 393 assert.Equal(t, ctx.errReported.(Error).errorType, PromptError) 394 assert.False(t, ctx.successReported) 395 } 396 397 func TestUpdateNoAsset(t *testing.T) { 398 testServer := testServerNotFound(t) 399 defer testServer.Close() 400 401 upr, err := newTestUpdaterWithServer(t, testServer, testUpdate(""), &testConfig{}) 402 assert.NoError(t, err) 403 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionApply, AutoUpdate: true}) 404 update, err := upr.Update(ctx) 405 assert.NoError(t, err) 406 assert.Nil(t, update.Asset) 407 } 408 409 func testUpdaterError(t *testing.T, errorType ErrorType) { 410 testServer := testServerForUpdateFile(t, testZipPath) 411 defer testServer.Close() 412 413 upr, _ := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{}) 414 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionApply, AutoUpdate: true}) 415 testErr := fmt.Errorf("Test error") 416 switch errorType { 417 case PromptError: 418 ctx.promptErr = testErr 419 case VerifyError: 420 ctx.verifyErr = testErr 421 } 422 423 _, err := upr.Update(ctx) 424 assert.EqualError(t, err, fmt.Sprintf("Update Error (%s): Test error", errorType.String())) 425 426 require.NotNil(t, ctx.errReported) 427 assert.Equal(t, ctx.errReported.(Error).errorType, errorType) 428 } 429 430 func TestUpdaterErrors(t *testing.T) { 431 testUpdaterError(t, PromptError) 432 testUpdaterError(t, VerifyError) 433 } 434 435 func TestUpdaterConfigError(t *testing.T) { 436 testServer := testServerForUpdateFile(t, testZipPath) 437 defer testServer.Close() 438 439 upr, _ := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{err: fmt.Errorf("Test config error")}) 440 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionApply, AutoUpdate: true}) 441 442 _, err := upr.Update(ctx) 443 assert.NoError(t, err) 444 445 require.NotNil(t, ctx.errReported) 446 assert.Equal(t, ConfigError, ctx.errReported.(Error).errorType) 447 } 448 449 func TestUpdaterAuto(t *testing.T) { 450 testServer := testServerForUpdateFile(t, testZipPath) 451 defer testServer.Close() 452 453 upr, _ := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{auto: true, autoSet: true}) 454 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionApply, AutoUpdate: true}) 455 456 _, err := upr.Update(ctx) 457 assert.NoError(t, err) 458 assert.Equal(t, UpdateActionAuto, ctx.actionReported) 459 } 460 461 func TestUpdaterDownloadNil(t *testing.T) { 462 upr, err := newTestUpdater(t) 463 require.NoError(t, err) 464 tmpDir, err := util.MakeTempDir("TestUpdaterDownloadNil", 0700) 465 defer util.RemoveFileAtPath(tmpDir) 466 require.NoError(t, err) 467 err = upr.downloadAsset(nil, tmpDir, UpdateOptions{}) 468 assert.EqualError(t, err, "No asset to download") 469 } 470 471 func TestUpdaterApplyError(t *testing.T) { 472 testServer := testServerForUpdateFile(t, testZipPath) 473 defer testServer.Close() 474 475 upr, _ := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{auto: true, autoSet: true}) 476 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionApply, AutoUpdate: true}) 477 478 ctx.beforeApplyErr = fmt.Errorf("Test before error") 479 _, err := upr.Update(ctx) 480 assert.EqualError(t, err, "Update Error (apply): Test before error") 481 ctx.beforeApplyErr = nil 482 483 ctx.afterApplyErr = fmt.Errorf("Test after error") 484 _, err = upr.Update(ctx) 485 assert.EqualError(t, err, "Update Error (apply): Test after error") 486 } 487 488 func TestUpdaterNotNeeded(t *testing.T) { 489 testServer := testServerForUpdateFile(t, testZipPath) 490 defer testServer.Close() 491 492 upr, err := newTestUpdaterWithServer(t, testServer, newTestUpdate(testServer.URL, false), &testConfig{}) 493 assert.NoError(t, err) 494 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionSnooze, AutoUpdate: true}) 495 update, err := upr.Update(ctx) 496 assert.NoError(t, err) 497 assert.Nil(t, update) 498 499 assert.False(t, ctx.successReported) 500 assert.Equal(t, "deadbeef", upr.config.GetInstallID()) 501 } 502 503 func TestUpdaterCheckAndUpdate(t *testing.T) { 504 if runtime.GOOS == "windows" { 505 t.Skip("Skipping on windows") 506 } 507 testServer := testServerForUpdateFile(t, testZipPath) 508 defer testServer.Close() 509 510 testUpdate := newTestUpdate(testServer.URL, false) 511 upr, err := newTestUpdaterWithServer(t, testServer, testUpdate, &testConfig{}) 512 assert.NoError(t, err) 513 defer func() { 514 err = upr.CleanupPreviousUpdates() 515 assert.NoError(t, err) 516 }() 517 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionSnooze, AutoUpdate: true}) 518 519 // 1.No update from the server 520 // Need update = false 521 // FindDownloadedAsset = false 522 // return updateAvailable = false, updateWasDownloaded = false 523 t.Logf("No update from the server") 524 updateAvailable, updateWasDownloaded, err := upr.CheckAndDownload(ctx) 525 assert.NoError(t, err) 526 assert.False(t, updateAvailable) 527 assert.False(t, updateWasDownloaded) 528 assert.False(t, ctx.successReported) 529 assert.Equal(t, "deadbeef", upr.config.GetInstallID()) 530 531 // 2. Download asset from URL 532 // Need update = true 533 t.Logf("Download asset from URL") 534 testUpdate.NeedUpdate = true 535 updateAvailable, updateWasDownloaded, err = upr.CheckAndDownload(ctx) 536 assert.NoError(t, err) 537 assert.True(t, updateAvailable) 538 assert.True(t, updateWasDownloaded) 539 assert.False(t, ctx.successReported) 540 assert.Equal(t, "deadbeef", upr.config.GetInstallID()) 541 542 // 3.Find existing downloaded asset 543 // Need update = true 544 // FindDownloadedAsset = true 545 // return updateAvailable = true, updateWasDownloaded = true 546 t.Logf("Find existing downloaded assert") 547 tmpDir := makeKeybaseUpdateTempDir(t, upr, testUpdate.Asset) 548 updateAvailable, updateWasDownloaded, err = upr.CheckAndDownload(ctx) 549 assert.NoError(t, err) 550 assert.True(t, updateAvailable) 551 assert.False(t, updateWasDownloaded) 552 assert.False(t, ctx.successReported) 553 assert.Equal(t, "deadbeef", upr.config.GetInstallID()) 554 555 // Run it again to ensure we don't accidentally download again 556 t.Logf("Find existing downloaded assert (again)") 557 updateAvailable, updateWasDownloaded, err = upr.CheckAndDownload(ctx) 558 assert.NoError(t, err) 559 assert.True(t, updateAvailable) 560 assert.False(t, updateWasDownloaded) 561 assert.False(t, ctx.successReported) 562 assert.Equal(t, "deadbeef", upr.config.GetInstallID()) 563 564 util.RemoveFileAtPath(tmpDir) 565 566 // 4.Verify fails b.c. bit flip 567 // Need update = true 568 // FindDownloadedAsset = true 569 // return updateAvailable = false, updateWasDownloaded = false 570 t.Logf("bit flip failure verify sig") 571 tmpDir = makeKeybaseUpdateTempDir(t, upr, testUpdate.Asset) 572 testUpdate.Asset.Signature = invalidSignature 573 574 updateAvailable, updateWasDownloaded, err = upr.CheckAndDownload(ctx) 575 assert.EqualError(t, err, "Update Error (verify): error verifying signature: failed to read header bytes") 576 assert.False(t, updateAvailable) 577 assert.False(t, updateWasDownloaded) 578 assert.False(t, ctx.successReported) 579 assert.Equal(t, "deadbeef", upr.config.GetInstallID()) 580 581 util.RemoveFileAtPath(tmpDir) 582 testUpdate.Asset.Signature = validSignature 583 584 // 5.Digest fails b.c. bit flip 585 // Need update = true 586 // FindDownloadedAsset = true 587 // return updateAvailable = false, updateWasDownloaded = false 588 t.Logf("bit flip failure verify digest") 589 tmpDir = makeKeybaseUpdateTempDir(t, upr, testUpdate.Asset) 590 testUpdate.Asset.Digest = invalidDigest 591 592 updateAvailable, updateWasDownloaded, err = upr.CheckAndDownload(ctx) 593 assert.EqualError(t, err, fmt.Sprintf("Update Error (verify): Invalid digest: 54970995e4d02da631e0634162ef66e2663e0eee7d018e816ac48ed6f7811c84 != 74970995e4d02da631e0634162ef66e2663e0eee7d018e816ac48ed6f7811c84 (%s)", filepath.Join(tmpDir, testUpdate.Asset.Name))) 594 assert.False(t, updateAvailable) 595 assert.False(t, updateWasDownloaded) 596 assert.False(t, ctx.successReported) 597 assert.Equal(t, "deadbeef", upr.config.GetInstallID()) 598 599 util.RemoveFileAtPath(tmpDir) 600 testUpdate.Asset.Digest = validDigest 601 } 602 603 func TestApplyDownloaded(t *testing.T) { 604 if runtime.GOOS == "windows" { 605 t.Skip("Skipping on windows") 606 } 607 testServer := testServerForUpdateFile(t, testZipPath) 608 defer testServer.Close() 609 610 testUpdate := newTestUpdate(testServer.URL, false) 611 testAsset := *testUpdate.Asset 612 upr, err := newTestUpdaterWithServer(t, testServer, testUpdate, &testConfig{}) 613 assert.NoError(t, err) 614 defer func() { 615 err = upr.CleanupPreviousUpdates() 616 assert.NoError(t, err) 617 }() 618 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionSnooze, AutoUpdate: true}) 619 resetCtxErr := func() { 620 ctx.promptErr = nil 621 ctx.verifyErr = nil 622 ctx.beforeApplyErr = nil 623 ctx.afterApplyErr = nil 624 ctx.errReported = nil 625 } 626 627 // 1. NeedUpdate = false -> return nil 628 applied, err := upr.ApplyDownloaded(ctx) 629 assert.EqualError(t, err, "No previously downloaded update to apply since client is update to date") 630 assert.False(t, applied) 631 assert.NotNil(t, ctx.errReported) 632 assert.Nil(t, ctx.updateReported) 633 assert.False(t, ctx.successReported) 634 635 resetCtxErr() 636 637 // 2. Update missing asset 638 testUpdate.NeedUpdate = true 639 testUpdate.Asset = nil 640 641 applied, err = upr.ApplyDownloaded(ctx) 642 assert.EqualError(t, err, "Update contained no asset to apply. Update version: 1.0.1") 643 assert.False(t, applied) 644 assert.NotNil(t, ctx.errReported) 645 assert.Nil(t, ctx.updateReported) 646 assert.False(t, ctx.successReported) 647 648 resetCtxErr() 649 testUpdate.Asset = &testAsset 650 tempURL := testUpdate.Asset.URL 651 testUpdate.Asset.URL = "" 652 653 applied, err = upr.ApplyDownloaded(ctx) 654 assert.EqualError(t, err, "Update contained no asset to apply. Update version: 1.0.1") 655 assert.False(t, applied) 656 assert.NotNil(t, ctx.errReported) 657 assert.Nil(t, ctx.updateReported) 658 assert.False(t, ctx.successReported) 659 660 resetCtxErr() 661 testUpdate.Asset.URL = tempURL 662 663 // 3. FindDownloadedAsset = false -> return nil 664 applied, err = upr.ApplyDownloaded(ctx) 665 assert.EqualError(t, err, "No downloaded asset found for version: 1.0.1") 666 assert.False(t, applied) 667 assert.NotNil(t, ctx.errReported) 668 assert.Nil(t, ctx.updateReported) 669 assert.False(t, ctx.successReported) 670 671 resetCtxErr() 672 673 // 4. FindDownloadedAsset = true -> digest fails 674 tmpDir := makeKeybaseUpdateTempDir(t, upr, testUpdate.Asset) 675 testUpdate.Asset.Digest = invalidDigest 676 677 applied, err = upr.ApplyDownloaded(ctx) 678 assert.EqualError(t, err, fmt.Sprintf("Update Error (verify): Invalid digest: 54970995e4d02da631e0634162ef66e2663e0eee7d018e816ac48ed6f7811c84 != 74970995e4d02da631e0634162ef66e2663e0eee7d018e816ac48ed6f7811c84 (%s)", filepath.Join(tmpDir, testUpdate.Asset.Name))) 679 assert.False(t, applied) 680 assert.NotNil(t, ctx.errReported) 681 assert.Nil(t, ctx.updateReported) 682 assert.False(t, ctx.successReported) 683 684 resetCtxErr() 685 testUpdate.Asset.Digest = validDigest 686 util.RemoveFileAtPath(tmpDir) 687 688 // 5. FindDownloadedAsset = true -> verify fails 689 tmpDir = makeKeybaseUpdateTempDir(t, upr, testUpdate.Asset) 690 testUpdate.Asset.Signature = invalidSignature 691 692 applied, err = upr.ApplyDownloaded(ctx) 693 assert.EqualError(t, err, "Update Error (verify): error verifying signature: failed to read header bytes") 694 assert.False(t, applied) 695 assert.NotNil(t, ctx.errReported) 696 assert.Nil(t, ctx.updateReported) 697 assert.False(t, ctx.successReported) 698 699 resetCtxErr() 700 testUpdate.Asset.Signature = validSignature 701 util.RemoveFileAtPath(tmpDir) 702 703 // 6. FindDownloadedAsset = true -> no error success 704 tmpDir = makeKeybaseUpdateTempDir(t, upr, testUpdate.Asset) 705 706 applied, err = upr.ApplyDownloaded(ctx) 707 assert.NoError(t, err) 708 assert.True(t, applied) 709 assert.Nil(t, ctx.errReported) 710 assert.NotNil(t, ctx.updateReported) 711 assert.True(t, ctx.successReported) 712 713 resetCtxErr() 714 util.RemoveFileAtPath(tmpDir) 715 } 716 717 func TestFindDownloadedAsset(t *testing.T) { 718 if runtime.GOOS == "windows" { 719 t.Skip("Skipping on windows") 720 } 721 upr, err := newTestUpdater(t) 722 assert.NoError(t, err) 723 defer func() { 724 err = upr.CleanupPreviousUpdates() 725 assert.NoError(t, err) 726 }() 727 728 // 1. empty asset 729 matchingAssetPath, err := upr.FindDownloadedAsset("") 730 assert.EqualError(t, err, "No asset name provided") 731 assert.Equal(t, "", matchingAssetPath) 732 733 // 2. assset given -> did not create KeybaseUpdate. 734 matchingAssetPath, err = upr.FindDownloadedAsset("temp") 735 assert.NoError(t, err) 736 assert.Equal(t, "", matchingAssetPath) 737 738 // 3. asset given -> created KeybaseUpdate. -> directory empty 739 tmpDir, err := util.MakeTempDir("KeybaseUpdater.", 0700) 740 assert.NoError(t, err) 741 require.NoError(t, err) 742 743 matchingAssetPath, err = upr.FindDownloadedAsset("temp") 744 assert.NoError(t, err) 745 assert.Equal(t, "", matchingAssetPath) 746 747 util.RemoveFileAtPath(tmpDir) 748 749 // 4. asset given -> created KeybaseUpdate. -> file exists but no match 750 tmpDir, err = util.MakeTempDir("KeybaseUpdater.", 0700) 751 assert.NoError(t, err) 752 tmpFile := filepath.Join(tmpDir, "nottemp") 753 err = os.WriteFile(tmpFile, []byte("Contents of temp file"), 0700) 754 require.NoError(t, err) 755 756 matchingAssetPath, err = upr.FindDownloadedAsset("temp") 757 assert.NoError(t, err) 758 assert.Equal(t, "", matchingAssetPath) 759 760 util.RemoveFileAtPath(tmpDir) 761 762 // 5. asset given -> created KeybaseUpdate. -> file exixst and matches 763 tmpDir, err = util.MakeTempDir("KeybaseUpdater.", 0700) 764 tmpFile = filepath.Join(tmpDir, "temp") 765 err = os.WriteFile(tmpFile, []byte("Contents of temp file"), 0700) 766 require.NoError(t, err) 767 768 matchingAssetPath, err = upr.FindDownloadedAsset("temp") 769 assert.NoError(t, err) 770 assert.Equal(t, tmpFile, matchingAssetPath) 771 772 util.RemoveFileAtPath(tmpDir) 773 774 } 775 776 func TestUpdaterGuiBusy(t *testing.T) { 777 testServer := testServerForUpdateFile(t, testZipPath) 778 defer testServer.Close() 779 780 upr, err := newTestUpdaterWithServer(t, testServer, testUpdate(testServer.URL), &testConfig{auto: true, autoSet: true}) 781 assert.NoError(t, err) 782 ctx := newTestContext(newDefaultTestUpdateOptions(), upr.config, &UpdatePromptResponse{Action: UpdateActionApply, AutoUpdate: true}) 783 // Expect no error when the app state config is not found, allowing auto update to continue 784 _, err = upr.Update(ctx) 785 assert.NoError(t, err) 786 787 // Now put the config file there and make sure the right error is returned 788 now := time.Now().Unix() * 1000 789 err = os.WriteFile(testAppStatePath, []byte(fmt.Sprintf(`{"isUserActive":true, "changedAtMs":%d}`, now)), 0644) 790 assert.NoError(t, err) 791 defer util.RemoveFileAtPath(testAppStatePath) 792 _, err = upr.Update(ctx) 793 assert.EqualError(t, err, "Update Error (guiBusy): User active, retrying later") 794 795 // If the user was recently active, they are still considered busy. 796 err = os.WriteFile(testAppStatePath, []byte(fmt.Sprintf(`{"isUserActive":false, "changedAtMs":%d}`, now)), 0644) 797 assert.NoError(t, err) 798 _, err = upr.Update(ctx) 799 assert.EqualError(t, err, "Update Error (guiBusy): User active, retrying later") 800 801 // Make sure check command doesn't skip update on active UI 802 ctx.isCheckCommand = true 803 _, err = upr.Update(ctx) 804 assert.NoError(t, err) 805 806 // If the user wasn't recently active, they are not considered busy 807 ctx.isCheckCommand = false 808 later := time.Now().Add(-5*time.Minute).Unix() * 1000 809 err = os.WriteFile(testAppStatePath, []byte(fmt.Sprintf(`{"isUserActive":false, "changedAtMs":%d}`, later)), 0644) 810 assert.NoError(t, err) 811 _, err = upr.Update(ctx) 812 assert.NoError(t, err) 813 }