From 2e3dcb200a7769dc8710d311ca08a7515012fbdd Mon Sep 17 00:00:00 2001 From: Patrik Date: Wed, 27 Jan 2021 17:23:15 +0100 Subject: [PATCH] test: add relationtuple definition tests (#415) --- internal/relationtuple/definitions.go | 11 +- internal/relationtuple/definitions_test.go | 403 +++++++++++++++++++++ 2 files changed, 410 insertions(+), 4 deletions(-) create mode 100644 internal/relationtuple/definitions_test.go diff --git a/internal/relationtuple/definitions.go b/internal/relationtuple/definitions.go index fa4b474f8..6dab8abae 100644 --- a/internal/relationtuple/definitions.go +++ b/internal/relationtuple/definitions.go @@ -268,12 +268,15 @@ func (r *InternalRelationTuple) FromURLQuery(query url.Values) (*InternalRelatio } func (r *InternalRelationTuple) ToURLQuery() url.Values { - return url.Values{ + vals := url.Values{ "namespace": []string{r.Namespace}, "object": []string{r.Object}, "relation": []string{r.Relation}, - "subject": []string{r.Subject.String()}, } + if r.Subject != nil { + vals.Set("subject", r.Subject.String()) + } + return vals } func (q *RelationQuery) FromProto(query *acl.ListRelationTuplesRequest_Query) *RelationQuery { @@ -350,13 +353,13 @@ func (r *InternalRelationTuple) Interface() interface{} { return r } -func NewProtoRelationCollection(rels []*acl.RelationTuple) cmdx.Table { +func NewProtoRelationCollection(rels []*acl.RelationTuple) *relationCollection { return &relationCollection{ protoRelations: rels, } } -func NewRelationCollection(rels []*InternalRelationTuple) cmdx.Table { +func NewRelationCollection(rels []*InternalRelationTuple) *relationCollection { return &relationCollection{ internalRelations: rels, } diff --git a/internal/relationtuple/definitions_test.go b/internal/relationtuple/definitions_test.go new file mode 100644 index 000000000..1a4ec147e --- /dev/null +++ b/internal/relationtuple/definitions_test.go @@ -0,0 +1,403 @@ +package relationtuple + +import ( + "encoding/json" + "fmt" + "net/url" + "reflect" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + acl "github.com/ory/keto/proto/ory/keto/acl/v1alpha1" +) + +func TestSubject(t *testing.T) { + t.Run("case=string encoding-decoding", func(t *testing.T) { + for i, sub := range []Subject{ + &SubjectSet{ + Namespace: "n", + Object: "o", + Relation: "r", + }, + &SubjectSet{}, + &SubjectID{ + ID: "id", + }, + &SubjectID{}, + } { + t.Run(fmt.Sprintf("case=%d/type=%T", i, sub), func(t *testing.T) { + enc := sub.String() + dec, err := SubjectFromString(enc) + require.NoError(t, err) + assert.Equal(t, sub, dec) + }) + } + }) + + t.Run("case=string decoding-encoding", func(t *testing.T) { + for i, tc := range []struct { + sub string + expectedType Subject + }{ + { + sub: "", + expectedType: &SubjectID{}, + }, + { + sub: "foobar", + expectedType: &SubjectID{}, + }, + { + sub: "foo:bar#baz", + expectedType: &SubjectSet{}, + }, + { + sub: ":#", + expectedType: &SubjectSet{}, + }, + } { + t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) { + dec, err := SubjectFromString(tc.sub) + require.NoError(t, err) + assert.Equal(t, reflect.TypeOf(tc.expectedType), reflect.TypeOf(dec)) + assert.Equal(t, tc.sub, dec.String()) + }) + } + }) + + t.Run("method=equals", func(t *testing.T) { + for i, tc := range []struct { + a, b Subject + equals bool + }{ + { + a: &SubjectID{ID: "foo"}, + b: &SubjectID{ID: "foo"}, + equals: true, + }, + { + a: &SubjectID{ID: "foo"}, + b: &SubjectID{ID: "bar"}, + equals: false, + }, + { + a: &SubjectSet{}, + b: &SubjectID{}, + equals: false, + }, + { + a: &SubjectSet{ + Namespace: "N", + Object: "O", + Relation: "R", + }, + b: &SubjectSet{ + Namespace: "N", + Object: "O", + Relation: "R", + }, + equals: true, + }, + { + a: &SubjectSet{ + Object: "O", + Relation: "R", + }, + b: &SubjectSet{ + Namespace: "N", + Object: "O", + Relation: "R", + }, + equals: false, + }, + { + a: &SubjectSet{ + Namespace: "N", + Relation: "R", + }, + b: &SubjectSet{ + Namespace: "N", + Object: "O", + Relation: "R", + }, + equals: false, + }, + { + a: &SubjectSet{ + Namespace: "N", + Object: "O", + }, + b: &SubjectSet{ + Namespace: "N", + Object: "O", + Relation: "R", + }, + equals: false, + }, + } { + t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) { + assert.Equal(t, tc.equals, tc.a.Equals(tc.b)) + assert.Equal(t, tc.equals, tc.b.Equals(tc.a)) + }) + } + }) + + t.Run("case=url encoding-decoding", func(t *testing.T) { + for i, sub := range []*SubjectSet{ + { + Namespace: "n", + Object: "o", + Relation: "r", + }, + {}, + } { + t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) { + enc := sub.ToURLQuery() + dec := (&SubjectSet{}).FromURLQuery(enc) + assert.Equal(t, sub, dec) + }) + } + }) + + t.Run("case=url decoding-encoding", func(t *testing.T) { + for i, vals := range []url.Values{ + { + "namespace": []string{"n"}, + "object": []string{"o"}, + "relation": []string{"r"}, + }, + { + "namespace": []string{""}, + "object": []string{""}, + "relation": []string{""}, + }, + } { + t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) { + dec := (&SubjectSet{}).FromURLQuery(vals) + assert.Equal(t, vals, dec.ToURLQuery()) + }) + } + }) + + t.Run("case=json encoding", func(t *testing.T) { + for i, tc := range []struct { + sub Subject + json string + }{ + { + sub: &SubjectSet{ + Namespace: "n", + Object: "o", + Relation: "r", + }, + json: "\"n:o#r\"", + }, + { + sub: &SubjectID{ID: "foo"}, + json: "\"foo\"", + }, + } { + t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) { + enc, err := json.Marshal(tc.sub) + require.NoError(t, err) + assert.Equal(t, string(enc), tc.json) + }) + } + }) +} + +func TestInternalRelationTuple(t *testing.T) { + t.Run("method=string encoding", func(t *testing.T) { + assert.Equal(t, "n:o#r@s", (&InternalRelationTuple{ + Namespace: "n", + Object: "o", + Relation: "r", + Subject: &SubjectID{ID: "s"}, + }).String()) + }) + + t.Run("case=url encoding-decoding", func(t *testing.T) { + for i, r := range []*InternalRelationTuple{ + { + Namespace: "n", + Object: "o", + Relation: "r", + Subject: &SubjectID{ID: "s"}, + }, + { + Namespace: "n", + Object: "o", + Relation: "r", + Subject: &SubjectSet{ + Namespace: "sn", + Object: "so", + Relation: "sr", + }, + }, + {}, + } { + t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) { + res, err := (&InternalRelationTuple{}).FromURLQuery(r.ToURLQuery()) + require.NoError(t, err) + 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": []string{"foo"}, + }, + { + "namespace": []string{"n"}, + "object": []string{"o"}, + "relation": []string{"r"}, + "subject": []string{"sn:so#sr"}, + }, + } { + t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) { + rt, err := (&InternalRelationTuple{}).FromURLQuery(v) + require.NoError(t, err) + assert.Equal(t, v, rt.ToURLQuery()) + }) + } + }) +} + +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": []string{"foo"}, + }, + r: &RelationQuery{ + Namespace: "n", + Object: "o", + Relation: "r", + Subject: &SubjectID{ID: "foo"}, + }, + }, + { + v: url.Values{ + "namespace": []string{"n"}, + "object": []string{"o"}, + "relation": []string{"r"}, + "subject": []string{"sn:so#sr"}, + }, + r: &RelationQuery{ + Namespace: "n", + Object: "o", + Relation: "r", + Subject: &SubjectSet{ + Namespace: "sn", + Object: "so", + Relation: "sr", + }, + }, + }, + { + v: url.Values{ + "namespace": []string{"n"}, + "relation": []string{"r"}, + }, + r: &RelationQuery{ + Namespace: "n", + Relation: "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) + }) + } + }) +} + +func TestRelationCollection(t *testing.T) { + t.Run("case=prints all", func(t *testing.T) { + expected := make([]*InternalRelationTuple, 3) + for i := range expected { + expected[i] = &InternalRelationTuple{ + Namespace: "n" + strconv.Itoa(i), + Object: "o" + strconv.Itoa(i), + Relation: "r" + strconv.Itoa(i), + Subject: &SubjectID{ID: "s" + strconv.Itoa(i)}, + } + } + expected[2].Subject = &SubjectSet{ + Namespace: "sn", + Object: "so", + Relation: "sr", + } + + proto := make([]*acl.RelationTuple, 3) + for i := range expected { + proto[i] = &acl.RelationTuple{ + Namespace: "n" + strconv.Itoa(i), + Object: "o" + strconv.Itoa(i), + Relation: "r" + strconv.Itoa(i), + Subject: (&SubjectID{ID: "s" + strconv.Itoa(i)}).ToProto(), + } + } + proto[2].Subject = (&SubjectSet{ + Namespace: "sn", + Object: "so", + Relation: "sr", + }).ToProto() + + NewRelationCollection([]*InternalRelationTuple{}) + NewProtoRelationCollection([]*acl.RelationTuple{}) + + for i, c := range []*relationCollection{ + NewRelationCollection(expected), + NewProtoRelationCollection(proto), + } { + t.Run(fmt.Sprintf("case=%d", i), func(t *testing.T) { + var vals []string + for _, row := range c.Table() { + vals = append(vals, row...) + } + + ev := reflect.ValueOf(expected) + for el := 0; el < ev.Len(); el++ { + et := reflect.TypeOf(expected).Elem().Elem() + + for f := 0; f < et.NumField(); f++ { + v := ev.Index(el).Elem().Field(f) + // private field + if !v.CanSet() { + continue + } + + switch v.Kind() { + case reflect.String: + assert.Contains(t, vals, v.String()) + default: + str := v.MethodByName("String").Call(nil)[0].String() + assert.Contains(t, vals, str) + } + } + } + }) + } + }) +}