...

Source file src/github.com/shurcooL/graphql/query_test.go

Documentation: github.com/shurcooL/graphql

     1  package graphql
     2  
     3  import (
     4  	"net/url"
     5  	"testing"
     6  	"time"
     7  )
     8  
     9  func TestConstructQuery(t *testing.T) {
    10  	tests := []struct {
    11  		inV         interface{}
    12  		inVariables map[string]interface{}
    13  		want        string
    14  	}{
    15  		{
    16  			inV: struct {
    17  				Viewer struct {
    18  					Login      String
    19  					CreatedAt  DateTime
    20  					ID         ID
    21  					DatabaseID Int
    22  				}
    23  				RateLimit struct {
    24  					Cost      Int
    25  					Limit     Int
    26  					Remaining Int
    27  					ResetAt   DateTime
    28  				}
    29  			}{},
    30  			want: `{viewer{login,createdAt,id,databaseId},rateLimit{cost,limit,remaining,resetAt}}`,
    31  		},
    32  		{
    33  			inV: struct {
    34  				Repository struct {
    35  					DatabaseID Int
    36  					URL        URI
    37  
    38  					Issue struct {
    39  						Comments struct {
    40  							Edges []struct {
    41  								Node struct {
    42  									Body   String
    43  									Author struct {
    44  										Login String
    45  									}
    46  									Editor struct {
    47  										Login String
    48  									}
    49  								}
    50  								Cursor String
    51  							}
    52  						} `graphql:"comments(first:1after:\"Y3Vyc29yOjE5NTE4NDI1Ng==\")"`
    53  					} `graphql:"issue(number:1)"`
    54  				} `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
    55  			}{},
    56  			want: `{repository(owner:"shurcooL-test"name:"test-repo"){databaseId,url,issue(number:1){comments(first:1after:"Y3Vyc29yOjE5NTE4NDI1Ng=="){edges{node{body,author{login},editor{login}},cursor}}}}}`,
    57  		},
    58  		{
    59  			inV: func() interface{} {
    60  				type actor struct {
    61  					Login     String
    62  					AvatarURL URI
    63  					URL       URI
    64  				}
    65  
    66  				return struct {
    67  					Repository struct {
    68  						DatabaseID Int
    69  						URL        URI
    70  
    71  						Issue struct {
    72  							Comments struct {
    73  								Edges []struct {
    74  									Node struct {
    75  										DatabaseID      Int
    76  										Author          actor
    77  										PublishedAt     DateTime
    78  										LastEditedAt    *DateTime
    79  										Editor          *actor
    80  										Body            String
    81  										ViewerCanUpdate Boolean
    82  									}
    83  									Cursor String
    84  								}
    85  							} `graphql:"comments(first:1)"`
    86  						} `graphql:"issue(number:1)"`
    87  					} `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
    88  				}{}
    89  			}(),
    90  			want: `{repository(owner:"shurcooL-test"name:"test-repo"){databaseId,url,issue(number:1){comments(first:1){edges{node{databaseId,author{login,avatarUrl,url},publishedAt,lastEditedAt,editor{login,avatarUrl,url},body,viewerCanUpdate},cursor}}}}}`,
    91  		},
    92  		{
    93  			inV: func() interface{} {
    94  				type actor struct {
    95  					Login     String
    96  					AvatarURL URI `graphql:"avatarUrl(size:72)"`
    97  					URL       URI
    98  				}
    99  
   100  				return struct {
   101  					Repository struct {
   102  						Issue struct {
   103  							Author         actor
   104  							PublishedAt    DateTime
   105  							LastEditedAt   *DateTime
   106  							Editor         *actor
   107  							Body           String
   108  							ReactionGroups []struct {
   109  								Content ReactionContent
   110  								Users   struct {
   111  									TotalCount Int
   112  								}
   113  								ViewerHasReacted Boolean
   114  							}
   115  							ViewerCanUpdate Boolean
   116  
   117  							Comments struct {
   118  								Nodes []struct {
   119  									DatabaseID     Int
   120  									Author         actor
   121  									PublishedAt    DateTime
   122  									LastEditedAt   *DateTime
   123  									Editor         *actor
   124  									Body           String
   125  									ReactionGroups []struct {
   126  										Content ReactionContent
   127  										Users   struct {
   128  											TotalCount Int
   129  										}
   130  										ViewerHasReacted Boolean
   131  									}
   132  									ViewerCanUpdate Boolean
   133  								}
   134  								PageInfo struct {
   135  									EndCursor   String
   136  									HasNextPage Boolean
   137  								}
   138  							} `graphql:"comments(first:1)"`
   139  						} `graphql:"issue(number:1)"`
   140  					} `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
   141  				}{}
   142  			}(),
   143  			want: `{repository(owner:"shurcooL-test"name:"test-repo"){issue(number:1){author{login,avatarUrl(size:72),url},publishedAt,lastEditedAt,editor{login,avatarUrl(size:72),url},body,reactionGroups{content,users{totalCount},viewerHasReacted},viewerCanUpdate,comments(first:1){nodes{databaseId,author{login,avatarUrl(size:72),url},publishedAt,lastEditedAt,editor{login,avatarUrl(size:72),url},body,reactionGroups{content,users{totalCount},viewerHasReacted},viewerCanUpdate},pageInfo{endCursor,hasNextPage}}}}}`,
   144  		},
   145  		{
   146  			inV: struct {
   147  				Repository struct {
   148  					Issue struct {
   149  						Body String
   150  					} `graphql:"issue(number: 1)"`
   151  				} `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
   152  			}{},
   153  			want: `{repository(owner:"shurcooL-test"name:"test-repo"){issue(number: 1){body}}}`,
   154  		},
   155  		{
   156  			inV: struct {
   157  				Repository struct {
   158  					Issue struct {
   159  						Body String
   160  					} `graphql:"issue(number: $issueNumber)"`
   161  				} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
   162  			}{},
   163  			inVariables: map[string]interface{}{
   164  				"repositoryOwner": String("shurcooL-test"),
   165  				"repositoryName":  String("test-repo"),
   166  				"issueNumber":     Int(1),
   167  			},
   168  			want: `query($issueNumber:Int!$repositoryName:String!$repositoryOwner:String!){repository(owner: $repositoryOwner, name: $repositoryName){issue(number: $issueNumber){body}}}`,
   169  		},
   170  		{
   171  			inV: struct {
   172  				Repository struct {
   173  					Issue struct {
   174  						ReactionGroups []struct {
   175  							Users struct {
   176  								Nodes []struct {
   177  									Login String
   178  								}
   179  							} `graphql:"users(first:10)"`
   180  						}
   181  					} `graphql:"issue(number: $issueNumber)"`
   182  				} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
   183  			}{},
   184  			inVariables: map[string]interface{}{
   185  				"repositoryOwner": String("shurcooL-test"),
   186  				"repositoryName":  String("test-repo"),
   187  				"issueNumber":     Int(1),
   188  			},
   189  			want: `query($issueNumber:Int!$repositoryName:String!$repositoryOwner:String!){repository(owner: $repositoryOwner, name: $repositoryName){issue(number: $issueNumber){reactionGroups{users(first:10){nodes{login}}}}}}`,
   190  		},
   191  		// Embedded structs without graphql tag should be inlined in query.
   192  		{
   193  			inV: func() interface{} {
   194  				type actor struct {
   195  					Login     String
   196  					AvatarURL URI
   197  					URL       URI
   198  				}
   199  				type event struct { // Common fields for all events.
   200  					Actor     actor
   201  					CreatedAt DateTime
   202  				}
   203  				type IssueComment struct {
   204  					Body String
   205  				}
   206  				return struct {
   207  					event                                         // Should be inlined.
   208  					IssueComment  `graphql:"... on IssueComment"` // Should not be, because of graphql tag.
   209  					CurrentTitle  String
   210  					PreviousTitle String
   211  					Label         struct {
   212  						Name  String
   213  						Color String
   214  					}
   215  				}{}
   216  			}(),
   217  			want: `{actor{login,avatarUrl,url},createdAt,... on IssueComment{body},currentTitle,previousTitle,label{name,color}}`,
   218  		},
   219  		{
   220  			inV: struct {
   221  				Viewer struct {
   222  					Login      string
   223  					CreatedAt  time.Time
   224  					ID         interface{}
   225  					DatabaseID int
   226  				}
   227  			}{},
   228  			want: `{viewer{login,createdAt,id,databaseId}}`,
   229  		},
   230  	}
   231  	for _, tc := range tests {
   232  		got := constructQuery(tc.inV, tc.inVariables)
   233  		if got != tc.want {
   234  			t.Errorf("\ngot:  %q\nwant: %q\n", got, tc.want)
   235  		}
   236  	}
   237  }
   238  
   239  func TestConstructMutation(t *testing.T) {
   240  	tests := []struct {
   241  		inV         interface{}
   242  		inVariables map[string]interface{}
   243  		want        string
   244  	}{
   245  		{
   246  			inV: struct {
   247  				AddReaction struct {
   248  					Subject struct {
   249  						ReactionGroups []struct {
   250  							Users struct {
   251  								TotalCount Int
   252  							}
   253  						}
   254  					}
   255  				} `graphql:"addReaction(input:$input)"`
   256  			}{},
   257  			inVariables: map[string]interface{}{
   258  				"input": AddReactionInput{
   259  					SubjectID: "MDU6SXNzdWUyMzE1MjcyNzk=",
   260  					Content:   ReactionContentThumbsUp,
   261  				},
   262  			},
   263  			want: `mutation($input:AddReactionInput!){addReaction(input:$input){subject{reactionGroups{users{totalCount}}}}}`,
   264  		},
   265  	}
   266  	for _, tc := range tests {
   267  		got := constructMutation(tc.inV, tc.inVariables)
   268  		if got != tc.want {
   269  			t.Errorf("\ngot:  %q\nwant: %q\n", got, tc.want)
   270  		}
   271  	}
   272  }
   273  
   274  func TestQueryArguments(t *testing.T) {
   275  	tests := []struct {
   276  		in   map[string]interface{}
   277  		want string
   278  	}{
   279  		{
   280  			in:   map[string]interface{}{"a": Int(123), "b": NewBoolean(true)},
   281  			want: "$a:Int!$b:Boolean",
   282  		},
   283  		{
   284  			in: map[string]interface{}{
   285  				"required": []IssueState{IssueStateOpen, IssueStateClosed},
   286  				"optional": &[]IssueState{IssueStateOpen, IssueStateClosed},
   287  			},
   288  			want: "$optional:[IssueState!]$required:[IssueState!]!",
   289  		},
   290  		{
   291  			in: map[string]interface{}{
   292  				"required": []IssueState(nil),
   293  				"optional": (*[]IssueState)(nil),
   294  			},
   295  			want: "$optional:[IssueState!]$required:[IssueState!]!",
   296  		},
   297  		{
   298  			in: map[string]interface{}{
   299  				"required": [...]IssueState{IssueStateOpen, IssueStateClosed},
   300  				"optional": &[...]IssueState{IssueStateOpen, IssueStateClosed},
   301  			},
   302  			want: "$optional:[IssueState!]$required:[IssueState!]!",
   303  		},
   304  		{
   305  			in:   map[string]interface{}{"id": ID("someID")},
   306  			want: "$id:ID!",
   307  		},
   308  		{
   309  			in:   map[string]interface{}{"ids": []ID{"someID", "anotherID"}},
   310  			want: `$ids:[ID!]!`,
   311  		},
   312  		{
   313  			in:   map[string]interface{}{"ids": &[]ID{"someID", "anotherID"}},
   314  			want: `$ids:[ID!]`,
   315  		},
   316  	}
   317  	for i, tc := range tests {
   318  		got := queryArguments(tc.in)
   319  		if got != tc.want {
   320  			t.Errorf("test case %d:\n got: %q\nwant: %q", i, got, tc.want)
   321  		}
   322  	}
   323  }
   324  
   325  // Custom GraphQL types for testing.
   326  type (
   327  	// DateTime is an ISO-8601 encoded UTC date.
   328  	DateTime struct{ time.Time }
   329  
   330  	// URI is an RFC 3986, RFC 3987, and RFC 6570 (level 4) compliant URI.
   331  	URI struct{ *url.URL }
   332  )
   333  
   334  func (u *URI) UnmarshalJSON(data []byte) error { panic("mock implementation") }
   335  
   336  // IssueState represents the possible states of an issue.
   337  type IssueState string
   338  
   339  // The possible states of an issue.
   340  const (
   341  	IssueStateOpen   IssueState = "OPEN"   // An issue that is still open.
   342  	IssueStateClosed IssueState = "CLOSED" // An issue that has been closed.
   343  )
   344  
   345  // ReactionContent represents emojis that can be attached to Issues, Pull Requests and Comments.
   346  type ReactionContent string
   347  
   348  // Emojis that can be attached to Issues, Pull Requests and Comments.
   349  const (
   350  	ReactionContentThumbsUp   ReactionContent = "THUMBS_UP"   // Represents the 👍 emoji.
   351  	ReactionContentThumbsDown ReactionContent = "THUMBS_DOWN" // Represents the 👎 emoji.
   352  	ReactionContentLaugh      ReactionContent = "LAUGH"       // Represents the 😄 emoji.
   353  	ReactionContentHooray     ReactionContent = "HOORAY"      // Represents the 🎉 emoji.
   354  	ReactionContentConfused   ReactionContent = "CONFUSED"    // Represents the 😕 emoji.
   355  	ReactionContentHeart      ReactionContent = "HEART"       // Represents the ❤️ emoji.
   356  )
   357  
   358  // AddReactionInput is an autogenerated input type of AddReaction.
   359  type AddReactionInput struct {
   360  	// The Node ID of the subject to modify. (Required.)
   361  	SubjectID ID `json:"subjectId"`
   362  	// The name of the emoji to react with. (Required.)
   363  	Content ReactionContent `json:"content"`
   364  
   365  	// A unique identifier for the client performing the mutation. (Optional.)
   366  	ClientMutationID *String `json:"clientMutationId,omitempty"`
   367  }
   368  

View as plain text