require File.join(File.dirname(__FILE__), 'spec_helper')

context "Migration classes" do
  before do
    Sequel::Migration.descendants.clear
  end

  specify "should be registred in Migration.descendants" do
    @class = Class.new(Sequel::Migration)
    
    Sequel::Migration.descendants.should == [@class]
  end
  
  specify "should be registered in the right order" do
    @c1 = Class.new(Sequel::Migration)
    @c2 = Class.new(Sequel::Migration)
    @c3 = Class.new(Sequel::Migration)
    
    Sequel::Migration.descendants.should == [@c1, @c2, @c3]
  end
end

context "Migration#apply" do
  before do
    @c = Class.new do
      define_method(:one) {|x| [1111, x]}
      define_method(:two) {|x| [2222, x]}
    end
    @db = @c.new
    
    @migration = Class.new(Sequel::Migration) do
      define_method(:up) {one(3333)}
      define_method(:down) {two(4444)}
    end
  end
  
  specify "should raise for an invalid direction" do
    proc {@migration.apply(@db, :hahaha)}.should raise_error(ArgumentError)
  end
  
  specify "should apply the up direction correctly" do
    @migration.apply(@db, :up).should == [1111, 3333]
  end

  specify "should apply the down direction correctly" do
    @migration.apply(@db, :down).should == [2222, 4444]
  end
end

MIGRATION_001 = %[
  class CreateSessions < Sequel::Migration
    def up
      create(1111)
    end
    
    def down
      drop(1111)
    end
  end
]

MIGRATION_002 = %[
  class CreateNodes < Sequel::Migration
    def up
      create(2222)
    end
    
    def down
      drop(2222)
    end
  end
]

MIGRATION_003 = %[
  class CreateUsers < Sequel::Migration
    def up
      create(3333)
    end
    
    def down
      drop(3333)
    end
  end
]

MIGRATION_005 = %[
  class CreateAttributes < Sequel::Migration
    def up
      create(5555)
    end
    
    def down
      drop(5555)
    end
  end
]

ALT_MIGRATION_001 = %[
  class CreateAltBasic < Sequel::Migration
    def up
      create(11111)
    end
    
    def down
      drop(11111)
    end
  end
]

ALT_MIGRATION_003 = %[
  class CreateAltAdvanced < Sequel::Migration
    def up
      create(33333)
    end
    
    def down
      drop(33333)
    end
  end
]

