mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
346 lines
9.9 KiB
Go
346 lines
9.9 KiB
Go
|
package graphql
|
||
|
|
||
|
import (
|
||
|
"net/url"
|
||
|
"reflect"
|
||
|
"testing"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
func TestConstructQuery(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
inV interface{}
|
||
|
inVariables map[string]interface{}
|
||
|
want string
|
||
|
}{
|
||
|
{
|
||
|
inV: struct {
|
||
|
Viewer struct {
|
||
|
Login String
|
||
|
CreatedAt DateTime
|
||
|
ID ID
|
||
|
DatabaseID Int
|
||
|
}
|
||
|
RateLimit struct {
|
||
|
Cost Int
|
||
|
Limit Int
|
||
|
Remaining Int
|
||
|
ResetAt DateTime
|
||
|
}
|
||
|
}{},
|
||
|
want: `{viewer{login,createdAt,id,databaseId},rateLimit{cost,limit,remaining,resetAt}}`,
|
||
|
},
|
||
|
{
|
||
|
inV: struct {
|
||
|
Repository struct {
|
||
|
DatabaseID Int
|
||
|
URL URI
|
||
|
|
||
|
Issue struct {
|
||
|
Comments struct {
|
||
|
Edges []struct {
|
||
|
Node struct {
|
||
|
Body String
|
||
|
Author struct {
|
||
|
Login String
|
||
|
}
|
||
|
Editor struct {
|
||
|
Login String
|
||
|
}
|
||
|
}
|
||
|
Cursor String
|
||
|
}
|
||
|
} `graphql:"comments(first:1after:\"Y3Vyc29yOjE5NTE4NDI1Ng==\")"`
|
||
|
} `graphql:"issue(number:1)"`
|
||
|
} `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
|
||
|
}{},
|
||
|
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}}}}}`,
|
||
|
},
|
||
|
{
|
||
|
inV: func() interface{} {
|
||
|
type actor struct {
|
||
|
Login String
|
||
|
AvatarURL URI
|
||
|
URL URI
|
||
|
}
|
||
|
|
||
|
return struct {
|
||
|
Repository struct {
|
||
|
DatabaseID Int
|
||
|
URL URI
|
||
|
|
||
|
Issue struct {
|
||
|
Comments struct {
|
||
|
Edges []struct {
|
||
|
Node struct {
|
||
|
DatabaseID Int
|
||
|
Author actor
|
||
|
PublishedAt DateTime
|
||
|
LastEditedAt *DateTime
|
||
|
Editor *actor
|
||
|
Body String
|
||
|
ViewerCanUpdate Boolean
|
||
|
}
|
||
|
Cursor String
|
||
|
}
|
||
|
} `graphql:"comments(first:1)"`
|
||
|
} `graphql:"issue(number:1)"`
|
||
|
} `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
|
||
|
}{}
|
||
|
}(),
|
||
|
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}}}}}`,
|
||
|
},
|
||
|
{
|
||
|
inV: func() interface{} {
|
||
|
type actor struct {
|
||
|
Login String
|
||
|
AvatarURL URI `graphql:"avatarUrl(size:72)"`
|
||
|
URL URI
|
||
|
}
|
||
|
|
||
|
return struct {
|
||
|
Repository struct {
|
||
|
Issue struct {
|
||
|
Author actor
|
||
|
PublishedAt DateTime
|
||
|
LastEditedAt *DateTime
|
||
|
Editor *actor
|
||
|
Body String
|
||
|
ReactionGroups []struct {
|
||
|
Content ReactionContent
|
||
|
Users struct {
|
||
|
TotalCount Int
|
||
|
}
|
||
|
ViewerHasReacted Boolean
|
||
|
}
|
||
|
ViewerCanUpdate Boolean
|
||
|
|
||
|
Comments struct {
|
||
|
Nodes []struct {
|
||
|
DatabaseID Int
|
||
|
Author actor
|
||
|
PublishedAt DateTime
|
||
|
LastEditedAt *DateTime
|
||
|
Editor *actor
|
||
|
Body String
|
||
|
ReactionGroups []struct {
|
||
|
Content ReactionContent
|
||
|
Users struct {
|
||
|
TotalCount Int
|
||
|
}
|
||
|
ViewerHasReacted Boolean
|
||
|
}
|
||
|
ViewerCanUpdate Boolean
|
||
|
}
|
||
|
PageInfo struct {
|
||
|
EndCursor String
|
||
|
HasNextPage Boolean
|
||
|
}
|
||
|
} `graphql:"comments(first:1)"`
|
||
|
} `graphql:"issue(number:1)"`
|
||
|
} `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
|
||
|
}{}
|
||
|
}(),
|
||
|
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}}}}}`,
|
||
|
},
|
||
|
{
|
||
|
inV: struct {
|
||
|
Repository struct {
|
||
|
Issue struct {
|
||
|
Body String
|
||
|
} `graphql:"issue(number: 1)"`
|
||
|
} `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
|
||
|
}{},
|
||
|
want: `{repository(owner:"shurcooL-test"name:"test-repo"){issue(number: 1){body}}}`,
|
||
|
},
|
||
|
{
|
||
|
inV: struct {
|
||
|
Repository struct {
|
||
|
Issue struct {
|
||
|
Body String
|
||
|
} `graphql:"issue(number: $issueNumber)"`
|
||
|
} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
|
||
|
}{},
|
||
|
inVariables: map[string]interface{}{
|
||
|
"repositoryOwner": String("shurcooL-test"),
|
||
|
"repositoryName": String("test-repo"),
|
||
|
"issueNumber": Int(1),
|
||
|
},
|
||
|
want: `query($issueNumber:Int!$repositoryName:String!$repositoryOwner:String!){repository(owner: $repositoryOwner, name: $repositoryName){issue(number: $issueNumber){body}}}`,
|
||
|
},
|
||
|
{
|
||
|
inV: struct {
|
||
|
Repository struct {
|
||
|
Issue struct {
|
||
|
ReactionGroups []struct {
|
||
|
Users struct {
|
||
|
Nodes []struct {
|
||
|
Login String
|
||
|
}
|
||
|
} `graphql:"users(first:10)"`
|
||
|
}
|
||
|
} `graphql:"issue(number: $issueNumber)"`
|
||
|
} `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
|
||
|
}{},
|
||
|
inVariables: map[string]interface{}{
|
||
|
"repositoryOwner": String("shurcooL-test"),
|
||
|
"repositoryName": String("test-repo"),
|
||
|
"issueNumber": Int(1),
|
||
|
},
|
||
|
want: `query($issueNumber:Int!$repositoryName:String!$repositoryOwner:String!){repository(owner: $repositoryOwner, name: $repositoryName){issue(number: $issueNumber){reactionGroups{users(first:10){nodes{login}}}}}}`,
|
||
|
},
|
||
|
// Embedded structs without graphql tag should be inlined in query.
|
||
|
{
|
||
|
inV: func() interface{} {
|
||
|
type actor struct {
|
||
|
Login String
|
||
|
AvatarURL URI
|
||
|
URL URI
|
||
|
}
|
||
|
type event struct { // Common fields for all events.
|
||
|
Actor actor
|
||
|
CreatedAt DateTime
|
||
|
}
|
||
|
type IssueComment struct {
|
||
|
Body String
|
||
|
}
|
||
|
return struct {
|
||
|
event // Should be inlined.
|
||
|
IssueComment `graphql:"... on IssueComment"` // Should not be, because of graphql tag.
|
||
|
CurrentTitle String
|
||
|
PreviousTitle String
|
||
|
Label struct {
|
||
|
Name String
|
||
|
Color String
|
||
|
}
|
||
|
}{}
|
||
|
}(),
|
||
|
want: `{actor{login,avatarUrl,url},createdAt,... on IssueComment{body},currentTitle,previousTitle,label{name,color}}`,
|
||
|
},
|
||
|
}
|
||
|
for _, tc := range tests {
|
||
|
qctx := &queryContext{
|
||
|
Scalars: []reflect.Type{
|
||
|
reflect.TypeOf(DateTime{}),
|
||
|
reflect.TypeOf(URI{}),
|
||
|
},
|
||
|
}
|
||
|
got := constructQuery(qctx, tc.inV, tc.inVariables)
|
||
|
if got != tc.want {
|
||
|
t.Errorf("\ngot: %q\nwant: %q\n", got, tc.want)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestConstructMutation(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
inV interface{}
|
||
|
inVariables map[string]interface{}
|
||
|
want string
|
||
|
}{
|
||
|
{
|
||
|
inV: struct {
|
||
|
AddReaction struct {
|
||
|
Subject struct {
|
||
|
ReactionGroups []struct {
|
||
|
Users struct {
|
||
|
TotalCount Int
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} `graphql:"addReaction(input:$input)"`
|
||
|
}{},
|
||
|
inVariables: map[string]interface{}{
|
||
|
"input": AddReactionInput{
|
||
|
SubjectID: "MDU6SXNzdWUyMzE1MjcyNzk=",
|
||
|
Content: ReactionContentThumbsUp,
|
||
|
},
|
||
|
},
|
||
|
want: `mutation($input:AddReactionInput!){addReaction(input:$input){subject{reactionGroups{users{totalCount}}}}}`,
|
||
|
},
|
||
|
}
|
||
|
for _, tc := range tests {
|
||
|
got := constructMutation(&queryContext{}, tc.inV, tc.inVariables)
|
||
|
if got != tc.want {
|
||
|
t.Errorf("\ngot: %q\nwant: %q\n", got, tc.want)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestQueryArguments(t *testing.T) {
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
in map[string]interface{}
|
||
|
want string
|
||
|
}{
|
||
|
{
|
||
|
in: map[string]interface{}{"a": Int(123), "b": NewBoolean(true)},
|
||
|
want: "$a:Int!$b:Boolean",
|
||
|
},
|
||
|
{
|
||
|
in: map[string]interface{}{"states": []IssueState{IssueStateOpen, IssueStateClosed}},
|
||
|
want: "$states:[IssueState!]",
|
||
|
},
|
||
|
{
|
||
|
in: map[string]interface{}{"states": []IssueState(nil)},
|
||
|
want: "$states:[IssueState!]",
|
||
|
},
|
||
|
{
|
||
|
in: map[string]interface{}{"states": [...]IssueState{IssueStateOpen, IssueStateClosed}},
|
||
|
want: "$states:[IssueState!]",
|
||
|
},
|
||
|
{
|
||
|
in: map[string]interface{}{"id": ID("someid")},
|
||
|
want: "$id:ID!",
|
||
|
},
|
||
|
}
|
||
|
for _, tc := range tests {
|
||
|
got := queryArguments(tc.in)
|
||
|
if got != tc.want {
|
||
|
t.Errorf("%s: got: %q, want: %q", tc.name, got, tc.want)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Custom GraphQL types for testing.
|
||
|
type (
|
||
|
// DateTime is an ISO-8601 encoded UTC date.
|
||
|
DateTime struct{ time.Time }
|
||
|
|
||
|
// URI is an RFC 3986, RFC 3987, and RFC 6570 (level 4) compliant URI.
|
||
|
URI struct{ *url.URL }
|
||
|
)
|
||
|
|
||
|
// IssueState represents the possible states of an issue.
|
||
|
type IssueState string
|
||
|
|
||
|
// The possible states of an issue.
|
||
|
const (
|
||
|
IssueStateOpen IssueState = "OPEN" // An issue that is still open.
|
||
|
IssueStateClosed IssueState = "CLOSED" // An issue that has been closed.
|
||
|
)
|
||
|
|
||
|
// ReactionContent represents emojis that can be attached to Issues, Pull Requests and Comments.
|
||
|
type ReactionContent string
|
||
|
|
||
|
// Emojis that can be attached to Issues, Pull Requests and Comments.
|
||
|
const (
|
||
|
ReactionContentThumbsUp ReactionContent = "THUMBS_UP" // Represents the 👍 emoji.
|
||
|
ReactionContentThumbsDown ReactionContent = "THUMBS_DOWN" // Represents the 👎 emoji.
|
||
|
ReactionContentLaugh ReactionContent = "LAUGH" // Represents the 😄 emoji.
|
||
|
ReactionContentHooray ReactionContent = "HOORAY" // Represents the 🎉 emoji.
|
||
|
ReactionContentConfused ReactionContent = "CONFUSED" // Represents the 😕 emoji.
|
||
|
ReactionContentHeart ReactionContent = "HEART" // Represents the ❤️ emoji.
|
||
|
)
|
||
|
|
||
|
// AddReactionInput is an autogenerated input type of AddReaction.
|
||
|
type AddReactionInput struct {
|
||
|
// The Node ID of the subject to modify. (Required.)
|
||
|
SubjectID ID `json:"subjectId"`
|
||
|
// The name of the emoji to react with. (Required.)
|
||
|
Content ReactionContent `json:"content"`
|
||
|
|
||
|
// A unique identifier for the client performing the mutation. (Optional.)
|
||
|
ClientMutationID *String `json:"clientMutationId,omitempty"`
|
||
|
}
|