EntityStore: Rename ObjectStore to EntityStore (part 1) (#59613)
This commit is contained in:
@@ -0,0 +1,382 @@
|
||||
package entity_server_tests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/store"
|
||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
type rawEntityMatcher struct {
|
||||
grn *entity.GRN
|
||||
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 {
|
||||
low := tsRange[0].UnixMilli() - 1
|
||||
high := tsRange[1].UnixMilli() + 1
|
||||
return ts >= low && ts <= high
|
||||
}
|
||||
|
||||
func requireEntityMatch(t *testing.T, obj *entity.Entity, m rawEntityMatcher) {
|
||||
t.Helper()
|
||||
require.NotNil(t, obj)
|
||||
|
||||
mismatches := ""
|
||||
if m.grn != nil {
|
||||
if m.grn.TenantId > 0 && m.grn.TenantId != obj.GRN.TenantId {
|
||||
mismatches += fmt.Sprintf("expected tenant: %d, actual: %d\n", m.grn.TenantId, obj.GRN.TenantId)
|
||||
}
|
||||
if m.grn.Kind != "" && m.grn.Kind != obj.GRN.Kind {
|
||||
mismatches += fmt.Sprintf("expected Kind: %s, actual: %s\n", m.grn.Kind, obj.GRN.Kind)
|
||||
}
|
||||
if m.grn.UID != "" && m.grn.UID != obj.GRN.UID {
|
||||
mismatches += fmt.Sprintf("expected UID: %s, actual: %s\n", m.grn.UID, obj.GRN.UID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(m.createdRange) == 2 && !timestampInRange(obj.CreatedAt, m.createdRange) {
|
||||
mismatches += fmt.Sprintf("expected Created range: [from %s to %s], actual created: %s\n", m.createdRange[0], m.createdRange[1], time.UnixMilli(obj.CreatedAt))
|
||||
}
|
||||
|
||||
if len(m.updatedRange) == 2 && !timestampInRange(obj.UpdatedAt, m.updatedRange) {
|
||||
mismatches += fmt.Sprintf("expected Updated range: [from %s to %s], actual updated: %s\n", m.updatedRange[0], m.updatedRange[1], time.UnixMilli(obj.UpdatedAt))
|
||||
}
|
||||
|
||||
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 len(m.body) > 0 {
|
||||
if json.Valid(m.body) {
|
||||
require.JSONEq(t, string(m.body), string(obj.Body), "expecting same body")
|
||||
} else 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))
|
||||
}
|
||||
}
|
||||
|
||||
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 *entity.EntityVersionInfo, 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.UpdatedAt, m.updatedRange) {
|
||||
mismatches += fmt.Sprintf("expected updatedRange range: [from %s to %s], actual updated: %s\n", m.updatedRange[0], m.updatedRange[1], time.UnixMilli(obj.UpdatedAt))
|
||||
}
|
||||
|
||||
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 TestIntegrationEntityServer(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
testCtx := createTestContext(t)
|
||||
ctx := metadata.AppendToOutgoingContext(testCtx.ctx, "authorization", fmt.Sprintf("Bearer %s", testCtx.authToken))
|
||||
|
||||
fakeUser := store.GetUserIDString(testCtx.user)
|
||||
firstVersion := "1"
|
||||
kind := models.StandardKindJSONObj
|
||||
grn := &entity.GRN{
|
||||
Kind: kind,
|
||||
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, &entity.ReadEntityRequest{
|
||||
GRN: grn,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotNil(t, resp)
|
||||
require.Nil(t, resp.Entity)
|
||||
})
|
||||
|
||||
t.Run("should be able to read persisted objects", func(t *testing.T) {
|
||||
before := time.Now()
|
||||
writeReq := &entity.WriteEntityRequest{
|
||||
GRN: grn,
|
||||
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.Entity, versionMatcher)
|
||||
|
||||
readResp, err := testCtx.client.Read(ctx, &entity.ReadEntityRequest{
|
||||
GRN: grn,
|
||||
Version: "",
|
||||
WithBody: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, readResp.SummaryJson)
|
||||
require.NotNil(t, readResp.Entity)
|
||||
|
||||
foundGRN := readResp.Entity.GRN
|
||||
require.NotNil(t, foundGRN)
|
||||
require.Equal(t, testCtx.user.OrgID, foundGRN.TenantId) // orgId becomes the tenant id when not set
|
||||
require.Equal(t, grn.Kind, foundGRN.Kind)
|
||||
require.Equal(t, grn.UID, foundGRN.UID)
|
||||
|
||||
objectMatcher := rawEntityMatcher{
|
||||
grn: grn,
|
||||
createdRange: []time.Time{before, time.Now()},
|
||||
updatedRange: []time.Time{before, time.Now()},
|
||||
createdBy: fakeUser,
|
||||
updatedBy: fakeUser,
|
||||
body: body,
|
||||
version: &firstVersion,
|
||||
}
|
||||
requireEntityMatch(t, readResp.Entity, objectMatcher)
|
||||
|
||||
deleteResp, err := testCtx.client.Delete(ctx, &entity.DeleteEntityRequest{
|
||||
GRN: grn,
|
||||
PreviousVersion: writeResp.Entity.Version,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.True(t, deleteResp.OK)
|
||||
|
||||
readRespAfterDelete, err := testCtx.client.Read(ctx, &entity.ReadEntityRequest{
|
||||
GRN: grn,
|
||||
Version: "",
|
||||
WithBody: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, readRespAfterDelete.Entity)
|
||||
})
|
||||
|
||||
t.Run("should be able to update an object", func(t *testing.T) {
|
||||
before := time.Now()
|
||||
grn := &entity.GRN{
|
||||
Kind: kind,
|
||||
UID: util.GenerateShortUID(),
|
||||
}
|
||||
|
||||
writeReq1 := &entity.WriteEntityRequest{
|
||||
GRN: grn,
|
||||
Body: body,
|
||||
Comment: "first entity!",
|
||||
}
|
||||
writeResp1, err := testCtx.client.Write(ctx, writeReq1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, entity.WriteEntityResponse_CREATED, writeResp1.Status)
|
||||
|
||||
body2 := []byte("{\"name\":\"John2\"}")
|
||||
|
||||
writeReq2 := &entity.WriteEntityRequest{
|
||||
GRN: grn,
|
||||
Body: body2,
|
||||
Comment: "update1",
|
||||
}
|
||||
writeResp2, err := testCtx.client.Write(ctx, writeReq2)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, writeResp1.Entity.Version, writeResp2.Entity.Version)
|
||||
|
||||
// Duplicate write (no change)
|
||||
writeDupRsp, err := testCtx.client.Write(ctx, writeReq2)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, writeDupRsp.Error)
|
||||
require.Equal(t, entity.WriteEntityResponse_UNCHANGED, writeDupRsp.Status)
|
||||
require.Equal(t, writeResp2.Entity.Version, writeDupRsp.Entity.Version)
|
||||
require.Equal(t, writeResp2.Entity.ETag, writeDupRsp.Entity.ETag)
|
||||
|
||||
body3 := []byte("{\"name\":\"John3\"}")
|
||||
writeReq3 := &entity.WriteEntityRequest{
|
||||
GRN: grn,
|
||||
Body: body3,
|
||||
Comment: "update3",
|
||||
}
|
||||
writeResp3, err := testCtx.client.Write(ctx, writeReq3)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, writeResp3.Entity.Version, writeResp2.Entity.Version)
|
||||
|
||||
latestMatcher := rawEntityMatcher{
|
||||
grn: grn,
|
||||
createdRange: []time.Time{before, time.Now()},
|
||||
updatedRange: []time.Time{before, time.Now()},
|
||||
createdBy: fakeUser,
|
||||
updatedBy: fakeUser,
|
||||
body: body3,
|
||||
version: &writeResp3.Entity.Version,
|
||||
}
|
||||
readRespLatest, err := testCtx.client.Read(ctx, &entity.ReadEntityRequest{
|
||||
GRN: grn,
|
||||
Version: "", // latest
|
||||
WithBody: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, readRespLatest.SummaryJson)
|
||||
requireEntityMatch(t, readRespLatest.Entity, latestMatcher)
|
||||
|
||||
readRespFirstVer, err := testCtx.client.Read(ctx, &entity.ReadEntityRequest{
|
||||
GRN: grn,
|
||||
Version: writeResp1.Entity.Version,
|
||||
WithBody: true,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, readRespFirstVer.SummaryJson)
|
||||
require.NotNil(t, readRespFirstVer.Entity)
|
||||
requireEntityMatch(t, readRespFirstVer.Entity, rawEntityMatcher{
|
||||
grn: grn,
|
||||
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, &entity.EntityHistoryRequest{
|
||||
GRN: grn,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []*entity.EntityVersionInfo{
|
||||
writeResp3.Entity,
|
||||
writeResp2.Entity,
|
||||
writeResp1.Entity,
|
||||
}, history.Versions)
|
||||
|
||||
deleteResp, err := testCtx.client.Delete(ctx, &entity.DeleteEntityRequest{
|
||||
GRN: grn,
|
||||
PreviousVersion: writeResp3.Entity.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 := models.StandardKindPlaylist
|
||||
w1, err := testCtx.client.Write(ctx, &entity.WriteEntityRequest{
|
||||
GRN: grn,
|
||||
Body: body,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
w2, err := testCtx.client.Write(ctx, &entity.WriteEntityRequest{
|
||||
GRN: &entity.GRN{
|
||||
UID: uid2,
|
||||
Kind: kind,
|
||||
},
|
||||
Body: body,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
w3, err := testCtx.client.Write(ctx, &entity.WriteEntityRequest{
|
||||
GRN: &entity.GRN{
|
||||
UID: uid3,
|
||||
Kind: kind2,
|
||||
},
|
||||
Body: body,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
w4, err := testCtx.client.Write(ctx, &entity.WriteEntityRequest{
|
||||
GRN: &entity.GRN{
|
||||
UID: uid4,
|
||||
Kind: kind2,
|
||||
},
|
||||
Body: body,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
search, err := testCtx.client.Search(ctx, &entity.EntitySearchRequest{
|
||||
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.GRN.UID)
|
||||
kinds = append(kinds, res.GRN.Kind)
|
||||
version = append(version, res.Version)
|
||||
}
|
||||
require.Equal(t, []string{"my-test-entity", "uid2", "uid3", "uid4"}, uids)
|
||||
require.Equal(t, []string{"jsonobj", "jsonobj", "playlist", "playlist"}, kinds)
|
||||
require.Equal(t, []string{
|
||||
w1.Entity.Version,
|
||||
w2.Entity.Version,
|
||||
w3.Entity.Version,
|
||||
w4.Entity.Version,
|
||||
}, version)
|
||||
|
||||
// Again with only one kind
|
||||
searchKind1, err := testCtx.client.Search(ctx, &entity.EntitySearchRequest{
|
||||
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.GRN.UID)
|
||||
kinds = append(kinds, res.GRN.Kind)
|
||||
version = append(version, res.Version)
|
||||
}
|
||||
require.Equal(t, []string{"my-test-entity", "uid2"}, uids)
|
||||
require.Equal(t, []string{"jsonobj", "jsonobj"}, kinds)
|
||||
require.Equal(t, []string{
|
||||
w1.Entity.Version,
|
||||
w2.Entity.Version,
|
||||
}, version)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user