# 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
  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, GraphQL::Types::ISO8601DateTime, 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, GraphQL::Types::ISO8601DateTime, null: false
    field :created_on, GraphQL::Types::ISO8601Date, 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),
        created_on: Date.new(1970, 1, 1),
        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),
            created_on: Date.new(1970, 1, 1),
            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

    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 person.respond_to?(:name)
    assert person.respond_to?(:company)
    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 raw_result, person.to_hash

    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_match "undefined field `nickname' on Person type. https://github.com/github-community-projects/graphql-client/blob/master/guides/unimplemented-field-error.md", 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://github.com/github-community-projects/graphql-client/blob/master/guides/unfetched-field-error.md" 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://github.com/github-community-projects/graphql-client/blob/master/guides/unfetched-field-error.md" 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://github.com/github-community-projects/graphql-client/blob/master/guides/implicitly-fetched-field-error.md" 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://github.com/github-community-projects/graphql-client/blob/master/guides/implicitly-fetched-field-error.md" 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://github.com/github-community-projects/graphql-client/blob/master/guides/implicitly-fetched-field-error.md" 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_datetime_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

  def test_date_scalar_casting
    Temp.const_set :Query, @client.parse(<<-'GRAPHQL')
      {
        me {
          name
          createdOn
        }
      }
    GRAPHQL

    response = @client.query(Temp::Query)

    person = response.data.me
    assert_equal "Josh", person.name
    assert_equal Date.new(1970, 1, 1), person.created_on
  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
    assert repo.respond_to?(:name)
    refute repo.owner.respond_to?(:login)

    owner = Temp::UserFragment.new(repo.owner)
    assert owner.respond_to?(:login)
    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_with_field_named_error
    Temp.const_set :UserFragment, @client.parse(<<-'GRAPHQL')
      fragment on User {
        errors: profileName
      }
    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 "Josh", user.errors
  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
    assert repo.respond_to?(:name)
    refute repo.owner.respond_to?(:login)

    owner = Temp::UserFragment.new(repo.owner)
    assert owner.respond_to?(:login)
    assert_equal "josh", owner.login

    owner = Temp::UserFragment.new(owner)
    assert owner.respond_to?(:login)
    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
