Files
upstream-ory-keto/ketoapi/enc_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

414 lines
8.5 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package ketoapi
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
rts "github.com/ory/keto/gen/go/ory/keto/relation_tuples/v1alpha2"
)
func TestRelationTuple(t *testing.T) {
t.Run("method=string encoding", func(t *testing.T) {
for _, tc := range []struct {
tuple *RelationTuple
expected string
}{
{ // full tuple
tuple: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectID: new("s"),
},
expected: "n:o#r@s",
},
{ // skip '#' in subject set when relation is empty
tuple: &RelationTuple{
Namespace: "groups",
Object: "dev",
Relation: "member",
SubjectSet: &SubjectSet{
Namespace: "users",
Object: "user",
},
},
expected: "groups:dev#member@users:user",
},
} {
t.Run("case="+tc.expected, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.tuple.String())
})
}
})
t.Run("method=string decoding", func(t *testing.T) {
for _, tc := range []struct {
enc string
err error
expected *RelationTuple
}{
{
enc: "n:o#r@s",
expected: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectID: new("s"),
},
},
{
enc: "n:o#r@n:o#r",
expected: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectSet: &SubjectSet{
Namespace: "n",
Object: "o",
Relation: "r",
},
},
},
{
enc: "n:o#r@n:o#...",
expected: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectSet: &SubjectSet{
Namespace: "n",
Object: "o",
Relation: "...",
},
},
},
{
enc: "n:o#r@n:o#",
expected: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectSet: &SubjectSet{
Namespace: "n",
Object: "o",
},
},
},
{
enc: "n:o#r@n:o",
expected: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectSet: &SubjectSet{
Namespace: "n",
Object: "o",
},
},
},
{
enc: "n:o#r@(n:o#r)",
expected: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectSet: &SubjectSet{
Namespace: "n",
Object: "o",
Relation: "r",
},
},
},
{
enc: "#dev:@ory#:working:@projects:keto#awesome",
expected: &RelationTuple{
Namespace: "#dev",
Object: "@ory",
Relation: ":working:",
SubjectSet: &SubjectSet{
Namespace: "projects",
Object: "keto",
Relation: "awesome",
},
},
},
{
enc: "no-colon#in@this",
err: ErrMalformedInput(),
},
{
enc: "no:hash-in@this",
err: ErrMalformedInput(),
},
{
enc: "no:at#in-this",
err: ErrMalformedInput(),
},
} {
t.Run(fmt.Sprintf("string=%s", tc.enc), func(t *testing.T) {
actual, err := (&RelationTuple{}).FromString(tc.enc)
assert.True(t, errors.Is(err, tc.err), "%+v", err)
assert.Equal(t, tc.expected, actual)
})
}
})
t.Run("case=url encoding-decoding", func(t *testing.T) {
for i, r := range []*RelationTuple{
{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectID: new("s"),
},
{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectSet: &SubjectSet{
Namespace: "sn",
Object: "so",
Relation: "sr",
},
},
{
SubjectID: new(""),
},
} {
t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) {
vals := r.ToURLQuery()
res, err := (&RelationTuple{}).FromURLQuery(vals)
require.NoError(t, err, "raw: %+v, enc: %+v", r, vals)
assert.Equal(t, r, res)
})
}
})
t.Run("case=url decoding-encoding", func(t *testing.T) {
for i, v := range []url.Values{
{
"namespace": []string{"n"},
"object": []string{"o"},
"relation": []string{"r"},
"subject_id": []string{"foo"},
},
{
"namespace": []string{"n"},
"object": []string{"o"},
"relation": []string{"r"},
"subject_set.namespace": []string{"sn"},
"subject_set.object": []string{"so"},
"subject_set.relation": []string{"sr"},
},
} {
t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) {
rt, err := (&RelationTuple{}).FromURLQuery(v)
require.NoError(t, err)
q := rt.ToURLQuery()
assert.Equal(t, v, q)
})
}
})
t.Run("case=proto decoding", func(t *testing.T) {
for i, tc := range []struct {
proto TupleData
expected *RelationTuple
err error
}{
{
proto: &rts.RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
Subject: nil,
},
err: ErrNilSubject(),
},
{
proto: &rts.RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
Subject: &rts.Subject{
Ref: &rts.Subject_Set{
Set: &rts.SubjectSet{
Namespace: "n",
Object: "o",
Relation: "r",
},
},
},
},
expected: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectSet: &SubjectSet{
Namespace: "n",
Object: "o",
Relation: "r",
},
},
},
{
proto: &rts.RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
Subject: &rts.Subject{
Ref: &rts.Subject_Id{
Id: "user",
},
},
},
expected: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectID: new("user"),
},
},
} {
t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) {
actual, err := (&RelationTuple{}).FromDataProvider(tc.proto)
require.ErrorIs(t, err, tc.err)
assert.Equal(t, tc.expected, actual)
})
}
})
t.Run("format=JSON", func(t *testing.T) {
t.Run("direction=encoding-decoding", func(t *testing.T) {
for _, tc := range []struct {
name string
rt *RelationTuple
expected string
}{
{
name: "with subject ID",
rt: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectID: new("s"),
},
expected: `
{
"namespace": "n",
"object": "o",
"relation": "r",
"subject_id": "s"
}`,
},
{
name: "with subject set",
rt: &RelationTuple{
Namespace: "n",
Object: "o",
Relation: "r",
SubjectSet: &SubjectSet{
Namespace: "sn",
Object: "so",
Relation: "sr",
},
},
expected: `
{
"namespace": "n",
"object": "o",
"relation": "r",
"subject_set": {
"namespace": "sn",
"object": "so",
"relation": "sr"
}
}`,
},
} {
t.Run("case="+tc.name, func(t *testing.T) {
raw, err := json.Marshal(tc.rt)
require.NoError(t, err)
assert.JSONEq(t, tc.expected, string(raw))
var dec RelationTuple
require.NoError(t, json.Unmarshal(raw, &dec))
assert.Equal(t, tc.rt, &dec)
})
}
})
})
}
func TestRelationQuery(t *testing.T) {
t.Run("case=url encoding-decoding-encoding", func(t *testing.T) {
for i, tc := range []struct {
v url.Values
r *RelationQuery
}{
{
v: url.Values{
"namespace": []string{"n"},
"object": []string{"o"},
"relation": []string{"r"},
"subject_id": []string{"foo"},
},
r: &RelationQuery{
Namespace: new("n"),
Object: new("o"),
Relation: new("r"),
SubjectID: new("foo"),
},
},
{
v: url.Values{
"namespace": []string{"n"},
"object": []string{"o"},
"relation": []string{"r"},
"subject_set.namespace": []string{"sn"},
"subject_set.object": []string{"so"},
"subject_set.relation": []string{"sr"},
},
r: &RelationQuery{
Namespace: new("n"),
Object: new("o"),
Relation: new("r"),
SubjectSet: &SubjectSet{
Namespace: "sn",
Object: "so",
Relation: "sr",
},
},
},
{
v: url.Values{
"namespace": []string{"n"},
"relation": []string{"r"},
},
r: &RelationQuery{
Namespace: new("n"),
Relation: new("r"),
},
},
} {
t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) {
enc := tc.r.ToURLQuery()
assert.Equal(t, tc.v, enc)
dec, err := (&RelationQuery{}).FromURLQuery(tc.v)
require.NoError(t, err)
assert.Equal(t, tc.r, dec)
})
}
})
}