Skip to content

Commit 8bc22ea

Browse files
authored
Tests(gateway): resolver.go (#71)
* resolver tests * introduced builder patter to field config args * better validation
1 parent 02f8282 commit 8bc22ea

File tree

17 files changed

+1732
-130
lines changed

17 files changed

+1732
-130
lines changed

.mockery.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ packages:
2626
dir: listener/controller/mocks
2727
outpkg: mocks
2828
interfaces:
29-
Client:
29+
Client:
30+
WithWatch:

Taskfile.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ vars:
77
ENVTEST_VERSION: release-0.17
88
tasks:
99
## Setup
10+
setup:mockery:
11+
internal: true
12+
cmds:
13+
- test -s {{.LOCAL_BIN}}/mockery || GOBIN=$(pwd)/{{.LOCAL_BIN}} go install github.com/vektra/mockery/[email protected]
1014
setup:envtest:
1115
internal: true
1216
cmds:
@@ -21,6 +25,10 @@ tasks:
2125
- test -s {{.LOCAL_BIN}}/go-test-coverage || GOBIN=$(pwd)/{{.LOCAL_BIN}} go install github.com/vladopajic/go-test-coverage/v2@latest
2226

2327
## Development
28+
mockery:
29+
deps: [ setup:mockery ]
30+
cmds:
31+
- "{{.LOCAL_BIN}}/mockery"
2432
build:
2533
cmds:
2634
- go build ./...
@@ -37,6 +45,7 @@ tasks:
3745
- task: fmt
3846
- "{{.LOCAL_BIN}}/golangci-lint run --timeout 10m ./..."
3947
envtest:
48+
deps: [mockery]
4049
env:
4150
KUBEBUILDER_ASSETS:
4251
sh: $(pwd)/{{.LOCAL_BIN}}/setup-envtest use {{.ENVTEST_K8S_VERSION}} --bin-dir $(pwd)/{{.LOCAL_BIN}} -p path
@@ -56,6 +65,7 @@ tasks:
5665
ADDITIONAL_COMMAND_ARGS: -coverprofile=./cover.out -covermode=atomic -coverpkg=./...
5766
validate:
5867
cmds:
68+
- task: mockery
5969
- task: lint
6070
- task: test
6171

gateway/manager/manager.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func (s *Service) OnFileDeleted(filename string) {
160160
}
161161

162162
func (s *Service) loadSchemaFromFile(filename string) (*graphql.Schema, error) {
163-
definitions, err := readDefinitionFromFile(filepath.Join(s.appCfg.OpenApiDefinitionsPath, filename))
163+
definitions, err := ReadDefinitionFromFile(filepath.Join(s.appCfg.OpenApiDefinitionsPath, filename))
164164
if err != nil {
165165
return nil, err
166166
}
@@ -291,7 +291,7 @@ func (s *Service) handleSubscription(w http.ResponseWriter, r *http.Request, sch
291291
fmt.Fprint(w, "event: complete\n\n")
292292
}
293293

294-
func readDefinitionFromFile(filePath string) (spec.Definitions, error) {
294+
func ReadDefinitionFromFile(filePath string) (spec.Definitions, error) {
295295
f, err := os.Open(filePath)
296296
if err != nil {
297297
return nil, err

gateway/resolver/arguments.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package resolver
2+
3+
import "github.com/graphql-go/graphql"
4+
5+
const (
6+
LabelSelectorArg = "labelselector"
7+
NameArg = "name"
8+
NamespaceArg = "namespace"
9+
ObjectArg = "object"
10+
SubscribeToAllArg = "subscribeToAll"
11+
)
12+
13+
// FieldConfigArgumentsBuilder helps construct GraphQL field config arguments
14+
type FieldConfigArgumentsBuilder struct {
15+
arguments graphql.FieldConfigArgument
16+
}
17+
18+
// NewFieldConfigArguments initializes a new builder
19+
func NewFieldConfigArguments() *FieldConfigArgumentsBuilder {
20+
return &FieldConfigArgumentsBuilder{
21+
arguments: graphql.FieldConfigArgument{},
22+
}
23+
}
24+
25+
func (b *FieldConfigArgumentsBuilder) WithNameArg() *FieldConfigArgumentsBuilder {
26+
b.arguments[NameArg] = &graphql.ArgumentConfig{
27+
Type: graphql.NewNonNull(graphql.String),
28+
Description: "The name of the object",
29+
}
30+
return b
31+
}
32+
33+
func (b *FieldConfigArgumentsBuilder) WithNamespaceArg() *FieldConfigArgumentsBuilder {
34+
b.arguments[NamespaceArg] = &graphql.ArgumentConfig{
35+
Type: graphql.String,
36+
Description: "The namespace in which to search for the objects",
37+
}
38+
return b
39+
}
40+
41+
func (b *FieldConfigArgumentsBuilder) WithLabelSelectorArg() *FieldConfigArgumentsBuilder {
42+
b.arguments[LabelSelectorArg] = &graphql.ArgumentConfig{
43+
Type: graphql.String,
44+
Description: "A label selector to filter the objects by",
45+
}
46+
return b
47+
}
48+
49+
func (b *FieldConfigArgumentsBuilder) WithObjectArg(resourceInputType *graphql.InputObject) *FieldConfigArgumentsBuilder {
50+
b.arguments[ObjectArg] = &graphql.ArgumentConfig{
51+
Type: graphql.NewNonNull(resourceInputType),
52+
Description: "The object to create or update",
53+
}
54+
return b
55+
}
56+
57+
func (b *FieldConfigArgumentsBuilder) WithSubscribeToAllArg() *FieldConfigArgumentsBuilder {
58+
b.arguments[SubscribeToAllArg] = &graphql.ArgumentConfig{
59+
Type: graphql.Boolean,
60+
DefaultValue: false,
61+
Description: "If true, events will be emitted on every field change",
62+
}
63+
return b
64+
}
65+
66+
// Complete returns the constructed arguments
67+
func (b *FieldConfigArgumentsBuilder) Complete() graphql.FieldConfigArgument {
68+
return b.arguments
69+
}

gateway/resolver/resolver.go

Lines changed: 53 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"errors"
66
"fmt"
7+
"github.com/rs/zerolog/log"
78
"regexp"
89

910
"github.com/graphql-go/graphql"
@@ -19,17 +20,9 @@ import (
1920
"github.com/openmfp/golang-commons/logger"
2021
)
2122

22-
const (
23-
labelSelectorArg = "labelselector"
24-
nameArg = "name"
25-
namespaceArg = "namespace"
26-
subscribeToAllArg = "subscribeToAll"
27-
)
28-
2923
type Provider interface {
3024
CrudProvider
3125
FieldResolverProvider
32-
ArgumentsProvider
3326
}
3427

3528
type CrudProvider interface {
@@ -48,13 +41,6 @@ type FieldResolverProvider interface {
4841
GetOriginalGroupName(string) string
4942
}
5043

51-
type ArgumentsProvider interface {
52-
GetListItemsArguments() graphql.FieldConfigArgument
53-
GetMutationArguments(resourceInputType *graphql.InputObject) graphql.FieldConfigArgument
54-
GetNameAndNamespaceArguments() graphql.FieldConfigArgument
55-
GetSubscriptionArguments(includeNameArg bool) graphql.FieldConfigArgument
56-
}
57-
5844
type Service struct {
5945
log *logger.Logger
6046
groupNames map[string]string
@@ -95,17 +81,17 @@ func (r *Service) ListItems(gvk schema.GroupVersionKind) graphql.FieldResolveFn
9581

9682
var opts []client.ListOption
9783
// Handle label selector argument
98-
if labelSelector, ok := p.Args[labelSelectorArg].(string); ok && labelSelector != "" {
84+
if labelSelector, ok := p.Args[LabelSelectorArg].(string); ok && labelSelector != "" {
9985
selector, err := labels.Parse(labelSelector)
10086
if err != nil {
101-
log.Error().Err(err).Str(labelSelectorArg, labelSelector).Msg("Unable to parse given label selector")
87+
log.Error().Err(err).Str(LabelSelectorArg, labelSelector).Msg("Unable to parse given label selector")
10288
return nil, err
10389
}
10490
opts = append(opts, client.MatchingLabelsSelector{Selector: selector})
10591
}
10692

10793
// Handle namespace argument
108-
if namespace, ok := p.Args[namespaceArg].(string); ok && namespace != "" {
94+
if namespace, ok := p.Args[NamespaceArg].(string); ok && namespace != "" {
10995
opts = append(opts, client.InNamespace(namespace))
11096
}
11197

@@ -144,15 +130,8 @@ func (r *Service) GetItem(gvk schema.GroupVersionKind) graphql.FieldResolveFn {
144130
}
145131

146132
// Retrieve required arguments
147-
name, ok := p.Args["name"].(string)
148-
if !ok || name == "" {
149-
log.Error().Err(errors.New("missing required argument: name")).Msg("Name argument is required")
150-
return nil, err
151-
}
152-
153-
namespace, ok := p.Args["namespace"].(string)
154-
if !ok || namespace == "" {
155-
log.Error().Err(errors.New("missing required argument: namespace")).Msg("Namespace argument is required")
133+
name, namespace, err := getNameAndNamespace(p.Args)
134+
if err != nil {
156135
return nil, err
157136
}
158137

@@ -182,7 +161,7 @@ func (r *Service) CreateItem(gvk schema.GroupVersionKind) graphql.FieldResolveFn
182161

183162
log := r.log.With().Str("operation", "create").Str("kind", gvk.Kind).Logger()
184163

185-
namespace := p.Args[namespaceArg].(string)
164+
namespace := p.Args[NamespaceArg].(string)
186165
objectInput := p.Args["object"].(map[string]interface{})
187166

188167
obj := &unstructured.Unstructured{
@@ -213,15 +192,12 @@ func (r *Service) UpdateItem(gvk schema.GroupVersionKind) graphql.FieldResolveFn
213192

214193
log := r.log.With().Str("operation", "update").Str("kind", gvk.Kind).Logger()
215194

216-
namespace := p.Args[namespaceArg].(string)
217-
objectInput := p.Args["object"].(map[string]interface{})
218-
219-
// Ensure metadata.name is set
220-
name, found, err := unstructured.NestedString(objectInput, "metadata", "name")
221-
if err != nil || !found || name == "" {
222-
return nil, errors.New("object metadata.name is required")
195+
name, namespace, err := getNameAndNamespace(p.Args)
196+
if err != nil {
197+
return nil, err
223198
}
224199

200+
objectInput := p.Args["object"].(map[string]interface{})
225201
// Marshal the input object to JSON to create the patch data
226202
patchData, err := json.Marshal(objectInput)
227203
if err != nil {
@@ -260,8 +236,10 @@ func (r *Service) DeleteItem(gvk schema.GroupVersionKind) graphql.FieldResolveFn
260236

261237
log := r.log.With().Str("operation", "delete").Str("kind", gvk.Kind).Logger()
262238

263-
name := p.Args[nameArg].(string)
264-
namespace := p.Args[namespaceArg].(string)
239+
name, namespace, err := getNameAndNamespace(p.Args)
240+
if err != nil {
241+
return nil, err
242+
}
265243

266244
obj := &unstructured.Unstructured{}
267245
obj.SetGroupVersionKind(gvk)
@@ -283,48 +261,6 @@ func (r *Service) CommonResolver() graphql.FieldResolveFn {
283261
}
284262
}
285263

286-
// GetListItemsArguments returns the GraphQL arguments for listing resources.
287-
func (r *Service) GetListItemsArguments() graphql.FieldConfigArgument {
288-
return graphql.FieldConfigArgument{
289-
labelSelectorArg: &graphql.ArgumentConfig{
290-
Type: graphql.String,
291-
Description: "A label selector to filter the objects by",
292-
},
293-
namespaceArg: &graphql.ArgumentConfig{
294-
Type: graphql.String,
295-
Description: "The namespace in which to search for the objects",
296-
},
297-
}
298-
}
299-
300-
// GetMutationArguments returns the GraphQL arguments for create and update mutations.
301-
func (r *Service) GetMutationArguments(resourceInputType *graphql.InputObject) graphql.FieldConfigArgument {
302-
return graphql.FieldConfigArgument{
303-
namespaceArg: &graphql.ArgumentConfig{
304-
Type: graphql.NewNonNull(graphql.String),
305-
Description: "The namespace of the object",
306-
},
307-
"object": &graphql.ArgumentConfig{
308-
Type: graphql.NewNonNull(resourceInputType),
309-
Description: "The object to create or update",
310-
},
311-
}
312-
}
313-
314-
// GetNameAndNamespaceArguments returns the GraphQL arguments for delete mutations.
315-
func (r *Service) GetNameAndNamespaceArguments() graphql.FieldConfigArgument {
316-
return graphql.FieldConfigArgument{
317-
nameArg: &graphql.ArgumentConfig{
318-
Type: graphql.NewNonNull(graphql.String),
319-
Description: "The name of the object",
320-
},
321-
namespaceArg: &graphql.ArgumentConfig{
322-
Type: graphql.NewNonNull(graphql.String),
323-
Description: "The namespace of the object",
324-
},
325-
}
326-
}
327-
328264
func (r *Service) SanitizeGroupName(groupName string) string {
329265
oldGroupName := groupName
330266

@@ -350,3 +286,41 @@ func (r *Service) GetOriginalGroupName(groupName string) string {
350286

351287
return groupName
352288
}
289+
290+
func getNameAndNamespace(args map[string]interface{}) (string, string, error) {
291+
name, err := getStringArg(args, NameArg)
292+
if err != nil {
293+
return "", "", err
294+
}
295+
296+
namespace, err := getStringArg(args, NamespaceArg)
297+
if err != nil {
298+
return "", "", err
299+
}
300+
301+
return name, namespace, nil
302+
}
303+
304+
func getStringArg(args map[string]interface{}, key string) (string, error) {
305+
val, exists := args[key]
306+
if !exists {
307+
err := errors.New("missing required argument: " + key)
308+
log.Error().Err(err).Msg(key + " argument is required")
309+
return "", err
310+
}
311+
312+
str, ok := val.(string)
313+
if !ok {
314+
err := errors.New("invalid type for argument: " + key)
315+
log.Error().Err(err).Msg(key + " argument must be a string")
316+
return "", err
317+
}
318+
319+
if str == "" {
320+
err := errors.New("empty value for argument: " + key)
321+
log.Error().Err(err).Msg(key + " argument cannot be empty")
322+
return "", err
323+
}
324+
325+
return str, nil
326+
}

0 commit comments

Comments
 (0)