# frozen_string_literal: true
require 'spec_helper'

describe GraphQL::Relay::Mutation do
  let(:query_string) {%|
    mutation addBagel($clientMutationId: String, $shipName: String = "Bagel") {
      introduceShip(input: {shipName: $shipName, factionId: "1", clientMutationId: $clientMutationId}) {
        clientMutationId
        shipEdge {
          node { name, id }
        }
        faction { name }
      }
    }
  |}
  let(:introspect) {%|
    {
      __schema {
        types { name, fields { name } }
      }
    }
  |}

  after do
    STAR_WARS_DATA["Ship"].delete("9")
    STAR_WARS_DATA["Faction"]["1"].ships.delete("9")
  end

  it "supports null values" do
    result = star_wars_query(query_string, "clientMutationId" => "1234", "shipName" => nil)

    expected = {"data" => {
      "introduceShip" => {
        "clientMutationId" => "1234",
        "shipEdge" => {
          "node" => {
            "name" => nil,
            "id" => GraphQL::Schema::UniqueWithinType.encode("Ship", "9"),
          },
        },
        "faction" => {"name" => STAR_WARS_DATA["Faction"]["1"].name }
      }
    }}
    assert_equal(expected, result)
  end

  it "supports lazy resolution" do
    result = star_wars_query(query_string, "clientMutationId" => "1234", "shipName" => "Slave II")
    assert_equal "Slave II", result["data"]["introduceShip"]["shipEdge"]["node"]["name"]
  end

  it "returns the result & clientMutationId" do
    result = star_wars_query(query_string, "clientMutationId" => "1234")
    expected = {"data" => {
      "introduceShip" => {
        "clientMutationId" => "1234",
        "shipEdge" => {
          "node" => {
            "name" => "Bagel",
            "id" => GraphQL::Schema::UniqueWithinType.encode("Ship", "9"),
          },
        },
        "faction" => {"name" => STAR_WARS_DATA["Faction"]["1"].name }
      }
    }}
    assert_equal(expected, result)
  end

  it "doesn't require a clientMutationId to perform mutations" do
    result = star_wars_query(query_string)
    new_ship_name = result["data"]["introduceShip"]["shipEdge"]["node"]["name"]
    assert_equal("Bagel", new_ship_name)
  end

  it "applies the description to the derived field" do
    assert_equal "Add a ship to this faction", IntroduceShipMutation.field.description
  end

  it "inserts itself into the derived objects' metadata" do
    assert_equal IntroduceShipMutation, IntroduceShipMutation.field.mutation
    assert_equal IntroduceShipMutation, IntroduceShipMutation.return_type.mutation
    assert_equal IntroduceShipMutation, IntroduceShipMutation.input_type.mutation
    assert_equal IntroduceShipMutation, IntroduceShipMutation.result_class.mutation
  end

  describe "aliased methods" do
    describe "on an unreached mutation" do
      it 'still ensures definitions' do
        UnreachedMutation = GraphQL::Relay::Mutation.define do
          name 'UnreachedMutation'
          description 'A mutation type not directly used in the schema.'

          input_field :input, types.String
          return_field :return, types.String
        end

        assert UnreachedMutation.input_fields['input']
        assert UnreachedMutation.return_fields['return']
      end
    end
  end

  describe "providing a return type" do
    let(:custom_return_type) {
      GraphQL::ObjectType.define do
        name "CustomReturnType"
        field :name, types.String
      end
    }

    let(:mutation) {
      custom_type = custom_return_type
      GraphQL::Relay::Mutation.define do
        name "CustomReturnTypeTest"

        input_field :nullDefault, types.String, default_value: nil
        input_field :noDefault, types.String
        input_field :stringDefault, types.String, default_value: 'String'

        return_type custom_type
        resolve ->(obj, input, ctx) {
          OpenStruct.new(name: "Custom Return Type Test")
        }
      end
    }

    let(:input) { mutation.field.arguments['input'].type.unwrap }

    let(:schema) {
      mutation_field = mutation.field

      mutation_root = GraphQL::ObjectType.define do
        name "Mutation"
        field :custom, mutation_field
      end

      GraphQL::Schema.define do
        mutation(mutation_root)
      end
    }

    it "uses the provided type" do
      assert_equal custom_return_type, mutation.return_type
      assert_equal custom_return_type, mutation.field.type

      result = schema.execute("mutation { custom(input: {}) { name } }")
      assert_equal "Custom Return Type Test", result["data"]["custom"]["name"]
    end

    it "doesn't get a mutation in the metadata" do
      assert_equal nil, custom_return_type.mutation
    end

    it "supports input fields with nil default value" do
      assert input.arguments['nullDefault'].default_value?
      assert_equal nil, input.arguments['nullDefault'].default_value
    end

    it "supports input fields with no default value" do
      assert !input.arguments['noDefault'].default_value?
      assert_equal nil, input.arguments['noDefault'].default_value
    end

    it "supports input fields with non-nil default value" do
      assert input.arguments['stringDefault'].default_value?
      assert_equal "String", input.arguments['stringDefault'].default_value
    end
  end

  describe "handling errors" do
    it "supports returning an error in resolve" do
      result = star_wars_query(query_string, "clientMutationId" => "5678", "shipName" => "Millennium Falcon")

      expected = {
        "data" => {
          "introduceShip" => {
            "clientMutationId" => "5678",
            "shipEdge" => nil,
            "faction" => nil,
          }
        },
        "errors" => [
          {
            "message" => "Sorry, Millennium Falcon ship is reserved",
            "locations" => [ { "line" => 3 , "column" => 7}],
            "path" => ["introduceShip"]
          }
        ]
      }

      assert_equal(expected, result)
    end
  end
end