update_service_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. //go:build unit
  2. package service
  3. import (
  4. "context"
  5. "errors"
  6. "testing"
  7. "time"
  8. "github.com/stretchr/testify/require"
  9. )
  10. type updateServiceCacheStub struct {
  11. data string
  12. }
  13. func (s *updateServiceCacheStub) GetUpdateInfo(context.Context) (string, error) {
  14. if s.data == "" {
  15. return "", errors.New("cache miss")
  16. }
  17. return s.data, nil
  18. }
  19. func (s *updateServiceCacheStub) SetUpdateInfo(_ context.Context, data string, _ time.Duration) error {
  20. s.data = data
  21. return nil
  22. }
  23. type updateServiceGitHubClientStub struct {
  24. release *GitHubRelease
  25. recentReleases []*GitHubRelease
  26. recentErr error
  27. }
  28. func (s *updateServiceGitHubClientStub) FetchLatestRelease(context.Context, string) (*GitHubRelease, error) {
  29. return s.release, nil
  30. }
  31. func (s *updateServiceGitHubClientStub) FetchRecentReleases(context.Context, string, int) ([]*GitHubRelease, error) {
  32. return s.recentReleases, s.recentErr
  33. }
  34. func (s *updateServiceGitHubClientStub) DownloadFile(context.Context, string, string, int64) error {
  35. panic("DownloadFile should not be called when no update is available")
  36. }
  37. func (s *updateServiceGitHubClientStub) FetchChecksumFile(context.Context, string) ([]byte, error) {
  38. panic("FetchChecksumFile should not be called when no update is available")
  39. }
  40. func TestUpdateServiceDistributionDisablesUpstreamMutation(t *testing.T) {
  41. svc := NewUpdateService(
  42. &updateServiceCacheStub{},
  43. &updateServiceGitHubClientStub{
  44. release: &GitHubRelease{
  45. TagName: "v0.1.132",
  46. Name: "v0.1.132",
  47. },
  48. },
  49. "0.1.132",
  50. "release",
  51. )
  52. err := svc.PerformUpdate(context.Background())
  53. require.Error(t, err)
  54. require.ErrorIs(t, err, ErrManagedUpdateRequired)
  55. require.ErrorIs(t, svc.Rollback(), ErrManagedUpdateRequired)
  56. require.ErrorIs(t, svc.RollbackToVersion(context.Background(), "0.1.100"), ErrManagedUpdateRequired)
  57. info, err := svc.CheckUpdate(context.Background(), true)
  58. require.NoError(t, err)
  59. require.False(t, info.HasUpdate)
  60. require.Equal(t, "source", info.BuildType)
  61. }
  62. func newRollbackTestService(current string, releases []*GitHubRelease) *UpdateService {
  63. svc := NewUpdateService(
  64. &updateServiceCacheStub{},
  65. &updateServiceGitHubClientStub{recentReleases: releases},
  66. current,
  67. "release",
  68. )
  69. // Exercise legacy version-selection helpers without enabling production updates.
  70. svc.inPlaceUpdatesEnabled = true
  71. return svc
  72. }
  73. func TestUpdateServiceListRollbackVersionsFiltersAndCaps(t *testing.T) {
  74. releases := []*GitHubRelease{
  75. {TagName: "v0.1.148", PublishedAt: "2026-07-09T00:00:00Z"}, // newer than current: excluded
  76. {TagName: "v0.1.147", PublishedAt: "2026-07-08T00:00:00Z"}, // current: excluded
  77. {TagName: "v0.1.146-rc1", PublishedAt: "2026-07-07T12:00:00Z", Prerelease: true}, // prerelease: excluded
  78. {TagName: "v0.1.146", PublishedAt: "2026-07-07T00:00:00Z"},
  79. {TagName: "v0.1.145", PublishedAt: "2026-07-06T00:00:00Z", Draft: true}, // draft: excluded
  80. {TagName: "v0.1.144", PublishedAt: "2026-07-05T00:00:00Z"},
  81. {TagName: "v0.1.144", PublishedAt: "2026-07-05T00:00:00Z"}, // duplicate: excluded
  82. {TagName: "v0.1.143", PublishedAt: "2026-07-04T00:00:00Z"},
  83. {TagName: "v0.1.142", PublishedAt: "2026-07-03T00:00:00Z"}, // beyond cap of 3: excluded
  84. }
  85. svc := newRollbackTestService("0.1.147", releases)
  86. versions, err := svc.ListRollbackVersions(context.Background())
  87. require.NoError(t, err)
  88. require.Len(t, versions, 3)
  89. require.Equal(t, "0.1.146", versions[0].Version)
  90. require.Equal(t, "0.1.144", versions[1].Version)
  91. require.Equal(t, "0.1.143", versions[2].Version)
  92. }
  93. func TestUpdateServiceListRollbackVersionsSortsUnorderedInput(t *testing.T) {
  94. releases := []*GitHubRelease{
  95. {TagName: "v0.1.144"},
  96. {TagName: "v0.1.146"},
  97. {TagName: "v0.1.145"},
  98. }
  99. svc := newRollbackTestService("0.1.147", releases)
  100. versions, err := svc.ListRollbackVersions(context.Background())
  101. require.NoError(t, err)
  102. require.Len(t, versions, 3)
  103. require.Equal(t, "0.1.146", versions[0].Version)
  104. require.Equal(t, "0.1.145", versions[1].Version)
  105. require.Equal(t, "0.1.144", versions[2].Version)
  106. }
  107. func TestUpdateServiceListRollbackVersionsEmptyWhenNoneOlder(t *testing.T) {
  108. releases := []*GitHubRelease{
  109. {TagName: "v0.1.147"},
  110. {TagName: "v0.1.148"},
  111. }
  112. svc := newRollbackTestService("0.1.147", releases)
  113. versions, err := svc.ListRollbackVersions(context.Background())
  114. require.NoError(t, err)
  115. require.Empty(t, versions)
  116. }
  117. func TestUpdateServiceListRollbackVersionsPropagatesFetchError(t *testing.T) {
  118. svc := NewUpdateService(
  119. &updateServiceCacheStub{},
  120. &updateServiceGitHubClientStub{recentErr: errors.New("github unavailable")},
  121. "0.1.147",
  122. "release",
  123. )
  124. svc.inPlaceUpdatesEnabled = true
  125. _, err := svc.ListRollbackVersions(context.Background())
  126. require.Error(t, err)
  127. require.Contains(t, err.Error(), "github unavailable")
  128. }
  129. func TestUpdateServiceRollbackToVersionRejectsDisallowedTargets(t *testing.T) {
  130. releases := []*GitHubRelease{
  131. {TagName: "v0.1.148"},
  132. {TagName: "v0.1.147"},
  133. {TagName: "v0.1.146"},
  134. {TagName: "v0.1.145"},
  135. {TagName: "v0.1.144"},
  136. {TagName: "v0.1.143"},
  137. {TagName: "v0.1.142"},
  138. }
  139. svc := newRollbackTestService("0.1.147", releases)
  140. for _, target := range []string{
  141. "", // empty
  142. "0.1.147", // current version
  143. "v0.1.147", // current version with prefix
  144. "0.1.148", // newer than current
  145. "0.1.142", // older than the 3 most recent
  146. "9.9.9", // nonexistent
  147. } {
  148. err := svc.RollbackToVersion(context.Background(), target)
  149. require.ErrorIs(t, err, ErrRollbackVersionNotAllowed, "target %q should be rejected", target)
  150. }
  151. }
  152. func TestUpdateServiceRollbackToVersionAcceptsVPrefix(t *testing.T) {
  153. // No platform asset in the release: the target passes the allowlist check
  154. // and fails later at asset lookup, proving the version itself was accepted.
  155. releases := []*GitHubRelease{
  156. {TagName: "v0.1.147"},
  157. {TagName: "v0.1.146"},
  158. }
  159. svc := newRollbackTestService("0.1.147", releases)
  160. err := svc.RollbackToVersion(context.Background(), "v0.1.146")
  161. require.Error(t, err)
  162. require.NotErrorIs(t, err, ErrRollbackVersionNotAllowed)
  163. require.Contains(t, err.Error(), "no compatible release found")
  164. }