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
414 lines
8.5 KiB
Go
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)
|
|
})
|
|
}
|
|
})
|
|
}
|