graphql-client/test/test_query_result.rb

1137 строки
27 KiB
Ruby

# frozen_string_literal: true
require "graphql"
require "graphql/client"
require "minitest/autorun"
require "time" # required for Time#iso8601
require "ostruct"
require_relative "foo_helper"
class TestQueryResult < MiniTest::Test
class DateTimeType < GraphQL::Schema::Scalar
def self.coerce_input(value, ctx)
Time.iso8601(value)
end
def self.coerce_result(value, ctx)
value.utc.iso8601
end
end
module NodeType
include GraphQL::Schema::Interface
field :id, ID, null: false
end
class PlanType < GraphQL::Schema::Enum
value("FREE")
value("SMALL")
value("LARGE")
end
module AdminUser
include GraphQL::Schema::Interface
field :password, String, null: false
end
class RepositoryType < GraphQL::Schema::Object
field :name, String, null: false
field :owner, "TestQueryResult::UserType", null: false
field :star_count, Integer, null: false
field :watchers, "[TestQueryResult::UserType]", null: false
end
class UserType < GraphQL::Schema::Object
implements NodeType, AdminUser
field :id, ID, null: false
field :first_name, String, null: false
field :last_name, String, null: false
field :name, String, null: false
field :login, String, null: false
field :login_url, String, null: false
field :profile_name, String, null: false
field :is_cool, Boolean, null: false
field :profile_pic, String, null: true do
argument :size, Integer, required: false
end
field :repositories, [RepositoryType], null: false
end
module HumanLike
include GraphQL::Schema::Interface
field :updated_at, DateTimeType, null: false
end
class PersonType < GraphQL::Schema::Object
implements HumanLike
field :login, String, null: true
field :name, String, null: true
field :first_name, String, null: true
field :last_name, String, null: true
field :company, String, null: true
field :homepageURL, String, null: true, method: :homepage_url, camelize: false
field :created_at, DateTimeType, null: false
field :hobbies, [String], null: true
field :plan, PlanType, null: false
end
class IssueType < GraphQL::Schema::Object
field :title, String, null: false
field :assignees, [UserType], null: false
end
class PullRequestType < GraphQL::Schema::Object
field :title, String, null: false
field :assignees, [UserType], null: false
end
class OrganizationType < GraphQL::Schema::Object
implements NodeType
field :name, String, null: false
end
class BotType < GraphQL::Schema::Object
field :login, String, null: true
end
class IssueOrPullRequest < GraphQL::Schema::Union
possible_types IssueType, PullRequestType
end
class Actor < GraphQL::Schema::Union
possible_types PersonType, BotType
end
PersonConnection = PersonType.connection_type
class QueryType < GraphQL::Schema::Object
field :me, PersonType, null: false
def me
OpenStruct.new(
login: "josh",
name: "Josh",
first_name: "Joshua",
last_name: "Peek",
company: "GitHub",
created_at: Time.at(0),
updated_at: Time.at(1),
hobbies: ["soccer", "coding"],
homepage_url: nil,
plan: "LARGE"
)
end
field :issue_or_pull_request, IssueOrPullRequest, null: false
def issue_or_pull_request
OpenStruct.new({
type: PullRequestType,
title: "Some issue",
assignees: [
OpenStruct.new(
login: "josh",
name: "Josh",
first_name: "Joshua",
last_name: "Peek",
company: "GitHub",
created_at: Time.at(0),
updated_at: Time.at(1),
hobbies: ["soccer", "coding"],
plan: "LARGE"
)
]
})
end
field :user_no_hobbies, PersonType, null: false
def user_no_hobbies
OpenStruct.new(
hobbies: nil
)
end
field :current_actor, Actor, null: false
def current_actor
OpenStruct.new(
type: PersonType,
login: "josh",
name: "Josh",
first_name: "Joshua",
last_name: "Peek",
updated_at: Time.at(1),
)
end
field :users, PersonConnection, null: true do
argument :first, Integer, required: false
end
def users
[
OpenStruct.new(login: "josh"),
OpenStruct.new(login: "mislav")
]
end
field :node, NodeType, null: true do
argument :id, ID, required: true
end
def node(id:)
{
"1" => OpenStruct.new({
type: UserType,
id: "1",
login: "josh",
password: "secret",
login_url: "/login",
profile_name: "Josh",
is_cool: true,
repositories: [
OpenStruct.new(
type: RepositoryType,
name: "github",
watchers: [
OpenStruct.new(login: "josh")
]
)
]
})
}[id]
end
field :user, UserType, null: true do
argument :id, ID, required: true
end
field :organization, OrganizationType, null: true do
argument :id, ID, required: true
end
field :repository, RepositoryType, null: true
def repository
OpenStruct.new({
name: "rails",
owner: OpenStruct.new(type: UserType, login: "josh")
})
end
end
class Schema < GraphQL::Schema
query(QueryType)
def self.resolve_type(_type, obj, _ctx)
obj.type
end
end
module Temp
end
def setup
@client = GraphQL::Client.new(schema: Schema, execute: Schema, enforce_collocated_callers: true)
end
def teardown
Temp.constants.each do |sym|
Temp.send(:remove_const, sym)
end
end
def test_define_simple_query_result
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
name
company
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
...TestQueryResult::Temp::Person
}
}
GRAPHQL
response = @client.query(Temp::Query)
refute response.data.me.respond_to?(:name)
refute response.data.me.respond_to?(:company)
person = Temp::Person.new(response.data.me)
assert_equal "Josh", person.name
assert_equal "GitHub", person.company
end
def test_snakecase_field_aliases
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
firstName
lastName
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
...TestQueryResult::Temp::Person
}
}
GRAPHQL
person = Temp::Person.new(@client.query(Temp::Query).data.me)
raw_result = {"firstName"=>"Joshua", "lastName"=>"Peek"}
assert_equal raw_result, person.to_h
assert_equal "Joshua", person.first_name
assert_equal "Peek", person.last_name
end
def test_predicate_aliases
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
name
homepageURL
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
...TestQueryResult::Temp::Person
}
}
GRAPHQL
person = Temp::Person.new(@client.query(Temp::Query).data.me)
assert_equal true, person.name?
assert_equal false, person.homepage_url?
end
def test_field_aliases
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
nickname: name
currentCompany: company
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
...TestQueryResult::Temp::Person
}
}
GRAPHQL
person = Temp::Person.new(@client.query(Temp::Query).data.me)
assert_equal "Josh", person.nickname
assert_equal "GitHub", person.current_company
end
def test_no_method_error
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
name
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
...TestQueryResult::Temp::Person
}
}
GRAPHQL
person = Temp::Person.new(@client.query(Temp::Query).data.me)
begin
person.nickname
flunk
rescue GraphQL::Client::UnimplementedFieldError => e
assert_equal "undefined field `nickname' on Person type. https://git.io/v1y3m", e.to_s
end
end
def test_no_method_when_field_exists_but_was_not_fetched
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
__typename
}
}
GRAPHQL
person = @client.query(Temp::Query).data.me
assert_raises GraphQL::Client::UnfetchedFieldError, "unfetched field `name' on Person type. https://git.io/v1y3U" do
person.name
end
end
def test_no_method_when_snakecase_field_exists_but_was_not_fetched
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
__typename
}
}
GRAPHQL
person = @client.query(Temp::Query).data.me
assert_raises GraphQL::Client::UnfetchedFieldError, "unfetched field `firstName' on Person type. https://git.io/v1y3U" do
person.first_name
end
end
def test_no_method_error_leaked_from_parent
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
__typename
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
name
...TestQueryResult::Temp::Person
}
}
GRAPHQL
person = Temp::Person.new(@client.query(Temp::Query).data.me)
assert_raises GraphQL::Client::ImplicitlyFetchedFieldError, "implicitly fetched field `name' on Person type. https://git.io/v1yGL" do
person.name
end
end
def test_no_method_error_snakecase_field_leaked_from_parent
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
__typename
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
firstName
...TestQueryResult::Temp::Person
}
}
GRAPHQL
person = Temp::Person.new(@client.query(Temp::Query).data.me)
assert_raises GraphQL::Client::ImplicitlyFetchedFieldError, "implicitly fetched field `firstName' on Person type. https://git.io/v1yGL" do
person.first_name
end
end
def test_no_method_error_leaked_from_child
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
name
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
...TestQueryResult::Temp::Person
}
}
GRAPHQL
person = @client.query(Temp::Query).data.me
assert_raises GraphQL::Client::ImplicitlyFetchedFieldError, "implicitly fetched field `name' on Person type. https://git.io/v1yGL" do
person.name
end
end
def test_merge_classes
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
... on Person {
name
company
}
... on Person {
name
login
}
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
...TestQueryResult::Temp::Person
}
}
GRAPHQL
person = Temp::Person.new(@client.query(Temp::Query).data.me)
assert_equal "josh", person.login
assert_equal "Josh", person.name
assert_equal "GitHub", person.company
end
def test_merge_nested_classes
Temp.const_set :Me, @client.parse(<<-'GRAPHQL')
fragment on Query {
... on Query {
me {
name
company
}
}
... on Query {
me {
name
login
}
}
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
...TestQueryResult::Temp::Me
}
GRAPHQL
person = Temp::Me.new(@client.query(Temp::Query).data)
assert_equal "josh", person.me.login
assert_equal "Josh", person.me.name
assert_equal "GitHub", person.me.company
end
def test_relay_connection_enumerator
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
users(first: 2) {
edges {
cursor
node {
login
}
}
}
}
GRAPHQL
data = @client.query(Temp::Query).data
assert_equal 2, data.users.edges.length
assert_equal "josh", data.users.edges[0].node.login
assert_equal "mislav", data.users.edges[1].node.login
assert_equal %w(josh mislav), data.users.edges.map(&:node).map(&:login)
end
def test_enum_values
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
name
plan
}
}
GRAPHQL
response = @client.query(Temp::Query)
person = response.data.me
assert_equal "Josh", person.name
assert_equal "LARGE", person.plan
end
def test_union_values
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
currentActor {
... on Person {
login
}
... on Bot {
login
}
}
}
GRAPHQL
response = @client.query(Temp::Query)
actor = response.data.current_actor
assert_equal "Person", actor.class.type.graphql_name
assert_equal "josh", actor.login
end
def test_interface_within_union_values
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
currentActor {
... on Person {
login
}
... on HumanLike {
updatedAt
}
... on Person {
name
}
}
}
GRAPHQL
response = @client.query(Temp::Query)
actor = response.data.current_actor
assert_equal "Person", actor.class.type.graphql_name
assert_equal "josh", actor.login
assert_equal "Josh", actor.name
assert_equal Time.at(1).utc, actor.updated_at
end
def test_date_scalar_casting
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
name
createdAt
updatedAt
}
}
GRAPHQL
response = @client.query(Temp::Query)
person = response.data.me
assert_equal "Josh", person.name
assert_equal Time.at(0), person.created_at
assert_equal Time.at(1), person.updated_at
end
include FooHelper
def test_source_location
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
name
company
}
GRAPHQL
assert_equal [__FILE__, __LINE__ - 6], Temp::Person.source_location
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
...TestQueryResult::Temp::Person
}
}
GRAPHQL
assert_equal [__FILE__, __LINE__ - 7], Temp::Query.source_location
end
def test_non_collocated_caller_error
Temp.const_set :Person, @client.parse(<<-'GRAPHQL')
fragment on Person {
name
company
firstName
lastName
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
...TestQueryResult::Temp::Person
}
}
GRAPHQL
response = @client.query(Temp::Query)
person = Temp::Person.new(response.data.me)
assert_equal "Josh", person.name
assert_equal "GitHub", person.company
assert_equal "Joshua", person.first_name
assert_equal "Peek", person.last_name
assert_raises GraphQL::Client::NonCollocatedCallerError do
format_person_info(person)
end
GraphQL::Client.allow_noncollocated_callers do
assert_equal "Josh works at GitHub", format_person_info(person)
end
GraphQL::Client.allow_noncollocated_callers do
assert_equal true, person_employed?(person)
end
GraphQL::Client.allow_noncollocated_callers do
assert_equal "Joshua Peek", format_person_name(person)
end
assert_raises GraphQL::Client::NonCollocatedCallerError do
format_person_name(person)
end
end
def test_list_of_string_scalar_casting
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me {
hobbies
}
}
GRAPHQL
response = @client.query(Temp::Query)
person = response.data.me
assert_equal ["soccer", "coding"], person.hobbies
end
def test_nullable_list
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
userNoHobbies {
hobbies
}
}
GRAPHQL
response = @client.query(Temp::Query)
person = response.data.user_no_hobbies
assert_nil person.hobbies
end
def test_empty_selection_existence
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me
}
GRAPHQL
response = @client.query(Temp::Query)
refute response.data.me.nil?
assert_equal "Person", response.data.me.class.type.graphql_name
assert response.data.me.is_a?(@client.types::Person)
end
def test_empty_selection_existence_with_fragment
Temp.const_set :Fragment, @client.parse(<<-'GRAPHQL')
fragment on Query {
me {
name
}
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
me
...TestQueryResult::Temp::Fragment
}
GRAPHQL
response = @client.query(Temp::Query)
refute response.data.me.nil?
assert_kind_of @client.types::Person, response.data.me
assert_equal "Person", response.data.me.class.type.graphql_name
assert response.data.me.is_a?(@client.types::Person)
person = Temp::Fragment.new(response.data).me
assert_equal "Josh", person.name
end
def test_parse_fragment_query_result_with_nested_fields
Temp.const_set :UserFragment, @client.parse(<<-'GRAPHQL')
fragment on User {
id
repositories {
name
watchers {
login
}
}
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
node(id: "1") {
...TestQueryResult::Temp::UserFragment
}
}
GRAPHQL
response = @client.query(Temp::Query)
user = Temp::UserFragment.new(response.data.node)
assert_equal "1", user.id
assert_kind_of Array, user.repositories
assert_equal "github", user.repositories[0].name
assert_equal "josh", user.repositories[0].watchers[0].login
end
def test_parse_fragment_spread_constant
Temp.const_set :UserFragment, @client.parse(<<-'GRAPHQL')
fragment on User {
login
}
GRAPHQL
Temp.const_set :RepositoryFragment, @client.parse(<<-'GRAPHQL')
fragment on Repository {
name
owner {
...TestQueryResult::Temp::UserFragment
}
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
repository {
...TestQueryResult::Temp::RepositoryFragment
}
}
GRAPHQL
response = @client.query(Temp::Query)
repo = Temp::RepositoryFragment.new(response.data.repository)
assert_equal "rails", repo.name
refute repo.owner.respond_to?(:login)
owner = Temp::UserFragment.new(repo.owner)
assert_equal "josh", owner.login
end
def test_parse_nested_inline_fragments_on_same_node
Temp.const_set :UserFragment, @client.parse(<<-'GRAPHQL')
fragment on Node {
id
... on User {
login
... on AdminUser {
password
}
}
... on Organization {
name
}
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
node(id: "1") {
...TestQueryResult::Temp::UserFragment
}
}
GRAPHQL
response = @client.query(Temp::Query)
user = Temp::UserFragment.new(response.data.node)
assert_equal "1", user.id
assert_equal "josh", user.login
assert_equal "secret", user.password
end
def test_parse_fragment_query_result_aliases
Temp.const_set :UserFragment, @client.parse(<<-'GRAPHQL')
fragment on User {
loginUrl
profileName
name: profileName
isCool
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
node(id: "1") {
...TestQueryResult::Temp::UserFragment
}
}
GRAPHQL
response = @client.query(Temp::Query)
user = Temp::UserFragment.new(response.data.node)
assert_equal "/login", user.login_url
assert_equal "Josh", user.profile_name
assert_equal "Josh", user.name
assert user.is_cool?
end
def test_parse_fragment_spread_with_inline_fragment
Temp.const_set :UserFragment, @client.parse(<<-'GRAPHQL')
fragment on User {
login
}
GRAPHQL
Temp.const_set :RepositoryFragment, @client.parse(<<-'GRAPHQL')
fragment on Repository {
name
owner {
... on User {
...TestQueryResult::Temp::UserFragment
}
}
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
repository {
...TestQueryResult::Temp::RepositoryFragment
}
}
GRAPHQL
response = @client.query(Temp::Query)
repo = Temp::RepositoryFragment.new(response.data.repository)
assert_equal "rails", repo.name
refute repo.owner.respond_to?(:login)
owner = Temp::UserFragment.new(repo.owner)
assert_equal "josh", owner.login
owner = Temp::UserFragment.new(owner)
assert_equal "josh", owner.login
end
def test_parse_fragment_spread_with_local_fragment
Temp.const_set :Queries, @client.parse(<<-'GRAPHQL')
fragment RepositoryFragment on Repository {
name
}
query Query {
repository {
...RepositoryFragment
}
}
GRAPHQL
response = @client.query(Temp::Queries::Query)
assert_equal "rails", response.data.repository.name
end
def test_supports_unions_with_array_fields
Temp.const_set :Fragment, @client.parse(<<-'GRAPHQL')
fragment on IssueOrPullRequest {
... on PullRequest {
assignees {
login
}
}
... on Issue {
assignees {
login
}
}
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
issueOrPullRequest {
...TestQueryResult::Temp::Fragment
}
}
GRAPHQL
response = @client.query(Temp::Query)
obj = Temp::Fragment.new(response.data.issue_or_pull_request)
assert_equal 1, obj.assignees.size
assert_equal "josh", obj.assignees[0].login
end
def test_parse_invalid_fragment_cast
Temp.const_set :UserFragment, @client.parse(<<-'GRAPHQL')
fragment on User {
login
}
GRAPHQL
Temp.const_set :RepositoryFragment, @client.parse(<<-'GRAPHQL')
fragment on Repository {
name
owner {
login
}
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
repository {
...TestQueryResult::Temp::RepositoryFragment
}
}
GRAPHQL
response = @client.query(Temp::Query)
repo = Temp::RepositoryFragment.new(response.data.repository)
assert_equal "rails", repo.name
assert_equal "josh", repo.owner.login
assert_raises TypeError, "TestQueryResult::Temp::UserFragment is not included in TestQueryResult::Temp::RepositoryFragment" do
Temp::UserFragment.new(repo.owner)
end
end
def test_parse_invalid_fragment_cast_on_spread
Temp.const_set :AdminFragment, @client.parse(<<-'GRAPHQL')
fragment on AdminUser {
password
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
node(id: "1") {
id
}
}
GRAPHQL
response = @client.query(Temp::Query)
assert_raises TypeError, "TestQueryResult::Temp::AdminFragment is not included in TestQueryResult::Temp::Query" do
Temp::AdminFragment.new(response.data.node)
end
end
def test_parse_valid_fragment_cast_on_spread
Temp.const_set :AdminFragment, @client.parse(<<-'GRAPHQL')
fragment on AdminUser {
password
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
node(id: "1") {
...TestQueryResult::Temp::AdminFragment
}
}
GRAPHQL
response = @client.query(Temp::Query)
admin = Temp::AdminFragment.new(response.data.node)
assert_equal "secret", admin.password
end
def test_client_parse_fragment_query_result_with_inline_fragments
Temp.const_set :UserFragment, @client.parse(<<-'GRAPHQL')
fragment on User {
id
repositories {
... on Repository {
name
watchers {
... on User {
login
}
}
}
}
}
GRAPHQL
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
node(id: "1") {
...TestQueryResult::Temp::UserFragment
}
}
GRAPHQL
response = @client.query(Temp::Query)
user = Temp::UserFragment.new(response.data.node)
assert_equal "1", user.id
assert_kind_of Array, user.repositories
assert_equal "github", user.repositories[0].name
assert_equal "josh", user.repositories[0].watchers[0].login
end
def test_client_query_result_with_include_and_skip_directives
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
query($includeId: Boolean!, $skipRepositories: Boolean!) {
node(id: "1") {
... on User {
id @include(if: $includeId)
repositories @skip(if: $skipRepositories) {
name
}
}
}
}
GRAPHQL
assert response = @client.query(Temp::Query, variables: { includeId: true, skipRepositories: false })
assert user = response.data.node
assert_equal "1", user.id
assert_kind_of Array, user.repositories
assert_equal "github", user.repositories[0].name
assert response = @client.query(Temp::Query, variables: { includeId: false, skipRepositories: false })
assert user = response.data.node
assert_nil user.id
assert_kind_of Array, user.repositories
assert_equal "github", user.repositories[0].name
assert response = @client.query(Temp::Query, variables: { includeId: true, skipRepositories: true })
assert user = response.data.node
assert_equal "1", user.id
assert_nil user.repositories
assert response = @client.query(Temp::Query, variables: { includeId: false, skipRepositories: true })
assert user = response.data.node
assert_nil user.id
assert_nil user.repositories
end
def test_client_query_result_with_type_mismatch
Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
{
node(id: "1") {
... on Organization {
id
}
}
}
GRAPHQL
assert response = @client.query(Temp::Query)
assert user = response.data.node
assert_kind_of @client.types::User, user
end
end