You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
354 lines
8.7 KiB
354 lines
8.7 KiB
// Copyright (c) 2021 Proton Technologies AG |
|
// |
|
// This file is part of ProtonMail Bridge. |
|
// |
|
// ProtonMail 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. |
|
// |
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. |
|
|
|
package updater |
|
|
|
import ( |
|
"bytes" |
|
"encoding/json" |
|
"errors" |
|
"io" |
|
"io/ioutil" |
|
"sync" |
|
"testing" |
|
"time" |
|
|
|
"github.com/Masterminds/semver/v3" |
|
"github.com/ProtonMail/proton-bridge/internal/config/settings" |
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi" |
|
"github.com/ProtonMail/proton-bridge/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() |
|
|
|
client := mocks.NewMockClient(c) |
|
|
|
updater := newTestUpdater(client, "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, |
|
}, |
|
} |
|
|
|
client.EXPECT().DownloadAndVerify( |
|
updater.getVersionFileURL(), |
|
updater.getVersionFileURL()+".sig", |
|
gomock.Any(), |
|
).Return(bytes.NewReader(mustMarshal(t, versionMap)), nil) |
|
|
|
client.EXPECT().Logout() |
|
|
|
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() |
|
|
|
client := mocks.NewMockClient(c) |
|
|
|
updater := newTestUpdater(client, "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, |
|
}, |
|
} |
|
|
|
client.EXPECT().DownloadAndVerify( |
|
updater.getVersionFileURL(), |
|
updater.getVersionFileURL()+".sig", |
|
gomock.Any(), |
|
).Return(bytes.NewReader(mustMarshal(t, versionMap)), nil) |
|
|
|
client.EXPECT().Logout() |
|
|
|
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() |
|
|
|
client := mocks.NewMockClient(c) |
|
|
|
updater := newTestUpdater(client, "1.2.0", false) |
|
|
|
client.EXPECT().DownloadAndVerify( |
|
updater.getVersionFileURL(), |
|
updater.getVersionFileURL()+".sig", |
|
gomock.Any(), |
|
).Return(nil, errors.New("bad signature")) |
|
|
|
client.EXPECT().Logout() |
|
|
|
_, err := updater.Check() |
|
|
|
assert.Error(t, err) |
|
} |
|
|
|
func TestIsUpdateApplicable(t *testing.T) { |
|
c := gomock.NewController(t) |
|
defer c.Finish() |
|
|
|
client := mocks.NewMockClient(c) |
|
|
|
updater := newTestUpdater(client, "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() |
|
|
|
client := mocks.NewMockClient(c) |
|
|
|
updater := newTestUpdater(client, "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() |
|
|
|
client := mocks.NewMockClient(c) |
|
|
|
updater := newTestUpdater(client, "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, |
|
} |
|
|
|
client.EXPECT().DownloadAndVerify( |
|
latestVersion.Package, |
|
latestVersion.Package+".sig", |
|
gomock.Any(), |
|
).Return(bytes.NewReader([]byte("tgz_data_here")), nil) |
|
|
|
client.EXPECT().Logout() |
|
|
|
err := updater.InstallUpdate(latestVersion) |
|
|
|
assert.NoError(t, err) |
|
} |
|
|
|
func TestInstallUpdateBadSignature(t *testing.T) { |
|
c := gomock.NewController(t) |
|
defer c.Finish() |
|
|
|
client := mocks.NewMockClient(c) |
|
|
|
updater := newTestUpdater(client, "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, |
|
} |
|
|
|
client.EXPECT().DownloadAndVerify( |
|
latestVersion.Package, |
|
latestVersion.Package+".sig", |
|
gomock.Any(), |
|
).Return(nil, errors.New("bad signature")) |
|
|
|
client.EXPECT().Logout() |
|
|
|
err := updater.InstallUpdate(latestVersion) |
|
|
|
assert.Error(t, err) |
|
} |
|
|
|
func TestInstallUpdateAlreadyOngoing(t *testing.T) { |
|
c := gomock.NewController(t) |
|
defer c.Finish() |
|
|
|
client := mocks.NewMockClient(c) |
|
|
|
updater := newTestUpdater(client, "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, |
|
} |
|
|
|
client.EXPECT().DownloadAndVerify( |
|
latestVersion.Package, |
|
latestVersion.Package+".sig", |
|
gomock.Any(), |
|
).Return(bytes.NewReader([]byte("tgz_data_here")), nil) |
|
|
|
client.EXPECT().Logout() |
|
|
|
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(client *mocks.MockClient, curVer string, earlyAccess bool) *Updater { |
|
return New( |
|
&fakeClientProvider{client: client}, |
|
&fakeInstaller{}, |
|
newFakeSettings(0.5, earlyAccess), |
|
nil, |
|
semver.MustParse(curVer), |
|
"bridge", "linux", |
|
) |
|
} |
|
|
|
type fakeClientProvider struct { |
|
client *mocks.MockClient |
|
} |
|
|
|
func (p *fakeClientProvider) GetAnonymousClient() pmapi.Client { |
|
return p.client |
|
} |
|
|
|
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 := ioutil.TempDir("", "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 |
|
}
|
|
|