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:
142
internal/persistence/sql/traverser_test.go
Normal file
142
internal/persistence/sql/traverser_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright © 2026 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ory/keto/internal/relationtuple"
|
||||
)
|
||||
|
||||
// existsFor returns the EXISTS subquery text that buildFoundExpr produces for the given subject.
|
||||
func existsFor(t *testing.T, subject relationtuple.Subject) string {
|
||||
t.Helper()
|
||||
subjectSQL, _, err := whereSubject(subject)
|
||||
require.NoError(t, err)
|
||||
return `EXISTS(
|
||||
SELECT 1 FROM keto_relation_tuples
|
||||
WHERE nid = current.nid AND
|
||||
namespace = current.subject_set_namespace AND
|
||||
object = current.subject_set_object AND
|
||||
relation = current.subject_set_relation AND
|
||||
` + subjectSQL + `
|
||||
)`
|
||||
}
|
||||
|
||||
func TestBuildFoundExpr(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("case=subject as SubjectID", func(t *testing.T) {
|
||||
subject := &relationtuple.SubjectID{ID: uuid.Must(uuid.NewV4())}
|
||||
existsExpr := existsFor(t, subject)
|
||||
|
||||
// when SubjectID is used, no `allowedSubjectSets` is expected
|
||||
t.Run("no allowed types: EXISTS runs for all rows", func(t *testing.T) {
|
||||
expr, args, err := buildFoundExpr(nil, subject)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, existsExpr, expr)
|
||||
assert.Equal(t, []any{subject.ID}, args)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("case=subject as SubjectSet", func(t *testing.T) {
|
||||
subject := &relationtuple.SubjectSet{
|
||||
Namespace: "User",
|
||||
Object: uuid.Must(uuid.NewV4()),
|
||||
}
|
||||
existsExpr := existsFor(t, subject)
|
||||
|
||||
t.Run("no allowed types: EXISTS runs for all rows", func(t *testing.T) {
|
||||
expr, args, err := buildFoundExpr(nil, subject)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, existsExpr, expr)
|
||||
assert.Equal(t, []any{subject.Namespace, subject.Object, subject.Relation}, args)
|
||||
})
|
||||
|
||||
// All types have AllowsDirect=false: OPL forbids direct membership for every type,
|
||||
// so the found column is always false without touching the inner table.
|
||||
t.Run("all AllowsDirect=false: found is always false", func(t *testing.T) {
|
||||
types := []relationtuple.SubjectSetType{
|
||||
{Namespace: "Group", Relation: "member", AllowsDirect: false},
|
||||
{Namespace: "Org", Relation: "admin", AllowsDirect: false},
|
||||
}
|
||||
expr, args, err := buildFoundExpr(types, subject)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "false", expr)
|
||||
assert.Nil(t, args)
|
||||
})
|
||||
|
||||
// All types allow direct: the WHERE clause already restricts rows to those types,
|
||||
// so EXISTS runs unconditionally — no extra condition needed.
|
||||
t.Run("all AllowsDirect=true: plain EXISTS, no extra conditions ", func(t *testing.T) {
|
||||
types := []relationtuple.SubjectSetType{
|
||||
{Namespace: "Group", Relation: "member", AllowsDirect: true},
|
||||
{Namespace: "Org", Relation: "admin", AllowsDirect: true},
|
||||
}
|
||||
expr, args, err := buildFoundExpr(types, subject)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, existsExpr, expr)
|
||||
assert.Equal(t, []any{subject.Namespace, subject.Object, subject.Relation}, args)
|
||||
})
|
||||
|
||||
// Mixed: EXISTS is gated to fire only for the AllowsDirect=true type;
|
||||
// the false type is excluded from the gate even though it passes the WHERE filter.
|
||||
t.Run("mixed AllowsDirect: EXISTS gated only on allowed type", func(t *testing.T) {
|
||||
types := []relationtuple.SubjectSetType{
|
||||
{Namespace: "Group", Relation: "member", AllowsDirect: true},
|
||||
{Namespace: "Org", Relation: "admin", AllowsDirect: false},
|
||||
}
|
||||
expr, args, err := buildFoundExpr(types, subject)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
`(`+existsExpr+` AND (current.subject_set_namespace, current.subject_set_relation) IN ((?, ?)))`,
|
||||
expr,
|
||||
)
|
||||
assert.Equal(t, []any{subject.Namespace, subject.Object, subject.Relation, "Group", "member"}, args)
|
||||
})
|
||||
|
||||
t.Run("mixed AllowsDirect: multiple allowed types with not-allowed type", func(t *testing.T) {
|
||||
types := []relationtuple.SubjectSetType{
|
||||
{Namespace: "Group", Relation: "member", AllowsDirect: true},
|
||||
{Namespace: "Org", Relation: "admin", AllowsDirect: true},
|
||||
{Namespace: "Team", Relation: "lead", AllowsDirect: false},
|
||||
}
|
||||
expr, args, err := buildFoundExpr(types, subject)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t,
|
||||
`(`+existsExpr+` AND (current.subject_set_namespace, current.subject_set_relation) IN ((?, ?), (?, ?)))`,
|
||||
expr,
|
||||
)
|
||||
assert.Equal(t, []any{subject.Namespace, subject.Object, subject.Relation, "Group", "member", "Org", "admin"}, args)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildSubjectSetTypeSQL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("no types: no filter", func(t *testing.T) {
|
||||
sqlFragment, args := buildSubjectSetTypeSQL(nil)
|
||||
assert.Empty(t, sqlFragment)
|
||||
assert.Nil(t, args)
|
||||
})
|
||||
|
||||
t.Run("multiple types share one IN list", func(t *testing.T) {
|
||||
sqlFragment, args := buildSubjectSetTypeSQL([]relationtuple.SubjectSetType{
|
||||
{Namespace: "Group", Relation: "member"},
|
||||
{Namespace: "Org", Relation: "admin"},
|
||||
})
|
||||
assert.Equal(t, "\nAND (current.subject_set_namespace, current.subject_set_relation) IN ((?, ?), (?, ?))", sqlFragment)
|
||||
assert.Equal(t, []any{"Group", "member", "Org", "admin"}, args)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user