855 lines
24 KiB
Go
855 lines
24 KiB
Go
package install
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/grafana/grafana-app-sdk/resource"
|
|
"github.com/stretchr/testify/require"
|
|
errorsK8s "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1"
|
|
)
|
|
|
|
func TestPluginInstall_ShouldUpdate(t *testing.T) {
|
|
baseExisting := &pluginsv0alpha1.Plugin{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "org-1",
|
|
Name: "plugin-1",
|
|
Annotations: map[string]string{
|
|
PluginInstallSourceAnnotation: SourcePluginStore,
|
|
},
|
|
},
|
|
Spec: pluginsv0alpha1.PluginSpec{
|
|
Id: "plugin-1",
|
|
Version: "1.0.0",
|
|
},
|
|
}
|
|
|
|
baseInstall := PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
Source: SourcePluginStore,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
modifyInstall func(*PluginInstall)
|
|
modifyExisting func(*pluginsv0alpha1.Plugin)
|
|
expectUpdate bool
|
|
}{
|
|
{
|
|
name: "no changes",
|
|
expectUpdate: false,
|
|
},
|
|
{
|
|
name: "version differs",
|
|
modifyInstall: func(pi *PluginInstall) {
|
|
pi.Version = "2.0.0"
|
|
},
|
|
expectUpdate: true,
|
|
},
|
|
{
|
|
name: "url differs",
|
|
modifyInstall: func(pi *PluginInstall) {
|
|
pi.URL = "https://example.com/plugin.zip"
|
|
},
|
|
expectUpdate: true,
|
|
},
|
|
{
|
|
name: "source differs",
|
|
modifyExisting: func(existing *pluginsv0alpha1.Plugin) {
|
|
existing.Annotations[PluginInstallSourceAnnotation] = SourceUnknown
|
|
},
|
|
expectUpdate: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
existing := baseExisting.DeepCopy()
|
|
install := baseInstall
|
|
|
|
if tt.modifyExisting != nil {
|
|
tt.modifyExisting(existing)
|
|
}
|
|
if tt.modifyInstall != nil {
|
|
tt.modifyInstall(&install)
|
|
}
|
|
|
|
require.Equal(t, tt.expectUpdate, install.ShouldUpdate(existing))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInstallRegistrar_Register(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
install *PluginInstall
|
|
existing *pluginsv0alpha1.Plugin
|
|
existingErr error
|
|
expectedCreates int
|
|
expectedUpdates int
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "creates plugin when not found",
|
|
install: &PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
Source: SourcePluginStore,
|
|
},
|
|
existingErr: errorsK8s.NewNotFound(pluginGroupResource(), "plugin-1"),
|
|
expectedCreates: 1,
|
|
},
|
|
{
|
|
name: "updates plugin when fields change",
|
|
install: &PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "2.0.0",
|
|
Source: SourcePluginStore,
|
|
},
|
|
existing: &pluginsv0alpha1.Plugin{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "org-1",
|
|
Name: "plugin-1",
|
|
ResourceVersion: "7",
|
|
Annotations: map[string]string{
|
|
PluginInstallSourceAnnotation: SourcePluginStore,
|
|
},
|
|
},
|
|
Spec: pluginsv0alpha1.PluginSpec{
|
|
Id: "plugin-1",
|
|
Version: "1.0.0",
|
|
},
|
|
},
|
|
expectedUpdates: 1,
|
|
},
|
|
{
|
|
name: "skips create when plugin matches",
|
|
install: &PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
Source: SourcePluginStore,
|
|
},
|
|
existing: &pluginsv0alpha1.Plugin{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "org-1",
|
|
Name: "plugin-1",
|
|
ResourceVersion: "9",
|
|
Annotations: map[string]string{
|
|
PluginInstallSourceAnnotation: SourcePluginStore,
|
|
},
|
|
},
|
|
Spec: pluginsv0alpha1.PluginSpec{
|
|
Id: "plugin-1",
|
|
Version: "1.0.0",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "returns error on unexpected get failure",
|
|
install: &PluginInstall{
|
|
ID: "plugin-err",
|
|
Version: "1.0.0",
|
|
Source: SourcePluginStore,
|
|
},
|
|
existingErr: errorsK8s.NewInternalError(errors.New("boom")),
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
createCalls := 0
|
|
updateCalls := 0
|
|
var receivedResourceVersions []string
|
|
var updatedPlugins []*pluginsv0alpha1.Plugin
|
|
|
|
fakeClient := &fakePluginInstallClient{
|
|
getFunc: func(context.Context, resource.Identifier) (*pluginsv0alpha1.Plugin, error) {
|
|
if tt.existingErr != nil {
|
|
return nil, tt.existingErr
|
|
}
|
|
if tt.existing == nil {
|
|
return nil, errorsK8s.NewNotFound(pluginGroupResource(), "plugin-1")
|
|
}
|
|
return tt.existing.DeepCopy(), nil
|
|
},
|
|
createFunc: func(context.Context, *pluginsv0alpha1.Plugin, resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) {
|
|
createCalls++
|
|
return tt.install.ToPluginInstallV0Alpha1("org-1"), nil
|
|
},
|
|
updateFunc: func(_ context.Context, obj *pluginsv0alpha1.Plugin, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) {
|
|
updateCalls++
|
|
receivedResourceVersions = append(receivedResourceVersions, opts.ResourceVersion)
|
|
updatedPlugins = append(updatedPlugins, obj)
|
|
return obj, nil
|
|
},
|
|
}
|
|
|
|
registrar := NewInstallRegistrar(&fakeClientGenerator{client: fakeClient})
|
|
|
|
err := registrar.Register(ctx, "org-1", tt.install)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expectedCreates, createCalls)
|
|
require.Equal(t, tt.expectedUpdates, updateCalls)
|
|
|
|
if tt.expectedUpdates > 0 {
|
|
require.Equal(t, []string{tt.existing.ResourceVersion}, receivedResourceVersions)
|
|
require.Len(t, updatedPlugins, 1)
|
|
require.Equal(t, tt.install.Version, updatedPlugins[0].Spec.Version)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func pluginGroupResource() schema.GroupResource {
|
|
return schema.GroupResource{Group: pluginsv0alpha1.APIGroup, Resource: "plugininstalls"}
|
|
}
|
|
|
|
type fakePluginInstallClient struct {
|
|
listAllFunc func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error)
|
|
getFunc func(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.Plugin, error)
|
|
createFunc func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error)
|
|
updateFunc func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error)
|
|
deleteFunc func(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error
|
|
}
|
|
|
|
func (f *fakePluginInstallClient) Get(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.Plugin, error) {
|
|
if f.getFunc != nil {
|
|
return f.getFunc(ctx, identifier)
|
|
}
|
|
return nil, errorsK8s.NewNotFound(pluginGroupResource(), identifier.Name)
|
|
}
|
|
|
|
func (f *fakePluginInstallClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) {
|
|
if f.listAllFunc != nil {
|
|
return f.listAllFunc(ctx, namespace, opts)
|
|
}
|
|
return &pluginsv0alpha1.PluginList{}, nil
|
|
}
|
|
|
|
func (f *fakePluginInstallClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) {
|
|
return f.ListAll(ctx, namespace, opts)
|
|
}
|
|
|
|
func (f *fakePluginInstallClient) Create(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) {
|
|
if f.createFunc != nil {
|
|
return f.createFunc(ctx, obj, opts)
|
|
}
|
|
return obj, nil
|
|
}
|
|
|
|
func (f *fakePluginInstallClient) Update(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) {
|
|
if f.updateFunc != nil {
|
|
return f.updateFunc(ctx, obj, opts)
|
|
}
|
|
return obj, nil
|
|
}
|
|
|
|
func (f *fakePluginInstallClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus pluginsv0alpha1.PluginStatus, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakePluginInstallClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*pluginsv0alpha1.Plugin, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakePluginInstallClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
|
if f.deleteFunc != nil {
|
|
return f.deleteFunc(ctx, identifier, opts)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type fakeClientGenerator struct {
|
|
client *fakePluginInstallClient
|
|
shouldError bool
|
|
}
|
|
|
|
func (f *fakeClientGenerator) ClientFor(resource.Kind) (resource.Client, error) {
|
|
if f.shouldError {
|
|
return nil, errors.New("client generation failed")
|
|
}
|
|
return &fakeResourceClient{client: f.client}, nil
|
|
}
|
|
|
|
type fakeResourceClient struct {
|
|
client *fakePluginInstallClient
|
|
}
|
|
|
|
func (f *fakeResourceClient) Get(ctx context.Context, identifier resource.Identifier) (resource.Object, error) {
|
|
return f.client.Get(ctx, identifier)
|
|
}
|
|
|
|
func (f *fakeResourceClient) GetInto(ctx context.Context, identifier resource.Identifier, into resource.Object) error {
|
|
obj, err := f.client.Get(ctx, identifier)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if target, ok := into.(*pluginsv0alpha1.Plugin); ok {
|
|
*target = *obj
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeResourceClient) List(ctx context.Context, namespace string, options resource.ListOptions) (resource.ListObject, error) {
|
|
return f.client.ListAll(ctx, namespace, options)
|
|
}
|
|
|
|
func (f *fakeResourceClient) ListInto(ctx context.Context, namespace string, options resource.ListOptions, into resource.ListObject) error {
|
|
list, err := f.client.ListAll(ctx, namespace, options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if target, ok := into.(*pluginsv0alpha1.PluginList); ok {
|
|
*target = *list
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeResourceClient) Create(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions) (resource.Object, error) {
|
|
plugin := obj.(*pluginsv0alpha1.Plugin)
|
|
return f.client.Create(ctx, plugin, options)
|
|
}
|
|
|
|
func (f *fakeResourceClient) CreateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions, into resource.Object) error {
|
|
created, err := f.Create(ctx, identifier, obj, options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if plugin, ok := created.(*pluginsv0alpha1.Plugin); ok {
|
|
if target, ok := into.(*pluginsv0alpha1.Plugin); ok {
|
|
*target = *plugin
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeResourceClient) Update(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions) (resource.Object, error) {
|
|
plugin := obj.(*pluginsv0alpha1.Plugin)
|
|
return f.client.Update(ctx, plugin, options)
|
|
}
|
|
|
|
func (f *fakeResourceClient) UpdateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions, into resource.Object) error {
|
|
updated, err := f.Update(ctx, identifier, obj, options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if plugin, ok := updated.(*pluginsv0alpha1.Plugin); ok {
|
|
if target, ok := into.(*pluginsv0alpha1.Plugin); ok {
|
|
*target = *plugin
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeResourceClient) Patch(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions) (resource.Object, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeResourceClient) PatchInto(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions, into resource.Object) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeResourceClient) Delete(ctx context.Context, identifier resource.Identifier, options resource.DeleteOptions) error {
|
|
return f.client.Delete(ctx, identifier, options)
|
|
}
|
|
|
|
func (f *fakeResourceClient) SubresourceRequest(ctx context.Context, identifier resource.Identifier, req resource.CustomRouteRequestOptions) ([]byte, error) {
|
|
return []byte{}, nil
|
|
}
|
|
|
|
func (f *fakeResourceClient) Watch(ctx context.Context, namespace string, options resource.WatchOptions) (resource.WatchResponse, error) {
|
|
return &fakeWatchResponse{}, nil
|
|
}
|
|
|
|
type fakeWatchResponse struct{}
|
|
|
|
func (f *fakeWatchResponse) Stop() {}
|
|
|
|
func (f *fakeWatchResponse) WatchEvents() <-chan resource.WatchEvent {
|
|
ch := make(chan resource.WatchEvent)
|
|
close(ch)
|
|
return ch
|
|
}
|
|
|
|
func TestPluginInstall_ToPluginInstallV0Alpha1(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
install PluginInstall
|
|
namespace string
|
|
validate func(*testing.T, *pluginsv0alpha1.Plugin)
|
|
}{
|
|
{
|
|
name: "empty URL creates nil pointer",
|
|
install: PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
Source: SourcePluginStore,
|
|
},
|
|
namespace: "org-1",
|
|
validate: func(t *testing.T, p *pluginsv0alpha1.Plugin) {
|
|
require.Nil(t, p.Spec.Url)
|
|
},
|
|
},
|
|
{
|
|
name: "non-empty URL creates pointer",
|
|
install: PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
URL: "https://example.com/plugin.zip",
|
|
Source: SourcePluginStore,
|
|
},
|
|
namespace: "org-1",
|
|
validate: func(t *testing.T, p *pluginsv0alpha1.Plugin) {
|
|
require.NotNil(t, p.Spec.Url)
|
|
require.Equal(t, "https://example.com/plugin.zip", *p.Spec.Url)
|
|
},
|
|
},
|
|
{
|
|
name: "source annotation is set correctly",
|
|
install: PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
Source: SourceUnknown,
|
|
},
|
|
namespace: "org-1",
|
|
validate: func(t *testing.T, p *pluginsv0alpha1.Plugin) {
|
|
require.Equal(t, SourceUnknown, p.Annotations[PluginInstallSourceAnnotation])
|
|
},
|
|
},
|
|
{
|
|
name: "namespace and name are set correctly",
|
|
install: PluginInstall{
|
|
ID: "my-plugin",
|
|
Version: "1.0.0",
|
|
Source: SourcePluginStore,
|
|
},
|
|
namespace: "my-namespace",
|
|
validate: func(t *testing.T, p *pluginsv0alpha1.Plugin) {
|
|
require.Equal(t, "my-namespace", p.Namespace)
|
|
require.Equal(t, "my-plugin", p.Name)
|
|
require.Equal(t, "my-plugin", p.Spec.Id)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := tt.install.ToPluginInstallV0Alpha1(tt.namespace)
|
|
require.NotNil(t, result)
|
|
require.Equal(t, tt.namespace, result.Namespace)
|
|
require.Equal(t, tt.install.ID, result.Name)
|
|
require.Equal(t, tt.install.ID, result.Spec.Id)
|
|
require.Equal(t, tt.install.Version, result.Spec.Version)
|
|
tt.validate(t, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEqualStringPointers(t *testing.T) {
|
|
str1 := "value1"
|
|
str2 := "value2"
|
|
str3 := "value1"
|
|
|
|
tests := []struct {
|
|
name string
|
|
a *string
|
|
b *string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "both nil",
|
|
a: nil,
|
|
b: nil,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "first nil, second non-nil",
|
|
a: nil,
|
|
b: &str1,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "first non-nil, second nil",
|
|
a: &str1,
|
|
b: nil,
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "both non-nil with same value",
|
|
a: &str1,
|
|
b: &str3,
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "both non-nil with different values",
|
|
a: &str1,
|
|
b: &str2,
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := equalStringPointers(tt.a, tt.b)
|
|
require.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPluginInstall_ShouldUpdate_URLTransitions(t *testing.T) {
|
|
existingURL := "https://old.example.com/plugin.zip"
|
|
newURL := "https://new.example.com/plugin.zip"
|
|
|
|
tests := []struct {
|
|
name string
|
|
install PluginInstall
|
|
existingURL *string
|
|
expectUpdate bool
|
|
}{
|
|
{
|
|
name: "URL transition from nil to non-nil",
|
|
install: PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
URL: newURL,
|
|
Source: SourcePluginStore,
|
|
},
|
|
existingURL: nil,
|
|
expectUpdate: true,
|
|
},
|
|
{
|
|
name: "URL transition from non-nil to nil",
|
|
install: PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
URL: "",
|
|
Source: SourcePluginStore,
|
|
},
|
|
existingURL: &existingURL,
|
|
expectUpdate: true,
|
|
},
|
|
{
|
|
name: "URL stays nil",
|
|
install: PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
URL: "",
|
|
Source: SourcePluginStore,
|
|
},
|
|
existingURL: nil,
|
|
expectUpdate: false,
|
|
},
|
|
{
|
|
name: "URL stays same non-nil value",
|
|
install: PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
URL: existingURL,
|
|
Source: SourcePluginStore,
|
|
},
|
|
existingURL: &existingURL,
|
|
expectUpdate: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
existing := &pluginsv0alpha1.Plugin{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "org-1",
|
|
Name: "plugin-1",
|
|
Annotations: map[string]string{
|
|
PluginInstallSourceAnnotation: SourcePluginStore,
|
|
},
|
|
},
|
|
Spec: pluginsv0alpha1.PluginSpec{
|
|
Id: "plugin-1",
|
|
Version: "1.0.0",
|
|
Url: tt.existingURL,
|
|
},
|
|
}
|
|
|
|
require.Equal(t, tt.expectUpdate, tt.install.ShouldUpdate(existing))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInstallRegistrar_GetClient(t *testing.T) {
|
|
t.Run("successfully creates client on first call", func(t *testing.T) {
|
|
fakeClient := &fakePluginInstallClient{}
|
|
generator := &fakeClientGenerator{client: fakeClient}
|
|
registrar := NewInstallRegistrar(generator)
|
|
|
|
client, err := registrar.GetClient()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, client)
|
|
})
|
|
|
|
t.Run("returns same client on subsequent calls", func(t *testing.T) {
|
|
fakeClient := &fakePluginInstallClient{}
|
|
generator := &fakeClientGenerator{client: fakeClient}
|
|
registrar := NewInstallRegistrar(generator)
|
|
|
|
client1, err1 := registrar.GetClient()
|
|
require.NoError(t, err1)
|
|
|
|
client2, err2 := registrar.GetClient()
|
|
require.NoError(t, err2)
|
|
|
|
require.Equal(t, client1, client2)
|
|
})
|
|
|
|
t.Run("returns error when client generation fails", func(t *testing.T) {
|
|
generator := &fakeClientGenerator{client: nil, shouldError: true}
|
|
registrar := NewInstallRegistrar(generator)
|
|
|
|
client, err := registrar.GetClient()
|
|
require.Error(t, err)
|
|
require.Nil(t, client)
|
|
})
|
|
}
|
|
|
|
func TestInstallRegistrar_Register_ErrorCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
install *PluginInstall
|
|
setupClient func(*fakePluginInstallClient)
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "create fails",
|
|
install: &PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
Source: SourcePluginStore,
|
|
},
|
|
setupClient: func(fc *fakePluginInstallClient) {
|
|
fc.getFunc = func(context.Context, resource.Identifier) (*pluginsv0alpha1.Plugin, error) {
|
|
return nil, errorsK8s.NewNotFound(pluginGroupResource(), "plugin-1")
|
|
}
|
|
fc.createFunc = func(context.Context, *pluginsv0alpha1.Plugin, resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) {
|
|
return nil, errors.New("create failed")
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "update fails",
|
|
install: &PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "2.0.0",
|
|
Source: SourcePluginStore,
|
|
},
|
|
setupClient: func(fc *fakePluginInstallClient) {
|
|
fc.getFunc = func(context.Context, resource.Identifier) (*pluginsv0alpha1.Plugin, error) {
|
|
return &pluginsv0alpha1.Plugin{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "org-1",
|
|
Name: "plugin-1",
|
|
ResourceVersion: "5",
|
|
Annotations: map[string]string{
|
|
PluginInstallSourceAnnotation: SourcePluginStore,
|
|
},
|
|
},
|
|
Spec: pluginsv0alpha1.PluginSpec{
|
|
Id: "plugin-1",
|
|
Version: "1.0.0",
|
|
},
|
|
}, nil
|
|
}
|
|
fc.updateFunc = func(context.Context, *pluginsv0alpha1.Plugin, resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) {
|
|
return nil, errors.New("update failed")
|
|
}
|
|
},
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
fakeClient := &fakePluginInstallClient{}
|
|
tt.setupClient(fakeClient)
|
|
|
|
registrar := NewInstallRegistrar(&fakeClientGenerator{client: fakeClient})
|
|
|
|
err := registrar.Register(ctx, "org-1", tt.install)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInstallRegistrar_Unregister(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
namespace string
|
|
pluginName string
|
|
source Source
|
|
existing *pluginsv0alpha1.Plugin
|
|
existingErr error
|
|
expectedCalls int
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "successfully deletes plugin with matching source",
|
|
namespace: "org-1",
|
|
pluginName: "plugin-1",
|
|
source: SourcePluginStore,
|
|
existing: &pluginsv0alpha1.Plugin{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "org-1",
|
|
Name: "plugin-1",
|
|
Annotations: map[string]string{
|
|
PluginInstallSourceAnnotation: SourcePluginStore,
|
|
},
|
|
},
|
|
},
|
|
expectedCalls: 1,
|
|
},
|
|
{
|
|
name: "plugin not found should not error",
|
|
namespace: "org-1",
|
|
pluginName: "plugin-nonexistent",
|
|
source: SourcePluginStore,
|
|
existingErr: errorsK8s.NewNotFound(pluginGroupResource(), "plugin-nonexistent"),
|
|
expectedCalls: 0,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "skips delete when source doesn't match",
|
|
namespace: "org-1",
|
|
pluginName: "plugin-1",
|
|
source: SourcePluginStore,
|
|
existing: &pluginsv0alpha1.Plugin{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "org-1",
|
|
Name: "plugin-1",
|
|
Annotations: map[string]string{
|
|
PluginInstallSourceAnnotation: SourceUnknown,
|
|
},
|
|
},
|
|
},
|
|
expectedCalls: 0,
|
|
},
|
|
{
|
|
name: "returns error on unexpected get failure",
|
|
namespace: "org-1",
|
|
pluginName: "plugin-err",
|
|
source: SourcePluginStore,
|
|
existingErr: errorsK8s.NewInternalError(errors.New("get failed")),
|
|
expectedCalls: 0,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "delete failure returns error",
|
|
namespace: "org-1",
|
|
pluginName: "plugin-1",
|
|
source: SourcePluginStore,
|
|
existing: &pluginsv0alpha1.Plugin{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "org-1",
|
|
Name: "plugin-1",
|
|
Annotations: map[string]string{
|
|
PluginInstallSourceAnnotation: SourcePluginStore,
|
|
},
|
|
},
|
|
},
|
|
expectedCalls: 1,
|
|
expectError: true,
|
|
},
|
|
{
|
|
name: "handles missing source annotation",
|
|
namespace: "org-1",
|
|
pluginName: "plugin-1",
|
|
source: SourcePluginStore,
|
|
existing: &pluginsv0alpha1.Plugin{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Namespace: "org-1",
|
|
Name: "plugin-1",
|
|
Annotations: map[string]string{},
|
|
},
|
|
},
|
|
expectedCalls: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
deleteCalls := 0
|
|
|
|
fakeClient := &fakePluginInstallClient{
|
|
getFunc: func(context.Context, resource.Identifier) (*pluginsv0alpha1.Plugin, error) {
|
|
if tt.existingErr != nil {
|
|
return nil, tt.existingErr
|
|
}
|
|
if tt.existing == nil {
|
|
return nil, errorsK8s.NewNotFound(pluginGroupResource(), tt.pluginName)
|
|
}
|
|
return tt.existing.DeepCopy(), nil
|
|
},
|
|
deleteFunc: func(context.Context, resource.Identifier, resource.DeleteOptions) error {
|
|
deleteCalls++
|
|
if tt.name == "delete failure returns error" {
|
|
return errors.New("delete failed")
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
registrar := NewInstallRegistrar(&fakeClientGenerator{client: fakeClient})
|
|
|
|
err := registrar.Unregister(ctx, tt.namespace, tt.pluginName, tt.source)
|
|
|
|
require.Equal(t, tt.expectedCalls, deleteCalls)
|
|
if tt.expectError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInstallRegistrar_GetClientError(t *testing.T) {
|
|
t.Run("Register returns error with nil client", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
generator := &fakeClientGenerator{client: nil, shouldError: true}
|
|
registrar := NewInstallRegistrar(generator)
|
|
|
|
install := &PluginInstall{
|
|
ID: "plugin-1",
|
|
Version: "1.0.0",
|
|
Source: SourcePluginStore,
|
|
}
|
|
|
|
err := registrar.Register(ctx, "org-1", install)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("Unregister returns error with nil client", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
generator := &fakeClientGenerator{client: nil, shouldError: true}
|
|
registrar := NewInstallRegistrar(generator)
|
|
|
|
err := registrar.Unregister(ctx, "org-1", "plugin-1", SourcePluginStore)
|
|
require.Error(t, err)
|
|
})
|
|
}
|