mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-11 13:16:53 +00:00
GODT-1779: Remove go-imap
This commit is contained in:
@ -17,15 +17,15 @@
|
||||
|
||||
package updater
|
||||
|
||||
// UpdateChannel represents an update channel users can be subscribed to.
|
||||
type UpdateChannel string
|
||||
// Channel represents an update channel users can be subscribed to.
|
||||
type Channel string
|
||||
|
||||
const (
|
||||
// StableChannel is the channel all users are subscribed to by default.
|
||||
StableChannel UpdateChannel = "stable"
|
||||
StableChannel Channel = "stable"
|
||||
|
||||
// EarlyChannel is the channel users subscribe to when they enable "Early Access".
|
||||
EarlyChannel UpdateChannel = "early"
|
||||
EarlyChannel Channel = "early"
|
||||
)
|
||||
|
||||
// DefaultUpdateChannel is the default update channel to subscribe to.
|
||||
@ -1,25 +0,0 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package updater
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrDownloadVerify = errors.New("failed to download or verify the update")
|
||||
ErrInstall = errors.New("failed to install the update")
|
||||
)
|
||||
@ -1,50 +0,0 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package updater
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var ErrOperationOngoing = errors.New("the operation is already ongoing")
|
||||
|
||||
// locker is an easy way to ensure we only perform one update at a time.
|
||||
type locker struct {
|
||||
ongoing atomic.Value
|
||||
}
|
||||
|
||||
func newLocker() *locker {
|
||||
l := &locker{}
|
||||
|
||||
l.ongoing.Store(false)
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *locker) doOnce(fn func() error) error {
|
||||
if l.ongoing.Load().(bool) { //nolint:forcetypeassert
|
||||
return ErrOperationOngoing
|
||||
}
|
||||
|
||||
l.ongoing.Store(true)
|
||||
defer func() { l.ongoing.Store(false) }()
|
||||
|
||||
return fn()
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package updater
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLocker(t *testing.T) {
|
||||
l := newLocker()
|
||||
|
||||
assert.NoError(t, l.doOnce(func() error {
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
func TestLockerForwardsErrors(t *testing.T) {
|
||||
l := newLocker()
|
||||
|
||||
assert.Error(t, l.doOnce(func() error {
|
||||
return errors.New("something went wrong")
|
||||
}))
|
||||
}
|
||||
|
||||
func TestLockerAllowsOnlyOneOperation(t *testing.T) {
|
||||
l := newLocker()
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
assert.NoError(t, l.doOnce(func() error {
|
||||
time.Sleep(2 * time.Second)
|
||||
wg.Done()
|
||||
return nil
|
||||
}))
|
||||
}()
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err := l.doOnce(func() error { return nil })
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, ErrOperationOngoing, err)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
90
internal/updater/mocks/mocks.go
Normal file
90
internal/updater/mocks/mocks.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/ProtonMail/proton-bridge/v2/internal/updater (interfaces: Downloader,Installer)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
io "io"
|
||||
reflect "reflect"
|
||||
|
||||
semver "github.com/Masterminds/semver/v3"
|
||||
crypto "github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockDownloader is a mock of Downloader interface.
|
||||
type MockDownloader struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockDownloaderMockRecorder
|
||||
}
|
||||
|
||||
// MockDownloaderMockRecorder is the mock recorder for MockDownloader.
|
||||
type MockDownloaderMockRecorder struct {
|
||||
mock *MockDownloader
|
||||
}
|
||||
|
||||
// NewMockDownloader creates a new mock instance.
|
||||
func NewMockDownloader(ctrl *gomock.Controller) *MockDownloader {
|
||||
mock := &MockDownloader{ctrl: ctrl}
|
||||
mock.recorder = &MockDownloaderMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockDownloader) EXPECT() *MockDownloaderMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// DownloadAndVerify mocks base method.
|
||||
func (m *MockDownloader) DownloadAndVerify(arg0 context.Context, arg1 *crypto.KeyRing, arg2, arg3 string) ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DownloadAndVerify", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DownloadAndVerify indicates an expected call of DownloadAndVerify.
|
||||
func (mr *MockDownloaderMockRecorder) DownloadAndVerify(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadAndVerify", reflect.TypeOf((*MockDownloader)(nil).DownloadAndVerify), arg0, arg1, arg2, arg3)
|
||||
}
|
||||
|
||||
// MockInstaller is a mock of Installer interface.
|
||||
type MockInstaller struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockInstallerMockRecorder
|
||||
}
|
||||
|
||||
// MockInstallerMockRecorder is the mock recorder for MockInstaller.
|
||||
type MockInstallerMockRecorder struct {
|
||||
mock *MockInstaller
|
||||
}
|
||||
|
||||
// NewMockInstaller creates a new mock instance.
|
||||
func NewMockInstaller(ctrl *gomock.Controller) *MockInstaller {
|
||||
mock := &MockInstaller{ctrl: ctrl}
|
||||
mock.recorder = &MockInstallerMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockInstaller) EXPECT() *MockInstallerMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// InstallUpdate mocks base method.
|
||||
func (m *MockInstaller) InstallUpdate(arg0 *semver.Version, arg1 io.Reader) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InstallUpdate", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// InstallUpdate indicates an expected call of InstallUpdate.
|
||||
func (mr *MockInstallerMockRecorder) InstallUpdate(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InstallUpdate", reflect.TypeOf((*MockInstaller)(nil).InstallUpdate), arg0, arg1)
|
||||
}
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@ -87,7 +88,7 @@ func removeMissing(folderToCleanPath, itemsToKeepPath string) (err error) {
|
||||
}
|
||||
|
||||
for _, removeThis := range delList {
|
||||
if err = os.RemoveAll(removeThis); err != nil && !os.IsNotExist(err) {
|
||||
if err = os.RemoveAll(removeThis); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
logrus.Error("remove error ", err)
|
||||
return
|
||||
}
|
||||
@ -195,7 +196,7 @@ func copyRecursively(srcDir, dstDir string) error { //nolint:funlen
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
} else if !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@ -1,91 +1,50 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package updater
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var ErrManualUpdateRequired = errors.New("manual update is required")
|
||||
var (
|
||||
ErrDownloadVerify = errors.New("failed to download or verify the update")
|
||||
ErrInstall = errors.New("failed to install the update")
|
||||
)
|
||||
|
||||
type Downloader interface {
|
||||
DownloadAndVerify(ctx context.Context, kr *crypto.KeyRing, url, sig string) ([]byte, error)
|
||||
}
|
||||
|
||||
type Installer interface {
|
||||
InstallUpdate(*semver.Version, io.Reader) error
|
||||
}
|
||||
|
||||
type Settings interface {
|
||||
Get(settings.Key) string
|
||||
Set(settings.Key, string)
|
||||
GetFloat64(settings.Key) float64
|
||||
}
|
||||
|
||||
type Updater struct {
|
||||
cm pmapi.Manager
|
||||
installer Installer
|
||||
settings Settings
|
||||
kr *crypto.KeyRing
|
||||
|
||||
curVer *semver.Version
|
||||
updateURLName string
|
||||
platform string
|
||||
|
||||
locker *locker
|
||||
verifier *crypto.KeyRing
|
||||
product string
|
||||
platform string
|
||||
}
|
||||
|
||||
func New(
|
||||
cm pmapi.Manager,
|
||||
installer Installer,
|
||||
s Settings,
|
||||
kr *crypto.KeyRing,
|
||||
curVer *semver.Version,
|
||||
updateURLName, platform string,
|
||||
) *Updater {
|
||||
// If there's some unexpected value in the preferences, we force it back onto the default channel.
|
||||
// This prevents users from screwing up silent updates by modifying their prefs.json file.
|
||||
if channel := UpdateChannel(s.Get(settings.UpdateChannelKey)); !(channel == StableChannel || channel == EarlyChannel) {
|
||||
s.Set(settings.UpdateChannelKey, string(DefaultUpdateChannel))
|
||||
}
|
||||
|
||||
func NewUpdater(installer Installer, verifier *crypto.KeyRing, product, platform string) *Updater {
|
||||
return &Updater{
|
||||
cm: cm,
|
||||
installer: installer,
|
||||
settings: s,
|
||||
kr: kr,
|
||||
curVer: curVer,
|
||||
updateURLName: updateURLName,
|
||||
platform: platform,
|
||||
locker: newLocker(),
|
||||
installer: installer,
|
||||
verifier: verifier,
|
||||
product: product,
|
||||
platform: platform,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updater) Check() (VersionInfo, error) {
|
||||
logrus.Info("Checking for updates")
|
||||
|
||||
b, err := u.cm.DownloadAndVerify(
|
||||
u.kr,
|
||||
func (u *Updater) GetVersionInfo(downloader Downloader, channel Channel) (VersionInfo, error) {
|
||||
b, err := downloader.DownloadAndVerify(
|
||||
context.Background(),
|
||||
u.verifier,
|
||||
u.getVersionFileURL(),
|
||||
u.getVersionFileURL()+".sig",
|
||||
)
|
||||
@ -99,7 +58,7 @@ func (u *Updater) Check() (VersionInfo, error) {
|
||||
return VersionInfo{}, err
|
||||
}
|
||||
|
||||
version, ok := versionMap[u.settings.Get(settings.UpdateChannelKey)]
|
||||
version, ok := versionMap[channel]
|
||||
if !ok {
|
||||
return VersionInfo{}, errors.New("no updates available for this channel")
|
||||
}
|
||||
@ -107,45 +66,27 @@ func (u *Updater) Check() (VersionInfo, error) {
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (u *Updater) IsUpdateApplicable(version VersionInfo) bool {
|
||||
if !version.Version.GreaterThan(u.curVer) {
|
||||
return false
|
||||
func (u *Updater) InstallUpdate(downloader Downloader, update VersionInfo) error {
|
||||
b, err := downloader.DownloadAndVerify(
|
||||
context.Background(),
|
||||
u.verifier,
|
||||
update.Package,
|
||||
update.Package+".sig",
|
||||
)
|
||||
if err != nil {
|
||||
return ErrDownloadVerify
|
||||
}
|
||||
|
||||
if u.settings.GetFloat64(settings.RolloutKey) > version.RolloutProportion {
|
||||
return false
|
||||
if err := u.installer.InstallUpdate(update.Version, bytes.NewReader(b)); err != nil {
|
||||
return ErrInstall
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Updater) IsDowngrade(version VersionInfo) bool {
|
||||
return version.Version.LessThan(u.curVer)
|
||||
}
|
||||
|
||||
func (u *Updater) CanInstall(version VersionInfo) bool {
|
||||
if version.MinAuto == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return !u.curVer.LessThan(version.MinAuto)
|
||||
}
|
||||
|
||||
func (u *Updater) InstallUpdate(update VersionInfo) error {
|
||||
return u.locker.doOnce(func() error {
|
||||
logrus.WithField("package", update.Package).Info("Installing update package")
|
||||
|
||||
b, err := u.cm.DownloadAndVerify(u.kr, update.Package, update.Package+".sig")
|
||||
if err != nil {
|
||||
return errors.Wrap(ErrDownloadVerify, err.Error())
|
||||
}
|
||||
|
||||
if err := u.installer.InstallUpdate(update.Version, bytes.NewReader(b)); err != nil {
|
||||
return errors.Wrap(ErrInstall, err.Error())
|
||||
}
|
||||
|
||||
u.curVer = update.Version
|
||||
|
||||
return nil
|
||||
})
|
||||
// getVersionFileURL returns the URL of the version file.
|
||||
// For example:
|
||||
// - https://protonmail.com/download/bridge/version_linux.json
|
||||
func (u *Updater) getVersionFileURL() string {
|
||||
return fmt.Sprintf("%v/%v/version_%v.json", Host, u.product, u.platform)
|
||||
}
|
||||
|
||||
@ -1,333 +0,0 @@
|
||||
// Copyright (c) 2022 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package updater
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi/mocks"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
c := gomock.NewController(t)
|
||||
defer c.Finish()
|
||||
|
||||
cm := mocks.NewMockManager(c)
|
||||
|
||||
updater := newTestUpdater(cm, "1.1.0", false)
|
||||
|
||||
versionMap := VersionMap{
|
||||
"stable": VersionInfo{
|
||||
Version: semver.MustParse("1.5.0"),
|
||||
MinAuto: semver.MustParse("1.4.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.5.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
cm.EXPECT().DownloadAndVerify(
|
||||
gomock.Any(),
|
||||
updater.getVersionFileURL(),
|
||||
updater.getVersionFileURL()+".sig",
|
||||
).Return(mustMarshal(t, versionMap), nil)
|
||||
|
||||
version, err := updater.Check()
|
||||
|
||||
assert.Equal(t, semver.MustParse("1.5.0"), version.Version)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCheckEarlyAccess(t *testing.T) {
|
||||
c := gomock.NewController(t)
|
||||
defer c.Finish()
|
||||
|
||||
cm := mocks.NewMockManager(c)
|
||||
|
||||
updater := newTestUpdater(cm, "1.1.0", true)
|
||||
|
||||
versionMap := VersionMap{
|
||||
"stable": VersionInfo{
|
||||
Version: semver.MustParse("1.5.0"),
|
||||
MinAuto: semver.MustParse("1.0.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.5.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
},
|
||||
"early": VersionInfo{
|
||||
Version: semver.MustParse("1.6.0"),
|
||||
MinAuto: semver.MustParse("1.0.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.6.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
},
|
||||
}
|
||||
|
||||
cm.EXPECT().DownloadAndVerify(
|
||||
gomock.Any(),
|
||||
updater.getVersionFileURL(),
|
||||
updater.getVersionFileURL()+".sig",
|
||||
).Return(mustMarshal(t, versionMap), nil)
|
||||
|
||||
version, err := updater.Check()
|
||||
|
||||
assert.Equal(t, semver.MustParse("1.6.0"), version.Version)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCheckBadSignature(t *testing.T) {
|
||||
c := gomock.NewController(t)
|
||||
defer c.Finish()
|
||||
|
||||
cm := mocks.NewMockManager(c)
|
||||
|
||||
updater := newTestUpdater(cm, "1.2.0", false)
|
||||
|
||||
cm.EXPECT().DownloadAndVerify(
|
||||
gomock.Any(),
|
||||
updater.getVersionFileURL(),
|
||||
updater.getVersionFileURL()+".sig",
|
||||
).Return(nil, errors.New("bad signature"))
|
||||
|
||||
_, err := updater.Check()
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIsUpdateApplicable(t *testing.T) {
|
||||
c := gomock.NewController(t)
|
||||
defer c.Finish()
|
||||
|
||||
cm := mocks.NewMockManager(c)
|
||||
|
||||
updater := newTestUpdater(cm, "1.4.0", false)
|
||||
|
||||
versionOld := VersionInfo{
|
||||
Version: semver.MustParse("1.3.0"),
|
||||
MinAuto: semver.MustParse("1.3.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.3.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
}
|
||||
|
||||
assert.Equal(t, false, updater.IsUpdateApplicable(versionOld))
|
||||
|
||||
versionEqual := VersionInfo{
|
||||
Version: semver.MustParse("1.4.0"),
|
||||
MinAuto: semver.MustParse("1.3.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.4.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
}
|
||||
|
||||
assert.Equal(t, false, updater.IsUpdateApplicable(versionEqual))
|
||||
|
||||
versionNew := VersionInfo{
|
||||
Version: semver.MustParse("1.5.0"),
|
||||
MinAuto: semver.MustParse("1.3.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.5.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
}
|
||||
|
||||
assert.Equal(t, true, updater.IsUpdateApplicable(versionNew))
|
||||
}
|
||||
|
||||
func TestCanInstall(t *testing.T) {
|
||||
c := gomock.NewController(t)
|
||||
defer c.Finish()
|
||||
|
||||
cm := mocks.NewMockManager(c)
|
||||
|
||||
updater := newTestUpdater(cm, "1.4.0", false)
|
||||
|
||||
versionManual := VersionInfo{
|
||||
Version: semver.MustParse("1.5.0"),
|
||||
MinAuto: semver.MustParse("1.5.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.5.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
}
|
||||
|
||||
assert.Equal(t, false, updater.CanInstall(versionManual))
|
||||
|
||||
versionAuto := VersionInfo{
|
||||
Version: semver.MustParse("1.5.0"),
|
||||
MinAuto: semver.MustParse("1.3.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.5.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
}
|
||||
|
||||
assert.Equal(t, true, updater.CanInstall(versionAuto))
|
||||
}
|
||||
|
||||
func TestInstallUpdate(t *testing.T) {
|
||||
c := gomock.NewController(t)
|
||||
defer c.Finish()
|
||||
|
||||
cm := mocks.NewMockManager(c)
|
||||
|
||||
updater := newTestUpdater(cm, "1.4.0", false)
|
||||
|
||||
latestVersion := VersionInfo{
|
||||
Version: semver.MustParse("1.5.0"),
|
||||
MinAuto: semver.MustParse("1.4.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.5.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
}
|
||||
|
||||
cm.EXPECT().DownloadAndVerify(
|
||||
gomock.Any(),
|
||||
latestVersion.Package,
|
||||
latestVersion.Package+".sig",
|
||||
).Return([]byte("tgz_data_here"), nil)
|
||||
|
||||
err := updater.InstallUpdate(latestVersion)
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestInstallUpdateBadSignature(t *testing.T) {
|
||||
c := gomock.NewController(t)
|
||||
defer c.Finish()
|
||||
|
||||
cm := mocks.NewMockManager(c)
|
||||
|
||||
updater := newTestUpdater(cm, "1.4.0", false)
|
||||
|
||||
latestVersion := VersionInfo{
|
||||
Version: semver.MustParse("1.5.0"),
|
||||
MinAuto: semver.MustParse("1.4.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.5.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
}
|
||||
|
||||
cm.EXPECT().DownloadAndVerify(
|
||||
gomock.Any(),
|
||||
latestVersion.Package,
|
||||
latestVersion.Package+".sig",
|
||||
).Return(nil, errors.New("bad signature"))
|
||||
|
||||
err := updater.InstallUpdate(latestVersion)
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestInstallUpdateAlreadyOngoing(t *testing.T) {
|
||||
c := gomock.NewController(t)
|
||||
defer c.Finish()
|
||||
|
||||
cm := mocks.NewMockManager(c)
|
||||
|
||||
updater := newTestUpdater(cm, "1.4.0", false)
|
||||
|
||||
updater.installer = &fakeInstaller{delay: 2 * time.Second}
|
||||
|
||||
latestVersion := VersionInfo{
|
||||
Version: semver.MustParse("1.5.0"),
|
||||
MinAuto: semver.MustParse("1.4.0"),
|
||||
Package: "https://protonmail.com/download/bridge/update_1.5.0_linux.tgz",
|
||||
RolloutProportion: 1.0,
|
||||
}
|
||||
|
||||
cm.EXPECT().DownloadAndVerify(
|
||||
gomock.Any(),
|
||||
latestVersion.Package,
|
||||
latestVersion.Package+".sig",
|
||||
).Return([]byte("tgz_data_here"), nil)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
assert.NoError(t, updater.InstallUpdate(latestVersion))
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// Wait for the installation to begin.
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err := updater.InstallUpdate(latestVersion)
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, ErrOperationOngoing, err)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func newTestUpdater(manager pmapi.Manager, curVer string, earlyAccess bool) *Updater {
|
||||
return New(
|
||||
manager,
|
||||
&fakeInstaller{},
|
||||
newFakeSettings(0.5, earlyAccess),
|
||||
nil,
|
||||
semver.MustParse(curVer),
|
||||
"bridge", "linux",
|
||||
)
|
||||
}
|
||||
|
||||
type fakeInstaller struct {
|
||||
bad bool
|
||||
delay time.Duration
|
||||
}
|
||||
|
||||
func (i *fakeInstaller) InstallUpdate(version *semver.Version, r io.Reader) error {
|
||||
if i.bad {
|
||||
return errors.New("bad install")
|
||||
}
|
||||
|
||||
time.Sleep(i.delay)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustMarshal(t *testing.T, v interface{}) []byte {
|
||||
b, err := json.Marshal(v)
|
||||
require.NoError(t, err)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
type fakeSettings struct {
|
||||
*settings.Settings
|
||||
}
|
||||
|
||||
// newFakeSettings creates a temporary folder for files.
|
||||
func newFakeSettings(rollout float64, earlyAccess bool) *fakeSettings {
|
||||
dir, err := os.MkdirTemp("", "test-settings")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
s := &fakeSettings{Settings: settings.New(dir)}
|
||||
|
||||
s.SetFloat64(settings.RolloutKey, rollout)
|
||||
|
||||
if earlyAccess {
|
||||
s.Set(settings.UpdateChannelKey, string(EarlyChannel))
|
||||
} else {
|
||||
s.Set(settings.UpdateChannelKey, string(StableChannel))
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
@ -18,8 +18,6 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
)
|
||||
|
||||
@ -80,12 +78,5 @@ type VersionInfo struct {
|
||||
// ...
|
||||
// }
|
||||
// }.
|
||||
type VersionMap map[string]VersionInfo
|
||||
|
||||
// getVersionFileURL returns the URL of the version file.
|
||||
// For example:
|
||||
// - https://proton.me/download/bridge/version_linux.json
|
||||
// - https://proton.me/download/ie/version_linux.json
|
||||
func (u *Updater) getVersionFileURL() string {
|
||||
return fmt.Sprintf("%v/%v/version_%v.json", Host, u.updateURLName, u.platform)
|
||||
}
|
||||
type VersionMap map[Channel]VersionInfo
|
||||
|
||||
Reference in New Issue
Block a user