Files
grafana/pkg/storage/unified/resource/eventstore_test.go
Georges Chaudy 4a272fb61b unistore: add eventstore and notifier (#107182)
* Add datastore

* too many slashes

* lint

* add metadata store

* simplify meta

* Add eventstore

* golint

* lint

* Add datastore

* too many slashes

* lint

* pr comments

* extract ParseKey

* readcloser

* remove get prefix

* use dedicated keys

* parsekey

* sameresource

* unrelated

* name

* renmae tests

* add key validation

* fix tests

* refactor a bit

* lint

* allow empty ns

* get keys instead of list

* rename the functions

* refactor yield candidate

* update test

* lint

* missing err check

* address comments

* increase the timeout
2025-06-30 11:20:57 +02:00

399 lines
9.3 KiB
Go

package resource
import (
"context"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func setupTestEventStore(t *testing.T) *eventStore {
db := setupTestBadgerDB(t)
t.Cleanup(func() {
err := db.Close()
require.NoError(t, err)
})
kv := NewBadgerKV(db)
return newEventStore(kv)
}
func TestNewEventStore(t *testing.T) {
store := setupTestEventStore(t)
assert.NotNil(t, store.kv)
}
func TestEventKey_String(t *testing.T) {
tests := []struct {
name string
eventKey EventKey
expected string
}{
{
name: "basic event key",
eventKey: EventKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-resource",
ResourceVersion: 1000,
},
expected: "1000~default~apps~resource~test-resource",
},
{
name: "empty namespace",
eventKey: EventKey{
Namespace: "",
Group: "apps",
Resource: "resource",
Name: "test-resource",
ResourceVersion: 2000,
},
expected: "2000~~apps~resource~test-resource",
},
{
name: "special characters in name",
eventKey: EventKey{
Namespace: "test-ns",
Group: "apps",
Resource: "resource",
Name: "test-resource-with-dashes",
ResourceVersion: 3000,
},
expected: "3000~test-ns~apps~resource~test-resource-with-dashes",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.eventKey.String()
assert.Equal(t, tt.expected, result)
})
}
}
func TestEventKey_Validate(t *testing.T) {
tests := []struct {
name string
key string
expected EventKey
expectError bool
}{
{
name: "valid key",
key: "1000~default~apps~resource~test-resource",
expected: EventKey{
ResourceVersion: 1000,
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-resource",
},
},
{
name: "empty namespace",
key: "2000~~apps~resource~test-resource",
expected: EventKey{
ResourceVersion: 2000,
Namespace: "",
Group: "apps",
Resource: "resource",
Name: "test-resource",
},
},
{
name: "special characters in name",
key: "3000~test-ns~apps~resource~test-resource-with-dashes",
expected: EventKey{
ResourceVersion: 3000,
Namespace: "test-ns",
Group: "apps",
Resource: "resource",
Name: "test-resource-with-dashes",
},
},
{
name: "invalid key - too few parts",
key: "1000~default~apps~resource",
expectError: true,
},
{
name: "invalid key - too many parts",
key: "1000~default~apps~resource~test~extra",
expectError: true,
},
{
name: "invalid resource version",
key: "invalid~default~apps~resource~test",
expectError: true,
},
{
name: "empty key",
key: "",
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseEventKey(tt.key)
if tt.expectError {
assert.Error(t, err)
assert.Equal(t, EventKey{}, result)
} else {
require.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}
func TestEventStore_ParseEventKey(t *testing.T) {
originalKey := EventKey{
ResourceVersion: 1234567890,
Namespace: "test-namespace",
Group: "apps",
Resource: "resource",
Name: "test-resource",
}
// Convert to string and back
keyString := originalKey.String()
parsedKey, err := ParseEventKey(keyString)
require.NoError(t, err)
assert.Equal(t, originalKey, parsedKey)
}
func TestEventStore_Save_Get(t *testing.T) {
ctx := context.Background()
store := setupTestEventStore(t)
event := Event{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-resource",
ResourceVersion: 1000,
Action: DataActionCreated,
Folder: "test-folder",
PreviousRV: 999,
}
// Save the event
err := store.Save(ctx, event)
require.NoError(t, err)
// Get the event back
eventKey := EventKey{
Namespace: event.Namespace,
Group: event.Group,
Resource: event.Resource,
Name: event.Name,
ResourceVersion: event.ResourceVersion,
}
retrievedEvent, err := store.Get(ctx, eventKey)
require.NoError(t, err)
assert.Equal(t, event, retrievedEvent)
}
func TestEventStore_Get_NotFound(t *testing.T) {
ctx := context.Background()
store := setupTestEventStore(t)
nonExistentKey := EventKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "non-existent",
ResourceVersion: 9999,
}
_, err := store.Get(ctx, nonExistentKey)
assert.Error(t, err)
}
func TestEventStore_LastEventKey(t *testing.T) {
ctx := context.Background()
store := setupTestEventStore(t)
// Test when no events exist
_, err := store.LastEventKey(ctx)
assert.Error(t, err)
assert.Equal(t, ErrNotFound, err)
// Add some events with different resource versions
events := []Event{
{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-1",
ResourceVersion: 1000,
Action: DataActionCreated,
},
{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-2",
ResourceVersion: 3000, // highest
Action: DataActionCreated,
},
{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-3",
ResourceVersion: 2000,
Action: DataActionCreated,
},
}
// Save all events
for _, event := range events {
err := store.Save(ctx, event)
require.NoError(t, err)
}
// Get the last event key (should be the one with highest RV)
lastKey, err := store.LastEventKey(ctx)
require.NoError(t, err)
expectedKey := EventKey{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-2",
ResourceVersion: 3000,
}
assert.Equal(t, expectedKey, lastKey)
}
func TestEventStore_ListSince(t *testing.T) {
ctx := context.Background()
store := setupTestEventStore(t)
// Add events with different resource versions
events := []Event{
{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-1",
ResourceVersion: 1000,
Action: DataActionCreated,
},
{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-2",
ResourceVersion: 2000,
Action: DataActionUpdated,
},
{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-3",
ResourceVersion: 3000,
Action: DataActionDeleted,
},
}
// Save all events
for _, event := range events {
err := store.Save(ctx, event)
require.NoError(t, err)
}
// List events since RV 1500 (should get events with RV 2000 and 3000)
retrievedEvents := make([]Event, 0, 2)
for event, err := range store.ListSince(ctx, 1500) {
require.NoError(t, err)
retrievedEvents = append(retrievedEvents, event)
}
// Should return events in descending order of resource version
require.Len(t, retrievedEvents, 2)
assert.Equal(t, int64(2000), retrievedEvents[0].ResourceVersion)
assert.Equal(t, int64(3000), retrievedEvents[1].ResourceVersion)
}
func TestEventStore_ListSince_Empty(t *testing.T) {
ctx := context.Background()
store := setupTestEventStore(t)
// List events when store is empty
retrievedEvents := make([]Event, 0)
for event, err := range store.ListSince(ctx, 0) {
require.NoError(t, err)
retrievedEvents = append(retrievedEvents, event)
}
assert.Empty(t, retrievedEvents)
}
func TestEvent_JSONSerialization(t *testing.T) {
event := Event{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test-resource",
ResourceVersion: 1000,
Action: DataActionCreated,
Folder: "test-folder",
PreviousRV: 999,
}
// Serialize to JSON
data, err := json.Marshal(event)
require.NoError(t, err)
// Deserialize from JSON
var deserializedEvent Event
err = json.Unmarshal(data, &deserializedEvent)
require.NoError(t, err)
assert.Equal(t, event, deserializedEvent)
}
func TestEventKey_Struct(t *testing.T) {
key := EventKey{
Namespace: "test-namespace",
Group: "apps",
Resource: "resource",
Name: "test-resource",
ResourceVersion: 1234567890,
}
assert.Equal(t, "test-namespace", key.Namespace)
assert.Equal(t, "apps", key.Group)
assert.Equal(t, "resource", key.Resource)
assert.Equal(t, "test-resource", key.Name)
assert.Equal(t, int64(1234567890), key.ResourceVersion)
}
func TestEventStore_Save_InvalidJSON(t *testing.T) {
ctx := context.Background()
store := setupTestEventStore(t)
// This should work fine as the Event struct should be serializable
event := Event{
Namespace: "default",
Group: "apps",
Resource: "resource",
Name: "test",
ResourceVersion: 1000,
Action: DataActionCreated,
}
err := store.Save(ctx, event)
assert.NoError(t, err)
}