# frozen_string_literal: true
require "spec_helper"

describe GraphQL::UnionType do
  let(:type_1) { OpenStruct.new(kind: GraphQL::TypeKinds::OBJECT, type: 1)}
  let(:type_2) { OpenStruct.new(kind: GraphQL::TypeKinds::OBJECT, type: 2)}
  let(:type_3) { OpenStruct.new(kind: GraphQL::TypeKinds::SCALAR, type: 3)}
  let(:union) {
    types = [type_1, type_2]
    GraphQL::UnionType.define {
      name("MyUnion")
      description("Some items")
      possible_types(types)
    }
  }

  let(:filtered_union) {
    defs = self
    GraphQL::UnionType.define {
      name("FilteredUnion")
      type_membership_class(FilterType2)

      possible_types([defs.type_2], visibility: { private: true })
      possible_types([defs.type_3])
    }
  }

  class FilterType2 < GraphQL::Schema::TypeMembership
    def initialize(*args, visibility: nil, **kwargs)
      @visibility = visibility
      super(*args, **kwargs)
    end

    def visible?(ctx)
      return true if @visibility.nil?

      @visibility[:private] && ctx[:private]
    end
  end


  it "has a name" do
    assert_equal("MyUnion", union.name)
  end

  it '#include? returns true if type in possible_types' do
    assert union.include?(type_1)
  end

  it '#include? returns false if type is not in possible_types' do
    assert_equal(false, union.include?(type_3))
  end

  it '#resolve_type raises error if resolved type is not in possible_types' do
    test_str = 'Hello world'
    union.resolve_type = ->(value, ctx) {
      "This is not the types you are looking for"
    }
    fake_ctx = OpenStruct.new(query: GraphQL::Query.new(GraphQL::Schema.new, ""))

    assert_raises(RuntimeError) {
      union.resolve_type(test_str, fake_ctx)
    }
  end

  it '#possible_types returns only filtered types if context is present' do
    assert_equal [type_3], filtered_union.possible_types(private: false)
  end

  it '#possible_types returns all possible types provided in a private context' do
    assert_equal [type_2, type_3], filtered_union.possible_types(private: true)
  end

  describe "#resolve_type" do
    let(:result) { Dummy::Schema.execute(query_string) }
    let(:query_string) {%|
      {
        allAnimal {
          type: __typename
          ... on Cow {
            cowName: name
          }
          ... on Goat {
            goatName: name
          }
        }

        allAnimalAsCow {
          type: __typename
          ... on Cow {
            name
          }
        }
      }
    |}

    it 'returns correct types for general schema and specific union' do
      expected_result = {
        # When using Query#resolve_type
        "allAnimal" => [
          { "type" => "Cow", "cowName" => "Billy" },
          { "type" => "Goat", "goatName" => "Gilly" }
        ],

        # When using UnionType#resolve_type
        "allAnimalAsCow" => [
          { "type" => "Cow", "name" => "Billy" },
          { "type" => "Cow", "name" => "Gilly" }
        ]
      }
      assert_equal expected_result, result["data"]
    end
  end

  describe "typecasting from union to union" do
    let(:result) { Dummy::Schema.execute(query_string) }
    let(:query_string) {%|
      {
        allDairy {
          dairyName: __typename
          ... on Beverage {
            bevName: __typename
            ... on Milk {
              flavors
            }
          }
        }
      }
    |}

    it "casts if the object belongs to both unions" do
      expected_result = [
        {"dairyName"=>"Cheese"},
        {"dairyName"=>"Cheese"},
        {"dairyName"=>"Cheese"},
        {"dairyName"=>"Milk", "bevName"=>"Milk", "flavors"=>["Natural", "Chocolate", "Strawberry"]},
      ]
      assert_equal expected_result, result["data"]["allDairy"]
    end
  end

  describe "list of union type" do
    describe "fragment spreads" do
      let(:result) { Dummy::Schema.execute(query_string) }
      let(:query_string) {%|
        {
          allDairy {
            __typename
            ... milkFields
            ... cheeseFields
          }
        }
        fragment milkFields on Milk {
          id
          source
          origin
          flavors
        }

        fragment cheeseFields on Cheese {
          id
          source
          origin
          flavor
        }
      |}

      it "resolves the right fragment on the right item" do
        all_dairy = result["data"]["allDairy"]
        cheeses = all_dairy.first(3)
        cheeses.each do |cheese|
          assert_equal "Cheese", cheese["__typename"]
          assert_equal ["__typename", "id", "source", "origin", "flavor"], cheese.keys
        end

        milks = all_dairy.last(1)
        milks.each do |milk|
          assert_equal "Milk", milk["__typename"]
          assert_equal ["__typename", "id", "source", "origin", "flavors"], milk.keys
        end
      end
    end
  end

  describe "#dup" do
    it "copies possible types without affecting the original" do
      union.possible_types # load the internal cache
      union_2 = union.dup
      assert_equal 2, union.possible_types.size
      assert_equal 2, union_2.possible_types.size

      union_2.possible_types = union_2.possible_types + [type_3]
      assert_equal 2, union.possible_types.size
      assert_equal 3, union_2.possible_types.size
    end
  end
end
