Files
upstream-ory-keto/internal/e2e/rest_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

223 lines
7.4 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
"github.com/ory/herodot"
"github.com/ory/x/healthx"
rts "github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2"
"github.com/ory/keto/internal/check"
"github.com/ory/keto/internal/expand"
httpclient "github.com/ory/keto/internal/httpclient"
"github.com/ory/keto/internal/relationtuple"
"github.com/ory/keto/ketoapi"
"github.com/ory/keto/schema"
)
var _ client = &restClient{}
type restClient struct {
readURL, writeURL, oplSyntaxURL string
}
func (rc *restClient) queryNamespaces(t testing.TB) (res ketoapi.GetNamespacesResponse) {
body, code := rc.makeRequest(t, http.MethodGet, "/namespaces", "", rc.readURL)
assert.Equal(t, http.StatusOK, code, body)
require.NoError(t, json.Unmarshal([]byte(body), &res))
return
}
func (rc *restClient) oplCheckSyntax(t testing.TB, content []byte) []*ketoapi.ParseError {
body, code := rc.makeRequest(t, http.MethodPost, schema.RouteBase, string(content), rc.oplSyntaxURL)
assert.Equal(t, http.StatusOK, code, body)
var response ketoapi.CheckOPLSyntaxResponse
require.NoError(t, json.Unmarshal([]byte(body), &response))
return response.Errors
}
func (rc *restClient) makeRequest(t testing.TB, method, path, body string, baseURL string) (string, int) {
var b io.Reader
if body != "" {
b = bytes.NewBufferString(body)
}
// t.Logf("Requesting %s %s%s with body %#v", method, baseURL, path, body)
req, err := http.NewRequest(method, baseURL+path, b)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req) //nolint:gosec // Test code with controlled URL.
require.NoError(t, err)
respBody, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
return string(respBody), resp.StatusCode
}
func (rc *restClient) createTuple(t testing.TB, r *ketoapi.RelationTuple) {
tEnc, err := json.Marshal(r)
require.NoError(t, err)
body, code := rc.makeRequest(t, http.MethodPut, relationtuple.WriteRouteBase, string(tEnc), rc.writeURL)
assert.Equal(t, http.StatusCreated, code, body)
}
func (rc *restClient) deleteTuple(t testing.TB, r *ketoapi.RelationTuple) {
body, code := rc.makeRequest(t, http.MethodDelete, relationtuple.WriteRouteBase+"?"+r.ToURLQuery().Encode(), "", rc.writeURL)
require.Equal(t, http.StatusNoContent, code, body)
}
func (rc *restClient) deleteAllTuples(t testing.TB, q *ketoapi.RelationQuery) {
body, code := rc.makeRequest(t, http.MethodDelete, relationtuple.WriteRouteBase+"?"+q.ToURLQuery().Encode(), "", rc.writeURL)
require.Equal(t, http.StatusNoContent, code, body)
}
func (rc *restClient) queryTuple(t testing.TB, q *ketoapi.RelationQuery, opts ...paginationOptionSetter) *ketoapi.GetResponse {
urlQuery := q.ToURLQuery()
pagination := getPaginationOptions(opts...)
if pagination.Size != 0 {
urlQuery.Set("page_size", strconv.Itoa(pagination.Size))
}
if pagination.Token != "" {
urlQuery.Set("page_token", pagination.Token)
}
body, code := rc.makeRequest(t, http.MethodGet, fmt.Sprintf("%s?%s", relationtuple.ReadRouteBase, urlQuery.Encode()), "", rc.readURL)
require.Equal(t, http.StatusOK, code, body)
var dec ketoapi.GetResponse
require.NoError(t, json.Unmarshal([]byte(body), &dec))
return &dec
}
func (rc *restClient) queryTupleErr(t testing.TB, expected *herodot.DefaultError, q *ketoapi.RelationQuery, opts ...paginationOptionSetter) {
urlQuery := q.ToURLQuery()
pagination := getPaginationOptions(opts...)
if pagination.Size != 0 {
urlQuery.Set("page_size", strconv.Itoa(pagination.Size))
}
if pagination.Token != "" {
urlQuery.Set("page_token", pagination.Token)
}
body, code := rc.makeRequest(t, http.MethodGet, fmt.Sprintf("%s?%s", relationtuple.ReadRouteBase, urlQuery.Encode()), "", rc.readURL)
assert.Equal(t, expected.CodeField, code)
assert.Equal(t, int64(expected.StatusCode()), gjson.Get(body, "error.code").Int())
assert.Equal(t, expected.Status(), gjson.Get(body, "error.status").String())
assert.Equal(t, expected.Error(), gjson.Get(body, "error.message").String(), body)
}
func (rc *restClient) check(t testing.TB, r *ketoapi.RelationTuple) bool {
q := r.ToURLQuery()
bodyGet, codeGet := rc.makeRequest(t, http.MethodGet, fmt.Sprintf("%s?%s", check.RouteBase, q.Encode()), "", rc.readURL)
var respGet rts.CheckResponse
require.NoError(t, json.Unmarshal([]byte(bodyGet), &respGet))
j, err := json.Marshal(r)
require.NoError(t, err)
bodyPost, codePost := rc.makeRequest(t, http.MethodPost, check.RouteBase, string(j), rc.readURL)
var respPost rts.CheckResponse
require.NoError(t, json.Unmarshal([]byte(bodyPost), &respPost))
if codeGet == http.StatusOK && codePost == http.StatusOK {
assert.Equal(t, true, respGet.Allowed, "%s", bodyGet)
assert.Equal(t, true, respPost.Allowed, "%s", bodyPost)
return true
}
assert.Equal(t, http.StatusForbidden, codeGet, bodyGet)
assert.Equal(t, http.StatusForbidden, codePost, bodyPost)
assert.Equal(t, false, respGet.Allowed)
assert.Equal(t, false, respPost.Allowed)
return false
}
func (rc *restClient) batchCheckErr(t testing.TB, requestTuples []*ketoapi.RelationTuple, expected *herodot.DefaultError) {
req := httpclient.BatchCheckPermissionBody{
Tuples: tuplesToRelationships(requestTuples),
}
j, err := json.Marshal(req)
require.NoError(t, err)
body, code := rc.makeRequest(t, http.MethodPost, check.BatchRoute, string(j), rc.readURL)
assert.Equal(t, expected.CodeField, code)
assert.Contains(t, body, expected.Reason())
}
func (rc *restClient) batchCheck(t testing.TB, requestTuples []*ketoapi.RelationTuple) []checkResponse {
req := httpclient.BatchCheckPermissionBody{
Tuples: tuplesToRelationships(requestTuples),
}
j, err := json.Marshal(req)
require.NoError(t, err)
body, code := rc.makeRequest(t, http.MethodPost, check.BatchRoute, string(j), rc.readURL)
require.Equal(t, http.StatusOK, code, "batch check failed unexpected with error code %d", code)
var respPost rts.BatchCheckResponse
require.NoError(t, json.Unmarshal([]byte(body), &respPost))
responseChecks := make([]checkResponse, len(respPost.Results))
for i, result := range respPost.Results {
responseChecks[i] = checkResponse{
allowed: result.Allowed,
errorMessage: result.Error,
}
}
return responseChecks
}
func (rc *restClient) expand(t testing.TB, r *ketoapi.SubjectSet, depth int) *ketoapi.Tree[*ketoapi.RelationTuple] {
query := r.ToURLQuery()
query.Set("max-depth", fmt.Sprintf("%d", depth))
body, code := rc.makeRequest(t, http.MethodGet, fmt.Sprintf("%s?%s", expand.RouteBase, query.Encode()), "", rc.readURL)
require.Equal(t, http.StatusOK, code, body)
tree := &ketoapi.Tree[*ketoapi.RelationTuple]{}
require.NoError(t, json.Unmarshal([]byte(body), tree))
return tree
}
func healthReady(t require.TestingT, readURL string) int {
req, err := http.NewRequest("GET", readURL+healthx.ReadyCheckPath, nil)
require.NoError(t, err)
resp, err := http.DefaultClient.Do(req) //nolint:gosec // Test code with controlled URL.
if err != nil {
return 0
}
defer func() { _ = resp.Body.Close() }()
return resp.StatusCode
}
func waitUntilLive(t testing.TB, readURL string) {
require.EventuallyWithT(t, func(t *assert.CollectT) {
assert.Equal(t, http.StatusOK, healthReady(t, readURL))
}, 5*time.Second, 10*time.Millisecond)
}
func (rc *restClient) waitUntilLive(t testing.TB) {
waitUntilLive(t, rc.readURL)
}