Files
grafana/pkg/storage/unified/resource/metadata_test.go
2025-07-11 11:10:19 +02:00

1356 lines
33 KiB
Go

package resource
import (
"context"
"encoding/json"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func setupTestMetadataStore(t *testing.T) *metadataStore {
kv := setupTestKV(t)
return newMetadataStore(kv)
}
func TestNewMetadataStore(t *testing.T) {
store := setupTestMetadataStore(t)
assert.NotNil(t, store)
}
func TestMetaDataKey_String(t *testing.T) {
rv := int64(56500267212345678)
key := MetaDataKey{
Folder: "test-folder",
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: rv,
Action: DataActionCreated,
}
expectedKey := "apps/resource/default/test-resource/56500267212345678~created~test-folder"
actualKey := key.String()
assert.Equal(t, expectedKey, actualKey)
}
func TestParseMetaDataKey(t *testing.T) {
rv := node.Generate()
key := "apps/resource/default/test-resource/" + rv.String() + "~" + string(DataActionCreated) + "~test-folder"
resourceKey, err := parseMetaDataKey(key)
require.NoError(t, err)
assert.Equal(t, "apps", resourceKey.Group)
assert.Equal(t, "resource", resourceKey.Resource)
assert.Equal(t, "default", resourceKey.Namespace)
assert.Equal(t, "test-resource", resourceKey.Name)
assert.Equal(t, rv.Int64(), resourceKey.ResourceVersion)
assert.Equal(t, DataActionCreated, resourceKey.Action)
assert.Equal(t, "test-folder", resourceKey.Folder)
}
func TestParseMetaDataKey_InvalidKey(t *testing.T) {
tests := []struct {
name string
key string
}{
{
name: "too few parts",
key: "apps",
},
{
name: "invalid uuid",
key: "apps/resource/default/test-resource/invalid-uuid",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := parseMetaDataKey(tt.key)
assert.Error(t, err)
})
}
}
func TestMetadataStore_Save(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
key := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: node.Generate().Int64(),
Action: DataActionCreated,
}
metadata := MetaData{
IndexableDocument: IndexableDocument{
Title: "This is a test resource",
Description: "This is a test resource description",
Tags: []string{"tag1", "tag2"},
Labels: map[string]string{
"label1": "label1",
"label2": "label2",
},
Folder: "test-folder",
},
}
err := store.Save(ctx, MetaDataObj{
Key: key,
Value: metadata,
})
require.NoError(t, err)
// Verify in the kv store that the metadata is saved
reader, err := store.kv.Get(ctx, metaSection, key.String())
require.NoError(t, err)
var retrivedMeta MetaData
actualData, err := io.ReadAll(reader)
require.NoError(t, err)
err = reader.Close()
require.NoError(t, err)
err = json.Unmarshal(actualData, &retrivedMeta)
require.NoError(t, err)
assert.Equal(t, metadata, retrivedMeta)
}
func TestMetadataStore_Get(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
key := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: node.Generate().Int64(),
Action: DataActionCreated,
}
metadata := MetaData{
IndexableDocument: IndexableDocument{
Title: "This is a test resource",
Description: "This is a test resource description",
Tags: []string{"tag1", "tag2"},
Labels: map[string]string{
"label1": "label1",
"label2": "label2",
},
Folder: "test-folder",
},
}
// Save first
err := store.Save(ctx, MetaDataObj{
Key: key,
Value: metadata,
})
require.NoError(t, err)
// Get it back
retrievedMetadata, err := store.Get(ctx, key)
require.NoError(t, err)
assert.Equal(t, metadata, retrievedMetadata)
}
func TestMetadataStore_Get_NotFound(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
key := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: node.Generate().Int64(),
Action: DataActionCreated,
}
_, err := store.Get(ctx, key)
assert.Equal(t, ErrNotFound, err)
}
func TestMetadataStore_GetLatestResourceKey(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
key := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
}
// Create multiple versions with different timestamps
rv1 := node.Generate().Int64()
rv2 := node.Generate().Int64()
rv3 := node.Generate().Int64()
// Save multiple versions (rv3 should be latest)
metadata1 := MetaData{
IndexableDocument: IndexableDocument{
Title: "Initial version",
},
}
metadata2 := MetaData{
IndexableDocument: IndexableDocument{
Title: "Updated version",
},
}
metadata3 := MetaData{
IndexableDocument: IndexableDocument{
Title: "Latest version",
},
}
key.ResourceVersion = rv1
key.Action = DataActionCreated
err := store.Save(ctx, MetaDataObj{
Key: key,
Value: metadata1,
})
require.NoError(t, err)
key.ResourceVersion = rv2
key.Action = DataActionUpdated
err = store.Save(ctx, MetaDataObj{
Key: key,
Value: metadata2,
})
require.NoError(t, err)
key.ResourceVersion = rv3
key.Action = DataActionCreated
err = store.Save(ctx, MetaDataObj{
Key: key,
Value: metadata3,
})
require.NoError(t, err)
// GetLatestKey should return rv3
latestKey, err := store.GetLatestResourceKey(ctx, MetaGetRequestKey{
Group: key.Group,
Resource: key.Resource,
Namespace: key.Namespace,
Name: key.Name,
})
require.NoError(t, err)
assert.Equal(t, key, latestKey)
assert.Equal(t, rv3, latestKey.ResourceVersion)
}
func TestMetadataStore_GetLatestKey_Deleted(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
key := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: node.Generate().Int64(),
Action: DataActionDeleted,
}
metadata := MetaData{}
err := store.Save(ctx, MetaDataObj{
Key: key,
Value: metadata,
})
require.NoError(t, err)
_, err = store.GetLatestResourceKey(ctx, MetaGetRequestKey{
Group: key.Group,
Resource: key.Resource,
Namespace: key.Namespace,
Name: key.Name,
})
assert.Equal(t, ErrNotFound, err)
}
func TestMetadataStore_GetResourceKeyAtRevision(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
key := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
}
// Create multiple versions
rv1 := node.Generate().Int64()
rv2 := node.Generate().Int64()
rv3 := node.Generate().Int64()
metadata1 := MetaData{}
metadata2 := MetaData{}
metadata3 := MetaData{}
key.ResourceVersion = rv1
key.Action = DataActionCreated
err := store.Save(ctx, MetaDataObj{Key: key, Value: metadata1})
require.NoError(t, err)
key.ResourceVersion = rv2
key.Action = DataActionUpdated
err = store.Save(ctx, MetaDataObj{Key: key, Value: metadata2})
require.NoError(t, err)
key.ResourceVersion = rv3
key.Action = DataActionUpdated
err = store.Save(ctx, MetaDataObj{Key: key, Value: metadata3})
require.NoError(t, err)
// Get key at rv2 should return rv2
metaKey, err := store.GetResourceKeyAtRevision(ctx, MetaGetRequestKey{
Group: key.Group,
Resource: key.Resource,
Namespace: key.Namespace,
Name: key.Name,
}, rv2)
require.NoError(t, err)
assert.Equal(t, rv2, metaKey.ResourceVersion)
assert.Equal(t, DataActionUpdated, metaKey.Action)
// Get key at rv1 should return rv1
metaKey, err = store.GetResourceKeyAtRevision(ctx, MetaGetRequestKey{
Group: key.Group,
Resource: key.Resource,
Namespace: key.Namespace,
Name: key.Name,
}, rv1)
require.NoError(t, err)
assert.Equal(t, rv1, metaKey.ResourceVersion)
assert.Equal(t, DataActionCreated, metaKey.Action)
}
func TestMetadataStore_ListLatestResourceKeys(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
key := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
}
// Save multiple metadata objects
rv1 := node.Generate().Int64()
rv2 := node.Generate().Int64()
metadata1 := MetaData{}
metadata2 := MetaData{}
key.ResourceVersion = rv1
key.Action = DataActionCreated
err := store.Save(ctx, MetaDataObj{
Key: key,
Value: metadata1,
})
require.NoError(t, err)
key.ResourceVersion = rv2
key.Action = DataActionCreated
err = store.Save(ctx, MetaDataObj{
Key: key,
Value: metadata2,
})
require.NoError(t, err)
// List latest metadata keys
resultKeys := make([]MetaDataKey, 0, 1)
for metaKey, err := range store.ListLatestResourceKeys(ctx, MetaListRequestKey{
Group: key.Group,
Resource: key.Resource,
Namespace: key.Namespace,
Name: key.Name,
}) {
require.NoError(t, err)
resultKeys = append(resultKeys, metaKey)
}
assert.Len(t, resultKeys, 1)
assert.Equal(t, key, resultKeys[0])
assert.Equal(t, rv2, resultKeys[0].ResourceVersion)
// Get the metadata to verify
metadata, err := store.Get(ctx, resultKeys[0])
require.NoError(t, err)
assert.Equal(t, metadata2, metadata)
}
func TestMetadataStore_ListResourceKeysAtRevision(t *testing.T) {
store := newMetadataStore(setupTestKV(t))
ctx := context.Background()
// Create multiple resources with different versions
rv1 := node.Generate().Int64()
rv2 := node.Generate().Int64()
rv3 := node.Generate().Int64()
rv4 := node.Generate().Int64()
rv5 := node.Generate().Int64()
// Resource 1: Created at rv1, updated at rv3
key1 := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "resource1",
ResourceVersion: rv1,
Action: DataActionCreated,
}
metadata1 := MetaData{}
err := store.Save(ctx, MetaDataObj{Key: key1, Value: metadata1})
require.NoError(t, err)
key1Updated := key1
key1Updated.ResourceVersion = rv3
key1Updated.Action = DataActionUpdated
metadata1Updated := MetaData{}
err = store.Save(ctx, MetaDataObj{Key: key1Updated, Value: metadata1Updated})
require.NoError(t, err)
// Resource 2: Created at rv2
key2 := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "resource2",
ResourceVersion: rv2,
Action: DataActionCreated,
}
metadata2 := MetaData{}
err = store.Save(ctx, MetaDataObj{Key: key2, Value: metadata2})
require.NoError(t, err)
// Resource 3: Created at rv4
key3 := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "resource3",
ResourceVersion: rv4,
Action: DataActionCreated,
}
metadata3 := MetaData{}
err = store.Save(ctx, MetaDataObj{Key: key3, Value: metadata3})
require.NoError(t, err)
// Resource 4: Created at rv2, deleted at rv5
key4 := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "resource4",
ResourceVersion: rv2,
Action: DataActionCreated,
}
metadata4 := MetaData{}
err = store.Save(ctx, MetaDataObj{Key: key4, Value: metadata4})
require.NoError(t, err)
key4Deleted := key4
key4Deleted.ResourceVersion = rv5
key4Deleted.Action = DataActionDeleted
err = store.Save(ctx, MetaDataObj{Key: key4Deleted, Value: metadata4})
require.NoError(t, err)
t.Run("list at revision rv1 - should return only resource1 initial version", func(t *testing.T) {
resultKeys := make([]MetaDataKey, 0, 1)
for metaKey, err := range store.ListResourceKeysAtRevision(ctx, MetaListRequestKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
}, rv1) {
require.NoError(t, err)
resultKeys = append(resultKeys, metaKey)
}
require.Len(t, resultKeys, 1)
assert.Equal(t, "resource1", resultKeys[0].Name)
assert.Equal(t, rv1, resultKeys[0].ResourceVersion)
assert.Equal(t, DataActionCreated, resultKeys[0].Action)
})
t.Run("list at revision rv2 - should return resource1, resource2 and resource4", func(t *testing.T) {
resultKeys := make([]MetaDataKey, 0, 3)
for metaKey, err := range store.ListResourceKeysAtRevision(ctx, MetaListRequestKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
}, rv2) {
require.NoError(t, err)
resultKeys = append(resultKeys, metaKey)
}
require.Len(t, resultKeys, 3) // resource1, resource2, resource4
names := make(map[string]int64)
for _, result := range resultKeys {
names[result.Name] = result.ResourceVersion
}
assert.Equal(t, rv1, names["resource1"]) // Should be the original version
assert.Equal(t, rv2, names["resource2"])
assert.Equal(t, rv2, names["resource4"])
})
t.Run("list at revision rv3 - should return resource1, resource2 and resource4", func(t *testing.T) {
resultKeys := make([]MetaDataKey, 0, 3)
for metaKey, err := range store.ListResourceKeysAtRevision(ctx, MetaListRequestKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
}, rv3) {
require.NoError(t, err)
resultKeys = append(resultKeys, metaKey)
}
require.Len(t, resultKeys, 3) // resource1 (updated), resource2, resource4
names := make(map[string]int64)
actions := make(map[string]DataAction)
for _, result := range resultKeys {
names[result.Name] = result.ResourceVersion
actions[result.Name] = result.Action
}
assert.Equal(t, rv3, names["resource1"]) // Should be the updated version
assert.Equal(t, DataActionUpdated, actions["resource1"])
assert.Equal(t, rv2, names["resource2"])
assert.Equal(t, rv2, names["resource4"])
})
t.Run("list at revision rv4 - should return", func(t *testing.T) {
resultKeys := make([]MetaDataKey, 0, 4)
for metaKey, err := range store.ListResourceKeysAtRevision(ctx, MetaListRequestKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
}, rv4) {
require.NoError(t, err)
resultKeys = append(resultKeys, metaKey)
}
require.Len(t, resultKeys, 4) // resource1 (updated), resource2, resource3, resource4
names := make(map[string]int64)
for _, result := range resultKeys {
names[result.Name] = result.ResourceVersion
}
assert.Equal(t, rv3, names["resource1"])
assert.Equal(t, rv2, names["resource2"])
assert.Equal(t, rv4, names["resource3"])
assert.Equal(t, rv2, names["resource4"])
})
t.Run("list at revision rv5 - should exclude deleted resource4", func(t *testing.T) {
resultKeys := make([]MetaDataKey, 0, 3)
for metaKey, err := range store.ListResourceKeysAtRevision(ctx, MetaListRequestKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
}, rv5) {
require.NoError(t, err)
resultKeys = append(resultKeys, metaKey)
}
require.Len(t, resultKeys, 3) // resource1 (updated), resource2, resource3 (resource4 excluded because deleted)
names := make(map[string]bool)
for _, result := range resultKeys {
names[result.Name] = true
}
assert.True(t, names["resource1"])
assert.True(t, names["resource2"])
assert.True(t, names["resource3"])
assert.False(t, names["resource4"]) // Should be excluded because it's deleted
})
t.Run("list with specific resource name", func(t *testing.T) {
resultKeys := make([]MetaDataKey, 0, 1)
for metaKey, err := range store.ListResourceKeysAtRevision(ctx, MetaListRequestKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "resource1",
}, rv3) {
require.NoError(t, err)
resultKeys = append(resultKeys, metaKey)
}
require.Len(t, resultKeys, 1)
assert.Equal(t, "resource1", resultKeys[0].Name)
assert.Equal(t, rv3, resultKeys[0].ResourceVersion)
assert.Equal(t, DataActionUpdated, resultKeys[0].Action)
})
t.Run("list at revision 0 should use MaxInt64", func(t *testing.T) {
resultKeys := make([]MetaDataKey, 0, 3)
for metaKey, err := range store.ListResourceKeysAtRevision(ctx, MetaListRequestKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
}, 0) {
require.NoError(t, err)
resultKeys = append(resultKeys, metaKey)
}
// Should return all non-deleted resources at their latest versions
require.Len(t, resultKeys, 3) // resource1 (updated), resource2, resource3
names := make(map[string]bool)
for _, result := range resultKeys {
names[result.Name] = true
}
assert.True(t, names["resource1"])
assert.True(t, names["resource2"])
assert.True(t, names["resource3"])
assert.False(t, names["resource4"]) // Excluded because deleted
})
}
func TestMetadataStore_ListResourceKeysAtRevision_ValidationErrors(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
tests := []struct {
name string
key MetaListRequestKey
}{
{
name: "missing namespace",
key: MetaListRequestKey{
Group: "apps",
Resource: "resource",
},
},
{
name: "missing group",
key: MetaListRequestKey{
Namespace: "default",
Resource: "resource",
},
},
{
name: "missing resource",
key: MetaListRequestKey{
Namespace: "default",
Group: "apps",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var resultKeys []MetaDataKey
for metaKey, err := range store.ListResourceKeysAtRevision(ctx, tt.key, 0) {
if err != nil {
assert.Error(t, err)
return
}
resultKeys = append(resultKeys, metaKey)
}
})
}
}
func TestMetadataStore_ListResourceKeysAtRevision_EmptyResults(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
resultKeys := make([]MetaDataKey, 0, 1)
for metaKey, err := range store.ListResourceKeysAtRevision(ctx, MetaListRequestKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
}, 0) {
require.NoError(t, err)
resultKeys = append(resultKeys, metaKey)
}
assert.Len(t, resultKeys, 0)
}
func TestMetadataStore_ListResourceKeysAtRevision_ResourcesNewerThanRevision(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
// Create a resource with a high resource version
rv := node.Generate().Int64()
key := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "future-resource",
ResourceVersion: rv,
Action: DataActionCreated,
}
metadata := MetaData{}
err := store.Save(ctx, MetaDataObj{Key: key, Value: metadata})
require.NoError(t, err)
// List at a revision before the resource was created
resultKeys := make([]MetaDataKey, 0, 1)
for metaKey, err := range store.ListResourceKeysAtRevision(ctx, MetaListRequestKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
}, rv-1000) {
require.NoError(t, err)
resultKeys = append(resultKeys, metaKey)
}
// Should return no results since the resource is newer than the target revision
assert.Len(t, resultKeys, 0)
}
func TestMetaDataKey_Validate_Valid(t *testing.T) {
validKey := MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionCreated,
Folder: "test-folder",
}
err := validKey.Validate()
assert.NoError(t, err)
}
func TestMetaDataKey_Validate_ValidEdgeCases(t *testing.T) {
tests := []struct {
name string
key MetaDataKey
}{
{
name: "valid with empty folder",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionCreated,
Folder: "", // empty folder should be allowed
},
},
{
name: "valid with single character names",
key: MetaDataKey{
Group: "a",
Resource: "r",
Namespace: "n",
Name: "t",
ResourceVersion: 123,
Action: DataActionCreated,
Folder: "f",
},
},
{
name: "valid with hyphens and dots",
key: MetaDataKey{
Group: "my-group.v1",
Resource: "my-resource.v2",
Namespace: "my-namespace.test",
Name: "my-name.test",
ResourceVersion: 123,
Action: DataActionUpdated,
Folder: "my-folder.test",
},
},
{
name: "valid with all action types",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionDeleted,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.key.Validate()
assert.NoError(t, err)
})
}
}
func TestMetaDataKey_Validate_Invalid(t *testing.T) {
tests := []struct {
name string
key MetaDataKey
wantError string
}{
{
name: "empty group",
key: MetaDataKey{
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionCreated,
},
wantError: "group is required",
},
{
name: "empty resource",
key: MetaDataKey{
Group: "apps",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionCreated,
},
wantError: "resource is required",
},
{
name: "empty namespace",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionCreated,
},
wantError: "namespace is required",
},
{
name: "empty name",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
ResourceVersion: 123,
Action: DataActionCreated,
},
wantError: "name is required",
},
{
name: "zero resource version",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 0,
Action: DataActionCreated,
},
wantError: "resource version must be positive",
},
{
name: "negative resource version",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: -1,
Action: DataActionCreated,
},
wantError: "resource version must be positive",
},
{
name: "empty action",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 123,
},
wantError: "action is required",
},
{
name: "invalid name with uppercase",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "Test-Resource",
ResourceVersion: 123,
Action: DataActionCreated,
},
wantError: "name 'Test-Resource' is invalid",
},
{
name: "invalid namespace with special chars",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default_ns",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionCreated,
},
wantError: "namespace 'default_ns' is invalid",
},
{
name: "invalid group with uppercase",
key: MetaDataKey{
Group: "Apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionCreated,
},
wantError: "group 'Apps' is invalid",
},
{
name: "invalid resource with special chars",
key: MetaDataKey{
Group: "apps",
Resource: "resource_type",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionCreated,
},
wantError: "resource 'resource_type' is invalid",
},
{
name: "invalid folder with special chars",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionCreated,
Folder: "invalid_folder",
},
wantError: "folder 'invalid_folder' is invalid",
},
{
name: "invalid action",
key: MetaDataKey{
Group: "apps",
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: 123,
Action: "invalid",
},
wantError: "action 'invalid' is invalid",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.key.Validate()
assert.Error(t, err)
assert.Contains(t, err.Error(), tt.wantError)
})
}
}
func TestMetaDataKey_SameResource(t *testing.T) {
baseKey := MetaDataKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-resource",
ResourceVersion: 123,
Action: DataActionCreated,
Folder: "test-folder",
}
tests := []struct {
name string
key1 MetaDataKey
key2 MetaDataKey
expected bool
}{
{
name: "identical keys",
key1: baseKey,
key2: baseKey,
expected: true,
},
{
name: "same identifying fields, different resource version",
key1: baseKey,
key2: MetaDataKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-resource",
ResourceVersion: 456, // Different resource version
Action: DataActionUpdated,
Folder: "other-folder",
},
expected: true, // Should still be equal as ResourceVersion, Action, and Folder don't matter
},
{
name: "different namespace",
key1: baseKey,
key2: MetaDataKey{
Namespace: "other-namespace",
Group: "apps",
Resource: "resource",
Name: "test-Resource",
ResourceVersion: 123,
Action: DataActionCreated,
Folder: "test-folder",
},
expected: false,
},
{
name: "different group",
key1: baseKey,
key2: MetaDataKey{
Namespace: "default",
Group: "extensions",
Resource: "resource",
Name: "test-Resource",
ResourceVersion: 123,
Action: DataActionCreated,
Folder: "test-folder",
},
expected: false,
},
{
name: "different resource",
key1: baseKey,
key2: MetaDataKey{
Namespace: "default",
Group: "apps",
Resource: "daemonsets",
Name: "test-Resource",
ResourceVersion: 123,
Action: DataActionCreated,
Folder: "test-folder",
},
expected: false,
},
{
name: "different name",
key1: baseKey,
key2: MetaDataKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "other-Resource",
ResourceVersion: 123,
Action: DataActionCreated,
Folder: "test-folder",
},
expected: false,
},
{
name: "empty keys",
key1: MetaDataKey{},
key2: MetaDataKey{},
expected: true,
},
{
name: "one empty key",
key1: baseKey,
key2: MetaDataKey{},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.key1.SameResource(tt.key2)
assert.Equal(t, tt.expected, result)
// Test symmetry: SameResource should be commutative
reverseResult := tt.key2.SameResource(tt.key1)
assert.Equal(t, result, reverseResult, "SameResource method should be commutative")
})
}
}
func TestMetadataStore_Save_InvalidKey(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
key := MetaDataKey{
Group: "", // invalid: empty group
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: node.Generate().Int64(),
Action: DataActionCreated,
}
metadata := MetaData{}
err := store.Save(ctx, MetaDataObj{
Key: key,
Value: metadata,
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid metadata key")
assert.Contains(t, err.Error(), "group is required")
}
func TestMetadataStore_Get_InvalidKey(t *testing.T) {
store := setupTestMetadataStore(t)
ctx := context.Background()
key := MetaDataKey{
Group: "", // invalid: empty group
Resource: "resource",
Namespace: "default",
Name: "test-resource",
ResourceVersion: node.Generate().Int64(),
Action: DataActionCreated,
}
_, err := store.Get(ctx, key)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid metadata key")
assert.Contains(t, err.Error(), "group is required")
}
func TestMetaListRequestKey_Validate(t *testing.T) {
tests := []struct {
name string
key MetaListRequestKey
expectErr bool
}{
{
name: "valid key with all fields",
key: MetaListRequestKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-resource",
},
expectErr: false,
},
{
name: "valid key without name (for listing multiple resources)",
key: MetaListRequestKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
},
expectErr: false,
},
{
name: "invalid key: name provided when namespace is empty",
key: MetaListRequestKey{
Group: "apps",
Resource: "resource",
Name: "test-resource",
},
expectErr: true,
},
{
name: "valid key with empty namespace and no name",
key: MetaListRequestKey{
Namespace: "",
Group: "apps",
Resource: "resource",
},
expectErr: false,
},
{
name: "missing group",
key: MetaListRequestKey{
Namespace: "default",
Resource: "resource",
Name: "test-Resource",
},
expectErr: true,
},
{
name: "missing resource",
key: MetaListRequestKey{
Namespace: "default",
Group: "apps",
Name: "test-Resource",
},
expectErr: true,
},
{
name: "invalid namespace",
key: MetaListRequestKey{
Namespace: "Default",
Group: "apps",
Resource: "resource",
},
expectErr: true,
},
{
name: "invalid name",
key: MetaListRequestKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "Test-Resource",
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.key.Validate()
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestMetaGetRequestKey_Validate(t *testing.T) {
tests := []struct {
name string
key MetaGetRequestKey
expectErr bool
}{
{
name: "valid key",
key: MetaGetRequestKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-resource",
},
expectErr: false,
},
{
name: "missing namespace",
key: MetaGetRequestKey{
Group: "apps",
Resource: "resource",
Name: "test- ",
},
expectErr: true,
},
{
name: "missing group",
key: MetaGetRequestKey{
Namespace: "default",
Resource: "resource",
Name: "test- ",
},
expectErr: true,
},
{
name: "missing resource",
key: MetaGetRequestKey{
Namespace: "default",
Group: "apps",
Name: "test- ",
},
expectErr: true,
},
{
name: "missing name",
key: MetaGetRequestKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
},
expectErr: true,
},
{
name: "invalid namespace",
key: MetaGetRequestKey{
Namespace: "Default",
Group: "apps",
Resource: "resource",
Name: "test- ",
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.key.Validate()
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestMetaListRequestKey_Prefix(t *testing.T) {
tests := []struct {
name string
key MetaListRequestKey
expectedPrefix string
}{
{
name: "full key with name",
key: MetaListRequestKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-resource",
},
expectedPrefix: "apps/resource/default/test-resource/",
},
{
name: "key without name",
key: MetaListRequestKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
},
expectedPrefix: "apps/resource/default/",
},
{
name: "key without namespace and without name",
key: MetaListRequestKey{
Namespace: "",
Group: "apps",
Resource: "resource",
},
expectedPrefix: "apps/resource/",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
prefix := tt.key.Prefix()
assert.Equal(t, tt.expectedPrefix, prefix)
})
}
}
func TestMetaGetRequestKey_Prefix(t *testing.T) {
key := MetaGetRequestKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test- ",
}
expectedPrefix := "apps/resource/default/test- /"
prefix := key.Prefix()
assert.Equal(t, expectedPrefix, prefix)
}