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

233 lines
5.7 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"cmp"
"slices"
"strconv"
"testing"
"time"
"github.com/ory/x/uuidx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ory/keto/internal/namespace"
"github.com/ory/keto/ketoapi"
)
func runTransactionCases(c transactClient, m *namespaceTestManager) func(*testing.T) {
return func(t *testing.T) {
t.Parallel()
t.Run("case=create and delete", func(t *testing.T) {
n := &namespace.Namespace{Name: t.Name()}
m.add(t, n)
tuples := []*ketoapi.RelationTuple{
{
Namespace: n.Name,
Object: "o",
Relation: "rel",
SubjectSet: &ketoapi.SubjectSet{
Namespace: n.Name,
Object: "o",
Relation: "rel",
},
},
{
Namespace: n.Name,
Object: "o",
Relation: "rel",
SubjectID: new("sid"),
},
}
c.transactTuples(t, tuples, nil)
resp := c.queryTuple(t, &ketoapi.RelationQuery{
Namespace: &n.Name,
})
for i := range tuples {
assert.Contains(t, resp.RelationTuples, tuples[i])
}
c.transactTuples(t, nil, tuples)
resp = c.queryTuple(t, &ketoapi.RelationQuery{
Namespace: &n.Name,
})
assert.Len(t, resp.RelationTuples, 0)
})
t.Run("case=duplicate string representations", func(t *testing.T) {
n := &namespace.Namespace{Name: t.Name()}
m.add(t, n)
c.transactTuples(t, []*ketoapi.RelationTuple{
{
Namespace: n.Name,
Object: "o",
Relation: "rel",
SubjectID: new("sid"),
},
{
Namespace: n.Name,
Object: "o",
Relation: "rel",
SubjectID: new("sid"),
},
}, nil)
})
t.Run("case=large inserts and deletes", func(t *testing.T) {
if testing.Short() {
t.Skip("This test is fairly expensive, especially the deletion.")
}
ns := []*namespace.Namespace{
{Name: uuidx.NewV4().String()},
{Name: uuidx.NewV4().String()},
}
m.add(t, ns...)
var tuples []*ketoapi.RelationTuple
for i := range 1200 {
tuples = append(tuples,
&ketoapi.RelationTuple{
Namespace: ns[0].Name,
Object: "o" + strconv.Itoa(i),
Relation: "rela",
SubjectSet: &ketoapi.SubjectSet{
Namespace: ns[1].Name,
Object: "o" + strconv.Itoa(i),
Relation: "relx",
},
},
&ketoapi.RelationTuple{
Namespace: ns[0].Name,
Object: "o" + strconv.Itoa(i),
Relation: "relb",
SubjectID: new("sid"),
},
)
}
t0 := time.Now()
c.transactTuples(t, tuples, nil)
t.Logf("insert took %s", time.Since(t0))
t0 = time.Now()
var resp []*ketoapi.RelationTuple
var pt string
for {
r := c.queryTuple(t, &ketoapi.RelationQuery{
Namespace: &ns[0].Name,
}, withSize(1000), withToken(pt))
resp = append(resp, r.RelationTuples...)
pt = r.NextPageToken
if pt == "" {
break
}
}
t.Logf("query took %s", time.Since(t0))
sort := func(a, b *ketoapi.RelationTuple) int {
return cmp.Or(
cmp.Compare(a.Namespace, b.Namespace),
cmp.Compare(a.Object, b.Object),
cmp.Compare(a.Relation, b.Relation),
)
}
slices.SortFunc(resp, sort)
slices.SortFunc(tuples, sort)
require.Equal(t, tuples, resp)
t0 = time.Now()
c.transactTuples(t, nil, tuples)
t.Logf("delete took %s", time.Since(t0))
resp = c.queryTuple(t, &ketoapi.RelationQuery{
Namespace: &ns[0].Name,
}).RelationTuples
assert.Len(t, resp, 0)
})
t.Run("case=expand-api-display-access docs code sample", func(t *testing.T) {
files := &namespace.Namespace{Name: t.Name() + "files"}
directories := &namespace.Namespace{Name: t.Name() + "directories"}
m.add(t, files, directories)
tuples := []*ketoapi.RelationTuple{
{
Namespace: directories.Name,
Object: "/photos",
Relation: "owner",
SubjectID: new("maureen"),
},
{
Namespace: files.Name,
Object: "/photos/beach.jpg",
Relation: "owner",
SubjectID: new("maureen"),
},
{
Namespace: files.Name,
Object: "/photos/mountains.jpg",
Relation: "owner",
SubjectID: new("laura"),
},
{
Namespace: directories.Name,
Object: "/photos",
Relation: "access",
SubjectID: new("laura"),
},
}
for _, o := range []struct{ n, o string }{
{files.Name, "/photos/beach.jpg"},
{files.Name, "/photos/mountains.jpg"},
{directories.Name, "/photos"},
} {
tuples = append(tuples, &ketoapi.RelationTuple{
Namespace: o.n,
Object: o.o,
Relation: "access",
SubjectSet: &ketoapi.SubjectSet{
Namespace: o.n,
Object: o.o,
Relation: "owner",
},
})
}
for _, obj := range []string{"/photos/beach.jpg", "/photos/mountains.jpg"} {
tuples = append(tuples, &ketoapi.RelationTuple{
Namespace: files.Name,
Object: obj,
Relation: "access",
SubjectSet: &ketoapi.SubjectSet{
Namespace: directories.Name,
Object: "/photos",
Relation: "access",
},
})
}
c.transactTuples(t, tuples, nil)
fileTuples := c.queryTuple(t, &ketoapi.RelationQuery{Namespace: new(files.Name)})
directoryTuples := c.queryTuple(t, &ketoapi.RelationQuery{Namespace: new(directories.Name)})
allTuples := append(fileTuples.RelationTuples, directoryTuples.RelationTuples...)
assert.ElementsMatch(t, allTuples, tuples)
c.transactTuples(t, nil, tuples)
fileTuples = c.queryTuple(t, &ketoapi.RelationQuery{Namespace: new(files.Name)})
directoryTuples = c.queryTuple(t, &ketoapi.RelationQuery{Namespace: new(directories.Name)})
allTuples = append(fileTuples.RelationTuples, directoryTuples.RelationTuples...)
assert.Len(t, allTuples, 0)
})
}
}