require 'test_helper'
require 'montage_rails/relation'
require 'montage_rails/base'

class MontageRails::BaseTest < MiniTest::Test
  context "initialization" do
    should "initialize all the variables with nil when nothing is passed in" do
      @movie = Movie.new

      assert_nil @movie.rank
      assert_nil @movie.rating
      assert_nil @movie.title
      assert_nil @movie.votes
      assert_nil @movie.year
    end

    should "initialize with the passed in parameters" do
      @movie = Movie.new(MontageRails::MovieResource.to_hash)

      assert_equal 4, @movie.rank
      assert_equal 2.0, @movie.rating
      assert_equal "The Jerk", @movie.title
      assert_equal 500, @movie.votes
      assert_equal 1983, @movie.year
    end

    should "set persisted to the value passed in if it is passed in" do
      @movie = Movie.new(persisted: true)

      assert @movie.persisted?
    end

    should "default persisted to false" do
      @movie = Movie.new

      assert !@movie.persisted?
    end
  end

  context "class setup" do
    setup do
      class FooBar < MontageRails::Base
        self.table_name = "movies"
      end
    end

    should "allow the table_name to be overriden" do
      assert_equal "movies", FooBar.table_name
    end
  end

  context "callbacks" do
    should "respond to the before_save callback and before_create callback when it's not persisted" do
      @movie = Movie.new(MontageRails::MovieResource.to_hash)

      @movie.save

      assert_equal "FOO", @movie.before_save_var
      assert_equal "BAR", @movie.before_create_var
    end

    should "only call the before_create callback if the record is not persisted" do
      @movie = Movie.create(MontageRails::MovieResource.to_hash)

      @movie.before_create_var = nil
      @movie.before_save_var = nil

      @movie.votes = 600
      @movie.save

      assert_nil @movie.before_create_var
      assert_equal "FOO", @movie.before_save_var
    end

    should "call the before_create callback on creation" do
      @movie = Movie.create(MontageRails::MovieResource.to_hash)

      assert_equal "BAR", @movie.before_create_var
    end

    should "respond to the after_save callback and after_create callback when it's not persisted" do
      @movie = Movie.new(MontageRails::MovieResource.to_hash)

      @movie.save

      assert_equal "AFTER SAVE", @movie.after_save_var
      assert_equal "AFTER CREATE", @movie.after_create_var
    end

    should "only call the after_create callback if the record is not persisted" do
      @movie = Movie.create(MontageRails::MovieResource.to_hash)

      @movie.after_create_var = nil
      @movie.after_save_var = nil

      @movie.votes = 600
      @movie.save

      assert_nil @movie.after_create_var
      assert_equal "AFTER SAVE", @movie.after_save_var
    end

    should "call the after_create callback on creation" do
      @movie = Movie.create(MontageRails::MovieResource.to_hash)

      assert_equal "AFTER CREATE", @movie.after_create_var
    end
  end

  context "delegation" do
    should "delegate the first query method called to a Relation object and return a relation" do
      assert_equal MontageRails::Relation, Movie.where(foo: "bar").class
      assert_equal MontageRails::Relation, Movie.limit(10).class
      assert_equal MontageRails::Relation, Movie.offset(10).class
      assert_equal MontageRails::Relation, Movie.order(foo: :asc).class
    end

    should "create finder methods for all the column names" do
      assert Movie.respond_to?(:find_by_rank)
      assert Movie.respond_to?(:find_by_title)
      assert Movie.respond_to?(:find_by_votes)
      assert Movie.respond_to?(:find_by_id)
      assert Movie.respond_to?(:find_by_year)
      assert Movie.respond_to?(:find_by_rating)
    end
  end

  context "column methods" do
    setup do
      @movie = Movie.new
    end

    should "respond to method names that correspond to column names" do
      assert @movie.respond_to?(:rank)
      assert @movie.respond_to?(:rating)
      assert @movie.respond_to?(:title)
      assert @movie.respond_to?(:votes)
      assert @movie.respond_to?(:year)
    end
  end

  context ".has_many" do
    setup do
      @movie = Movie.create(MontageRails::MovieResource.to_hash)
      @actor = Actor.create(MontageRails::ActorResource.steve_martin)
      @actor2 = Actor.create(MontageRails::ActorResource.mark_hamill)
    end

    should "define an instance method for the given table name" do
      assert @movie.respond_to?(:actors)

      assert_equal 1, @movie.actors.count
      assert_equal @actor.attributes, @movie.actors.first.attributes
    end

    should "allow the resulting relation to be chainable" do
      assert_equal @actor.attributes, @movie.actors.where(name: "Steve Martin").first.attributes
    end

    context "when the table name has been overridden" do
      setup do
        class TestClass < MontageRails::Base
          self.table_name = "movies"

          has_many :actors
        end

        @test = TestClass.where(title: "The Jerk").first
      end

      should "use the new table name to define the methods" do
        assert_equal "Steve Martin", @test.actors.first.name
      end
    end
  end

  context ".belongs_to" do
    should "define an instance method for the given table name" do
      @movie = Movie.find_by_title("The Jerk")
      @studio = Studio.create(name: "Universal")

      assert @movie.respond_to?(:studio)
      assert @movie.respond_to?(:studio=)
      assert_equal @studio.attributes, @movie.studio.attributes

      @movie.studio = @studio
      @movie.save

      assert_equal @studio.attributes, @movie.studio.attributes
    end
  end

  context ".columns" do
    should "retrieve the column names from the database if they have not been already" do
      Movie.columns.each_with_index do |column, index|
        assert_equal column.name, Movie.columns[index].name
        assert_equal column.type, Movie.columns[index].type
        assert_equal column.required, Movie.columns[index].required
      end
    end

    should "not call out to the database if the columns have already been retrieved" do
      Movie.columns

      MontageRails.connection.expects(:schemas).never

      Movie.columns
    end
  end

  context ".create" do
    should "save a new object and return an instance of the class" do
      @movie = Movie.create(MontageRails::MovieResource.to_hash)

      assert @movie.persisted?
      assert_equal 4, @movie.rank
      assert_equal 2.0, @movie.rating
      assert_equal "The Jerk", @movie.title
      assert_equal 600, @movie.votes
      assert_equal 1983, @movie.year
    end
  end

  context ".find_or_initialize_by" do
    context "when the document is found" do
      should "return an instance of the document" do
        @movie = Movie.find_or_initialize_by(title: "The Jerk")

        assert @movie.persisted?
        assert_equal 4, @movie.rank
        assert_equal 2.0, @movie.rating
        assert_equal "The Jerk", @movie.title
        assert_equal 600, @movie.votes
        assert_equal 1983, @movie.year
      end
    end

    context "when the document is not found" do
      should "return a new instance of the document" do
        @movie = Movie.find_or_initialize_by(title: "Foo")

        assert !@movie.persisted?
        assert_equal "Foo", @movie.title
      end
    end
  end

  context ".all" do
    setup do
      @movies = Movie.all
    end

    should "fetch all the movies" do
      refute @movies.empty?
    end
  end

  context ".column_names" do
    setup do
      @column_names = Movie.column_names

      @expected = %w(id created_at updated_at studio_id rank rating title votes year)
    end

    should "return an array of strings that contains the column names" do
      assert_equal @expected, @column_names
    end
  end

  context "#inspect" do
    setup do
      @inspect = Movie.inspect
    end

    should "return a Rails style formatted inspect string" do
      assert_equal "Movie(id: text, created_at: datetime, updated_at: datetime, studio_id: text, rank: numeric, rating: numeric, title: text, votes: numeric, year: numeric)", @inspect
    end
  end

  context "#attributes_valid?" do
    should "return false if there is an invalid attribute" do
      @movie = Movie.new(rank: nil, rating: 2.0, title: "Foo", votes: 1, year: 1983, studio_id: "19442e09-5c2d-4e5d-8f34-675570e81414")

      assert !@movie.attributes_valid?
    end
  end

  context "#save" do
    context "when valid attributes are provided" do
      setup do
        @movie = Movie.new(MontageRails::MovieResource.to_hash)
      end

      should "successfully save the document, return a copy of itself, and set persisted to true" do
        @movie.save

        assert @movie.persisted?
      end
    end

    context "when valid attributes are not provided" do
      setup do
        @movie = Movie.new
      end

      should "not save the document and return nil" do
        @movie.save

        assert !@movie.persisted?
      end
    end
  end

  context "#update_attributes" do
    setup do
      @movie = Movie.create(MontageRails::MovieResource.to_hash)
    end

    context "when valid attributes are given" do
      should "update the attributes and return a copy of self" do
        @movie.update_attributes(votes: 600)

        assert_equal 600, @movie.votes
      end
    end

    context "when the document is not yet persisted and the attributes are valid" do
      setup do
        @movie = Movie.new
      end

      should "create a new document" do
        @movie.update_attributes(MontageRails::MovieResource.to_hash)

        assert @movie.persisted?
      end
    end

    context "when invalid attributes are given" do
      should "not update any attributes and return false" do
        assert !@movie.update_attributes(votes: nil)

        assert_equal 600, @movie.votes
      end
    end

    context "when none of the attributes have changed" do
      should "not update the document" do
        MontageRails.connection.expects(:update_document).never
        @movie.update_attributes(MontageRails::MovieResource.update_body.shift)
      end
    end

    context "when only one, unchanged attribute is passed in" do
      should "not update the document" do
        MontageRails.connection.expects(:update_document).never
        @movie.update_attributes(rank: 4)
      end
    end

    context "when two, unchanged attributes are passed in" do
      should "not update the document" do
        MontageRails.connection.expects(:update_document).never
        @movie.update_attributes(rank: 4, rating: 2.0)
      end
    end

    context "when data types passed in don't match and attributes haven't changed" do
      should "not update the document" do
        MontageRails.connection.expects(:update_document).never
        @movie.update_attributes(rank: "4", rating: "2.0")
      end
    end
  end
end