share_examples_for 'A serialization method that also serializes core classes' do
  # This spec ensures that we don't break any serialization methods attached
  # to core classes, such as Array
  before(:all) do
    %w[ @harness ].each do |ivar|
      raise "+#{ivar}+ should be defined in before block" unless instance_variable_get(ivar)
    end

    DataMapper.auto_migrate!
  end

  before(:each) do
    DataMapper::Model.descendants.each { |model| model.destroy! }
  end

  it 'serializes an array of extended objects' do
    Cow.create(
      :id        => 89,
      :composite => 34,
      :name      => 'Berta',
      :breed     => 'Guernsey'
    )
    result = @harness.test(Cow.all.to_a)
    result[0].values_at("id", "composite", "name", "breed").should ==
      [89, 34, "Berta", "Guernsey"]
  end

  it 'serializes an array of collections' do
    query = DataMapper::Query.new(DataMapper::repository(:default), Cow)

    keys = %w[ id composite name breed ]

    resources = [
      Hash[ keys.zip([  1,  2, 'Betsy', 'Jersey'   ]) ],
      Hash[ keys.zip([ 89, 34, 'Berta', 'Guernsey' ]) ],
    ]

    collection = DataMapper::Collection.new(query, query.model.load(resources, query))

    result = @harness.test(collection)
    result[0].values_at(*keys).should == resources[0].values_at(*keys)
    result[1].values_at(*keys).should == resources[1].values_at(*keys)
  end
end

