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
223 lines
7.4 KiB
Go
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)
|
|
}
|