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:
168
cmd/relationtuple/create_test.go
Normal file
168
cmd/relationtuple/create_test.go
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package relationtuple
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/x/cmdx"
|
||||
"github.com/ory/x/randx"
|
||||
|
||||
"github.com/ory/keto/cmd/client/clienttest"
|
||||
"github.com/ory/keto/internal/namespace"
|
||||
"github.com/ory/keto/ketoapi"
|
||||
)
|
||||
|
||||
func renderTable(table cmdx.Table) string {
|
||||
var buf bytes.Buffer
|
||||
cmdx.PrintTablef(&buf, table)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func TestCreateCmd(t *testing.T) {
|
||||
nspace := &namespace.Namespace{Name: t.Name()}
|
||||
nspaceUser := &namespace.Namespace{Name: "User"}
|
||||
|
||||
newCmd := func() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "keto",
|
||||
}
|
||||
RegisterCommandsRecursive(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
otherNspace := &namespace.Namespace{Name: "other"}
|
||||
ts := clienttest.NewTestServer(t, []*namespace.Namespace{nspace, nspaceUser, otherNspace}, newCmd)
|
||||
|
||||
createFile := func(t *testing.T, tuple any) string {
|
||||
dir := t.TempDir()
|
||||
|
||||
data, err := json.Marshal(tuple)
|
||||
require.NoError(t, err)
|
||||
|
||||
filename := randx.MustString(10, randx.AlphaLowerNum) + ".json"
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, filename), data, 0o600))
|
||||
return filepath.Join(dir, filename)
|
||||
}
|
||||
|
||||
type getTuple func() *ketoapi.RelationTuple
|
||||
|
||||
fns := map[string]getTuple{
|
||||
"subjectID": func() *ketoapi.RelationTuple {
|
||||
return clienttest.RandomTupleWithSubjectID(nspace.Name)
|
||||
},
|
||||
"subjectSet": func() *ketoapi.RelationTuple {
|
||||
return clienttest.RandomTupleWithSubjectSet(nspace.Name, nspaceUser.Name)
|
||||
},
|
||||
}
|
||||
|
||||
for name, createTuple := range fns {
|
||||
t.Run("case="+name, func(t *testing.T) {
|
||||
t.Run("case=creates single tuple from files", func(t *testing.T) {
|
||||
tuple1 := createTuple()
|
||||
tuple2 := createTuple()
|
||||
|
||||
tmpFile := createFile(t, tuple1)
|
||||
tmpFile2 := createFile(t, []*ketoapi.RelationTuple{tuple2})
|
||||
|
||||
stdOut := ts.Cmd.ExecNoErr(t, "relation-tuple", "create", "-f", tmpFile, "-f", tmpFile2)
|
||||
require.Equal(t, renderTable(NewAPICollection([]*ketoapi.RelationTuple{tuple1, tuple2})), stdOut)
|
||||
|
||||
// assert that we can `get` it
|
||||
res1 := ts.Cmd.ExecNoErr(t, "relation-tuple", "get", "--namespace", tuple1.Namespace, "--object", tuple1.Object)
|
||||
require.Equal(t, renderTable(&responseOutput{
|
||||
RelationTuples: NewAPICollection([]*ketoapi.RelationTuple{tuple1}),
|
||||
IsLastPage: true,
|
||||
}), res1)
|
||||
|
||||
// assert that we can `get` it
|
||||
res2 := ts.Cmd.ExecNoErr(t, "relation-tuple", "get", "--namespace", tuple2.Namespace, "--object", tuple2.Object)
|
||||
require.Equal(t, renderTable(&responseOutput{
|
||||
RelationTuples: NewAPICollection([]*ketoapi.RelationTuple{tuple2}),
|
||||
IsLastPage: true,
|
||||
}), res2)
|
||||
})
|
||||
|
||||
t.Run("case=creates tuples from files and stdin", func(t *testing.T) {
|
||||
tuple1 := createTuple()
|
||||
tupleStdin := createTuple()
|
||||
|
||||
tupleFile := createFile(t, tuple1)
|
||||
data, err := json.Marshal(tupleStdin)
|
||||
require.NoError(t, err)
|
||||
|
||||
stdOut, stdErr, err := ts.Cmd.Exec(bytes.NewReader(data), "relation-tuple", "create", "-f", "-", "-f", tupleFile)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, stdErr)
|
||||
require.Equal(t, renderTable(NewAPICollection([]*ketoapi.RelationTuple{tupleStdin, tuple1})), stdOut)
|
||||
})
|
||||
|
||||
t.Run("case=creates tuples from directory", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
tuple1 := createTuple()
|
||||
tuple2 := createTuple()
|
||||
|
||||
data1, err := json.Marshal(tuple1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "tuple1.json"), data1, 0o600))
|
||||
|
||||
data2, err := json.Marshal(tuple2)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(dir, "tuple2.json"), data2, 0o600))
|
||||
|
||||
stdOut := ts.Cmd.ExecNoErr(t, "relation-tuple", "create", "-f="+dir)
|
||||
require.Equal(t, renderTable(NewAPICollection([]*ketoapi.RelationTuple{tuple1, tuple2})), stdOut)
|
||||
})
|
||||
|
||||
t.Run("case=creates inline tuple", func(t *testing.T) {
|
||||
tuple := createTuple()
|
||||
|
||||
subject := ""
|
||||
if tuple.SubjectID != nil {
|
||||
subject = *tuple.SubjectID
|
||||
} else {
|
||||
subject = tuple.SubjectSet.String()
|
||||
}
|
||||
|
||||
stdOut := ts.Cmd.ExecNoErr(t, "relation-tuple", "create", subject, tuple.Relation, tuple.Namespace+":"+tuple.Object)
|
||||
require.Equal(t, renderTable(NewAPICollection([]*ketoapi.RelationTuple{tuple})), stdOut)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("case=fails on invalid JSON", func(t *testing.T) {
|
||||
tmpFile := filepath.Join(t.TempDir(), "invalid.json")
|
||||
require.NoError(t, os.WriteFile(tmpFile, []byte("not valid json"), 0o600))
|
||||
|
||||
_, stdErr, err := ts.Cmd.Exec(nil, "relation-tuple", "create", "-f="+tmpFile)
|
||||
require.Error(t, err)
|
||||
require.Contains(t, stdErr, "could not decode")
|
||||
})
|
||||
|
||||
t.Run("case=fails on nonexistent file", func(t *testing.T) {
|
||||
_, stdErr, err := ts.Cmd.Exec(nil, "relation-tuple", "create", "-f=/nonexistent/file.json")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, stdErr, "error getting stats")
|
||||
})
|
||||
|
||||
t.Run("case=fails on json file arguments without -f", func(t *testing.T) {
|
||||
_, stdErr, err := ts.Cmd.Exec(nil, "relation-tuple", "create", "file1.json", "file2.json")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, stdErr, "expected inline arguments or JSON files")
|
||||
})
|
||||
|
||||
t.Run("case=missing colon between ns and obj", func(t *testing.T) {
|
||||
_, stdErr, err := ts.Cmd.Exec(nil, "relation-tuple", "create", "sns:so1", "rel", "nsobj")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, stdErr, "expected <object_namespace>:<object_id> format, got \"nsobj\"")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user