469 lines
14 KiB
Go
469 lines
14 KiB
Go
package plugins
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1"
|
|
)
|
|
|
|
func TestAutoUpdateServiceCheckReplacesInstalledPluginsInPlace(t *testing.T) {
|
|
installations := &fakeAutoUpdateInstallations{
|
|
list: []*Installation{
|
|
{ID: 41, RepositoryID: pluginRepositoryID(7), PluginID: "silo.tmdb", Version: "1.0.0", UpdatePolicy: "auto", Enabled: true},
|
|
},
|
|
}
|
|
host := &fakeAutoUpdateHost{}
|
|
installer := &fakeAutoUpdateInstaller{}
|
|
catalog := &fakeAutoUpdateCatalog{
|
|
entries: []CatalogEntry{
|
|
{
|
|
RepositoryID: 7,
|
|
Manifest: &pluginv1.PluginManifest{
|
|
PluginId: "silo.tmdb",
|
|
Version: "1.1.0",
|
|
},
|
|
},
|
|
},
|
|
resolved: &ResolvedCatalogInstall{
|
|
RepositoryID: 7,
|
|
ArchiveURL: "https://plugins.example.test/tmdb",
|
|
Checksum: "deadbeef",
|
|
},
|
|
}
|
|
service := NewAutoUpdateService(
|
|
&fakeAutoUpdateRepositories{list: []*Repository{{ID: 7, Enabled: true}}},
|
|
installations,
|
|
catalog,
|
|
installer,
|
|
host,
|
|
nil,
|
|
nil,
|
|
)
|
|
|
|
summary, err := service.Check(context.Background(), AutoUpdateOptions{
|
|
SeedDefaultRepository: true,
|
|
AutoInstallDefaults: false,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Check() returned error: %v", err)
|
|
}
|
|
if summary.UpdatesApplied != 1 {
|
|
t.Fatalf("UpdatesApplied = %d, want 1", summary.UpdatesApplied)
|
|
}
|
|
if len(installations.deletedIDs) != 0 {
|
|
t.Fatalf("deleted installation IDs = %#v, want none", installations.deletedIDs)
|
|
}
|
|
if len(installer.replaceBinary) != 1 {
|
|
t.Fatalf("replace binary calls = %d, want 1", len(installer.replaceBinary))
|
|
}
|
|
if installer.replaceBinary[0].existingID != 41 {
|
|
t.Fatalf("replace binary existing_id = %d, want 41", installer.replaceBinary[0].existingID)
|
|
}
|
|
if len(host.stopped) != 1 || host.stopped[0] != 41 {
|
|
t.Fatalf("stopped installations = %#v, want [41]", host.stopped)
|
|
}
|
|
}
|
|
|
|
func TestAutoUpdateServiceCheckMatchesRenamedRepositoryByStablePluginIdentity(t *testing.T) {
|
|
installations := &fakeAutoUpdateInstallations{
|
|
list: []*Installation{
|
|
{ID: 41, RepositoryID: pluginRepositoryID(7), PluginID: "silo.tmdb", Version: "1.0.0", UpdatePolicy: "auto", Enabled: true},
|
|
},
|
|
}
|
|
installer := &fakeAutoUpdateInstaller{}
|
|
catalog := &fakeAutoUpdateCatalog{
|
|
entries: []CatalogEntry{
|
|
{
|
|
RepositoryID: 7,
|
|
RepoURL: "https://github.com/Silo-Server/silo-plugin-metadata-tmdb",
|
|
Manifest: &pluginv1.PluginManifest{
|
|
PluginId: "silo.tmdb",
|
|
Version: "1.1.0",
|
|
},
|
|
},
|
|
},
|
|
resolved: &ResolvedCatalogInstall{
|
|
RepositoryID: 7,
|
|
ArchiveURL: "https://github.com/Silo-Server/silo-plugin-metadata-tmdb/releases/download/v1.1.0/plugin-linux-amd64",
|
|
Checksum: "deadbeef",
|
|
},
|
|
}
|
|
service := NewAutoUpdateService(
|
|
&fakeAutoUpdateRepositories{list: []*Repository{{
|
|
ID: 7,
|
|
URL: DefaultRepositoryURL,
|
|
Enabled: true,
|
|
}}},
|
|
installations,
|
|
catalog,
|
|
installer,
|
|
&fakeAutoUpdateHost{},
|
|
nil,
|
|
nil,
|
|
)
|
|
|
|
summary, err := service.Check(context.Background(), AutoUpdateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("Check() returned error: %v", err)
|
|
}
|
|
if summary.UpdatesApplied != 1 {
|
|
t.Fatalf("UpdatesApplied = %d, want 1", summary.UpdatesApplied)
|
|
}
|
|
if len(installer.replaceBinary) != 1 || installer.replaceBinary[0].existingID != 41 {
|
|
t.Fatalf("replace binary calls = %#v, want one in-place replacement for installation 41", installer.replaceBinary)
|
|
}
|
|
if len(installations.deletedIDs) != 0 {
|
|
t.Fatalf("deleted installation IDs = %#v, want none", installations.deletedIDs)
|
|
}
|
|
}
|
|
|
|
func TestAutoUpdateServiceCheckFiresOnChangeAfterMutation(t *testing.T) {
|
|
newOnChangeService := func(installations *fakeAutoUpdateInstallations, opts AutoUpdateOptions, calls *int) (AutoUpdateSummary, error) {
|
|
catalog := &fakeAutoUpdateCatalog{
|
|
entries: []CatalogEntry{{
|
|
RepositoryID: 7,
|
|
Manifest: &pluginv1.PluginManifest{
|
|
PluginId: "silo.tmdb",
|
|
Version: "1.1.0",
|
|
},
|
|
}},
|
|
resolved: &ResolvedCatalogInstall{
|
|
RepositoryID: 7,
|
|
ArchiveURL: "https://plugins.example.test/tmdb",
|
|
Checksum: "deadbeef",
|
|
},
|
|
}
|
|
service := NewAutoUpdateService(
|
|
&fakeAutoUpdateRepositories{list: []*Repository{{ID: 7, Enabled: true}}},
|
|
installations,
|
|
catalog,
|
|
&fakeAutoUpdateInstaller{},
|
|
&fakeAutoUpdateHost{},
|
|
nil,
|
|
func(context.Context) { *calls++ },
|
|
)
|
|
return service.Check(context.Background(), opts)
|
|
}
|
|
|
|
t.Run("applied auto-update fires onChange", func(t *testing.T) {
|
|
var calls int
|
|
installations := &fakeAutoUpdateInstallations{
|
|
list: []*Installation{{ID: 41, RepositoryID: pluginRepositoryID(7), PluginID: "silo.tmdb", Version: "1.0.0", UpdatePolicy: "auto", Enabled: true}},
|
|
}
|
|
summary, err := newOnChangeService(installations, AutoUpdateOptions{}, &calls)
|
|
if err != nil {
|
|
t.Fatalf("Check() returned error: %v", err)
|
|
}
|
|
if summary.UpdatesApplied != 1 {
|
|
t.Fatalf("UpdatesApplied = %d, want 1", summary.UpdatesApplied)
|
|
}
|
|
if calls != 1 {
|
|
t.Fatalf("onChange calls = %d, want 1", calls)
|
|
}
|
|
})
|
|
|
|
t.Run("notify update fires onChange", func(t *testing.T) {
|
|
var calls int
|
|
installations := &fakeAutoUpdateInstallations{
|
|
list: []*Installation{{ID: 42, RepositoryID: pluginRepositoryID(7), PluginID: "silo.tmdb", Version: "1.0.0", UpdatePolicy: "notify", Enabled: true}},
|
|
}
|
|
summary, err := newOnChangeService(installations, AutoUpdateOptions{}, &calls)
|
|
if err != nil {
|
|
t.Fatalf("Check() returned error: %v", err)
|
|
}
|
|
if summary.UpdatesAvailable != 1 {
|
|
t.Fatalf("UpdatesAvailable = %d, want 1", summary.UpdatesAvailable)
|
|
}
|
|
if calls != 1 {
|
|
t.Fatalf("onChange calls = %d, want 1", calls)
|
|
}
|
|
})
|
|
|
|
t.Run("no mutation does not fire onChange", func(t *testing.T) {
|
|
var calls int
|
|
installations := &fakeAutoUpdateInstallations{
|
|
list: []*Installation{{ID: 43, RepositoryID: pluginRepositoryID(7), PluginID: "silo.tmdb", Version: "1.1.0", UpdatePolicy: "auto", Enabled: true}},
|
|
}
|
|
summary, err := newOnChangeService(installations, AutoUpdateOptions{}, &calls)
|
|
if err != nil {
|
|
t.Fatalf("Check() returned error: %v", err)
|
|
}
|
|
if summary.UpdatesApplied != 0 || summary.UpdatesAvailable != 0 {
|
|
t.Fatalf("unexpected mutation summary: %+v", summary)
|
|
}
|
|
if calls != 0 {
|
|
t.Fatalf("onChange calls = %d, want 0", calls)
|
|
}
|
|
})
|
|
|
|
t.Run("nil onChange is safe after mutation", func(t *testing.T) {
|
|
installations := &fakeAutoUpdateInstallations{
|
|
list: []*Installation{{ID: 44, RepositoryID: pluginRepositoryID(7), PluginID: "silo.tmdb", Version: "1.0.0", UpdatePolicy: "auto", Enabled: true}},
|
|
}
|
|
service := NewAutoUpdateService(
|
|
&fakeAutoUpdateRepositories{list: []*Repository{{ID: 7, Enabled: true}}},
|
|
installations,
|
|
&fakeAutoUpdateCatalog{
|
|
entries: []CatalogEntry{{
|
|
RepositoryID: 7,
|
|
Manifest: &pluginv1.PluginManifest{PluginId: "silo.tmdb", Version: "1.1.0"},
|
|
}},
|
|
resolved: &ResolvedCatalogInstall{RepositoryID: 7, ArchiveURL: "https://plugins.example.test/tmdb", Checksum: "deadbeef"},
|
|
},
|
|
&fakeAutoUpdateInstaller{},
|
|
&fakeAutoUpdateHost{},
|
|
nil,
|
|
nil,
|
|
)
|
|
summary, err := service.Check(context.Background(), AutoUpdateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("Check() with nil onChange returned error: %v", err)
|
|
}
|
|
if summary.UpdatesApplied != 1 {
|
|
t.Fatalf("UpdatesApplied = %d, want 1", summary.UpdatesApplied)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCompareVersions(t *testing.T) {
|
|
tests := []struct {
|
|
a, b string
|
|
want int
|
|
}{
|
|
{"1.2.9", "1.2.9", 0},
|
|
{"1.2.10", "1.2.9", 1},
|
|
{"1.2.9", "1.2.10", -1},
|
|
{"2.0.0", "1.9.9", 1},
|
|
{"1.0.0", "1.1.0", -1},
|
|
{"1.10.0", "1.9.0", 1},
|
|
{"0.0.30", "0.0.29", 1},
|
|
{"1.2.3", "1.2.3.1", -1},
|
|
}
|
|
for _, tt := range tests {
|
|
got := compareVersions(tt.a, tt.b)
|
|
if got != tt.want {
|
|
t.Errorf("compareVersions(%q, %q) = %d, want %d", tt.a, tt.b, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAutoUpdateMultiDigitVersion(t *testing.T) {
|
|
installations := &fakeAutoUpdateInstallations{
|
|
list: []*Installation{{ID: 50, RepositoryID: pluginRepositoryID(7), PluginID: "silo.tmdb", Version: "1.2.9", UpdatePolicy: "auto", Enabled: true}},
|
|
}
|
|
host := &fakeAutoUpdateHost{}
|
|
installer := &fakeAutoUpdateInstaller{}
|
|
catalog := &fakeAutoUpdateCatalog{
|
|
entries: []CatalogEntry{{
|
|
RepositoryID: 7,
|
|
Manifest: &pluginv1.PluginManifest{
|
|
PluginId: "silo.tmdb",
|
|
Version: "1.2.10",
|
|
},
|
|
}},
|
|
resolved: &ResolvedCatalogInstall{
|
|
RepositoryID: 7,
|
|
ArchiveURL: "https://plugins.example.test/tmdb",
|
|
Checksum: "deadbeef",
|
|
},
|
|
}
|
|
service := NewAutoUpdateService(
|
|
&fakeAutoUpdateRepositories{list: []*Repository{{ID: 7, Enabled: true}}},
|
|
installations,
|
|
catalog,
|
|
installer,
|
|
host,
|
|
nil,
|
|
nil,
|
|
)
|
|
|
|
summary, err := service.Check(context.Background(), AutoUpdateOptions{
|
|
SeedDefaultRepository: true,
|
|
AutoInstallDefaults: false,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Check() returned error: %v", err)
|
|
}
|
|
if summary.UpdatesApplied != 1 {
|
|
t.Fatalf("UpdatesApplied = %d, want 1 (1.2.10 should be newer than 1.2.9)", summary.UpdatesApplied)
|
|
}
|
|
}
|
|
|
|
func TestAutoUpdateServiceKeepsUpdatesPinnedToInstallationRepository(t *testing.T) {
|
|
installations := &fakeAutoUpdateInstallations{
|
|
list: []*Installation{{
|
|
ID: 60,
|
|
RepositoryID: pluginRepositoryID(7),
|
|
PluginID: "silo.requests.arr",
|
|
Version: "1.0.0",
|
|
UpdatePolicy: "auto",
|
|
Enabled: true,
|
|
}},
|
|
}
|
|
catalog := &fakeAutoUpdateCatalog{
|
|
entries: []CatalogEntry{
|
|
{RepositoryID: 7, Manifest: &pluginv1.PluginManifest{PluginId: "silo.requests.arr", Version: "1.1.0"}},
|
|
{RepositoryID: 8, Manifest: &pluginv1.PluginManifest{PluginId: "silo.requests.arr", Version: "9.0.0"}},
|
|
},
|
|
resolved: &ResolvedCatalogInstall{RepositoryID: 7, ArchiveURL: "https://plugins.example.test/arr", Checksum: "deadbeef"},
|
|
}
|
|
service := NewAutoUpdateService(
|
|
&fakeAutoUpdateRepositories{list: []*Repository{{ID: 7, Enabled: true}, {ID: 8, Enabled: true}}},
|
|
installations,
|
|
catalog,
|
|
&fakeAutoUpdateInstaller{},
|
|
&fakeAutoUpdateHost{},
|
|
nil,
|
|
nil,
|
|
)
|
|
|
|
summary, err := service.Check(context.Background(), AutoUpdateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("Check() returned error: %v", err)
|
|
}
|
|
if summary.UpdatesApplied != 1 {
|
|
t.Fatalf("UpdatesApplied = %d, want 1", summary.UpdatesApplied)
|
|
}
|
|
if len(catalog.resolveRequests) != 1 {
|
|
t.Fatalf("resolve requests = %d, want 1", len(catalog.resolveRequests))
|
|
}
|
|
request := catalog.resolveRequests[0]
|
|
if request.RepositoryID != 7 || request.Version != "1.1.0" {
|
|
t.Fatalf("resolved repository/version = %d/%s, want 7/1.1.0", request.RepositoryID, request.Version)
|
|
}
|
|
}
|
|
|
|
func TestAutoUpdateServiceDoesNotAttachUploadsToCatalogRepositories(t *testing.T) {
|
|
service := NewAutoUpdateService(
|
|
&fakeAutoUpdateRepositories{list: []*Repository{{ID: 7, Enabled: true}}},
|
|
&fakeAutoUpdateInstallations{list: []*Installation{{
|
|
ID: 61,
|
|
PluginID: "silo.requests.arr",
|
|
Version: "1.0.0",
|
|
UpdatePolicy: "auto",
|
|
Enabled: true,
|
|
}}},
|
|
&fakeAutoUpdateCatalog{entries: []CatalogEntry{{
|
|
RepositoryID: 7,
|
|
Manifest: &pluginv1.PluginManifest{PluginId: "silo.requests.arr", Version: "2.0.0"},
|
|
}}},
|
|
&fakeAutoUpdateInstaller{},
|
|
&fakeAutoUpdateHost{},
|
|
nil,
|
|
nil,
|
|
)
|
|
|
|
summary, err := service.Check(context.Background(), AutoUpdateOptions{})
|
|
if err != nil {
|
|
t.Fatalf("Check() returned error: %v", err)
|
|
}
|
|
if summary.UpdatesApplied != 0 || summary.UpdatesAvailable != 0 {
|
|
t.Fatalf("upload unexpectedly received catalog update: %+v", summary)
|
|
}
|
|
}
|
|
|
|
type fakeAutoUpdateRepositories struct {
|
|
list []*Repository
|
|
}
|
|
|
|
func (f *fakeAutoUpdateRepositories) List(context.Context) ([]*Repository, error) {
|
|
return f.list, nil
|
|
}
|
|
|
|
func (f *fakeAutoUpdateRepositories) Create(_ context.Context, input CreateRepositoryInput) (*Repository, error) {
|
|
repo := &Repository{
|
|
ID: len(f.list) + 1,
|
|
URL: input.URL,
|
|
DisplayName: input.DisplayName,
|
|
Enabled: input.Enabled == nil || *input.Enabled,
|
|
}
|
|
f.list = append(f.list, repo)
|
|
return repo, nil
|
|
}
|
|
|
|
type fakeAutoUpdateInstallations struct {
|
|
list []*Installation
|
|
updates []UpdateInstallationInput
|
|
deletedIDs []int
|
|
}
|
|
|
|
func (f *fakeAutoUpdateInstallations) List(context.Context) ([]*Installation, error) {
|
|
return f.list, nil
|
|
}
|
|
|
|
func (f *fakeAutoUpdateInstallations) Update(_ context.Context, _ int, input UpdateInstallationInput) error {
|
|
f.updates = append(f.updates, input)
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeAutoUpdateInstallations) Delete(_ context.Context, id int) error {
|
|
f.deletedIDs = append(f.deletedIDs, id)
|
|
return nil
|
|
}
|
|
|
|
type fakeAutoUpdateCatalog struct {
|
|
entries []CatalogEntry
|
|
resolved *ResolvedCatalogInstall
|
|
resolveRequests []InstallCatalogRequest
|
|
}
|
|
|
|
func (f *fakeAutoUpdateCatalog) Fetch(context.Context) ([]CatalogEntry, error) {
|
|
return f.entries, nil
|
|
}
|
|
|
|
func (f *fakeAutoUpdateCatalog) ResolveInstall(_ context.Context, request InstallCatalogRequest) (*ResolvedCatalogInstall, error) {
|
|
f.resolveRequests = append(f.resolveRequests, request)
|
|
return f.resolved, nil
|
|
}
|
|
|
|
func pluginRepositoryID(id int) *int {
|
|
return &id
|
|
}
|
|
|
|
type replaceBinaryCall struct {
|
|
existingID int
|
|
req InstallBinaryRequest
|
|
}
|
|
|
|
type replaceRemoteCall struct {
|
|
existingID int
|
|
req InstallArchiveRequest
|
|
}
|
|
|
|
type fakeAutoUpdateInstaller struct {
|
|
replaceBinary []replaceBinaryCall
|
|
replaceRemote []replaceRemoteCall
|
|
binary []InstallBinaryRequest
|
|
remote []InstallArchiveRequest
|
|
}
|
|
|
|
func (f *fakeAutoUpdateInstaller) InstallRemote(_ context.Context, req InstallArchiveRequest) (*InstallResult, error) {
|
|
f.remote = append(f.remote, req)
|
|
return &InstallResult{}, nil
|
|
}
|
|
|
|
func (f *fakeAutoUpdateInstaller) InstallBinary(_ context.Context, req InstallBinaryRequest) (*InstallResult, error) {
|
|
f.binary = append(f.binary, req)
|
|
return &InstallResult{}, nil
|
|
}
|
|
|
|
func (f *fakeAutoUpdateInstaller) ReplaceRemote(_ context.Context, existing *Installation, req InstallArchiveRequest) (*InstallResult, error) {
|
|
f.replaceRemote = append(f.replaceRemote, replaceRemoteCall{existingID: existing.ID, req: req})
|
|
return &InstallResult{Installation: existing}, nil
|
|
}
|
|
|
|
func (f *fakeAutoUpdateInstaller) ReplaceBinary(_ context.Context, existing *Installation, req InstallBinaryRequest) (*InstallResult, error) {
|
|
f.replaceBinary = append(f.replaceBinary, replaceBinaryCall{existingID: existing.ID, req: req})
|
|
return &InstallResult{Installation: existing}, nil
|
|
}
|
|
|
|
type fakeAutoUpdateHost struct {
|
|
stopped []int
|
|
}
|
|
|
|
func (f *fakeAutoUpdateHost) Stop(installationID int) error {
|
|
f.stopped = append(f.stopped, installationID)
|
|
return nil
|
|
}
|