Skip to content

Commit

Permalink
fix(GraphQL): Apollo federation now works with lambda (GRAPHQL-1084) (#…
Browse files Browse the repository at this point in the history
…7558)

This PR makes the `@requires` directive work with `@custom/@lambda` directives.
  • Loading branch information
abhimanyusinghgaur authored and aman-bansal committed Mar 16, 2021
1 parent c84ffdc commit a05f7f8
Show file tree
Hide file tree
Showing 24 changed files with 470 additions and 140 deletions.
6 changes: 6 additions & 0 deletions graphql/e2e/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@ func RunAll(t *testing.T) {
t.Run("query id directive with int64", idDirectiveWithInt64)
t.Run("query id directive with float", idDirectiveWithFloat)
t.Run("query filter ID values coercion to List", queryFilterWithIDInputCoercion)

// mutation tests
t.Run("add mutation", addMutation)
t.Run("update mutation by ids", updateMutationByIds)
Expand Down Expand Up @@ -913,6 +914,7 @@ func RunAll(t *testing.T) {
t.Run("lambda on query using dql", lambdaOnQueryUsingDql)
t.Run("lambda on mutation using graphql", lambdaOnMutationUsingGraphQL)
t.Run("query lambda field in a mutation with duplicate @id", lambdaInMutationWithDuplicateId)
t.Run("lambda with apollo federation", lambdaWithApolloFederation)
}

func gunzipData(data []byte) ([]byte, error) {
Expand Down Expand Up @@ -1181,6 +1183,10 @@ func RequireNoGQLErrors(t *testing.T, resp *GraphQLResponse) {
}
}

func (gqlRes *GraphQLResponse) RequireNoGQLErrors(t *testing.T) {
RequireNoGQLErrors(t, gqlRes)
}

func PopulateGraphQLData(client *dgo.Dgraph, data []byte) error {
mu := &api.Mutation{
CommitNow: true,
Expand Down
73 changes: 73 additions & 0 deletions graphql/e2e/common/lambda.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,76 @@ func lambdaInMutationWithDuplicateId(t *testing.T) {
DeleteGqlType(t, "Chapter", GetXidFilter("chapterId", []interface{}{1, 2, 3, 4}), 4, nil)
DeleteGqlType(t, "Book", GetXidFilter("bookId", []interface{}{1, 2}), 2, nil)
}

func lambdaWithApolloFederation(t *testing.T) {
addMissionParams := &GraphQLParams{
Query: `mutation {
addMission(input: [
{id: "M1", designation: "Apollo 1", crew: [
{id: "14", name: "Gus Grissom", isActive: false}
{id: "30", name: "Ed White", isActive: true}
{id: "7", name: "Roger B. Chaffee", isActive: false}
]}
]) {
numUids
}
}`,
}
resp := addMissionParams.ExecuteAsPost(t, GraphqlURL)
resp.RequireNoGQLErrors(t)

// entities query should get correct bio built using the age & name given in representations
entitiesQueryParams := &GraphQLParams{
Query: `query _entities($typeName: String!) {
_entities(representations: [
{__typename: $typeName, id: "14", name: "Gus Grissom", age: 70}
{__typename: $typeName, id: "30", name: "Ed White", age: 80}
{__typename: $typeName, id: "7", name: "An updated name", age: 65}
]) {
... on Astronaut {
name
bio
}
}
}`,
Variables: map[string]interface{}{
"typeName": "Astronaut",
},
}
resp = entitiesQueryParams.ExecuteAsPost(t, GraphqlURL)
resp.RequireNoGQLErrors(t)

testutil.CompareJSON(t, `{
"_entities": [
{"name": "Gus Grissom", "bio": "Name - Gus Grissom, Age - 70, isActive - false"},
{"name": "Ed White", "bio": "Name - Ed White, Age - 80, isActive - true"},
{"name": "Roger B. Chaffee", "bio": "Name - An updated name, Age - 65, isActive - false"}
]
}`, string(resp.Data))

// directly querying from an auto-generated query should give undefined age in bio
// name in bio should be from dgraph
dgQueryParams := &GraphQLParams{
Query: `query {
queryAstronaut {
name
bio
}
}`,
}
resp = dgQueryParams.ExecuteAsPost(t, GraphqlURL)
resp.RequireNoGQLErrors(t)

testutil.CompareJSON(t, `{
"queryAstronaut": [
{"name": "Gus Grissom", "bio": "Name - Gus Grissom, Age - undefined, isActive - false"},
{"name": "Ed White", "bio": "Name - Ed White, Age - undefined, isActive - true"},
{"name": "Roger B. Chaffee", "bio": "Name - Roger B. Chaffee, Age - undefined, isActive - false"}
]
}`, string(resp.Data))

// cleanup
DeleteGqlType(t, "Mission", GetXidFilter("id", []interface{}{"M1"}), 1, nil)
DeleteGqlType(t, "Astronaut", map[string]interface{}{"id": []interface{}{"14", "30", "7"}}, 3,
nil)
}
18 changes: 18 additions & 0 deletions graphql/e2e/custom_logic/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,21 @@ func humanBioHandler(w http.ResponseWriter, r *http.Request) {
check2(w.Write([]byte(`"My name is Han and I have 10 credits."`)))
}

func shippingEstimate(w http.ResponseWriter, r *http.Request) {
err := verifyRequest(r, expectedRequest{
method: http.MethodPost,
urlSuffix: "/shippingEstimate",
body: `[{"price":999,"upc":"1","weight":500},{"price":2000,"upc":"2","weight":100}]`,
})
if err != nil {
w.WriteHeader(400)
check2(w.Write([]byte(err.Error())))
return
}

check2(w.Write([]byte(`[250,0]`)))
}

func emptyQuerySchema(w http.ResponseWriter, r *http.Request) {
if _, err := verifyGraphqlRequest(r, expectedGraphqlRequest{
urlSuffix: "/noquery",
Expand Down Expand Up @@ -1305,6 +1320,9 @@ func main() {
http.HandleFunc("/schoolName", schoolNameHandler)
http.HandleFunc("/humanBio", humanBioHandler)

// for apollo federation
http.HandleFunc("/shippingEstimate", shippingEstimate)

/*************************************
* For testing http with graphql
*************************************/
Expand Down
55 changes: 55 additions & 0 deletions graphql/e2e/custom_logic/custom_logic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3173,6 +3173,61 @@ func TestCustomFieldIsResolvedWhenNoModeGiven(t *testing.T) {
common.DeleteGqlType(t, "MarketStats", map[string]interface{}{}, 1, nil)
}

func TestApolloFederationWithCustom(t *testing.T) {
sch := `
type Product @key(fields: "upc") @extends {
upc: String! @id @external
weight: Int @external
price: Int @external
inStock: Boolean
shippingEstimate: Int @requires(fields: "price weight") @custom(http: {
url: "http://mock:8888/shippingEstimate"
method: POST
mode: BATCH
body: "{upc: $upc, weight: $weight, price: $price}"
skipIntrospection: true
})
}`
common.SafelyUpdateGQLSchemaOnAlpha1(t, sch)

mutation := &common.GraphQLParams{
Query: `mutation {
addProduct(input: [
{ upc: "1", inStock: true },
{ upc: "2", inStock: false }
]) { numUids }
}`,
}
resp := mutation.ExecuteAsPost(t, common.GraphqlURL)
resp.RequireNoGQLErrors(t)

query := &common.GraphQLParams{
Query: `query _entities($typeName: String!) {
_entities(representations: [
{__typename: $typeName, upc: "2", price: 2000, weight: 100}
{__typename: $typeName, upc: "1", price: 999, weight: 500}
]) {
... on Product {
upc
shippingEstimate
}
}
}`,
Variables: map[string]interface{}{"typeName": "Product"},
}
resp = query.ExecuteAsPost(t, common.GraphqlURL)
resp.RequireNoGQLErrors(t)

testutil.CompareJSON(t, `{
"_entities": [
{ "upc": "2", "shippingEstimate": 0 },
{ "upc": "1", "shippingEstimate": 250 }
]
}`, string(resp.Data))

common.DeleteGqlType(t, "Product", map[string]interface{}{}, 2, nil)
}

func TestMain(m *testing.M) {
err := common.CheckGraphQLStarted(common.GraphqlAdminURL)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions graphql/e2e/directives/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ type Mission @key(fields: "id") {
type Astronaut @key(fields: "id") @extends {
id: ID! @external
name: String @external
age: Int @external
isActive: Boolean
bio: String @requires(fields: "name age") @lambda
missions: [Mission]
}

Expand Down
10 changes: 10 additions & 0 deletions graphql/e2e/directives/schema_response.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
"predicate": "Astronaut.name",
"type": "string"
},
{
"predicate": "Astronaut.isActive",
"type": "bool"
},
{
"predicate": "Astronaut.missions",
"type": "uid",
Expand Down Expand Up @@ -791,6 +795,12 @@
{
"name": "Astronaut.id"
},
{
"name": "Astronaut.name"
},
{
"name": "Astronaut.isActive"
},
{
"name": "Astronaut.missions"
}
Expand Down
2 changes: 2 additions & 0 deletions graphql/e2e/directives/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const characterBio = ({parent: {name}}) => `My name is ${name}.`
const humanBio = ({parent: {name, totalCredits}}) => `My name is ${name}. I have ${totalCredits} credits.`
const droidBio = ({parent: {name, primaryFunction}}) => `My name is ${name}. My primary function is ${primaryFunction}.`
const summary = () => `hi`
const astronautBio = ({parent: {name, age, isActive}}) => `Name - ${name}, Age - ${age}, isActive - ${isActive}`

async function authorsByName({args, dql}) {
const results = await dql.query(`query queryAuthor($name: string) {
Expand Down Expand Up @@ -34,6 +35,7 @@ self.addGraphQLResolvers({
"Human.bio": humanBio,
"Droid.bio": droidBio,
"Book.summary": summary,
"Astronaut.bio": astronautBio,
"Query.authorsByName": authorsByName,
"Mutation.newAuthor": newAuthor
})
Expand Down
4 changes: 4 additions & 0 deletions graphql/e2e/normal/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ type Employer {
worker: [Worker]
}

# sample data: https://github.com/mandiwise/space-camp-federation-demo/blob/master/db.json
type Mission @key(fields: "id") {
id: String! @id
crew: [Astronaut] @provides(fields: "name") @hasInverse(field: missions)
Expand All @@ -337,6 +338,9 @@ type Mission @key(fields: "id") {
type Astronaut @key(fields: "id") @extends {
id: ID! @external
name: String! @external
age: Int @external
isActive: Boolean
bio: String @requires(fields: "name age") @lambda
missions: [Mission]
}

Expand Down
10 changes: 10 additions & 0 deletions graphql/e2e/normal/schema_response.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
"predicate": "Astronaut.name",
"type": "string"
},
{
"predicate": "Astronaut.isActive",
"type": "bool"
},
{
"predicate": "Astronaut.missions",
"type": "uid",
Expand Down Expand Up @@ -791,6 +795,12 @@
{
"name": "Astronaut.id"
},
{
"name": "Astronaut.name"
},
{
"name": "Astronaut.isActive"
},
{
"name": "Astronaut.missions"
}
Expand Down
2 changes: 2 additions & 0 deletions graphql/e2e/normal/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const characterBio = ({parent: {name}}) => `My name is ${name}.`
const humanBio = ({parent: {name, totalCredits}}) => `My name is ${name}. I have ${totalCredits} credits.`
const droidBio = ({parent: {name, primaryFunction}}) => `My name is ${name}. My primary function is ${primaryFunction}.`
const summary = () => `hi`
const astronautBio = ({parent: {name, age, isActive}}) => `Name - ${name}, Age - ${age}, isActive - ${isActive}`

async function authorsByName({args, dql}) {
const results = await dql.query(`query queryAuthor($name: string) {
Expand Down Expand Up @@ -34,6 +35,7 @@ self.addGraphQLResolvers({
"Human.bio": humanBio,
"Droid.bio": droidBio,
"Book.summary": summary,
"Astronaut.bio": astronautBio,
"Query.authorsByName": authorsByName,
"Mutation.newAuthor": newAuthor
})
Expand Down
2 changes: 0 additions & 2 deletions graphql/e2e/schema/apollo_service_response.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,7 @@ directive @withSubscription on OBJECT | INTERFACE | FIELD_DEFINITION
directive @secret(field: String!, pred: String) on OBJECT | INTERFACE
directive @remote on OBJECT | INTERFACE | UNION | INPUT_OBJECT | ENUM
directive @remoteResponse(name: String) on FIELD_DEFINITION
directive @cascade(fields: [String]) on FIELD
directive @lambda on FIELD_DEFINITION
directive @cacheControl(maxAge: Int!) on QUERY

input IntFilter {
eq: Int
Expand Down
Loading

0 comments on commit a05f7f8

Please sign in to comment.