Skip to content

Commit f116ea1

Browse files
committed
add: interface object test
1 parent dbc2e5d commit f116ea1

1 file changed

Lines changed: 289 additions & 0 deletions

File tree

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
package planner_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/n9te9/go-graphql-federation-gateway/federation/graph"
7+
"github.com/n9te9/go-graphql-federation-gateway/federation/planner"
8+
"github.com/n9te9/graphql-parser/lexer"
9+
"github.com/n9te9/graphql-parser/parser"
10+
)
11+
12+
// TestPlannerV2_InterfaceObjectDirective_BasicQuery tests query planning when
13+
// a type is defined with @interfaceObject (object type pattern).
14+
// Scenario: CoreService has `type Node @interfaceObject @key(fields: "id")` (base query field)
15+
//
16+
// MetadataService has `type Node @interfaceObject @key(fields: "id")` (additional field)
17+
func TestPlannerV2_InterfaceObjectDirective_BasicQuery(t *testing.T) {
18+
coreSchema := `
19+
type Node @key(fields: "id") @interfaceObject {
20+
id: ID!
21+
}
22+
23+
type Query {
24+
node(id: ID!): Node
25+
}
26+
`
27+
28+
reviewsSchema := `
29+
extend type Node @key(fields: "id") @interfaceObject {
30+
id: ID! @external
31+
reviewCount: Int!
32+
}
33+
`
34+
35+
coreSG, err := graph.NewSubGraphV2("core", []byte(coreSchema), "http://core.example.com")
36+
if err != nil {
37+
t.Fatalf("NewSubGraphV2 failed for core: %v", err)
38+
}
39+
40+
reviewsSG, err := graph.NewSubGraphV2("reviews", []byte(reviewsSchema), "http://reviews.example.com")
41+
if err != nil {
42+
t.Fatalf("NewSubGraphV2 failed for reviews: %v", err)
43+
}
44+
45+
superGraph, err := graph.NewSuperGraphV2([]*graph.SubGraphV2{coreSG, reviewsSG})
46+
if err != nil {
47+
t.Fatalf("NewSuperGraphV2 failed: %v", err)
48+
}
49+
50+
p := planner.NewPlannerV2(superGraph)
51+
52+
query := `
53+
query {
54+
node(id: "123") {
55+
id
56+
reviewCount
57+
}
58+
}
59+
`
60+
61+
l := lexer.New(query)
62+
pr := parser.New(l)
63+
doc := pr.ParseDocument()
64+
if len(pr.Errors()) > 0 {
65+
t.Fatalf("parse error: %v", pr.Errors())
66+
}
67+
68+
plan, err := p.Plan(doc, nil)
69+
if err != nil {
70+
t.Fatalf("Plan failed: %v", err)
71+
}
72+
73+
// Should have at least 2 steps:
74+
// Step 0: root query to core (node { id })
75+
// Step 1: entity fetch to reviews (reviewCount)
76+
if len(plan.Steps) < 2 {
77+
t.Fatalf("expected at least 2 steps, got %d", len(plan.Steps))
78+
}
79+
80+
// Step 0: Query step to core service
81+
if plan.Steps[0].StepType != planner.StepTypeQuery {
82+
t.Errorf("expected step 0 to be query type, got %v", plan.Steps[0].StepType)
83+
}
84+
if plan.Steps[0].SubGraph.Name != "core" {
85+
t.Errorf("expected step 0 to go to 'core', got '%s'", plan.Steps[0].SubGraph.Name)
86+
}
87+
88+
// Find the entity step for reviews
89+
var reviewsStep *planner.StepV2
90+
for _, step := range plan.Steps {
91+
if step.StepType == planner.StepTypeEntity && step.SubGraph.Name == "reviews" {
92+
reviewsStep = step
93+
break
94+
}
95+
}
96+
97+
if reviewsStep == nil {
98+
t.Fatal("expected an entity step for reviews service, got none")
99+
}
100+
101+
// The entity step should resolve the Node entity
102+
if reviewsStep.ParentType != "Node" {
103+
t.Errorf("expected entity step ParentType to be 'Node', got '%s'", reviewsStep.ParentType)
104+
}
105+
}
106+
107+
// TestPlannerV2_InterfaceObjectDirective_InterfaceType tests query planning when
108+
// a type is defined as `interface Node @interfaceObject @key(fields: "id")`.
109+
func TestPlannerV2_InterfaceObjectDirective_InterfaceType(t *testing.T) {
110+
// CoreService: Node is defined as an interface with @interfaceObject
111+
coreSchema := `
112+
interface Node @interfaceObject @key(fields: "id") {
113+
id: ID!
114+
}
115+
116+
type Query {
117+
node(id: ID!): Node
118+
}
119+
`
120+
121+
// MetadataService: Node is extended as interface with @interfaceObject
122+
metadataSchema := `
123+
extend interface Node @interfaceObject @key(fields: "id") {
124+
id: ID! @external
125+
createdAt: String!
126+
}
127+
`
128+
129+
coreSG, err := graph.NewSubGraphV2("core", []byte(coreSchema), "http://core.example.com")
130+
if err != nil {
131+
t.Fatalf("NewSubGraphV2 failed for core: %v", err)
132+
}
133+
134+
metadataSG, err := graph.NewSubGraphV2("metadata", []byte(metadataSchema), "http://metadata.example.com")
135+
if err != nil {
136+
t.Fatalf("NewSubGraphV2 failed for metadata: %v", err)
137+
}
138+
139+
superGraph, err := graph.NewSuperGraphV2([]*graph.SubGraphV2{coreSG, metadataSG})
140+
if err != nil {
141+
t.Fatalf("NewSuperGraphV2 failed: %v", err)
142+
}
143+
144+
p := planner.NewPlannerV2(superGraph)
145+
146+
query := `
147+
query {
148+
node(id: "123") {
149+
id
150+
createdAt
151+
}
152+
}
153+
`
154+
155+
l := lexer.New(query)
156+
pr := parser.New(l)
157+
doc := pr.ParseDocument()
158+
if len(pr.Errors()) > 0 {
159+
t.Fatalf("parse error: %v", pr.Errors())
160+
}
161+
162+
plan, err := p.Plan(doc, nil)
163+
if err != nil {
164+
t.Fatalf("Plan failed: %v", err)
165+
}
166+
167+
// Should have at least 2 steps:
168+
// Step 0: root query to core (node { id })
169+
// Step 1: entity fetch to metadata (createdAt)
170+
if len(plan.Steps) < 2 {
171+
t.Fatalf("expected at least 2 steps, got %d", len(plan.Steps))
172+
}
173+
174+
// Step 0: should be a query step to core
175+
if plan.Steps[0].StepType != planner.StepTypeQuery {
176+
t.Errorf("expected step 0 to be query type, got %v", plan.Steps[0].StepType)
177+
}
178+
if plan.Steps[0].SubGraph.Name != "core" {
179+
t.Errorf("expected step 0 to go to 'core', got '%s'", plan.Steps[0].SubGraph.Name)
180+
}
181+
182+
// Find the entity step for metadata service
183+
var metadataStep *planner.StepV2
184+
for _, step := range plan.Steps {
185+
if step.StepType == planner.StepTypeEntity && step.SubGraph.Name == "metadata" {
186+
metadataStep = step
187+
break
188+
}
189+
}
190+
191+
if metadataStep == nil {
192+
t.Fatal("expected an entity step for metadata service, got none")
193+
}
194+
195+
// ParentType should be "Node"
196+
if metadataStep.ParentType != "Node" {
197+
t.Errorf("expected entity step ParentType to be 'Node', got '%s'", metadataStep.ParentType)
198+
}
199+
}
200+
201+
// TestPlannerV2_InterfaceObjectDirective_MultipleSubgraphs tests planning with
202+
// three subgraphs where Node @interfaceObject is extended in two services.
203+
func TestPlannerV2_InterfaceObjectDirective_MultipleSubgraphs(t *testing.T) {
204+
coreSchema := `
205+
type Node @key(fields: "id") @interfaceObject {
206+
id: ID!
207+
}
208+
209+
type Query {
210+
node(id: ID!): Node
211+
}
212+
`
213+
214+
reviewsSchema := `
215+
extend type Node @key(fields: "id") @interfaceObject {
216+
id: ID! @external
217+
reviewCount: Int!
218+
}
219+
`
220+
221+
analyticsSchema := `
222+
extend type Node @key(fields: "id") @interfaceObject {
223+
id: ID! @external
224+
viewCount: Int!
225+
}
226+
`
227+
228+
coreSG, _ := graph.NewSubGraphV2("core", []byte(coreSchema), "http://core.example.com")
229+
reviewsSG, _ := graph.NewSubGraphV2("reviews", []byte(reviewsSchema), "http://reviews.example.com")
230+
analyticsSG, _ := graph.NewSubGraphV2("analytics", []byte(analyticsSchema), "http://analytics.example.com")
231+
232+
superGraph, err := graph.NewSuperGraphV2([]*graph.SubGraphV2{coreSG, reviewsSG, analyticsSG})
233+
if err != nil {
234+
t.Fatalf("NewSuperGraphV2 failed: %v", err)
235+
}
236+
237+
p := planner.NewPlannerV2(superGraph)
238+
239+
query := `
240+
query {
241+
node(id: "123") {
242+
id
243+
reviewCount
244+
viewCount
245+
}
246+
}
247+
`
248+
249+
l := lexer.New(query)
250+
pr := parser.New(l)
251+
doc := pr.ParseDocument()
252+
253+
plan, err := p.Plan(doc, nil)
254+
if err != nil {
255+
t.Fatalf("Plan failed: %v", err)
256+
}
257+
258+
// Should have 3 steps: 1 root + 2 entity fetches
259+
if len(plan.Steps) < 3 {
260+
t.Fatalf("expected at least 3 steps (root + 2 entity), got %d", len(plan.Steps))
261+
}
262+
263+
// Verify both reviews and analytics have entity steps
264+
reviewsFound := false
265+
analyticsFound := false
266+
for _, step := range plan.Steps {
267+
if step.StepType == planner.StepTypeEntity {
268+
switch step.SubGraph.Name {
269+
case "reviews":
270+
reviewsFound = true
271+
if step.ParentType != "Node" {
272+
t.Errorf("expected reviews entity step ParentType 'Node', got '%s'", step.ParentType)
273+
}
274+
case "analytics":
275+
analyticsFound = true
276+
if step.ParentType != "Node" {
277+
t.Errorf("expected analytics entity step ParentType 'Node', got '%s'", step.ParentType)
278+
}
279+
}
280+
}
281+
}
282+
283+
if !reviewsFound {
284+
t.Error("expected entity step for reviews service")
285+
}
286+
if !analyticsFound {
287+
t.Error("expected entity step for analytics service")
288+
}
289+
}

0 commit comments

Comments
 (0)