github.com/dgraph-io/dgraph@v1.2.8/graphql/schema/wrappers_test.go (about)

     1  /*
     2   * Copyright 2019 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package schema
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/pkg/errors"
    24  	"github.com/stretchr/testify/require"
    25  	"github.com/vektah/gqlparser/ast"
    26  )
    27  
    28  func TestDgraphMapping_WithoutDirectives(t *testing.T) {
    29  	schemaStr := `
    30  type Author {
    31          id: ID!
    32  
    33          name: String! @search(by: [hash, trigram])
    34          dob: DateTime @search
    35          reputation: Float @search
    36          posts: [Post!] @hasInverse(field: author)
    37  }
    38  
    39  type Post {
    40          postID: ID!
    41          postType: PostType @search
    42          author: Author! @hasInverse(field: posts)
    43  }
    44  
    45  enum PostType {
    46          Fact
    47          Question
    48          Opinion
    49  }
    50  
    51  interface Employee {
    52          ename: String!
    53  }
    54  
    55  interface Character {
    56          id: ID!
    57          name: String! @search(by: [exact])
    58          appearsIn: [Episode!] @search
    59  }
    60  
    61  type Human implements Character & Employee {
    62          starships: [Starship]
    63          totalCredits: Float
    64  }
    65  
    66  type Droid implements Character {
    67          primaryFunction: String
    68  }
    69  
    70  enum Episode {
    71          NEWHOPE
    72          EMPIRE
    73          JEDI
    74  }
    75  
    76  type Starship {
    77          id: ID!
    78          name: String! @search(by: [term])
    79          length: Float
    80  }`
    81  
    82  	schHandler, errs := NewHandler(schemaStr)
    83  	require.NoError(t, errs)
    84  	sch, err := FromString(schHandler.GQLSchema())
    85  	require.NoError(t, err)
    86  
    87  	s, ok := sch.(*schema)
    88  	require.True(t, ok, "expected to be able to convert sch to internal schema type")
    89  
    90  	author := map[string]string{
    91  		"name":       "Author.name",
    92  		"dob":        "Author.dob",
    93  		"reputation": "Author.reputation",
    94  		"posts":      "Author.posts",
    95  	}
    96  	post := map[string]string{
    97  		"postType": "Post.postType",
    98  		"author":   "Post.author",
    99  	}
   100  	character := map[string]string{
   101  		"name":      "Character.name",
   102  		"appearsIn": "Character.appearsIn",
   103  	}
   104  	human := map[string]string{
   105  		"ename":        "Employee.ename",
   106  		"name":         "Character.name",
   107  		"appearsIn":    "Character.appearsIn",
   108  		"starships":    "Human.starships",
   109  		"totalCredits": "Human.totalCredits",
   110  	}
   111  	droid := map[string]string{
   112  		"name":            "Character.name",
   113  		"appearsIn":       "Character.appearsIn",
   114  		"primaryFunction": "Droid.primaryFunction",
   115  	}
   116  	starship := map[string]string{
   117  		"name":   "Starship.name",
   118  		"length": "Starship.length",
   119  	}
   120  
   121  	expected := map[string]map[string]string{
   122  		"Author":              author,
   123  		"UpdateAuthorPayload": author,
   124  		"DeleteAuthorPayload": author,
   125  		"Post":                post,
   126  		"UpdatePostPayload":   post,
   127  		"DeletePostPayload":   post,
   128  		"Employee": map[string]string{
   129  			"ename": "Employee.ename",
   130  		},
   131  		"Character":              character,
   132  		"UpdateCharacterPayload": character,
   133  		"DeleteCharacterPayload": character,
   134  		"Human":                  human,
   135  		"UpdateHumanPayload":     human,
   136  		"DeleteHumanPayload":     human,
   137  		"Droid":                  droid,
   138  		"UpdateDroidPayload":     droid,
   139  		"DeleteDroidPayload":     droid,
   140  		"Starship":               starship,
   141  		"UpdateStarshipPayload":  starship,
   142  		"DeleteStarshipPayload":  starship,
   143  	}
   144  
   145  	if diff := cmp.Diff(expected, s.dgraphPredicate); diff != "" {
   146  		t.Errorf("dgraph predicate map mismatch (-want +got):\n%s", diff)
   147  	}
   148  }
   149  
   150  func TestDgraphMapping_WithDirectives(t *testing.T) {
   151  	schemaStr := `
   152  	type Author @dgraph(type: "dgraph.author") {
   153  			id: ID!
   154  
   155  			name: String! @search(by: [hash, trigram])
   156  			dob: DateTime @search
   157  			reputation: Float @search
   158  			posts: [Post!] @hasInverse(field: author)
   159  	}
   160  
   161  	type Post @dgraph(type: "dgraph.Post") {
   162  			postID: ID!
   163  			postType: PostType @search @dgraph(pred: "dgraph.post_type")
   164  			author: Author! @hasInverse(field: posts) @dgraph(pred: "dgraph.post_author")
   165  	}
   166  
   167  	enum PostType {
   168  			Fact
   169  			Question
   170  			Opinion
   171  	}
   172  
   173  	interface Employee @dgraph(type: "dgraph.employee.en") {
   174  			ename: String!
   175  	}
   176  
   177  	interface Character @dgraph(type: "performance.character") {
   178  			id: ID!
   179  			name: String! @search(by: [exact])
   180  			appearsIn: [Episode!] @search @dgraph(pred: "appears_in")
   181  	}
   182  
   183  	type Human implements Character & Employee {
   184  			starships: [Starship]
   185  			totalCredits: Float @dgraph(pred: "credits")
   186  	}
   187  
   188  	type Droid implements Character @dgraph(type: "roboDroid") {
   189  			primaryFunction: String
   190  	}
   191  
   192  	enum Episode {
   193  			NEWHOPE
   194  			EMPIRE
   195  			JEDI
   196  	}
   197  
   198  	type Starship @dgraph(type: "star.ship") {
   199  			id: ID!
   200  			name: String! @search(by: [term]) @dgraph(pred: "star.ship.name")
   201  			length: Float
   202  	}`
   203  
   204  	schHandler, errs := NewHandler(schemaStr)
   205  	require.NoError(t, errs)
   206  	sch, err := FromString(schHandler.GQLSchema())
   207  	require.NoError(t, err)
   208  
   209  	s, ok := sch.(*schema)
   210  	require.True(t, ok, "expected to be able to convert sch to internal schema type")
   211  
   212  	author := map[string]string{
   213  		"name":       "dgraph.author.name",
   214  		"dob":        "dgraph.author.dob",
   215  		"reputation": "dgraph.author.reputation",
   216  		"posts":      "dgraph.author.posts",
   217  	}
   218  	post := map[string]string{
   219  		"postType": "dgraph.post_type",
   220  		"author":   "dgraph.post_author",
   221  	}
   222  	character := map[string]string{
   223  		"name":      "performance.character.name",
   224  		"appearsIn": "appears_in",
   225  	}
   226  	human := map[string]string{
   227  		"ename":        "dgraph.employee.en.ename",
   228  		"name":         "performance.character.name",
   229  		"appearsIn":    "appears_in",
   230  		"starships":    "Human.starships",
   231  		"totalCredits": "credits",
   232  	}
   233  	droid := map[string]string{
   234  		"name":            "performance.character.name",
   235  		"appearsIn":       "appears_in",
   236  		"primaryFunction": "roboDroid.primaryFunction",
   237  	}
   238  	starship := map[string]string{
   239  		"name":   "star.ship.name",
   240  		"length": "star.ship.length",
   241  	}
   242  
   243  	expected := map[string]map[string]string{
   244  		"Author":              author,
   245  		"UpdateAuthorPayload": author,
   246  		"DeleteAuthorPayload": author,
   247  		"Post":                post,
   248  		"UpdatePostPayload":   post,
   249  		"DeletePostPayload":   post,
   250  		"Employee": map[string]string{
   251  			"ename": "dgraph.employee.en.ename",
   252  		},
   253  		"Character":              character,
   254  		"UpdateCharacterPayload": character,
   255  		"DeleteCharacterPayload": character,
   256  		"Human":                  human,
   257  		"UpdateHumanPayload":     human,
   258  		"DeleteHumanPayload":     human,
   259  		"Droid":                  droid,
   260  		"UpdateDroidPayload":     droid,
   261  		"DeleteDroidPayload":     droid,
   262  		"Starship":               starship,
   263  		"UpdateStarshipPayload":  starship,
   264  		"DeleteStarshipPayload":  starship,
   265  	}
   266  
   267  	if diff := cmp.Diff(expected, s.dgraphPredicate); diff != "" {
   268  		t.Errorf("dgraph predicate map mismatch (-want +got):\n%s", diff)
   269  	}
   270  }
   271  
   272  func TestCheckNonNulls(t *testing.T) {
   273  
   274  	gqlSchema, err := FromString(`
   275  	type T { 
   276  		req: String!
   277  		notReq: String
   278  		alsoReq: String!
   279  	}`)
   280  	require.NoError(t, err)
   281  
   282  	tcases := map[string]struct {
   283  		obj map[string]interface{}
   284  		exc string
   285  		err error
   286  	}{
   287  		"all present": {
   288  			obj: map[string]interface{}{"req": "here", "notReq": "here", "alsoReq": "here"},
   289  			err: nil,
   290  		},
   291  		"only non-null": {
   292  			obj: map[string]interface{}{"req": "here", "alsoReq": "here"},
   293  			err: nil,
   294  		},
   295  		"missing non-null": {
   296  			obj: map[string]interface{}{"req": "here", "notReq": "here"},
   297  			err: errors.Errorf("type T requires a value for field alsoReq, but no value present"),
   298  		},
   299  		"missing all non-null": {
   300  			obj: map[string]interface{}{"notReq": "here"},
   301  			err: errors.Errorf("type T requires a value for field req, but no value present"),
   302  		},
   303  		"with exclusion": {
   304  			obj: map[string]interface{}{"req": "here", "notReq": "here"},
   305  			exc: "alsoReq",
   306  			err: nil,
   307  		},
   308  	}
   309  
   310  	typ := &astType{
   311  		typ:      &ast.Type{NamedType: "T"},
   312  		inSchema: (gqlSchema.(*schema)).schema,
   313  	}
   314  
   315  	for name, test := range tcases {
   316  		t.Run(name, func(t *testing.T) {
   317  			err := typ.EnsureNonNulls(test.obj, test.exc)
   318  			if test.err == nil {
   319  				require.NoError(t, err)
   320  			} else {
   321  				require.EqualError(t, err, test.err.Error())
   322  			}
   323  		})
   324  	}
   325  }