share_examples_for 'A serialization method' do
  before(:all) do
    %w[ @harness ].each do |ivar|
      raise "+#{ivar}+ should be defined in before block" unless instance_variable_get(ivar)
    end

    DataMapper.auto_migrate!
  end

  before(:each) do
    DataMapper::Model.descendants.each { |model| model.destroy! }
  end

  describe '(serializing single resources)' do
    it 'should serialize Model.first' do
      # At the moment this is implied by serializing a resource, but this
      # test ensures the contract even if dm-core changes
      Cow.create(
        :id        => 89,
        :composite => 34,
        :name      => 'Berta',
        :breed     => 'Guernsey'
      )
      result = @harness.test(Cow.first)
      result.values_at("name", "breed").should == ["Berta", "Guernsey"]
    end

    it 'should serialize a resource' do
      cow = Cow.new(
        :id        => 89,
        :composite => 34,
        :name      => 'Berta',
        :breed     => 'Guernsey'
      )

      result = @harness.test(cow)
      result.values_at("id", "composite", "name", "breed").should == [89,  34, 'Berta', 'Guernsey']
    end

    it 'should exclude nil properties' do
      cow = Cow.new(
        :id        => 89,
        :name      => nil
      )

      result = @harness.test(cow)
      result.values_at("id", "composite").should == [89,  nil]
    end

    it "should only includes properties given to :only option" do
      pending_if 'Psych provides no way to pass in parameters', @ruby_192 && @to_yaml do
        planet = Planet.new(
          :name     => "Mars",
          :aphelion => 249_209_300.4
        )

        result = @harness.test(planet, :only => [:name])
        result.values_at("name", "aphelion").should == ["Mars", nil]
      end
    end

    it "should serialize values returned by an array of methods given to :methods option" do
      pending_if 'Psych provides no way to pass in parameters', @ruby_192 && @to_yaml do
        planet = Planet.new(
          :name     => "Mars",
          :aphelion => 249_209_300.4
        )

        result = @harness.test(planet, :methods => [:category, :has_known_form_of_life?])
        # XML currently can't serialize ? at the end of method names
        boolean_method_name = @harness.method_name == :to_xml ? "has_known_form_of_life" : "has_known_form_of_life?"
        result.values_at("category", boolean_method_name).should == ["terrestrial", false]
      end
    end

    it "should serialize values returned by a single method given to :methods option" do
      pending_if 'Psych provides no way to pass in parameters', @ruby_192 && @to_yaml do
        planet = Planet.new(
          :name     => "Mars",
          :aphelion => 249_209_300.4
        )

        result = @harness.test(planet, :methods => :category)
        result.values_at("category").should == ["terrestrial"]
      end
    end

    it "should only include properties given to :only option" do
      pending_if 'Psych provides no way to pass in parameters', @ruby_192 && @to_yaml do
        planet = Planet.new(
          :name     => "Mars",
          :aphelion => 249_209_300.4
        )

        result = @harness.test(planet, :only => [:name])
        result.values_at("name", "aphelion").should == ["Mars", nil]
      end
    end

    it "should exclude properties given to :exclude option" do
      pending_if 'Psych provides no way to pass in parameters', @ruby_192 && @to_yaml do
        planet = Planet.new(
          :name     => "Mars",
          :aphelion => 249_209_300.4
        )

        result = @harness.test(planet, :exclude => [:aphelion])
        result.values_at("name", "aphelion").should == ["Mars", nil]
      end
    end

    it "should give higher precendence to :only option over :exclude" do
      pending_if 'Psych provides no way to pass in parameters', @ruby_192 && @to_yaml do
        planet = Planet.new(
          :name     => "Mars",
          :aphelion => 249_209_300.4
        )

        result = @harness.test(planet, :only => [:name], :exclude => [:name])
        result.values_at("name", "aphelion").should == ["Mars", nil]
      end
    end

    it 'should support child associations included via the :methods parameter' do
      pending_if 'Psych provides no way to pass in parameters', @ruby_192 && @to_yaml do
        solar_system = SolarSystem.create(:name => "one")
        planet = Planet.new(:name => "earth")
        planet.solar_system = solar_system
        result = @harness.test(planet, :methods => [:solar_system])
        result['solar_system'].values_at('name', 'id').should == ['one', 1]
      end
    end
  end

  describe "(collections and proxies)" do
    it 'should serialize Model.all' do
      # At the moment this is implied by serializing a collection, but this
      # test ensures the contract even if dm-core changes
      Cow.create(
        :id        => 89,
        :composite => 34,
        :name      => 'Berta',
        :breed     => 'Guernsey'
      )
      result = @harness.test(Cow.all)
      result[0].values_at("name", "breed").should == ["Berta", "Guernsey"]
    end

    it 'should serialize a collection' do
      query = DataMapper::Query.new(DataMapper::repository(:default), Cow)

      keys = %w[ id composite name breed ]

      resources = [
        Hash[ keys.zip([  1,  2, 'Betsy', 'Jersey'   ]) ],
        Hash[ keys.zip([ 10, 20, 'Berta', 'Guernsey' ]) ],
      ]

      collection = DataMapper::Collection.new(query, query.model.load(resources, query))

      result = @harness.test(collection)
      result[0].values_at(*keys).should == resources[0].values_at(*keys)
      result[1].values_at(*keys).should == resources[1].values_at(*keys)
    end

    it 'should serialize an empty collection' do
      query = DataMapper::Query.new(DataMapper::repository(:default), Cow)
      collection = DataMapper::Collection.new(query)

      result = @harness.test(collection)
      result.should be_empty
    end

    it "serializes a one to many relationship" do
      parent = Cow.new(:id => 1, :composite => 322, :name => "Harry", :breed => "Angus")
      baby = Cow.new(:mother_cow => parent, :id => 2, :composite => 321, :name => "Felix", :breed => "Angus")

      parent.save
      baby.save

      result = @harness.test(parent.baby_cows)
      result.should be_kind_of(Array)

      result[0].values_at(*%w{id composite name breed}).should == [2, 321, "Felix", "Angus"]
    end

    it "serializes a many to one relationship" do
      parent = Cow.new(:id => 1, :composite => 322, :name => "Harry", :breed => "Angus")
      baby = Cow.new(:mother_cow => parent, :id => 2, :composite => 321, :name => "Felix", :breed => "Angus")

      parent.save
      baby.save

      result = @harness.test(baby.mother_cow)
      result.should be_kind_of(Hash)
      result.values_at(*%w{id composite name breed}).should == [1, 322, "Harry", "Angus"]
    end

    it "serializes a many to many relationship" do
      pending 'TODO: fix many to many in dm-core' do
        p1 = Planet.create(:name => 'earth')
        p2 = Planet.create(:name => 'mars')

        FriendedPlanet.create(:planet => p1, :friend_planet => p2)

        result = @harness.test(p1.reload.friend_planets)
        result.should be_kind_of(Array)

        result[0]["name"].should == "mars"
      end
    end
  end

  with_alternate_adapter do

    describe "(multiple repositories)" do
      before(:all) do
        [ :default, :alternate ].each do |repository_name|
          DataMapper.repository(repository_name) do
            QuanTum::Cat.auto_migrate!
            QuanTum::Cat.destroy!
          end
        end
      end

      it "should use the repsoitory for the model" do
        alternate_repo = DataMapper::Spec.spec_adapters[:alternate].name
        gerry = QuanTum::Cat.create(:name => "gerry")
        george = DataMapper.repository(alternate_repo){ QuanTum::Cat.create(:name => "george", :is_dead => false) }
        @harness.test(gerry )['is_dead'].should be(nil)
        @harness.test(george)['is_dead'].should be(false)
      end
    end

  end

  it 'should integrate with dm-validations' do
    planet = Planet.create(:name => 'a')
    results = @harness.test(planet.errors)
    results.should == {
      "name"            => planet.errors[:name].map { |e| e.to_s },
      "solar_system_id" => planet.errors[:solar_system_id].map { |e| e.to_s }
    }
  end
end
