mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
380 lines
11 KiB
Go
380 lines
11 KiB
Go
package object_server_tests
|
|
|
|
import (
|
|
"context"
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana/pkg/services/store/object"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc/metadata"
|
|
)
|
|
|
|
func createContentsHash(contents []byte) string {
|
|
hash := md5.Sum(contents)
|
|
return hex.EncodeToString(hash[:])
|
|
}
|
|
|
|
type rawObjectMatcher struct {
|
|
uid *string
|
|
kind *string
|
|
createdRange []time.Time
|
|
updatedRange []time.Time
|
|
createdBy string
|
|
updatedBy string
|
|
body []byte
|
|
version *string
|
|
}
|
|
|
|
type objectVersionMatcher struct {
|
|
updatedRange []time.Time
|
|
updatedBy string
|
|
version *string
|
|
etag *string
|
|
comment *string
|
|
}
|
|
|
|
func timestampInRange(ts int64, tsRange []time.Time) bool {
|
|
return ts >= tsRange[0].Unix() && ts <= tsRange[1].Unix()
|
|
}
|
|
|
|
func requireObjectMatch(t *testing.T, obj *object.RawObject, m rawObjectMatcher) {
|
|
t.Helper()
|
|
require.NotNil(t, obj)
|
|
|
|
mismatches := ""
|
|
if m.uid != nil && *m.uid != obj.UID {
|
|
mismatches += fmt.Sprintf("expected UID: %s, actual UID: %s\n", *m.uid, obj.UID)
|
|
}
|
|
|
|
if m.kind != nil && *m.kind != obj.Kind {
|
|
mismatches += fmt.Sprintf("expected kind: %s, actual kind: %s\n", *m.kind, obj.Kind)
|
|
}
|
|
|
|
if len(m.createdRange) == 2 && !timestampInRange(obj.Created, m.createdRange) {
|
|
mismatches += fmt.Sprintf("expected createdBy range: [from %s to %s], actual created: %s\n", m.createdRange[0], m.createdRange[1], time.Unix(obj.Created, 0))
|
|
}
|
|
|
|
if len(m.updatedRange) == 2 && !timestampInRange(obj.Updated, m.updatedRange) {
|
|
mismatches += fmt.Sprintf("expected updatedRange range: [from %s to %s], actual updated: %s\n", m.updatedRange[0], m.updatedRange[1], time.Unix(obj.Updated, 0))
|
|
}
|
|
|
|
if m.createdBy != "" && m.createdBy != obj.CreatedBy {
|
|
mismatches += fmt.Sprintf("createdBy: expected:%s, found:%s\n", m.createdBy, obj.CreatedBy)
|
|
}
|
|
|
|
if m.updatedBy != "" && m.updatedBy != obj.UpdatedBy {
|
|
mismatches += fmt.Sprintf("updatedBy: expected:%s, found:%s\n", m.updatedBy, obj.UpdatedBy)
|
|
}
|
|
|
|
if !reflect.DeepEqual(m.body, obj.Body) {
|
|
mismatches += fmt.Sprintf("expected body len: %d, actual body len: %d\n", len(m.body), len(obj.Body))
|
|
}
|
|
|
|
expectedHash := createContentsHash(m.body)
|
|
actualHash := createContentsHash(obj.Body)
|
|
if expectedHash != actualHash {
|
|
mismatches += fmt.Sprintf("expected body hash: %s, actual body hash: %s\n", expectedHash, actualHash)
|
|
}
|
|
|
|
if m.version != nil && *m.version != obj.Version {
|
|
mismatches += fmt.Sprintf("expected version: %s, actual version: %s\n", *m.version, obj.Version)
|
|
}
|
|
|
|
require.True(t, len(mismatches) == 0, mismatches)
|
|
}
|
|
|
|
func requireVersionMatch(t *testing.T, obj *object.ObjectVersionInfo, m objectVersionMatcher) {
|
|
t.Helper()
|
|
mismatches := ""
|
|
|
|
if m.etag != nil && *m.etag != obj.ETag {
|
|
mismatches += fmt.Sprintf("expected etag: %s, actual etag: %s\n", *m.etag, obj.ETag)
|
|
}
|
|
|
|
if len(m.updatedRange) == 2 && !timestampInRange(obj.Updated, m.updatedRange) {
|
|
mismatches += fmt.Sprintf("expected updatedRange range: [from %s to %s], actual updated: %s\n", m.updatedRange[0], m.updatedRange[1], time.Unix(obj.Updated, 0))
|
|
}
|
|
|
|
if m.updatedBy != "" && m.updatedBy != obj.UpdatedBy {
|
|
mismatches += fmt.Sprintf("updatedBy: expected:%s, found:%s\n", m.updatedBy, obj.UpdatedBy)
|
|
}
|
|
|
|
if m.version != nil && *m.version != obj.Version {
|
|
mismatches += fmt.Sprintf("expected version: %s, actual version: %s\n", *m.version, obj.Version)
|
|
}
|
|
|
|
require.True(t, len(mismatches) == 0, mismatches)
|
|
}
|
|
|
|
func TestObjectServer(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
testCtx := createTestContext(t)
|
|
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", fmt.Sprintf("Bearer %s", testCtx.authToken))
|
|
|
|
fakeUser := fmt.Sprintf("user:%d:%s", testCtx.user.UserID, testCtx.user.Login)
|
|
firstVersion := "1"
|
|
kind := "dummy"
|
|
uid := "my-test-entity"
|
|
body := []byte("{\"name\":\"John\"}")
|
|
|
|
t.Run("should not retrieve non-existent objects", func(t *testing.T) {
|
|
resp, err := testCtx.client.Read(ctx, &object.ReadObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, resp)
|
|
require.Nil(t, resp.Object)
|
|
})
|
|
|
|
t.Run("should be able to read persisted objects", func(t *testing.T) {
|
|
before := time.Now()
|
|
writeReq := &object.WriteObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
Body: body,
|
|
Comment: "first entity!",
|
|
}
|
|
writeResp, err := testCtx.client.Write(ctx, writeReq)
|
|
require.NoError(t, err)
|
|
|
|
versionMatcher := objectVersionMatcher{
|
|
updatedRange: []time.Time{before, time.Now()},
|
|
updatedBy: fakeUser,
|
|
version: &firstVersion,
|
|
comment: &writeReq.Comment,
|
|
}
|
|
requireVersionMatch(t, writeResp.Object, versionMatcher)
|
|
|
|
readResp, err := testCtx.client.Read(ctx, &object.ReadObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
Version: "",
|
|
WithBody: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Nil(t, readResp.SummaryJson)
|
|
|
|
objectMatcher := rawObjectMatcher{
|
|
uid: &uid,
|
|
kind: &kind,
|
|
createdRange: []time.Time{before, time.Now()},
|
|
updatedRange: []time.Time{before, time.Now()},
|
|
createdBy: fakeUser,
|
|
updatedBy: fakeUser,
|
|
body: body,
|
|
version: &firstVersion,
|
|
}
|
|
requireObjectMatch(t, readResp.Object, objectMatcher)
|
|
|
|
deleteResp, err := testCtx.client.Delete(ctx, &object.DeleteObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
PreviousVersion: writeResp.Object.Version,
|
|
})
|
|
require.NoError(t, err)
|
|
require.True(t, deleteResp.OK)
|
|
|
|
readRespAfterDelete, err := testCtx.client.Read(ctx, &object.ReadObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
Version: "",
|
|
WithBody: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Nil(t, readRespAfterDelete.Object)
|
|
})
|
|
|
|
t.Run("should be able to update an object", func(t *testing.T) {
|
|
before := time.Now()
|
|
writeReq1 := &object.WriteObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
Body: body,
|
|
Comment: "first entity!",
|
|
}
|
|
writeResp1, err := testCtx.client.Write(ctx, writeReq1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, object.WriteObjectResponse_CREATED, writeResp1.Status)
|
|
|
|
body2 := []byte("{\"name\":\"John2\"}")
|
|
|
|
writeReq2 := &object.WriteObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
Body: body2,
|
|
Comment: "update1",
|
|
}
|
|
writeResp2, err := testCtx.client.Write(ctx, writeReq2)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, writeResp1.Object.Version, writeResp2.Object.Version)
|
|
|
|
// Duplicate write (no change)
|
|
writeDupRsp, err := testCtx.client.Write(ctx, writeReq2)
|
|
require.NoError(t, err)
|
|
require.Nil(t, writeDupRsp.Error)
|
|
require.Equal(t, object.WriteObjectResponse_UNCHANGED, writeDupRsp.Status)
|
|
require.Equal(t, writeResp2.Object.Version, writeDupRsp.Object.Version)
|
|
require.Equal(t, writeResp2.Object.ETag, writeDupRsp.Object.ETag)
|
|
|
|
body3 := []byte("{\"name\":\"John3\"}")
|
|
writeReq3 := &object.WriteObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
Body: body3,
|
|
Comment: "update3",
|
|
}
|
|
writeResp3, err := testCtx.client.Write(ctx, writeReq3)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, writeResp3.Object.Version, writeResp2.Object.Version)
|
|
|
|
latestMatcher := rawObjectMatcher{
|
|
uid: &uid,
|
|
kind: &kind,
|
|
createdRange: []time.Time{before, time.Now()},
|
|
updatedRange: []time.Time{before, time.Now()},
|
|
createdBy: fakeUser,
|
|
updatedBy: fakeUser,
|
|
body: body3,
|
|
version: &writeResp3.Object.Version,
|
|
}
|
|
readRespLatest, err := testCtx.client.Read(ctx, &object.ReadObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
Version: "", // latest
|
|
WithBody: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Nil(t, readRespLatest.SummaryJson)
|
|
requireObjectMatch(t, readRespLatest.Object, latestMatcher)
|
|
|
|
readRespFirstVer, err := testCtx.client.Read(ctx, &object.ReadObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
Version: writeResp1.Object.Version,
|
|
WithBody: true,
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
require.Nil(t, readRespFirstVer.SummaryJson)
|
|
require.NotNil(t, readRespFirstVer.Object)
|
|
requireObjectMatch(t, readRespFirstVer.Object, rawObjectMatcher{
|
|
uid: &uid,
|
|
kind: &kind,
|
|
createdRange: []time.Time{before, time.Now()},
|
|
updatedRange: []time.Time{before, time.Now()},
|
|
createdBy: fakeUser,
|
|
updatedBy: fakeUser,
|
|
body: body,
|
|
version: &firstVersion,
|
|
})
|
|
|
|
history, err := testCtx.client.History(ctx, &object.ObjectHistoryRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, []*object.ObjectVersionInfo{
|
|
writeResp3.Object,
|
|
writeResp2.Object,
|
|
writeResp1.Object,
|
|
}, history.Versions)
|
|
|
|
deleteResp, err := testCtx.client.Delete(ctx, &object.DeleteObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
PreviousVersion: writeResp3.Object.Version,
|
|
})
|
|
require.NoError(t, err)
|
|
require.True(t, deleteResp.OK)
|
|
})
|
|
|
|
t.Run("should be able to search for objects", func(t *testing.T) {
|
|
uid2 := "uid2"
|
|
uid3 := "uid3"
|
|
uid4 := "uid4"
|
|
kind2 := "kind2"
|
|
w1, err := testCtx.client.Write(ctx, &object.WriteObjectRequest{
|
|
UID: uid,
|
|
Kind: kind,
|
|
Body: body,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
w2, err := testCtx.client.Write(ctx, &object.WriteObjectRequest{
|
|
UID: uid2,
|
|
Kind: kind,
|
|
Body: body,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
w3, err := testCtx.client.Write(ctx, &object.WriteObjectRequest{
|
|
UID: uid3,
|
|
Kind: kind2,
|
|
Body: body,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
w4, err := testCtx.client.Write(ctx, &object.WriteObjectRequest{
|
|
UID: uid4,
|
|
Kind: kind2,
|
|
Body: body,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
search, err := testCtx.client.Search(ctx, &object.ObjectSearchRequest{
|
|
Kind: []string{kind, kind2},
|
|
WithBody: false,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, search)
|
|
uids := make([]string, 0, len(search.Results))
|
|
kinds := make([]string, 0, len(search.Results))
|
|
version := make([]string, 0, len(search.Results))
|
|
for _, res := range search.Results {
|
|
uids = append(uids, res.UID)
|
|
kinds = append(kinds, res.Kind)
|
|
version = append(version, res.Version)
|
|
}
|
|
require.Equal(t, []string{"my-test-entity", "uid2", "uid3", "uid4"}, uids)
|
|
require.Equal(t, []string{"dummy", "dummy", "kind2", "kind2"}, kinds)
|
|
require.Equal(t, []string{
|
|
w1.Object.Version,
|
|
w2.Object.Version,
|
|
w3.Object.Version,
|
|
w4.Object.Version,
|
|
}, version)
|
|
|
|
// Again with only one kind
|
|
searchKind1, err := testCtx.client.Search(ctx, &object.ObjectSearchRequest{
|
|
Kind: []string{kind},
|
|
})
|
|
require.NoError(t, err)
|
|
uids = make([]string, 0, len(searchKind1.Results))
|
|
kinds = make([]string, 0, len(searchKind1.Results))
|
|
version = make([]string, 0, len(searchKind1.Results))
|
|
for _, res := range searchKind1.Results {
|
|
uids = append(uids, res.UID)
|
|
kinds = append(kinds, res.Kind)
|
|
version = append(version, res.Version)
|
|
}
|
|
require.Equal(t, []string{"my-test-entity", "uid2"}, uids)
|
|
require.Equal(t, []string{"dummy", "dummy"}, kinds)
|
|
require.Equal(t, []string{
|
|
w1.Object.Version,
|
|
w2.Object.Version,
|
|
}, version)
|
|
})
|
|
}
|