diff --git a/pkg/file/builder_test.go b/pkg/file/builder_test.go index 9cc1504..34a75f3 100644 --- a/pkg/file/builder_test.go +++ b/pkg/file/builder_test.go @@ -389,6 +389,23 @@ func existingFilterChainState() *state.KongState { return s } +func existingDegraphqlRouteState() *state.KongState { + s, _ := state.NewKongState() + s.DegraphqlRoutes.Add( + state.DegraphqlRoute{ + DegraphqlRoute: kong.DegraphqlRoute{ + ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), + Service: &kong.Service{ + ID: kong.String("ba54b737-38aa-49d1-87c4-64e756b0c6f9"), + }, + Methods: kong.StringSlice("GET"), + URI: kong.String("/example"), + Query: kong.String("query{ example { foo } }"), + }, + }) + return s +} + var deterministicUUID = func() *string { version := byte(4) uuid := make([]byte, 16) @@ -3853,3 +3870,151 @@ func Test_stateBuilder_expressionRoutes_kong370_withKonnect(t *testing.T) { }) } } + +func Test_stateBuilder_ingestPluginEntities(t *testing.T) { + rand.Seed(42) + type fields struct { + currentState *state.KongState + targetContent *Content + } + tests := []struct { + name string + fields fields + want *utils.KongRawState + }{ + { + name: "generates a new degraphql route from valid config passed", + fields: fields{ + targetContent: &Content{ + PluginEntities: []FPluginEntity{ + { + Type: kong.String("degraphql_routes"), + Fields: PluginEntityConfiguration{ + "uri": kong.String("/foo"), + "query": kong.String("query { foo { bar }}"), + "service": kong.String("fdfd14cc-cd69-49a0-9e23-cd3375b6c0cd"), + }, + }, + }, + }, + currentState: emptyState(), + }, + want: &utils.KongRawState{ + DegraphqlRoutes: []*kong.DegraphqlRoute{ + { + ID: kong.String("538c7f96-b164-4f1b-97bb-9f4bb472e89f"), + URI: kong.String("/foo"), + Query: kong.String("query { foo { bar }}"), + Service: &kong.Service{ + ID: kong.String("fdfd14cc-cd69-49a0-9e23-cd3375b6c0cd"), + }, + Methods: kong.StringSlice("GET"), + }, + }, + }, + }, + { + name: "matches ID for an existing degraphql route", + fields: fields{ + targetContent: &Content{ + PluginEntities: []FPluginEntity{ + { + Type: kong.String("degraphql_routes"), + Fields: PluginEntityConfiguration{ + "uri": kong.String("/example"), + "query": kong.String("query{ example { foo } }"), + "service": kong.String("ba54b737-38aa-49d1-87c4-64e756b0c6f9"), + }, + }, + }, + }, + currentState: existingDegraphqlRouteState(), + }, + want: &utils.KongRawState{ + DegraphqlRoutes: []*kong.DegraphqlRoute{ + { + ID: kong.String("4bfcb11f-c962-4817-83e5-9433cf20b663"), + Service: &kong.Service{ + ID: kong.String("ba54b737-38aa-49d1-87c4-64e756b0c6f9"), + }, + Methods: kong.StringSlice("GET"), + URI: kong.String("/example"), + Query: kong.String("query{ example { foo } }"), + }, + }, + }, + }, + { + name: "accepts multi line query input and service name", + fields: fields{ + targetContent: &Content{ + Services: []FService{ + { + Service: kong.Service{ + Name: kong.String("foo"), + }, + }, + }, + PluginEntities: []FPluginEntity{ + { + Type: kong.String("degraphql_routes"), + Fields: PluginEntityConfiguration{ + "uri": kong.String("/foo"), + "query": kong.String(`query SearchPosts($filters: PostsFilters) { + posts(filter: $filters) { + id + title + author + } + }`), + "service": kong.String("foo"), + "methods": kong.StringSlice("GET", "POST"), + }, + }, + }, + }, + currentState: emptyState(), + }, + want: &utils.KongRawState{ + DegraphqlRoutes: []*kong.DegraphqlRoute{ + { + ID: kong.String("dfd79b4d-7642-4b61-ba0c-9f9f0d3ba55b"), + Service: &kong.Service{ + ID: kong.String("foo"), + }, + Methods: kong.StringSlice("GET", "POST"), + URI: kong.String("/foo"), + Query: kong.String(`query SearchPosts($filters: PostsFilters) { + posts(filter: $filters) { + id + title + author + } + }`), + }, + }, + Services: []*kong.Service{ + { + ID: kong.String("5b1484f2-5209-49d9-b43e-92ba09dd9d52"), + Name: kong.String("foo"), + Protocol: kong.String("http"), + ConnectTimeout: kong.Int(60000), + WriteTimeout: kong.Int(60000), + ReadTimeout: kong.Int(60000), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &stateBuilder{ + targetContent: tt.fields.targetContent, + currentState: tt.fields.currentState, + } + b.build() + assert.Equal(t, tt.want, b.rawState) + }) + } +} diff --git a/pkg/file/writer_test.go b/pkg/file/writer_test.go index 991a284..b4f36f4 100644 --- a/pkg/file/writer_test.go +++ b/pkg/file/writer_test.go @@ -220,6 +220,17 @@ func Test_compareOrder(t *testing.T) { }, expected: true, }, + { + sortable1: FPluginEntity{ + ID: kong.String("degraphql-route-1"), + Type: kong.String("degraphql_routes"), + }, + sortable2: FPluginEntity{ + ID: kong.String("degraphql-route-2"), + Type: kong.String("degraphql_routes"), + }, + expected: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/state/degraphql_route_test.go b/pkg/state/degraphql_route_test.go new file mode 100644 index 0000000..f768aa2 --- /dev/null +++ b/pkg/state/degraphql_route_test.go @@ -0,0 +1,278 @@ +package state + +import ( + "testing" + + "github.com/kong/go-kong/kong" + "github.com/stretchr/testify/assert" +) + +func degraphqlRoutesCollection() *DegraphqlRoutesCollection { + return state().DegraphqlRoutes +} + +func TestDegraphqlRouteAdd(t *testing.T) { + collection := degraphqlRoutesCollection() + + tests := []struct { + name string + degraphqlRoute DegraphqlRoute + wantErr bool + }{ + { + name: "adds a degraphql route to the collection", + degraphqlRoute: DegraphqlRoute{ + DegraphqlRoute: kong.DegraphqlRoute{ + ID: kong.String("first"), + URI: kong.String("/foo"), + Query: kong.String("query { hello }"), + Service: &kong.Service{ + ID: kong.String("some-service"), + }, + Methods: kong.StringSlice("GET"), + }, + }, + wantErr: false, + }, + { + name: "adds a degraphql route with complex query to the collection", + degraphqlRoute: DegraphqlRoute{ + DegraphqlRoute: kong.DegraphqlRoute{ + ID: kong.String("second"), + URI: kong.String("/bar"), + Query: kong.String(`query SearchPosts($filters: PostsFilters) { + posts(filter: $filters) { + id + title + author + } + }`), + Service: &kong.Service{ + ID: kong.String("some-service"), + }, + Methods: kong.StringSlice("GET", "POST"), + }, + }, + wantErr: false, + }, + { + name: "returns an error when the degraphql route already exists", + degraphqlRoute: DegraphqlRoute{ + DegraphqlRoute: kong.DegraphqlRoute{ + ID: kong.String("first"), + URI: kong.String("/foo"), + Query: kong.String("query { hello }"), + Service: &kong.Service{ + ID: kong.String("some-service"), + }, + Methods: kong.StringSlice("GET"), + }, + }, + wantErr: true, + }, + { + name: "returns an error if an id is not provided", + degraphqlRoute: DegraphqlRoute{ + DegraphqlRoute: kong.DegraphqlRoute{ + URI: kong.String("/foo"), + Query: kong.String("query { hello }"), + Service: &kong.Service{ + ID: kong.String("some-service"), + }, + Methods: kong.StringSlice("GET"), + }, + }, + wantErr: true, + }, + { + name: "returns an error if no empty degraphql route is provided", + degraphqlRoute: DegraphqlRoute{ + DegraphqlRoute: kong.DegraphqlRoute{}, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if err := collection.Add(tt.degraphqlRoute); (err != nil) != tt.wantErr { + t.Errorf("DegraphqlRoutesCollection.Add() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDegraphqlRouteGet(t *testing.T) { + assert := assert.New(t) + collection := degraphqlRoutesCollection() + + var degraphqlRoute DegraphqlRoute + degraphqlRoute.ID = kong.String("example") + degraphqlRoute.URI = kong.String("/foo") + degraphqlRoute.Query = kong.String("query { hello }") + degraphqlRoute.Service = &kong.Service{ + ID: kong.String("some-service"), + } + degraphqlRoute.Methods = kong.StringSlice("GET") + + err := collection.Add(degraphqlRoute) + assert.Nil(err) + + // Fetch the currently added entity + res, err := collection.Get("example") + assert.Nil(err) + assert.NotNil(res) + assert.Equal("example", *res.ID) + assert.Equal("some-service", *res.Service.ID) + + // Fetch non-existent entity + res, err = collection.Get("does-not-exist") + assert.NotNil(err) + assert.Nil(res) +} + +func TestDegraphqlRouteUpdate(t *testing.T) { + assert := assert.New(t) + collection := degraphqlRoutesCollection() + + var degraphqlRoute DegraphqlRoute + degraphqlRoute.ID = kong.String("example") + degraphqlRoute.URI = kong.String("/foo") + degraphqlRoute.Query = kong.String("query { hello }") + degraphqlRoute.Service = &kong.Service{ + ID: kong.String("some-service"), + } + degraphqlRoute.Methods = kong.StringSlice("GET") + + err := collection.Add(degraphqlRoute) + assert.Nil(err) + + // Fetch the currently added entity + res, err := collection.Get("example") + assert.Nil(err) + assert.NotNil(res) + assert.Equal("query { hello }", *res.Query) + + // Update query field + res.Query = kong.String("query { hello world }") + err = collection.Update(*res) + assert.Nil(err) + + // Fetch again + res, err = collection.Get("example") + assert.Nil(err) + assert.NotNil(res) + assert.Equal("query { hello world }", *res.Query) +} + +func TestDegraphqlRouteDelete(t *testing.T) { + assert := assert.New(t) + collection := degraphqlRoutesCollection() + + var degraphqlRoute DegraphqlRoute + degraphqlRoute.ID = kong.String("example") + degraphqlRoute.URI = kong.String("/foo") + degraphqlRoute.Query = kong.String("query { hello }") + degraphqlRoute.Service = &kong.Service{ + ID: kong.String("some-service"), + } + degraphqlRoute.Methods = kong.StringSlice("GET") + + err := collection.Add(degraphqlRoute) + assert.Nil(err) + + // Fetch the currently added entity + res, err := collection.Get("example") + assert.Nil(err) + assert.NotNil(res) + + // Delete entity + err = collection.Delete(*res.ID) + assert.Nil(err) + + // Fetch again + res, err = collection.Get("example") + assert.NotNil(err) + assert.Nil(res) +} + +func TestDegraphqlRouteGetAll(t *testing.T) { + assert := assert.New(t) + collection := degraphqlRoutesCollection() + + populateDegraphqlRoutes(assert, collection) + + degraphqlRoutes, err := collection.GetAll() + assert.Nil(err) + assert.Equal(5, len(degraphqlRoutes)) + assert.IsType([]*DegraphqlRoute{}, degraphqlRoutes) +} + +func populateDegraphqlRoutes(assert *assert.Assertions, + collection *DegraphqlRoutesCollection, +) { + degraphqlRoutes := []DegraphqlRoute{ + { + DegraphqlRoute: kong.DegraphqlRoute{ + ID: kong.String("first"), + URI: kong.String("/foo"), + Query: kong.String("query { hello }"), + Service: &kong.Service{ + ID: kong.String("some-service"), + }, + Methods: kong.StringSlice("GET"), + }, + }, + { + DegraphqlRoute: kong.DegraphqlRoute{ + ID: kong.String("second"), + URI: kong.String("/bar"), + Query: kong.String("query { hello }"), + Service: &kong.Service{ + ID: kong.String("some-service"), + }, + Methods: kong.StringSlice("GET"), + }, + }, + { + DegraphqlRoute: kong.DegraphqlRoute{ + ID: kong.String("third"), + URI: kong.String("/foo"), + Query: kong.String("query { hello }"), + Service: &kong.Service{ + ID: kong.String("some-service"), + }, + Methods: kong.StringSlice("GET"), + }, + }, + { + DegraphqlRoute: kong.DegraphqlRoute{ + ID: kong.String("fourth"), + URI: kong.String("/bar"), + Query: kong.String("query { hello }"), + Service: &kong.Service{ + ID: kong.String("some-service"), + }, + Methods: kong.StringSlice("GET"), + }, + }, + { + DegraphqlRoute: kong.DegraphqlRoute{ + ID: kong.String("fifth"), + URI: kong.String("/foo"), + Query: kong.String("query { hello }"), + Service: &kong.Service{ + ID: kong.String("some-service"), + }, + Methods: kong.StringSlice("GET"), + }, + }, + } + + for _, d := range degraphqlRoutes { + err := collection.Add(d) + assert.Nil(err) + } +} diff --git a/pkg/state/types.go b/pkg/state/types.go index 1c30695..a9802d4 100644 --- a/pkg/state/types.go +++ b/pkg/state/types.go @@ -1806,6 +1806,9 @@ type DegraphqlRoute struct { // Identifier returns the ID of the DegraphqlRoute. func (d *DegraphqlRoute) GetPluginEntityID() string { + if d.ID == nil { + return "" + } return *d.ID }