forked from Silverfish/proton-bridge
334 lines
8.2 KiB
Go
334 lines
8.2 KiB
Go
// 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
|
|
}
|