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, 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, ) 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 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, 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, ) 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) } } 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 } func (f *fakeAutoUpdateCatalog) Fetch(context.Context) ([]CatalogEntry, error) { return f.entries, nil } func (f *fakeAutoUpdateCatalog) ResolveInstall(_ context.Context, _ InstallCatalogRequest) (*ResolvedCatalogInstall, error) { return f.resolved, nil } 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 }