context "Sequel::Migrator" do
  before do
    dbc = Class.new(MockDatabase) do
      attr_reader :creates, :drops, :tables_created, :columns_created, :versions
      def initialize(*args)
        super
        @creates = []
        @drops = []
        @tables_created = []
        @columns_created = []
        @versions = {}
      end
  
      def create(x); @creates << x; end
      def drop(x); @drops << x; end

      def create_table(name, opts={}, &block)
        super
        @columns_created << / \(?(\w+) integer\)?\z/.match(sqls.last)[1].to_sym
        @tables_created << name
      end
      
      def dataset(opts={})
        ds = super
        ds.extend(Module.new do
          def columns; db.columns_created end
          def insert(h); db.versions.merge!(h); super(h) end
          def update(h); db.versions.merge!(h); super(h) end
          def fetch_rows(sql); db.execute(sql); yield(db.versions) unless db.versions.empty? end
        end)
        ds
      end

      def table_exists?(name)
        @tables_created.include?(name)
      end
    end
    @db = dbc.new
    
    File.open('001_create_sessions.rb', 'w') {|f| f << MIGRATION_001}
    File.open('002_create_nodes.rb', 'w') {|f| f << MIGRATION_002}
    File.open('003_create_users.rb', 'w') {|f| f << MIGRATION_003}
    File.open('005_5_create_attributes.rb', 'w') {|f| f << MIGRATION_005}
    Dir.mkdir("alt_app")
    File.open('alt_app/001_create_alt_basic.rb', 'w') {|f| f << ALT_MIGRATION_001}
    File.open('alt_app/003_create_alt_advanced.rb', 'w') {|f| f << ALT_MIGRATION_003}
  end
  
  after do
    Object.send(:remove_const, "CreateSessions") if Object.const_defined?("CreateSessions")
    Object.send(:remove_const, "CreateNodes") if Object.const_defined?("CreateNodes")
    Object.send(:remove_const, "CreateUsers") if Object.const_defined?("CreateUsers")
    Object.send(:remove_const, "CreateAttributes") if Object.const_defined?("CreateAttributes")
    Object.send(:remove_const, "CreateAltBasic") if Object.const_defined?("CreateAltBasic")
    Object.send(:remove_const, "CreateAltAdvanced") if Object.const_defined?("CreateAltAdvanced")

    File.delete('001_create_sessions.rb')
    File.delete('002_create_nodes.rb')
    File.delete('003_create_users.rb')
    File.delete('005_5_create_attributes.rb')
    File.delete("alt_app/001_create_alt_basic.rb")
    File.delete("alt_app/003_create_alt_advanced.rb")
    Dir.rmdir("alt_app")
  end
  
  specify "#migration_files should return the list of files for a specified version range" do
    Sequel::Migrator.migration_files('.', 1..1).should == ['./001_create_sessions.rb']
    Sequel::Migrator.migration_files('.', 1..3).should == ['./001_create_sessions.rb', './002_create_nodes.rb', './003_create_users.rb']
    Sequel::Migrator.migration_files('.', 3..6).should == ['./003_create_users.rb', './005_5_create_attributes.rb']
    Sequel::Migrator.migration_files('.', 7..8).should == []
    Sequel::Migrator.migration_files('alt_app', 1..1).should == ['alt_app/001_create_alt_basic.rb']
    Sequel::Migrator.migration_files('alt_app', 1..3).should == ['alt_app/001_create_alt_basic.rb','alt_app/003_create_alt_advanced.rb']
  end
  
  specify "#latest_migration_version should return the latest version available" do
    Sequel::Migrator.latest_migration_version('.').should == 5
    Sequel::Migrator.latest_migration_version('alt_app').should == 3
  end
  
  specify "#migration_classes should load the migration classes for the specified range for the up direction" do
    Sequel::Migrator.migration_classes('.', 3, 0, :up).should == [CreateSessions, CreateNodes, CreateUsers]
    Sequel::Migrator.migration_classes('alt_app', 3, 0, :up).should == [CreateAltBasic, CreateAltAdvanced]
  end
  
  specify "#migration_classes should load the migration classes for the specified range for the down direction" do
    Sequel::Migrator.migration_classes('.', 0, 5, :down).should == [CreateAttributes, CreateUsers, CreateNodes, CreateSessions]
    Sequel::Migrator.migration_classes('alt_app', 0, 3, :down).should == [CreateAltAdvanced, CreateAltBasic]
  end
  
  specify "#migration_classes should start from current + 1 for the up direction" do
    Sequel::Migrator.migration_classes('.', 3, 1, :up).should == [CreateNodes, CreateUsers]
    Sequel::Migrator.migration_classes('alt_app', 3, 2, :up).should == [CreateAltAdvanced]
  end
  
  specify "#migration_classes should end on current + 1 for the down direction" do
    Sequel::Migrator.migration_classes('.', 2, 5, :down).should == [CreateAttributes, CreateUsers]
    Sequel::Migrator.migration_classes('alt_app', 2, 4, :down).should == [CreateAltAdvanced]
  end
  
  specify "#schema_info_dataset should automatically create the schema_info table" do
    @db.table_exists?(:schema_info).should be_false
    Sequel::Migrator.schema_info_dataset(@db)
    @db.table_exists?(:schema_info).should be_true
    @db.sqls.should == ["CREATE TABLE schema_info (version integer)"]
  end

  specify "should automatically create new APP_version column in schema_info" do
    @db.table_exists?(:schema_info).should be_false
    Sequel::Migrator.schema_info_dataset(@db, :column => :alt_version)
    @db.table_exists?(:schema_info).should be_true
    @db.sqls.should == ["CREATE TABLE schema_info (alt_version integer)"]
  end
  
  specify "should automatically create new APP_version column in schema_info" do
    @db.table_exists?(:alt_table).should be_false
    Sequel::Migrator.schema_info_dataset(@db, :table => :alt_table)
    @db.table_exists?(:alt_table).should be_true
    Sequel::Migrator.schema_info_dataset(@db, :table => :alt_table, :column=>:alt_version)
    @db.sqls.should == ["CREATE TABLE alt_table (version integer)",
      "ALTER TABLE alt_table ADD COLUMN alt_version integer"]
  end
  
  specify "should return a dataset for the correct table" do
    Sequel::Migrator.schema_info_dataset(@db).first_source_alias.should == :schema_info
    Sequel::Migrator.schema_info_dataset(@db, :table=>:blah).first_source_alias.should == :blah
  end
  
  specify "should assume a migration version of 0 if no migration information exists in the database" do
    Sequel::Migrator.get_current_migration_version(@db).should == 0
    @db.sqls.should == ["CREATE TABLE schema_info (version integer)", "SELECT * FROM schema_info LIMIT 1"]
  end
  specify "should use the migration version stored in the database" do
    Sequel::Migrator.schema_info_dataset(@db).insert(:version => 4321)
    Sequel::Migrator.get_current_migration_version(@db).should == 4321
    @db.sqls.should == ["CREATE TABLE schema_info (version integer)", "INSERT INTO schema_info (version) VALUES (4321)", "SELECT * FROM schema_info LIMIT 1"]
  end
  
  specify "should set the migration version stored in the database" do
    Sequel::Migrator.set_current_migration_version(@db, 6666)
    Sequel::Migrator.get_current_migration_version(@db).should == 6666
    @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
      "SELECT * FROM schema_info LIMIT 1",
      "INSERT INTO schema_info (version) VALUES (6666)",
      "SELECT * FROM schema_info LIMIT 1"]
  end
  
  specify "should apply migrations correctly in the up direction" do
    Sequel::Migrator.apply(@db, '.', 3, 2)
    @db.creates.should == [3333]
    
    Sequel::Migrator.get_current_migration_version(@db).should == 3

    Sequel::Migrator.apply(@db, '.', 5)
    @db.creates.should == [3333, 5555]

    Sequel::Migrator.get_current_migration_version(@db).should == 5
    @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
      "SELECT * FROM schema_info LIMIT 1",
      "INSERT INTO schema_info (version) VALUES (3)",
      "SELECT * FROM schema_info LIMIT 1",
      "SELECT * FROM schema_info LIMIT 1",
      "SELECT * FROM schema_info LIMIT 1",
      "UPDATE schema_info SET version = 5",
      "SELECT * FROM schema_info LIMIT 1"]
  end
  
  specify "should apply migrations correctly in the down direction" do
    Sequel::Migrator.apply(@db, '.', 1, 5)
    @db.drops.should == [5555, 3333, 2222]

    Sequel::Migrator.get_current_migration_version(@db).should == 1
    @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
      "SELECT * FROM schema_info LIMIT 1",
      "INSERT INTO schema_info (version) VALUES (1)",
      "SELECT * FROM schema_info LIMIT 1"]
  end

  specify "should apply migrations up to the latest version if no target is given" do
    Sequel::Migrator.apply(@db, '.')
    @db.creates.should == [1111, 2222, 3333, 5555]

    Sequel::Migrator.get_current_migration_version(@db).should == 5
    @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
      "SELECT * FROM schema_info LIMIT 1",
      "SELECT * FROM schema_info LIMIT 1",
      "INSERT INTO schema_info (version) VALUES (5)",
      "SELECT * FROM schema_info LIMIT 1"]
  end

  specify "should apply migrations down to 0 version correctly" do
    Sequel::Migrator.apply(@db, '.', 0, 5)
    @db.drops.should == [5555, 3333, 2222, 1111]

    Sequel::Migrator.get_current_migration_version(@db).should == 0
    @db.sqls.should == ["CREATE TABLE schema_info (version integer)",
      "SELECT * FROM schema_info LIMIT 1",
      "INSERT INTO schema_info (version) VALUES (0)",
      "SELECT * FROM schema_info LIMIT 1"]
  end
  
  specify "should return the target version" do
    Sequel::Migrator.apply(@db, '.', 3, 2).should == 3
    Sequel::Migrator.apply(@db, '.', 0).should == 0
    Sequel::Migrator.apply(@db, '.').should == 5
  end
end
