1 package federation
2
3 import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7 "github.com/stretchr/testify/require"
8
9 "github.com/99designs/gqlgen/codegen"
10 "github.com/99designs/gqlgen/codegen/config"
11 "github.com/99designs/gqlgen/plugin/federation/fieldset"
12 )
13
14 func TestWithEntities(t *testing.T) {
15 f, cfg := load(t, "testdata/allthethings/gqlgen.yml")
16
17 require.Equal(t, []string{"ExternalExtension", "Hello", "MoreNesting", "MultiHelloMultiKey", "NestedKey", "VeryNestedKey", "World"}, cfg.Schema.Types["_Entity"].Types)
18
19 require.Len(t, cfg.Schema.Types["Entity"].Fields, 8)
20
21 require.Equal(t, "findExternalExtensionByUpc", cfg.Schema.Types["Entity"].Fields[0].Name)
22 require.Equal(t, "findHelloByName", cfg.Schema.Types["Entity"].Fields[1].Name)
23
24
25 require.Equal(t, "findManyMultiHelloMultiKeyByNames", cfg.Schema.Types["Entity"].Fields[2].Name)
26 require.Equal(t, "findManyMultiHelloMultiKeyByKey2s", cfg.Schema.Types["Entity"].Fields[3].Name)
27 require.Equal(t, "findNestedKeyByIDAndHelloName", cfg.Schema.Types["Entity"].Fields[4].Name)
28 require.Equal(t, "findVeryNestedKeyByIDAndHelloNameAndWorldFooAndWorldBarAndMoreWorldFoo", cfg.Schema.Types["Entity"].Fields[5].Name)
29 require.Equal(t, "findWorldByFoo", cfg.Schema.Types["Entity"].Fields[6].Name)
30 require.Equal(t, "findWorldByBar", cfg.Schema.Types["Entity"].Fields[7].Name)
31
32 require.NoError(t, f.MutateConfig(cfg))
33
34 require.Len(t, f.Entities, 7)
35
36 require.Equal(t, "ExternalExtension", f.Entities[0].Name)
37 require.Len(t, f.Entities[0].Resolvers, 1)
38 require.Len(t, f.Entities[0].Resolvers[0].KeyFields, 1)
39 require.Equal(t, "upc", f.Entities[0].Resolvers[0].KeyFields[0].Definition.Name)
40 require.Equal(t, "String", f.Entities[0].Resolvers[0].KeyFields[0].Definition.Type.Name())
41
42 require.Equal(t, "Hello", f.Entities[1].Name)
43 require.Len(t, f.Entities[1].Resolvers, 1)
44 require.Len(t, f.Entities[1].Resolvers[0].KeyFields, 1)
45 require.Equal(t, "name", f.Entities[1].Resolvers[0].KeyFields[0].Definition.Name)
46 require.Equal(t, "String", f.Entities[1].Resolvers[0].KeyFields[0].Definition.Type.Name())
47
48 require.Equal(t, "MoreNesting", f.Entities[2].Name)
49 require.Len(t, f.Entities[2].Resolvers, 0)
50
51 require.Equal(t, "MultiHelloMultiKey", f.Entities[3].Name)
52 require.Len(t, f.Entities[3].Resolvers, 2)
53 require.Len(t, f.Entities[3].Resolvers[0].KeyFields, 1)
54 require.Len(t, f.Entities[3].Resolvers[1].KeyFields, 1)
55 require.Equal(t, "name", f.Entities[3].Resolvers[0].KeyFields[0].Definition.Name)
56 require.Equal(t, "String", f.Entities[3].Resolvers[0].KeyFields[0].Definition.Type.Name())
57 require.Equal(t, "key2", f.Entities[3].Resolvers[1].KeyFields[0].Definition.Name)
58 require.Equal(t, "String", f.Entities[3].Resolvers[1].KeyFields[0].Definition.Type.Name())
59
60 require.Equal(t, "NestedKey", f.Entities[4].Name)
61 require.Len(t, f.Entities[4].Resolvers, 1)
62 require.Len(t, f.Entities[4].Resolvers[0].KeyFields, 2)
63 require.Equal(t, "id", f.Entities[4].Resolvers[0].KeyFields[0].Definition.Name)
64 require.Equal(t, "String", f.Entities[4].Resolvers[0].KeyFields[0].Definition.Type.Name())
65 require.Equal(t, "helloName", f.Entities[4].Resolvers[0].KeyFields[1].Definition.Name)
66 require.Equal(t, "String", f.Entities[4].Resolvers[0].KeyFields[1].Definition.Type.Name())
67
68 require.Equal(t, "VeryNestedKey", f.Entities[5].Name)
69 require.Len(t, f.Entities[5].Resolvers, 1)
70 require.Len(t, f.Entities[5].Resolvers[0].KeyFields, 5)
71 require.Equal(t, "id", f.Entities[5].Resolvers[0].KeyFields[0].Definition.Name)
72 require.Equal(t, "String", f.Entities[5].Resolvers[0].KeyFields[0].Definition.Type.Name())
73 require.Equal(t, "helloName", f.Entities[5].Resolvers[0].KeyFields[1].Definition.Name)
74 require.Equal(t, "String", f.Entities[5].Resolvers[0].KeyFields[1].Definition.Type.Name())
75 require.Equal(t, "worldFoo", f.Entities[5].Resolvers[0].KeyFields[2].Definition.Name)
76 require.Equal(t, "String", f.Entities[5].Resolvers[0].KeyFields[2].Definition.Type.Name())
77 require.Equal(t, "worldBar", f.Entities[5].Resolvers[0].KeyFields[3].Definition.Name)
78 require.Equal(t, "Int", f.Entities[5].Resolvers[0].KeyFields[3].Definition.Type.Name())
79 require.Equal(t, "moreWorldFoo", f.Entities[5].Resolvers[0].KeyFields[4].Definition.Name)
80 require.Equal(t, "String", f.Entities[5].Resolvers[0].KeyFields[4].Definition.Type.Name())
81
82 require.Len(t, f.Entities[5].Requires, 2)
83 require.Equal(t, f.Entities[5].Requires[0].Name, "id")
84 require.Equal(t, f.Entities[5].Requires[1].Name, "helloSecondary")
85
86 require.Equal(t, "World", f.Entities[6].Name)
87 require.Len(t, f.Entities[6].Resolvers, 2)
88 require.Len(t, f.Entities[6].Resolvers[0].KeyFields, 1)
89 require.Equal(t, "foo", f.Entities[6].Resolvers[0].KeyFields[0].Definition.Name)
90 require.Equal(t, "String", f.Entities[6].Resolvers[0].KeyFields[0].Definition.Type.Name())
91 require.Len(t, f.Entities[6].Resolvers[1].KeyFields, 1)
92 require.Equal(t, "bar", f.Entities[6].Resolvers[1].KeyFields[0].Definition.Name)
93 require.Equal(t, "Int", f.Entities[6].Resolvers[1].KeyFields[0].Definition.Type.Name())
94 }
95
96 func TestNoEntities(t *testing.T) {
97 f, cfg := load(t, "testdata/entities/nokey.yml")
98
99 err := f.MutateConfig(cfg)
100 require.NoError(t, err)
101 require.Len(t, f.Entities, 0)
102 }
103
104 func TestUnusedInterfaceKeyDirective(t *testing.T) {
105 f, cfg := load(t, "testdata/interfaces/unused_key.yml")
106
107 err := f.MutateConfig(cfg)
108 require.NoError(t, err)
109 require.Len(t, f.Entities, 0)
110 }
111
112 func TestInterfaceKeyDirective(t *testing.T) {
113 f, cfg := load(t, "testdata/interfaces/key.yml")
114
115 err := f.MutateConfig(cfg)
116 require.NoError(t, err)
117 require.Len(t, cfg.Schema.Types["_Entity"].Types, 1)
118 require.Len(t, f.Entities, 2)
119 }
120
121 func TestInterfaceExtendsDirective(t *testing.T) {
122 require.Panics(t, func() {
123 load(t, "testdata/interfaces/extends.yml")
124 })
125 }
126
127 func TestEntityInterfaces(t *testing.T) {
128 f, cfg := load(t, "testdata/entityinterfaces/interface.yml")
129 err := f.MutateConfig(cfg)
130
131 require.NoError(t, err)
132 require.Len(t, f.Entities[0].Resolvers, 1)
133 require.Equal(t, "Hello", f.Entities[0].Name)
134 require.NotEmpty(t, f.Entities[1].Resolvers)
135 }
136
137 func TestCodeGeneration(t *testing.T) {
138 f, cfg := load(t, "testdata/allthethings/gqlgen.yml")
139
140 require.Len(t, cfg.Schema.Types["_Entity"].Types, 7)
141 require.Len(t, f.Entities, 7)
142
143 require.NoError(t, f.MutateConfig(cfg))
144
145 data, err := codegen.BuildData(cfg)
146 if err != nil {
147 panic(err)
148 }
149 require.NoError(t, f.GenerateCode(data))
150 }
151
152 func TestCodeGenerationFederation2(t *testing.T) {
153 f, cfg := load(t, "testdata/federation2/federation2.yml")
154 err := f.MutateConfig(cfg)
155
156 require.NoError(t, err)
157 require.Equal(t, "ExternalExtension", f.Entities[0].Name)
158 require.Len(t, f.Entities[0].Resolvers, 1)
159 require.Equal(t, "Hello", f.Entities[1].Name)
160 require.Empty(t, f.Entities[1].Resolvers)
161 require.Equal(t, "World", f.Entities[2].Name)
162 require.Empty(t, f.Entities[2].Resolvers)
163
164 data, err := codegen.BuildData(cfg)
165 if err != nil {
166 panic(err)
167 }
168 require.NoError(t, f.GenerateCode(data))
169 }
170
171
172
173 func TestMultiWithOmitSliceElemPointersCfg(t *testing.T) {
174
175 staticRepsString := "reps: [HelloByNamesInput]!"
176 t.Run("OmitSliceElementPointers true", func(t *testing.T) {
177 f, cfg := load(t, "testdata/multi/multi.yml")
178 cfg.OmitSliceElementPointers = true
179 err := f.MutateConfig(cfg)
180 require.NoError(t, err)
181 require.Len(t, cfg.Schema.Types["_Entity"].Types, 1)
182 require.Len(t, f.Entities, 1)
183
184 entityGraphqlGenerated := false
185 for _, source := range cfg.Sources {
186 if source.Name != "federation/entity.graphql" {
187 continue
188 }
189 entityGraphqlGenerated = true
190 require.Contains(t, source.Input, staticRepsString)
191 }
192 require.True(t, entityGraphqlGenerated)
193 })
194
195 t.Run("OmitSliceElementPointers false", func(t *testing.T) {
196 f, cfg := load(t, "testdata/multi/multi.yml")
197 cfg.OmitSliceElementPointers = false
198 err := f.MutateConfig(cfg)
199 require.NoError(t, err)
200 require.Len(t, cfg.Schema.Types["_Entity"].Types, 1)
201 require.Len(t, f.Entities, 1)
202
203 entityGraphqlGenerated := false
204 for _, source := range cfg.Sources {
205 if source.Name != "federation/entity.graphql" {
206 continue
207 }
208 entityGraphqlGenerated = true
209 require.Contains(t, source.Input, staticRepsString)
210 }
211 require.True(t, entityGraphqlGenerated)
212 })
213 }
214
215 func TestHandlesRequiresArgumentCorrectlyIfNoSpace(t *testing.T) {
216 requiresFieldSet := fieldset.New("foo bar baz(limit:4)", nil)
217 assert.Equal(t, 3, len(requiresFieldSet))
218 assert.Equal(t, "Foo", requiresFieldSet[0].ToGo())
219 assert.Equal(t, "Bar", requiresFieldSet[1].ToGo())
220 assert.Equal(t, "Baz(limit:4)", requiresFieldSet[2].ToGo())
221 }
222
223 func TestHandlesArgumentGeneration(t *testing.T) {
224 e := &Entity{
225 Name: "",
226 Def: nil,
227 Resolvers: nil,
228 Requires: nil,
229 }
230
231 raw := "foo bar baz(limit:4)"
232 requiresFieldSet := fieldset.New(raw, nil)
233 for _, field := range requiresFieldSet {
234
235 e.Requires = append(e.Requires, &Requires{
236 Name: field.ToGoPrivate(),
237 Field: field,
238 })
239 }
240 }
241
242 func TestInjectSourceLate(t *testing.T) {
243 _, cfg := load(t, "testdata/allthethings/gqlgen.yml")
244 entityGraphqlGenerated := false
245 for _, source := range cfg.Sources {
246 if source.Name != "federation/entity.graphql" {
247 continue
248 }
249 entityGraphqlGenerated = true
250 require.Contains(t, source.Input, "union _Entity")
251 require.Contains(t, source.Input, "type _Service {")
252 require.Contains(t, source.Input, "extend type Query {")
253 require.Contains(t, source.Input, "_entities(representations: [_Any!]!): [_Entity]!")
254 require.Contains(t, source.Input, "_service: _Service!")
255 }
256 require.True(t, entityGraphqlGenerated)
257
258 _, cfg = load(t, "testdata/entities/nokey.yml")
259 entityGraphqlGenerated = false
260 for _, source := range cfg.Sources {
261 if source.Name != "federation/entity.graphql" {
262 continue
263 }
264 entityGraphqlGenerated = true
265 require.NotContains(t, source.Input, "union _Entity")
266 require.Contains(t, source.Input, "type _Service {")
267 require.Contains(t, source.Input, "extend type Query {")
268 require.NotContains(t, source.Input, "_entities(representations: [_Any!]!): [_Entity]!")
269 require.Contains(t, source.Input, "_service: _Service!")
270 }
271 require.True(t, entityGraphqlGenerated)
272
273 _, cfg = load(t, "testdata/schema/customquerytype.yml")
274 for _, source := range cfg.Sources {
275 if source.Name != "federation/entity.graphql" {
276 continue
277 }
278 require.Contains(t, source.Input, "extend type CustomQuery {")
279 }
280 }
281
282 func load(t *testing.T, name string) (*federation, *config.Config) {
283 t.Helper()
284
285 cfg, err := config.LoadConfig(name)
286 require.NoError(t, err)
287
288 if cfg.Federation.Version == 0 {
289 cfg.Federation.Version = 1
290 }
291
292 f := &federation{Version: cfg.Federation.Version}
293 cfg.Sources = append(cfg.Sources, f.InjectSourceEarly())
294 require.NoError(t, cfg.LoadSchema())
295
296 if src := f.InjectSourceLate(cfg.Schema); src != nil {
297 cfg.Sources = append(cfg.Sources, src)
298 }
299 require.NoError(t, cfg.LoadSchema())
300
301 require.NoError(t, cfg.Init())
302 return f, cfg
303 }
304
View as plain text