snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
345
internal/relationtuple/read_server_test.go
Normal file
345
internal/relationtuple/read_server_test.go
Normal file
@@ -0,0 +1,345 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package relationtuple_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/ory/x/httprouterx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
rts "github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2"
|
||||
"github.com/ory/keto/internal/driver"
|
||||
"github.com/ory/keto/internal/driver/config"
|
||||
"github.com/ory/keto/internal/namespace"
|
||||
"github.com/ory/keto/internal/relationtuple"
|
||||
"github.com/ory/keto/internal/testhelpers"
|
||||
"github.com/ory/keto/ketoapi"
|
||||
)
|
||||
|
||||
func TestReadHandlers(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
r := httprouterx.NewRouterPublic()
|
||||
reg := driver.NewSqliteTestRegistry(t)
|
||||
h := relationtuple.NewReadHandler(reg)
|
||||
h.RegisterReadRoutes(r)
|
||||
ts, clientTLS := driver.HTTP2TestServer(t, r)
|
||||
|
||||
var newNamespace func(*testing.T) *namespace.Namespace
|
||||
{
|
||||
nspaces := 0
|
||||
newNamespace = func(t *testing.T) *namespace.Namespace {
|
||||
n := &namespace.Namespace{Name: fmt.Sprintf("relation tuple read test %d", nspaces)}
|
||||
nspaces++
|
||||
require.NoError(t, reg.Config(ctx).Set(config.KeyNamespaces, []*namespace.Namespace{n}))
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("method=get", func(t *testing.T) {
|
||||
t.Run("case=empty response is not nil", func(t *testing.T) {
|
||||
nspace := newNamespace(t)
|
||||
resp, err := ts.Client().Get(ts.URL + relationtuple.ReadRouteBase + "?" + url.Values{
|
||||
"namespace": {nspace.Name},
|
||||
}.Encode())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "[]", gjson.GetBytes(body, "relation_tuples").Raw)
|
||||
|
||||
var respMsg ketoapi.GetResponse
|
||||
require.NoError(t, json.Unmarshal(body, &respMsg))
|
||||
|
||||
assert.Equal(t, ketoapi.GetResponse{
|
||||
RelationTuples: []*ketoapi.RelationTuple{},
|
||||
NextPageToken: "",
|
||||
}, respMsg)
|
||||
})
|
||||
|
||||
t.Run("case=returns tuples", func(t *testing.T) {
|
||||
nspace := newNamespace(t)
|
||||
tuples := []*ketoapi.RelationTuple{
|
||||
{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o1",
|
||||
Relation: "r1",
|
||||
SubjectID: new("s1"),
|
||||
},
|
||||
{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o2",
|
||||
Relation: "r2",
|
||||
SubjectSet: &ketoapi.SubjectSet{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o1",
|
||||
Relation: "r1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testhelpers.MapAndInsertTuples(t, reg, tuples...)
|
||||
|
||||
resp, err := ts.Client().Get(ts.URL + relationtuple.ReadRouteBase + "?" + url.Values{
|
||||
"namespace": {nspace.Name},
|
||||
}.Encode())
|
||||
require.NoError(t, err)
|
||||
|
||||
var respMsg ketoapi.GetResponse
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&respMsg))
|
||||
|
||||
assert.ElementsMatch(t, tuples, respMsg.RelationTuples)
|
||||
assert.Equal(t, "", respMsg.NextPageToken)
|
||||
})
|
||||
|
||||
t.Run("case=return tuples without namespace", func(t *testing.T) {
|
||||
nspace := newNamespace(t)
|
||||
|
||||
tuples := []*ketoapi.RelationTuple{
|
||||
{
|
||||
Namespace: nspace.Name,
|
||||
Object: "obj",
|
||||
Relation: "r1",
|
||||
SubjectID: new("s1"),
|
||||
},
|
||||
}
|
||||
|
||||
testhelpers.MapAndInsertTuples(t, reg, tuples...)
|
||||
|
||||
resp, err := ts.Client().Get(ts.URL + relationtuple.ReadRouteBase + "?" + url.Values{
|
||||
"object": {"obj"},
|
||||
}.Encode())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, resp.StatusCode, http.StatusOK)
|
||||
|
||||
var respMsg ketoapi.GetResponse
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&respMsg))
|
||||
assert.Equal(t, 1, len(respMsg.RelationTuples))
|
||||
assert.Containsf(t, tuples, respMsg.RelationTuples[0], "expected to find %q in %q", respMsg.RelationTuples[0].String(), tuples)
|
||||
assert.Equal(t, "", respMsg.NextPageToken)
|
||||
})
|
||||
|
||||
t.Run("case=returns bad request on malformed subject", func(t *testing.T) {
|
||||
resp, err := ts.Client().Get(ts.URL + relationtuple.ReadRouteBase + "?" + url.Values{
|
||||
"subject": {"not#a valid subject"},
|
||||
}.Encode())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("case=paginates", func(t *testing.T) {
|
||||
nspace := newNamespace(t)
|
||||
|
||||
tuples := []*ketoapi.RelationTuple{
|
||||
{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o1",
|
||||
Relation: "r1",
|
||||
SubjectID: new("s1"),
|
||||
},
|
||||
{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o2",
|
||||
Relation: "r2",
|
||||
SubjectID: new("s2"),
|
||||
},
|
||||
}
|
||||
testhelpers.MapAndInsertTuples(t, reg, tuples...)
|
||||
|
||||
var firstResp ketoapi.GetResponse
|
||||
resp, err := ts.Client().Get(ts.URL + relationtuple.ReadRouteBase + "?" + url.Values{
|
||||
"namespace": {nspace.Name},
|
||||
"page_size": {"1"},
|
||||
}.Encode())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&firstResp))
|
||||
require.Len(t, firstResp.RelationTuples, 1)
|
||||
assert.Contains(t, tuples, firstResp.RelationTuples[0])
|
||||
assert.NotEqual(t, "", firstResp.NextPageToken)
|
||||
|
||||
// second page
|
||||
resp, err = ts.Client().Get(ts.URL + relationtuple.ReadRouteBase + "?" + url.Values{
|
||||
"namespace": {nspace.Name},
|
||||
"page_size": {"1"},
|
||||
"page_token": {firstResp.NextPageToken},
|
||||
}.Encode())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
secondResp := ketoapi.GetResponse{}
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&secondResp))
|
||||
require.Len(t, secondResp.RelationTuples, 1)
|
||||
|
||||
assert.NotEqual(t, firstResp.RelationTuples, secondResp.RelationTuples)
|
||||
assert.Contains(t, tuples, secondResp.RelationTuples[0])
|
||||
assert.Equal(t, "", secondResp.NextPageToken)
|
||||
})
|
||||
|
||||
t.Run("case=returs bad request on invalid page size", func(t *testing.T) {
|
||||
resp, err := ts.Client().Get(ts.URL + relationtuple.ReadRouteBase + "?" + url.Values{
|
||||
"page_size": {"foo"},
|
||||
}.Encode())
|
||||
require.NoError(t, err)
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
assert.Contains(t, string(body), "invalid syntax")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("method=grpc", func(t *testing.T) {
|
||||
type requestEnhancer = func(req *rts.ListRelationTuplesRequest, query *ketoapi.RelationQuery)
|
||||
withRelationQuery := func(req *rts.ListRelationTuplesRequest, query *ketoapi.RelationQuery) {
|
||||
req.RelationQuery = query.ToProto()
|
||||
}
|
||||
withDeprecatedQuery := func(req *rts.ListRelationTuplesRequest, query *ketoapi.RelationQuery) {
|
||||
pq := query.ToProto()
|
||||
//nolint:staticcheck
|
||||
req.Query = &rts.ListRelationTuplesRequest_Query{ //lint:ignore SA1019 backwards compatibility
|
||||
Subject: pq.Subject,
|
||||
}
|
||||
if pq.Namespace != nil {
|
||||
//nolint:staticcheck
|
||||
req.Query.Namespace = *pq.Namespace //lint:ignore SA1019 backwards compatibility
|
||||
}
|
||||
if pq.Object != nil {
|
||||
//nolint:staticcheck
|
||||
req.Query.Object = *pq.Object //lint:ignore SA1019 backwards compatibility
|
||||
}
|
||||
if pq.Relation != nil {
|
||||
//nolint:staticcheck
|
||||
req.Query.Relation = *pq.Relation //lint:ignore SA1019 backwards compatibility
|
||||
}
|
||||
}
|
||||
apiTuplesFromProto := func(t *testing.T, pts ...*rts.RelationTuple) []*ketoapi.RelationTuple {
|
||||
actual := make([]*ketoapi.RelationTuple, len(pts))
|
||||
for i, rt := range pts {
|
||||
var err error
|
||||
actual[i], err = (&ketoapi.RelationTuple{}).FromDataProvider(rt)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
return actual
|
||||
}
|
||||
|
||||
con, err := grpc.NewClient(ts.Listener.Addr().String(), grpc.WithTransportCredentials(clientTLS))
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("method=list", func(t *testing.T) {
|
||||
client := rts.NewReadServiceClient(con)
|
||||
|
||||
for key, enhancer := range map[string]requestEnhancer{"relation query": withRelationQuery, "deprecated query": withDeprecatedQuery} {
|
||||
t.Run("enhancer="+key, func(t *testing.T) {
|
||||
t.Run("case=returns empty list on no tuples", func(t *testing.T) {
|
||||
nspace := newNamespace(t)
|
||||
req := &rts.ListRelationTuplesRequest{}
|
||||
enhancer(req, &ketoapi.RelationQuery{
|
||||
Namespace: &nspace.Name,
|
||||
})
|
||||
resp, err := client.ListRelationTuples(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, resp.RelationTuples, 0)
|
||||
})
|
||||
|
||||
t.Run("case=gets tuples", func(t *testing.T) {
|
||||
nspace := newNamespace(t)
|
||||
tuples := []*ketoapi.RelationTuple{
|
||||
{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o1",
|
||||
Relation: "rel",
|
||||
SubjectID: new("s1"),
|
||||
},
|
||||
{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o2",
|
||||
Relation: "rel",
|
||||
SubjectSet: &ketoapi.SubjectSet{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o1",
|
||||
Relation: "r1",
|
||||
},
|
||||
},
|
||||
}
|
||||
testhelpers.MapAndInsertTuples(t, reg, tuples...)
|
||||
|
||||
req := &rts.ListRelationTuplesRequest{}
|
||||
enhancer(req, &ketoapi.RelationQuery{
|
||||
Namespace: &nspace.Name,
|
||||
})
|
||||
|
||||
resp, err := client.ListRelationTuples(ctx, req)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, resp.RelationTuples, 2)
|
||||
|
||||
assert.ElementsMatch(t, tuples, apiTuplesFromProto(t, resp.RelationTuples...))
|
||||
})
|
||||
|
||||
t.Run("case=paginates", func(t *testing.T) {
|
||||
nspace := newNamespace(t)
|
||||
tuples := []*ketoapi.RelationTuple{
|
||||
{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o1",
|
||||
Relation: "rel",
|
||||
SubjectID: new("s1"),
|
||||
},
|
||||
{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o2",
|
||||
Relation: "rel",
|
||||
SubjectID: new("s2"),
|
||||
},
|
||||
{
|
||||
Namespace: nspace.Name,
|
||||
Object: "o3",
|
||||
Relation: "rel",
|
||||
SubjectID: new("s3"),
|
||||
},
|
||||
}
|
||||
testhelpers.MapAndInsertTuples(t, reg, tuples...)
|
||||
|
||||
query := &ketoapi.RelationQuery{
|
||||
Namespace: &nspace.Name,
|
||||
}
|
||||
firstReq := &rts.ListRelationTuplesRequest{}
|
||||
enhancer(firstReq, query)
|
||||
firstReq.PageSize = int32(2)
|
||||
|
||||
firstResp, err := client.ListRelationTuples(ctx, firstReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
secondReq := &rts.ListRelationTuplesRequest{}
|
||||
enhancer(secondReq, query)
|
||||
secondReq.PageSize = int32(2)
|
||||
secondReq.PageToken = firstResp.NextPageToken
|
||||
|
||||
secondResp, err := client.ListRelationTuples(ctx, secondReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, firstResp.RelationTuples, 2)
|
||||
assert.Len(t, secondResp.RelationTuples, 1)
|
||||
assert.Zero(t, secondResp.NextPageToken)
|
||||
|
||||
assert.ElementsMatch(t, tuples, apiTuplesFromProto(t, append(firstResp.RelationTuples, secondResp.RelationTuples...)...))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user