4161f3a5ca
What This commit refactors the logic to restore a dashboard from a version. The logic is moved from the API handler to the dashboard versions service, which now supports restoring dashboards of different API versions. Why To make sure that dashboard version restoration works with v2 dashboards API, as well as future API versions. Signed-off-by: Igor Suleymanov <igor.suleymanov@grafana.com>
758 lines
25 KiB
Go
758 lines
25 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
|
|
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
|
|
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
|
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1"
|
|
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
|
)
|
|
|
|
type testSetup struct {
|
|
t *testing.T
|
|
handler *K8sClientWithFallback
|
|
mockMetrics *k8sClientMetrics
|
|
mockFactoryCalls map[string]int
|
|
mockClientV0Alpha1 *client.MockK8sHandler
|
|
mockClientV1Beta1 *client.MockK8sHandler
|
|
mockClientV2Alpha1 *client.MockK8sHandler
|
|
mockClientV2Beta1 *client.MockK8sHandler
|
|
}
|
|
|
|
func setupTest(t *testing.T) *testSetup {
|
|
var (
|
|
mockClientV0Alpha1 = &client.MockK8sHandler{}
|
|
mockClientV1Beta1 = &client.MockK8sHandler{}
|
|
mockClientV2Alpha1 = &client.MockK8sHandler{}
|
|
mockClientV2Beta1 = &client.MockK8sHandler{}
|
|
)
|
|
|
|
mockMetrics := newK8sClientMetrics(prometheus.NewRegistry())
|
|
mockFactoryCalls := make(map[string]int)
|
|
|
|
handler := &K8sClientWithFallback{
|
|
K8sHandler: mockClientV1Beta1,
|
|
newClientFunc: func(_ context.Context, version string) client.K8sHandler {
|
|
mockFactoryCalls[version]++
|
|
|
|
switch version {
|
|
case v0alpha1.VERSION:
|
|
return mockClientV0Alpha1
|
|
case v1beta1.VERSION:
|
|
return mockClientV1Beta1
|
|
case v2alpha1.VERSION:
|
|
return mockClientV2Alpha1
|
|
case v2beta1.VERSION:
|
|
return mockClientV2Beta1
|
|
case "v1":
|
|
return mockClientV1Beta1
|
|
default:
|
|
t.Fatalf("Unexpected call to newClientFunc with version %s", version)
|
|
return nil
|
|
}
|
|
},
|
|
log: log.New("test"),
|
|
metrics: mockMetrics,
|
|
}
|
|
|
|
return &testSetup{
|
|
t: t,
|
|
handler: handler,
|
|
mockMetrics: mockMetrics,
|
|
mockFactoryCalls: mockFactoryCalls,
|
|
mockClientV0Alpha1: mockClientV0Alpha1,
|
|
mockClientV1Beta1: mockClientV1Beta1,
|
|
mockClientV2Alpha1: mockClientV2Alpha1,
|
|
mockClientV2Beta1: mockClientV2Beta1,
|
|
}
|
|
}
|
|
|
|
func TestK8sHandlerWithFallback_Get(t *testing.T) {
|
|
t.Run("Get without fallback", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
|
|
ctx := context.Background()
|
|
name := "test-dashboard"
|
|
orgID := int64(1)
|
|
options := metav1.GetOptions{}
|
|
|
|
expectedResult := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"name": name,
|
|
},
|
|
"status": map[string]interface{}{
|
|
"someOtherStatus": "ok",
|
|
},
|
|
},
|
|
}
|
|
|
|
setup.mockClientV1Beta1.On("Get", mock.Anything, name, orgID, options, mock.Anything).Return(expectedResult, nil).Once()
|
|
|
|
result, err := setup.handler.Get(ctx, name, orgID, options)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, 0, len(setup.mockFactoryCalls), "Factory should not be called for non-fallback case")
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Get with fallback due to conversion error", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
|
|
ctx := context.Background()
|
|
name := "test-dashboard-fallback"
|
|
orgID := int64(2)
|
|
options := metav1.GetOptions{ResourceVersion: "123"}
|
|
storedVersion := v2alpha1.VERSION
|
|
conversionErr := "failed to convert"
|
|
|
|
v1alpha1Result := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"name": name,
|
|
},
|
|
"status": map[string]interface{}{
|
|
"conversion": map[string]interface{}{
|
|
"failed": true,
|
|
"storedVersion": storedVersion,
|
|
"error": conversionErr,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
expectedResultFallback := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "dashboard/v2alpha1",
|
|
"kind": "Dashboard",
|
|
"metadata": map[string]interface{}{
|
|
"name": name,
|
|
},
|
|
},
|
|
}
|
|
|
|
setup.mockClientV1Beta1.On("Get", mock.Anything, name, orgID, options, mock.Anything).Return(v1alpha1Result, nil).Once()
|
|
setup.mockClientV2Alpha1.On("Get", mock.Anything, name, orgID, options, mock.Anything).Return(expectedResultFallback, nil).Once()
|
|
|
|
result, err := setup.handler.Get(ctx, name, orgID, options)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedResultFallback, result)
|
|
require.Equal(t, 1, setup.mockFactoryCalls[v2alpha1.VERSION], "Factory should be called once with v2alpha1")
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Get initial error", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
|
|
ctx := context.Background()
|
|
name := "test-dashboard-error"
|
|
orgID := int64(3)
|
|
options := metav1.GetOptions{}
|
|
expectedErr := errors.New("initial get failed")
|
|
|
|
setup.mockClientV1Beta1.On("Get", mock.Anything, name, orgID, options, mock.Anything).Return(nil, expectedErr).Once()
|
|
|
|
_, err := setup.handler.Get(ctx, name, orgID, options)
|
|
require.Error(t, err)
|
|
require.Equal(t, expectedErr, err)
|
|
require.Equal(t, 0, len(setup.mockFactoryCalls), "Factory should not be called for error case")
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Get with fallback fails", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
|
|
ctx := context.Background()
|
|
name := "test-dashboard-fallback-error"
|
|
orgID := int64(4)
|
|
options := metav1.GetOptions{}
|
|
storedVersion := v2alpha1.VERSION
|
|
conversionErr := "failed to convert again"
|
|
fallbackErr := errors.New("fallback get failed")
|
|
|
|
v1alpha1Result := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"name": name,
|
|
},
|
|
"status": map[string]interface{}{
|
|
"conversion": map[string]interface{}{
|
|
"failed": true,
|
|
"storedVersion": storedVersion,
|
|
"error": conversionErr,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
setup.mockClientV1Beta1.On("Get", mock.Anything, name, orgID, options, mock.Anything).Return(v1alpha1Result, nil).Once()
|
|
setup.mockClientV2Alpha1.On("Get", mock.Anything, name, orgID, options, mock.Anything).Return(nil, fallbackErr).Once()
|
|
|
|
_, err := setup.handler.Get(ctx, name, orgID, options)
|
|
require.Error(t, err)
|
|
require.Equal(t, fallbackErr, err)
|
|
require.Equal(t, 1, setup.mockFactoryCalls[v2alpha1.VERSION], "Factory should be called once with v2alpha1")
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
}
|
|
|
|
func TestK8sHandlerWithFallback_List(t *testing.T) {
|
|
// Helper function to create a dashboard item
|
|
createDashboard := func(name, resourceVersion string, status map[string]interface{}) unstructured.Unstructured {
|
|
return unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"name": name,
|
|
"resourceVersion": resourceVersion,
|
|
},
|
|
"status": status,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Helper function to create a fallback dashboard item
|
|
createFallbackDashboard := func(name, resourceVersion, apiVersion string) unstructured.Unstructured {
|
|
return unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": apiVersion,
|
|
"kind": "Dashboard",
|
|
"metadata": map[string]interface{}{
|
|
"name": name,
|
|
"resourceVersion": resourceVersion,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Helper function to create conversion status
|
|
conversionStatus := func(failed bool, storedVersion, errorMsg string) map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"conversion": map[string]interface{}{
|
|
"failed": failed,
|
|
"storedVersion": storedVersion,
|
|
"error": errorMsg,
|
|
},
|
|
}
|
|
}
|
|
|
|
t.Run("List without fallback needed", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
expectedResult := &unstructured.UnstructuredList{
|
|
Items: []unstructured.Unstructured{
|
|
createDashboard("dashboard-1", "123", map[string]interface{}{"someOtherStatus": "ok"}),
|
|
createDashboard("dashboard-2", "456", map[string]interface{}{"anotherStatus": "ok"}),
|
|
},
|
|
}
|
|
|
|
setup.mockClientV1Beta1.On("List", mock.Anything, int64(1), metav1.ListOptions{}).Return(expectedResult, nil).Once()
|
|
|
|
result, err := setup.handler.List(context.Background(), 1, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedResult, result)
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("List with some items needing fallback", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
initialResult := &unstructured.UnstructuredList{
|
|
Items: []unstructured.Unstructured{
|
|
createDashboard("dashboard-ok", "123", map[string]interface{}{"someOtherStatus": "ok"}),
|
|
createDashboard("dashboard-fallback", "456", conversionStatus(true, v2alpha1.VERSION, "conversion failed")),
|
|
},
|
|
}
|
|
|
|
fallbackResult := createFallbackDashboard("dashboard-fallback", "456", "dashboard/"+v2alpha1.VERSION)
|
|
|
|
setup.mockClientV1Beta1.On("List", mock.Anything, int64(2), metav1.ListOptions{}).Return(initialResult, nil).Once()
|
|
setup.mockClientV2Alpha1.On("Get", mock.Anything, "dashboard-fallback", int64(2), metav1.GetOptions{ResourceVersion: "456"}, mock.Anything).Return(&fallbackResult, nil).Once()
|
|
|
|
result, err := setup.handler.List(context.Background(), 2, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Items, 2)
|
|
|
|
expectedItems := []unstructured.Unstructured{
|
|
createDashboard("dashboard-ok", "123", map[string]interface{}{"someOtherStatus": "ok"}),
|
|
fallbackResult,
|
|
}
|
|
require.ElementsMatch(t, expectedItems, result.Items)
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("List with all items needing fallback", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
initialResult := &unstructured.UnstructuredList{
|
|
Items: []unstructured.Unstructured{
|
|
createDashboard("dashboard-1-fallback", "111", conversionStatus(true, v2alpha1.VERSION, "conversion failed 1")),
|
|
createDashboard("dashboard-2-fallback", "222", conversionStatus(true, v2alpha1.VERSION, "conversion failed 2")),
|
|
},
|
|
}
|
|
|
|
fallbackResult1 := createFallbackDashboard("dashboard-1-fallback", "111", "dashboard/"+v2alpha1.VERSION)
|
|
fallbackResult2 := createFallbackDashboard("dashboard-2-fallback", "222", "dashboard/"+v2alpha1.VERSION)
|
|
|
|
setup.mockClientV1Beta1.On("List", mock.Anything, int64(3), metav1.ListOptions{}).Return(initialResult, nil).Once()
|
|
setup.mockClientV2Alpha1.On("Get", mock.Anything, "dashboard-1-fallback", int64(3), metav1.GetOptions{ResourceVersion: "111"}, mock.Anything).Return(&fallbackResult1, nil).Once()
|
|
setup.mockClientV2Alpha1.On("Get", mock.Anything, "dashboard-2-fallback", int64(3), metav1.GetOptions{ResourceVersion: "222"}, mock.Anything).Return(&fallbackResult2, nil).Once()
|
|
|
|
result, err := setup.handler.List(context.Background(), 3, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Items, 2)
|
|
|
|
expectedItems := []unstructured.Unstructured{fallbackResult1, fallbackResult2}
|
|
require.ElementsMatch(t, expectedItems, result.Items)
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("List with different versions needing fallback", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
initialResult := &unstructured.UnstructuredList{
|
|
Items: []unstructured.Unstructured{
|
|
createDashboard("dashboard-v2alpha1", "333", conversionStatus(true, v2alpha1.VERSION, "conversion failed v2alpha1")),
|
|
createDashboard("dashboard-v1beta1", "444", conversionStatus(true, v1beta1.VERSION, "conversion failed v1beta1")),
|
|
},
|
|
}
|
|
|
|
fallbackResultV2Alpha1 := createFallbackDashboard("dashboard-v2alpha1", "333", "dashboard/"+v2alpha1.VERSION)
|
|
fallbackResultV1Beta1 := createFallbackDashboard("dashboard-v1beta1", "444", "dashboard/"+v1beta1.VERSION)
|
|
|
|
setup.mockClientV1Beta1.On("List", mock.Anything, int64(4), metav1.ListOptions{}).Return(initialResult, nil).Once()
|
|
setup.mockClientV2Alpha1.On("Get", mock.Anything, "dashboard-v2alpha1", int64(4), metav1.GetOptions{ResourceVersion: "333"}, mock.Anything).Return(&fallbackResultV2Alpha1, nil).Once()
|
|
setup.mockClientV1Beta1.On("Get", mock.Anything, "dashboard-v1beta1", int64(4), metav1.GetOptions{ResourceVersion: "444"}, mock.Anything).Return(&fallbackResultV1Beta1, nil).Once()
|
|
|
|
result, err := setup.handler.List(context.Background(), 4, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Items, 2)
|
|
|
|
expectedItems := []unstructured.Unstructured{fallbackResultV2Alpha1, fallbackResultV1Beta1}
|
|
require.ElementsMatch(t, expectedItems, result.Items)
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("List with initial fetch error", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
expectedErr := errors.New("initial list failed")
|
|
|
|
setup.mockClientV1Beta1.On("List", mock.Anything, int64(5), metav1.ListOptions{}).Return(nil, expectedErr).Once()
|
|
|
|
_, err := setup.handler.List(context.Background(), 5, metav1.ListOptions{})
|
|
require.Error(t, err)
|
|
require.Equal(t, expectedErr, err)
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("List with fallback fetch error", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
initialResult := &unstructured.UnstructuredList{
|
|
Items: []unstructured.Unstructured{
|
|
createDashboard("dashboard-fallback-error", "555", conversionStatus(true, v2alpha1.VERSION, "conversion failed")),
|
|
},
|
|
}
|
|
|
|
fallbackErr := errors.New("fallback get failed")
|
|
|
|
setup.mockClientV1Beta1.On("List", mock.Anything, int64(6), metav1.ListOptions{}).Return(initialResult, nil).Once()
|
|
setup.mockClientV2Alpha1.On("Get", mock.Anything, "dashboard-fallback-error", int64(6), metav1.GetOptions{ResourceVersion: "555"}, mock.Anything).Return(nil, fallbackErr).Once()
|
|
|
|
_, err := setup.handler.List(context.Background(), 6, metav1.ListOptions{})
|
|
require.Error(t, err)
|
|
require.Equal(t, fallbackErr, err)
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("List with empty result", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
emptyResult := &unstructured.UnstructuredList{Items: []unstructured.Unstructured{}}
|
|
|
|
setup.mockClientV1Beta1.On("List", mock.Anything, int64(7), metav1.ListOptions{}).Return(emptyResult, nil).Once()
|
|
|
|
result, err := setup.handler.List(context.Background(), 7, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Items, 0)
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
}
|
|
|
|
func TestK8sHandlerWithFallback_Update(t *testing.T) {
|
|
t.Run("Update without fallback", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
|
|
ctx := context.Background()
|
|
obj := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": v0alpha1.VERSION,
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-dashboard",
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"title": "Updated Dashboard",
|
|
},
|
|
},
|
|
}
|
|
orgID := int64(1)
|
|
options := metav1.UpdateOptions{}
|
|
|
|
expectedResult := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": v0alpha1.VERSION,
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-dashboard",
|
|
"resourceVersion": "123",
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"title": "Updated Dashboard",
|
|
},
|
|
},
|
|
}
|
|
|
|
setup.mockClientV0Alpha1.On("Update", mock.Anything, obj, orgID, options).Return(expectedResult, nil).Once()
|
|
|
|
result, err := setup.handler.Update(ctx, obj, orgID, options)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, 1, setup.mockFactoryCalls[v0alpha1.VERSION], "Factory should be called once with v0alpha1")
|
|
|
|
setup.mockClientV0Alpha1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Update with different API version", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
|
|
ctx := context.Background()
|
|
obj := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": v2alpha1.VERSION,
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-dashboard-v2",
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"title": "Updated Dashboard V2",
|
|
},
|
|
},
|
|
}
|
|
orgID := int64(2)
|
|
options := metav1.UpdateOptions{}
|
|
|
|
expectedResult := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": v2alpha1.VERSION,
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-dashboard-v2",
|
|
"resourceVersion": "456",
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"title": "Updated Dashboard V2",
|
|
},
|
|
},
|
|
}
|
|
|
|
setup.mockClientV2Alpha1.On("Update", mock.Anything, obj, orgID, options).Return(expectedResult, nil).Once()
|
|
|
|
result, err := setup.handler.Update(ctx, obj, orgID, options)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, 1, setup.mockFactoryCalls[v2alpha1.VERSION], "Factory should be called once with v2alpha1")
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Update with error", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
|
|
ctx := context.Background()
|
|
obj := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": v0alpha1.VERSION,
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-dashboard-error",
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"title": "Error Dashboard",
|
|
},
|
|
},
|
|
}
|
|
orgID := int64(3)
|
|
options := metav1.UpdateOptions{}
|
|
expectedErr := errors.New("update failed")
|
|
|
|
setup.mockClientV0Alpha1.On("Update", mock.Anything, obj, orgID, options).Return(nil, expectedErr).Once()
|
|
|
|
result, err := setup.handler.Update(ctx, obj, orgID, options)
|
|
require.Error(t, err)
|
|
require.Nil(t, result)
|
|
require.Equal(t, expectedErr, err)
|
|
require.Equal(t, 1, setup.mockFactoryCalls[v0alpha1.VERSION], "Factory should be called once with v0alpha1")
|
|
|
|
setup.mockClientV0Alpha1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Update with different version and error", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
|
|
ctx := context.Background()
|
|
obj := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": v2alpha1.VERSION,
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-dashboard-v2-error",
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"title": "Error Dashboard V2",
|
|
},
|
|
},
|
|
}
|
|
orgID := int64(4)
|
|
options := metav1.UpdateOptions{}
|
|
expectedErr := errors.New("v2alpha1 update failed")
|
|
|
|
setup.mockClientV2Alpha1.On("Update", mock.Anything, obj, orgID, options).Return(nil, expectedErr).Once()
|
|
|
|
result, err := setup.handler.Update(ctx, obj, orgID, options)
|
|
require.Error(t, err)
|
|
require.Nil(t, result)
|
|
require.Equal(t, expectedErr, err)
|
|
require.Equal(t, 1, setup.mockFactoryCalls[v2alpha1.VERSION], "Factory should be called once with v2alpha1")
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Update with unknown API version", func(t *testing.T) {
|
|
setup := setupTest(t)
|
|
|
|
ctx := context.Background()
|
|
obj := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "unknown/v1",
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-dashboard-unknown",
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"title": "Unknown Dashboard",
|
|
},
|
|
},
|
|
}
|
|
orgID := int64(5)
|
|
options := metav1.UpdateOptions{}
|
|
|
|
expectedResult := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "unknown/v1",
|
|
"metadata": map[string]interface{}{
|
|
"name": "test-dashboard-unknown",
|
|
"resourceVersion": "789",
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"title": "Unknown Dashboard",
|
|
},
|
|
},
|
|
}
|
|
|
|
setup.mockClientV1Beta1.On("Update", mock.Anything, obj, orgID, options).Return(expectedResult, nil).Once()
|
|
|
|
result, err := setup.handler.Update(ctx, obj, orgID, options)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, 1, setup.mockFactoryCalls["v1"], "Factory should be called once with v1")
|
|
|
|
setup.mockClientV1Beta1.AssertExpectations(t)
|
|
setup.mockClientV2Alpha1.AssertExpectations(t)
|
|
})
|
|
}
|
|
|
|
func TestGetConversionStatus(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
obj *unstructured.Unstructured
|
|
expectedFailed bool
|
|
expectedStoredVersion string
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "No status field",
|
|
obj: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{"name": "test"},
|
|
}},
|
|
expectedFailed: false,
|
|
expectedStoredVersion: "",
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "Status field, but no conversion field",
|
|
obj: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{"name": "test"},
|
|
"status": map[string]interface{}{"someOtherStatus": "ok"},
|
|
}},
|
|
expectedFailed: false,
|
|
expectedStoredVersion: "",
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "Conversion field, failed=true, with storedVersion and error",
|
|
obj: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{"name": "test"},
|
|
"status": map[string]interface{}{
|
|
"conversion": map[string]interface{}{
|
|
"failed": true,
|
|
"storedVersion": v2alpha1.VERSION,
|
|
"error": "conversion failed",
|
|
},
|
|
},
|
|
}},
|
|
expectedFailed: true,
|
|
expectedStoredVersion: "v2alpha1",
|
|
expectedError: "conversion failed",
|
|
},
|
|
{
|
|
name: "Conversion field, failed=false",
|
|
obj: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{"name": "test"},
|
|
"status": map[string]interface{}{
|
|
"conversion": map[string]interface{}{
|
|
"failed": false,
|
|
"storedVersion": "v1alpha1",
|
|
"error": "",
|
|
},
|
|
},
|
|
}},
|
|
expectedFailed: false,
|
|
expectedStoredVersion: "v1alpha1",
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "Conversion field, missing failed (defaults to false)",
|
|
obj: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{"name": "test"},
|
|
"status": map[string]interface{}{
|
|
"conversion": map[string]interface{}{
|
|
|
|
"storedVersion": "v1alpha1",
|
|
"error": "",
|
|
},
|
|
},
|
|
}},
|
|
expectedFailed: false,
|
|
expectedStoredVersion: "v1alpha1",
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "Conversion field, failed=true, missing storedVersion",
|
|
obj: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{"name": "test"},
|
|
"status": map[string]interface{}{
|
|
"conversion": map[string]interface{}{
|
|
"failed": true,
|
|
|
|
"error": "conversion failed",
|
|
},
|
|
},
|
|
}},
|
|
expectedFailed: true,
|
|
expectedStoredVersion: "",
|
|
expectedError: "conversion failed",
|
|
},
|
|
{
|
|
name: "Conversion field, failed=true, missing error",
|
|
obj: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{"name": "test"},
|
|
"status": map[string]interface{}{
|
|
"conversion": map[string]interface{}{
|
|
"failed": true,
|
|
"storedVersion": v2alpha1.VERSION,
|
|
},
|
|
},
|
|
}},
|
|
expectedFailed: true,
|
|
expectedStoredVersion: "v2alpha1",
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "Empty object",
|
|
obj: &unstructured.Unstructured{Object: map[string]interface{}{}},
|
|
expectedFailed: false,
|
|
expectedStoredVersion: "",
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "Nil object",
|
|
obj: nil,
|
|
expectedFailed: false,
|
|
expectedStoredVersion: "",
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "Status not a map",
|
|
obj: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{"name": "test"},
|
|
"status": "not a map",
|
|
}},
|
|
expectedFailed: false,
|
|
expectedStoredVersion: "",
|
|
expectedError: "",
|
|
},
|
|
{
|
|
name: "Conversion not a map",
|
|
obj: &unstructured.Unstructured{Object: map[string]interface{}{
|
|
"metadata": map[string]interface{}{"name": "test"},
|
|
"status": map[string]interface{}{
|
|
"conversion": "not a map",
|
|
},
|
|
}},
|
|
expectedFailed: false,
|
|
expectedStoredVersion: "",
|
|
expectedError: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var input *unstructured.Unstructured
|
|
if tt.obj != nil {
|
|
input = tt.obj.DeepCopy()
|
|
} else {
|
|
input = &unstructured.Unstructured{Object: map[string]interface{}{}}
|
|
}
|
|
|
|
failed, storedVersion, conversionErr := getConversionStatus(input)
|
|
require.Equal(t, tt.expectedFailed, failed, "failed mismatch")
|
|
require.Equal(t, tt.expectedStoredVersion, storedVersion, "storedVersion mismatch")
|
|
require.Equal(t, tt.expectedError, conversionErr, "conversionErr mismatch")
|
|
})
|
|
}
|
|
}
|