Files
upstream-ory-keto/internal/e2e/sdk_client_test.go
upstream-mirror 9456a4e922
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
snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:08:34 +05:00

361 lines
9.9 KiB
Go

// 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
}