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:
360
internal/e2e/sdk_client_test.go
Normal file
360
internal/e2e/sdk_client_test.go
Normal file
@@ -0,0 +1,360 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
|
||||
httpclient "github.com/ory/keto/internal/httpclient"
|
||||
"github.com/ory/keto/ketoapi"
|
||||
)
|
||||
|
||||
type (
|
||||
sdkClient struct {
|
||||
rc, wc, sc *httpclient.APIClient
|
||||
readRemote, writeRemote, syntaxRemote string
|
||||
}
|
||||
)
|
||||
|
||||
var _ client = (*sdkClient)(nil)
|
||||
|
||||
var requestTimeout = 5 * time.Second
|
||||
|
||||
func (c *sdkClient) requestCtx(t testing.TB) context.Context {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
t.Cleanup(cancel)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c *sdkClient) oplCheckSyntax(t testing.TB, content []byte) (parseErrors []*ketoapi.ParseError) {
|
||||
res, _, err := c.getOPLSyntaxClient().
|
||||
RelationshipApi.
|
||||
CheckOplSyntax(c.requestCtx(t)).
|
||||
Body(string(content)).
|
||||
Execute()
|
||||
require.NoError(t, err)
|
||||
|
||||
raw, err := json.Marshal(res.Errors)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(raw, &parseErrors)
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *sdkClient) getReadClient() *httpclient.APIClient {
|
||||
if c.rc == nil {
|
||||
cfg := httpclient.NewConfiguration()
|
||||
cfg.Host = c.readRemote
|
||||
cfg.Scheme = "http"
|
||||
c.rc = httpclient.NewAPIClient(cfg)
|
||||
}
|
||||
return c.rc
|
||||
}
|
||||
|
||||
func (c *sdkClient) getWriteClient() *httpclient.APIClient {
|
||||
if c.wc == nil {
|
||||
cfg := httpclient.NewConfiguration()
|
||||
cfg.Host = c.writeRemote
|
||||
cfg.Scheme = "http"
|
||||
c.wc = httpclient.NewAPIClient(cfg)
|
||||
}
|
||||
return c.wc
|
||||
}
|
||||
|
||||
func (c *sdkClient) getOPLSyntaxClient() *httpclient.APIClient {
|
||||
if c.sc == nil {
|
||||
cfg := httpclient.NewConfiguration()
|
||||
cfg.Host = c.syntaxRemote
|
||||
cfg.Scheme = "http"
|
||||
c.sc = httpclient.NewAPIClient(cfg)
|
||||
}
|
||||
return c.sc
|
||||
}
|
||||
|
||||
func (c *sdkClient) createTuple(t testing.TB, r *ketoapi.RelationTuple) {
|
||||
payload := httpclient.CreateRelationshipBody{
|
||||
Namespace: new(r.Namespace),
|
||||
Object: new(r.Object),
|
||||
Relation: new(r.Relation),
|
||||
SubjectId: r.SubjectID,
|
||||
}
|
||||
if r.SubjectID == nil {
|
||||
payload.SubjectSet = &httpclient.SubjectSet{
|
||||
Namespace: r.SubjectSet.Namespace,
|
||||
Object: r.SubjectSet.Object,
|
||||
Relation: r.SubjectSet.Relation,
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err := c.getWriteClient().RelationshipApi.
|
||||
CreateRelationship(c.requestCtx(t)).
|
||||
CreateRelationshipBody(payload).
|
||||
Execute()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func withSubject[P interface {
|
||||
SubjectId(string) P
|
||||
SubjectSetNamespace(string) P
|
||||
SubjectSetObject(string) P
|
||||
SubjectSetRelation(string) P
|
||||
}](params P, subID *string, subSet *ketoapi.SubjectSet) P {
|
||||
if subID != nil {
|
||||
return params.SubjectId(*subID)
|
||||
}
|
||||
if subSet != nil {
|
||||
return params.
|
||||
SubjectSetNamespace(subSet.Namespace).
|
||||
SubjectSetObject(subSet.Object).
|
||||
SubjectSetRelation(subSet.Relation)
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func (c *sdkClient) deleteTuple(t testing.TB, r *ketoapi.RelationTuple) {
|
||||
request := c.getWriteClient().RelationshipApi.
|
||||
DeleteRelationships(c.requestCtx(t)).
|
||||
Namespace(r.Namespace).
|
||||
Object(r.Object).
|
||||
Relation(r.Relation)
|
||||
request = withSubject(request, r.SubjectID, r.SubjectSet)
|
||||
|
||||
_, err := request.Execute()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (c *sdkClient) deleteAllTuples(t testing.TB, q *ketoapi.RelationQuery) {
|
||||
request := c.getWriteClient().RelationshipApi.DeleteRelationships(c.requestCtx(t))
|
||||
if q.Namespace != nil {
|
||||
request = request.Namespace(*q.Namespace)
|
||||
}
|
||||
if q.Object != nil {
|
||||
request = request.Object(*q.Object)
|
||||
}
|
||||
if q.Relation != nil {
|
||||
request = request.Relation(*q.Relation)
|
||||
}
|
||||
request = withSubject(request, q.SubjectID, q.SubjectSet)
|
||||
_, err := request.Execute()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func compileParams(req httpclient.RelationshipApiApiGetRelationshipsRequest, q *ketoapi.RelationQuery, opts []paginationOptionSetter) httpclient.RelationshipApiApiGetRelationshipsRequest {
|
||||
if q.Namespace != nil {
|
||||
req = req.Namespace(*q.Namespace)
|
||||
}
|
||||
if q.Relation != nil {
|
||||
req = req.Relation(*q.Relation)
|
||||
}
|
||||
if q.Object != nil {
|
||||
req = req.Object(*q.Object)
|
||||
}
|
||||
if q.SubjectID != nil {
|
||||
req = req.SubjectId(*q.SubjectID)
|
||||
}
|
||||
if q.SubjectSet != nil {
|
||||
req = req.
|
||||
SubjectSetNamespace(q.SubjectSet.Namespace).
|
||||
SubjectSetObject(q.SubjectSet.Object).
|
||||
SubjectSetRelation(q.SubjectSet.Relation)
|
||||
}
|
||||
|
||||
pagination := getPaginationOptions(opts...)
|
||||
if pagination.Size != 0 {
|
||||
req = req.PageSize(int64(pagination.Size))
|
||||
}
|
||||
if pagination.Token != "" {
|
||||
req = req.PageToken(pagination.Token)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *sdkClient) queryTuple(t testing.TB, q *ketoapi.RelationQuery, opts ...paginationOptionSetter) *ketoapi.GetResponse {
|
||||
request := c.getReadClient().RelationshipApi.GetRelationships(c.requestCtx(t))
|
||||
request = compileParams(request, q, opts)
|
||||
|
||||
resp, _, err := request.Execute()
|
||||
require.NoError(t, err)
|
||||
|
||||
getResp := &ketoapi.GetResponse{
|
||||
RelationTuples: make([]*ketoapi.RelationTuple, len(resp.RelationTuples)),
|
||||
NextPageToken: resp.GetNextPageToken(),
|
||||
}
|
||||
|
||||
for i, rt := range resp.RelationTuples {
|
||||
getResp.RelationTuples[i] = &ketoapi.RelationTuple{
|
||||
Namespace: rt.Namespace,
|
||||
Object: rt.Object,
|
||||
Relation: rt.Relation,
|
||||
}
|
||||
if rt.SubjectSet != nil {
|
||||
getResp.RelationTuples[i].SubjectSet = &ketoapi.SubjectSet{
|
||||
Namespace: rt.SubjectSet.Namespace,
|
||||
Object: rt.SubjectSet.Object,
|
||||
Relation: rt.SubjectSet.Relation,
|
||||
}
|
||||
} else {
|
||||
getResp.RelationTuples[i].SubjectID = rt.SubjectId
|
||||
}
|
||||
}
|
||||
|
||||
return getResp
|
||||
}
|
||||
|
||||
func (c *sdkClient) queryTupleErr(t testing.TB, expected *herodot.DefaultError, q *ketoapi.RelationQuery, opts ...paginationOptionSetter) {
|
||||
request := c.getReadClient().RelationshipApi.GetRelationships(c.requestCtx(t))
|
||||
request = compileParams(request, q, opts)
|
||||
_, _, err := request.Execute()
|
||||
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
require.FailNow(t, "expected error but got nil")
|
||||
case *httpclient.GenericOpenAPIError:
|
||||
assert.Equal(t, expected.CodeField, http.StatusNotFound)
|
||||
default:
|
||||
require.FailNow(t, "got unknown error %+v\nexpected %+v", err, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sdkClient) check(t testing.TB, r *ketoapi.RelationTuple) bool {
|
||||
request := c.getReadClient().PermissionApi.CheckPermission(c.requestCtx(t)).
|
||||
Namespace(r.Namespace).
|
||||
Object(r.Object).
|
||||
Relation(r.Relation)
|
||||
request = withSubject(request, r.SubjectID, r.SubjectSet)
|
||||
|
||||
resp, _, err := request.Execute()
|
||||
require.NoError(t, err)
|
||||
|
||||
return resp.GetAllowed()
|
||||
}
|
||||
|
||||
func (c *sdkClient) batchCheckErr(t testing.TB, requestTuples []*ketoapi.RelationTuple, expected *herodot.DefaultError) {
|
||||
request := c.getReadClient().PermissionApi.BatchCheckPermission(c.requestCtx(t)).
|
||||
BatchCheckPermissionBody(httpclient.BatchCheckPermissionBody{
|
||||
Tuples: tuplesToRelationships(requestTuples),
|
||||
})
|
||||
|
||||
_, _, err := request.Execute()
|
||||
switch typedErr := err.(type) {
|
||||
case nil:
|
||||
require.FailNow(t, "expected error but got nil")
|
||||
case *httpclient.GenericOpenAPIError:
|
||||
assert.Contains(t, typedErr.Error(), expected.Reason())
|
||||
default:
|
||||
require.FailNow(t, "got unknown error %+v\nexpected %+v", err, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *sdkClient) batchCheck(t testing.TB, requestTuples []*ketoapi.RelationTuple) []checkResponse {
|
||||
request := c.getReadClient().PermissionApi.BatchCheckPermission(c.requestCtx(t)).
|
||||
BatchCheckPermissionBody(httpclient.BatchCheckPermissionBody{
|
||||
Tuples: tuplesToRelationships(requestTuples),
|
||||
})
|
||||
|
||||
resp, _, err := request.Execute()
|
||||
require.NoError(t, err)
|
||||
|
||||
responses := make([]checkResponse, len(resp.Results))
|
||||
for i, result := range resp.Results {
|
||||
errMsg := ""
|
||||
if result.Error != nil {
|
||||
errMsg = *result.Error
|
||||
}
|
||||
responses[i] = checkResponse{
|
||||
allowed: result.Allowed,
|
||||
errorMessage: errMsg,
|
||||
}
|
||||
}
|
||||
return responses
|
||||
}
|
||||
|
||||
func tuplesToRelationships(tuples []*ketoapi.RelationTuple) []httpclient.Relationship {
|
||||
relationships := make([]httpclient.Relationship, len(tuples))
|
||||
for i, requestTuple := range tuples {
|
||||
relationship := httpclient.Relationship{
|
||||
Namespace: requestTuple.Namespace,
|
||||
Object: requestTuple.Object,
|
||||
Relation: requestTuple.Relation,
|
||||
SubjectId: requestTuple.SubjectID,
|
||||
}
|
||||
if requestTuple.SubjectSet != nil {
|
||||
relationship.SubjectSet = &httpclient.SubjectSet{
|
||||
Namespace: requestTuple.SubjectSet.Namespace,
|
||||
Object: requestTuple.SubjectSet.Object,
|
||||
Relation: requestTuple.SubjectSet.Relation,
|
||||
}
|
||||
}
|
||||
relationships[i] = relationship
|
||||
}
|
||||
return relationships
|
||||
}
|
||||
|
||||
func buildTree(t testing.TB, mt *httpclient.ExpandedPermissionTree) *ketoapi.Tree[*ketoapi.RelationTuple] {
|
||||
result := &ketoapi.Tree[*ketoapi.RelationTuple]{
|
||||
Type: ketoapi.TreeNodeType(mt.Type),
|
||||
}
|
||||
if mt.Tuple.SubjectSet != nil {
|
||||
result.Tuple = &ketoapi.RelationTuple{
|
||||
SubjectSet: &ketoapi.SubjectSet{
|
||||
Namespace: mt.Tuple.SubjectSet.Namespace,
|
||||
Object: mt.Tuple.SubjectSet.Object,
|
||||
Relation: mt.Tuple.SubjectSet.Relation,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
result.Tuple = &ketoapi.RelationTuple{
|
||||
SubjectID: mt.Tuple.SubjectId,
|
||||
}
|
||||
}
|
||||
|
||||
if result.Type != ketoapi.TreeNodeLeaf && len(mt.Children) != 0 {
|
||||
result.Children = make([]*ketoapi.Tree[*ketoapi.RelationTuple], len(mt.Children))
|
||||
for i, c := range mt.Children {
|
||||
c := c
|
||||
result.Children[i] = buildTree(t, &c)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *sdkClient) expand(t testing.TB, r *ketoapi.SubjectSet, depth int) *ketoapi.Tree[*ketoapi.RelationTuple] {
|
||||
request := c.getReadClient().PermissionApi.ExpandPermissions(c.requestCtx(t)).
|
||||
Namespace(r.Namespace).
|
||||
Object(r.Object).
|
||||
Relation(r.Relation).
|
||||
MaxDepth(int64(depth))
|
||||
|
||||
resp, _, err := request.Execute()
|
||||
require.NoError(t, err)
|
||||
|
||||
return buildTree(t, resp)
|
||||
}
|
||||
|
||||
func (c *sdkClient) waitUntilLive(t testing.TB) {
|
||||
resp, _, err := c.getReadClient().MetadataApi.IsReady(c.requestCtx(t)).Execute()
|
||||
for err != nil {
|
||||
resp, _, err = c.getReadClient().MetadataApi.IsReady(c.requestCtx(t)).Execute()
|
||||
}
|
||||
require.Equal(t, "ok", resp.Status)
|
||||
}
|
||||
|
||||
func (c *sdkClient) queryNamespaces(t testing.TB) (response ketoapi.GetNamespacesResponse) {
|
||||
res, _, err := c.getReadClient().RelationshipApi.ListRelationshipNamespaces(c.requestCtx(t)).Execute()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, convert(res, &response))
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user