package apistore import ( "context" "testing" "time" "github.com/bwmarrin/snowflake" "github.com/stretchr/testify/require" "golang.org/x/exp/rand" "k8s.io/apimachinery/pkg/api/apitesting" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apiserver/pkg/storage" authtypes "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" ) var scheme = runtime.NewScheme() var codecs = serializer.NewCodecFactory(scheme) func TestPrepareObjectForStorage(t *testing.T) { _ = v0alpha1.AddToScheme(scheme) node, err := snowflake.NewNode(rand.Int63n(1024)) require.NoError(t, err) s := &Storage{ codec: apitesting.TestCodec(codecs, v0alpha1.DashboardResourceInfo.GroupVersion()), snowflake: node, opts: StorageOptions{ LargeObjectSupport: nil, }, } ctx := authtypes.WithAuthInfo(context.Background(), &identity.StaticRequester{UserID: 1, UserUID: "user-uid", Type: authtypes.TypeUser}) t.Run("Error getting auth info from context", func(t *testing.T) { _, err := s.prepareObjectForStorage(context.Background(), nil) require.Error(t, err) require.Contains(t, err.Error(), "missing auth info") }) t.Run("Error on missing name", func(t *testing.T) { dashboard := v0alpha1.Dashboard{} _, err := s.prepareObjectForStorage(ctx, dashboard.DeepCopyObject()) require.Error(t, err) require.Contains(t, err.Error(), "missing name") }) t.Run("Error on non-empty resource version", func(t *testing.T) { dashboard := v0alpha1.Dashboard{} dashboard.Name = "test-name" dashboard.ResourceVersion = "123" _, err := s.prepareObjectForStorage(ctx, dashboard.DeepCopyObject()) require.Error(t, err) require.Equal(t, storage.ErrResourceVersionSetOnCreate, err) }) t.Run("Generate UID and leave deprecated ID empty, if not required", func(t *testing.T) { dashboard := v0alpha1.Dashboard{} dashboard.Name = "test-name" encodedData, err := s.prepareObjectForStorage(ctx, dashboard.DeepCopyObject()) require.NoError(t, err) newObject, _, err := s.codec.Decode(encodedData, nil, &v0alpha1.Dashboard{}) require.NoError(t, err) obj, err := utils.MetaAccessor(newObject) require.NoError(t, err) require.NotEmpty(t, obj.GetUID(), "") require.Empty(t, obj.GetDeprecatedInternalID()) // nolint:staticcheck require.Empty(t, obj.GetGenerateName()) require.Empty(t, obj.GetResourceVersion()) require.Empty(t, obj.GetSelfLink()) require.Empty(t, obj.GetUpdatedBy()) require.Equal(t, obj.GetCreatedBy(), "user:user-uid") updatedTS, err := obj.GetUpdatedTimestamp() require.NoError(t, err) require.Empty(t, updatedTS) }) t.Run("Should keep repo info", func(t *testing.T) { dashboard := v0alpha1.Dashboard{} dashboard.Name = "test-name" obj := dashboard.DeepCopyObject() meta, err := utils.MetaAccessor(obj) require.NoError(t, err) now := time.Now() meta.SetManagerProperties(utils.ManagerProperties{ Kind: utils.ManagerKindRepo, Identity: "test-repo", }) meta.SetSourceProperties(utils.SourceProperties{ Path: "test/path", Checksum: "hash", TimestampMillis: now.UnixMilli(), }) encodedData, err := s.prepareObjectForStorage(ctx, obj) require.NoError(t, err) newObject, _, err := s.codec.Decode(encodedData, nil, &v0alpha1.Dashboard{}) require.NoError(t, err) meta, err = utils.MetaAccessor(newObject) require.NoError(t, err) m, ok := meta.GetManagerProperties() require.True(t, ok) s, ok := meta.GetSourceProperties() require.True(t, ok) require.Equal(t, m.Identity, "test-repo") require.Equal(t, s.Checksum, "hash") require.Equal(t, s.Path, "test/path") require.Equal(t, s.TimestampMillis, now.UnixMilli()) }) s.opts.RequireDeprecatedInternalID = true t.Run("Should generate internal id", func(t *testing.T) { dashboard := v0alpha1.Dashboard{} dashboard.Name = "test-name" encodedData, err := s.prepareObjectForStorage(ctx, dashboard.DeepCopyObject()) require.NoError(t, err) newObject, _, err := s.codec.Decode(encodedData, nil, &v0alpha1.Dashboard{}) require.NoError(t, err) obj, err := utils.MetaAccessor(newObject) require.NoError(t, err) require.NotEmpty(t, obj.GetDeprecatedInternalID()) // nolint:staticcheck // must be less than the max number value in javascript to avoid precision loss require.LessOrEqual(t, obj.GetDeprecatedInternalID(), int64(9007199254740991)) // nolint:staticcheck }) t.Run("Should use deprecated ID if given it", func(t *testing.T) { dashboard := v0alpha1.Dashboard{} dashboard.Name = "test-name" obj := dashboard.DeepCopyObject() meta, err := utils.MetaAccessor(obj) require.NoError(t, err) meta.SetDeprecatedInternalID(1) // nolint:staticcheck encodedData, err := s.prepareObjectForStorage(ctx, obj) require.NoError(t, err) newObject, _, err := s.codec.Decode(encodedData, nil, &v0alpha1.Dashboard{}) require.NoError(t, err) meta, err = utils.MetaAccessor(newObject) require.NoError(t, err) require.Equal(t, meta.GetDeprecatedInternalID(), int64(1)) // nolint:staticcheck }) }