diff --git a/contrib/docs-code-samples/simple-access-check-guide/00-write-direct-access/curl.sh b/contrib/docs-code-samples/simple-access-check-guide/00-write-direct-access/curl.sh index 060dfb4af..72b60243b 100755 --- a/contrib/docs-code-samples/simple-access-check-guide/00-write-direct-access/curl.sh +++ b/contrib/docs-code-samples/simple-access-check-guide/00-write-direct-access/curl.sh @@ -11,6 +11,6 @@ relationtuple=' curl --fail --silent -X PUT \ --data "$relationtuple" \ - http://127.0.0.1:4467/relationtuple \ + http://127.0.0.1:4467/relationtuple > /dev/null \ && echo "Successfully created tuple" \ || echo "Encountered error" diff --git a/internal/relationtuple/read_server_test.go b/internal/relationtuple/read_server_test.go new file mode 100644 index 000000000..db4b5ceed --- /dev/null +++ b/internal/relationtuple/read_server_test.go @@ -0,0 +1,93 @@ +package relationtuple_test + +import ( + "context" + "encoding/json" + "io" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" + + "github.com/julienschmidt/httprouter" + "github.com/stretchr/testify/require" + + "github.com/ory/keto/internal/driver" + "github.com/ory/keto/internal/namespace" + "github.com/ory/keto/internal/relationtuple" + "github.com/ory/keto/internal/x" +) + +func TestReadHandlers(t *testing.T) { + setup := func(t *testing.T) (ts *httptest.Server, reg driver.Registry) { + r := &x.ReadRouter{Router: httprouter.New()} + reg = driver.NewMemoryTestRegistry(t, []*namespace.Namespace{{Name: t.Name()}}) + h := relationtuple.NewHandler(reg) + h.RegisterReadRoutes(r) + ts = httptest.NewServer(r) + t.Cleanup(ts.Close) + + return + } + + t.Run("method=get", func(t *testing.T) { + t.Run("case=empty response is not nil", func(t *testing.T) { + ts, _ := setup(t) + + resp, err := ts.Client().Get(ts.URL + relationtuple.RouteBase + "?namespace=" + url.QueryEscape(t.Name())) + require.NoError(t, err) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + assert.Equal(t, "[]", gjson.GetBytes(body, "relation_tuples").Raw) + + var respMsg relationtuple.GetResponse + require.NoError(t, json.Unmarshal(body, &respMsg)) + + assert.Equal(t, relationtuple.GetResponse{ + RelationTuples: []*relationtuple.InternalRelationTuple{}, + NextPageToken: x.PageTokenEnd, + IsLastPage: true, + }, respMsg) + }) + + t.Run("case=gets first page", func(t *testing.T) { + ts, reg := setup(t) + + rts := []*relationtuple.InternalRelationTuple{ + { + Namespace: t.Name(), + Object: "o1", + Relation: "r1", + Subject: &relationtuple.SubjectID{ID: "s1"}, + }, + { + Namespace: t.Name(), + Object: "o2", + Relation: "r2", + Subject: &relationtuple.SubjectSet{ + Namespace: t.Name(), + Object: "o1", + Relation: "r1", + }, + }, + } + + require.NoError(t, reg.RelationTupleManager().WriteRelationTuples(context.Background(), rts...)) + + resp, err := ts.Client().Get(ts.URL + relationtuple.RouteBase + "?namespace=" + url.QueryEscape(t.Name())) + require.NoError(t, err) + + var respMsg relationtuple.GetResponse + require.NoError(t, json.NewDecoder(resp.Body).Decode(&respMsg)) + + for i := range rts { + assert.Contains(t, respMsg.RelationTuples, rts[i]) + } + assert.Equal(t, x.PageTokenEnd, respMsg.NextPageToken) + }) + }) +} diff --git a/internal/relationtuple/transact_server.go b/internal/relationtuple/transact_server.go index 1c4edfc23..7894b1714 100644 --- a/internal/relationtuple/transact_server.go +++ b/internal/relationtuple/transact_server.go @@ -56,7 +56,7 @@ func (h *handler) createRelation(w http.ResponseWriter, r *http.Request, _ httpr var rel InternalRelationTuple if err := json.NewDecoder(r.Body).Decode(&rel); err != nil { - h.d.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest)) + h.d.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithError(err.Error()))) return } @@ -64,11 +64,11 @@ func (h *handler) createRelation(w http.ResponseWriter, r *http.Request, _ httpr if err := h.d.RelationTupleManager().WriteRelationTuples(r.Context(), &rel); err != nil { h.d.Logger().WithError(err).WithField("relationtuple", rel).Errorf("got an error while creating the relation tuple") - h.d.Writer().WriteError(w, r, errors.WithStack(herodot.ErrInternalServerError)) + h.d.Writer().WriteError(w, r, errors.WithStack(herodot.ErrInternalServerError.WithError(err.Error()))) return } - w.WriteHeader(http.StatusCreated) + h.d.Writer().WriteCreated(w, r, RouteBase+"?"+rel.ToURLQuery().Encode(), rel) } func (h *handler) deleteRelation(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { diff --git a/internal/relationtuple/transact_server_test.go b/internal/relationtuple/transact_server_test.go new file mode 100644 index 000000000..3a3564677 --- /dev/null +++ b/internal/relationtuple/transact_server_test.go @@ -0,0 +1,57 @@ +package relationtuple_test + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/julienschmidt/httprouter" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ory/keto/internal/driver" + "github.com/ory/keto/internal/namespace" + "github.com/ory/keto/internal/relationtuple" + "github.com/ory/keto/internal/x" +) + +func TestWriteHandlers(t *testing.T) { + t.Run("method=create", func(t *testing.T) { + r := &x.WriteRouter{Router: httprouter.New()} + reg := driver.NewMemoryTestRegistry(t, []*namespace.Namespace{{Name: "handler test"}}) + h := relationtuple.NewHandler(reg) + h.RegisterWriteRoutes(r) + ts := httptest.NewServer(r) + defer ts.Close() + + c := ts.Client() + + rt := &relationtuple.InternalRelationTuple{ + Namespace: "handler test", + Object: "obj", + Relation: "rel", + Subject: &relationtuple.SubjectID{ID: "subj"}, + } + payload, err := json.Marshal(rt) + require.NoError(t, err) + + req, err := http.NewRequest(http.MethodPut, ts.URL+relationtuple.RouteBase, bytes.NewBuffer(payload)) + require.NoError(t, err) + resp, err := c.Do(req) + require.NoError(t, err) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + assert.JSONEq(t, string(payload), string(body)) + + // set a size just to make sure it gets all + actualRTs, _, err := reg.RelationTupleManager().GetRelationTuples(context.Background(), (*relationtuple.RelationQuery)(rt), x.WithSize(1000)) + require.NoError(t, err) + assert.Equal(t, []*relationtuple.InternalRelationTuple{rt}, actualRTs) + }) +}