add Mad migration tests
authorChuck Hagenbuch <chuck@horde.org>
Mon, 16 Feb 2009 20:07:04 +0000 (15:07 -0500)
committerChuck Hagenbuch <chuck@horde.org>
Mon, 16 Feb 2009 20:07:04 +0000 (15:07 -0500)
14 files changed:
framework/Db/test/Horde/Db/Migration/BaseTest.php [new file with mode: 0644]
framework/Db/test/Horde/Db/Migration/MigratorTest.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations/1_users_have_last_names1.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations/2_we_need_reminders1.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations/3_innocent_jointable1.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations_with_decimal/1_give_me_big_numbers.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/1_users_have_last_names2.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/2_we_need_reminders2.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/3_foo.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/3_innocent_jointable2.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/1000_users_have_middle_names.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/1_users_have_last_names3.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/3_we_need_reminders.php [new file with mode: 0644]
framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/4_innocent_jointable3.php [new file with mode: 0644]

diff --git a/framework/Db/test/Horde/Db/Migration/BaseTest.php b/framework/Db/test/Horde/Db/Migration/BaseTest.php
new file mode 100644 (file)
index 0000000..0a7db70
--- /dev/null
@@ -0,0 +1,525 @@
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @author     Derek DeVries <derek@maintainable.com>
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php
+ * @category   Horde
+ * @package    Horde_Db
+ * @subpackage UnitTests
+ */
+
+require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/fixtures/migrations/1_users_have_last_names1.php';
+require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/fixtures/migrations/2_we_need_reminders1.php';
+require_once dirname(dirname(dirname(dirname(__FILE__)))) . '/fixtures/migrations_with_decimal/1_give_me_big_numbers.php';
+
+/**
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @author     Derek DeVries <derek@maintainable.com>
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php
+ * @group      horde_db
+ * @category   Horde
+ * @package    Horde_Db
+ * @subpackage UnitTests
+ */
+class Horde_Db_Migration_BaseTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        Horde_Db_Migration_Base::$verbose = false;
+    }
+
+    public function tearDown()
+    {
+        $this->_conn->initializeSchemaInformation();
+        $this->_conn->update("UPDATE schema_info SET version = 0");
+
+        // drop tables
+        foreach (array('reminders', 'users_reminders', 'testings', 'octopuses',
+                       'octopi', 'binary_testings', 'big_numbers') as $table) {
+            try {
+                $this->_conn->dropTable($table);
+            } catch (Exception $e) {}
+        }
+
+        // drop cols
+        foreach (array('first_name', 'middle_name', 'last_name', 'key', 'male',
+                       'bio', 'age', 'height', 'wealth', 'birthday', 'group',
+                       'favorite_day', 'moment_of_truth', 'administrator',
+                       'exgirlfriend', 'contributor', 'nick_name',
+                       'intelligence_quotient') as $col) {
+            try {
+                $this->_conn->removeColumn('users', $col);
+            } catch (Exception $e) {}
+        }
+        $this->_conn->addColumn('users', 'first_name', 'string', array('limit' => 40));
+        $this->_conn->changeColumn('users', 'approved', 'boolean', array('default' => true));
+    }
+
+    public function testAddIndex()
+    {
+        // Limit size of last_name and key columns to support Firebird index limitations
+        $this->_conn->addColumn('users', 'last_name',     'string',  array('limit' => 100));
+        $this->_conn->addColumn('users', 'key',           'string',  array('limit' => 100));
+        $this->_conn->addColumn('users', 'administrator', 'boolean');
+
+        $this->_conn->addIndex('users', 'last_name');
+        $this->_conn->removeIndex('users', 'last_name');
+
+        $this->_conn->addIndex('users', array('last_name', 'first_name'));
+        $this->_conn->removeIndex('users', array('column' => array('last_name', 'first_name')));
+
+        $this->_conn->addIndex('users', array('last_name', 'first_name'));
+        $this->_conn->removeIndex('users', array('name' => 'index_users_on_last_name_and_first_name'));
+
+        $this->_conn->addIndex('users', array('last_name', 'first_name'));
+        $this->_conn->removeIndex('users', 'last_name_and_first_name');
+
+        // # quoting
+
+        $this->_conn->addIndex('users', array('key'), array('name' => 'key_idx', 'unique' => true));
+        $this->_conn->removeIndex('users', array('name' => 'key_idx', 'unique' => true));
+
+        $this->_conn->addIndex('users', array('last_name', 'first_name', 'administrator'),
+                                        array('name' => "named_admin"));
+
+        $this->_conn->removeIndex('users', array('name' => 'named_admin'));
+    }
+
+    public function testCreateTableAddsId()
+    {
+        $table = $this->_conn->createTable('testings');
+            $table->column('foo', 'string');
+        $table->end();
+
+        $columns = array();
+        foreach ($this->_conn->columns('testings') as $col) {
+            $columns[] = $col->getName();
+        }
+        sort($columns);
+        $this->assertEquals(array('foo', 'id'), $columns);
+    }
+
+    public function testCreateTableWithNotNullColumn()
+    {
+        $table = $this->_conn->createTable('testings');
+            $table->column('foo', 'string', array('null' => false));
+        $table->end();
+
+        try {
+            $this->_conn->execute("INSERT INTO testings (foo) VALUES (NULL)");
+        } catch (Exception $e) { return; }
+        $this->fail('Expected exception wasn\'t raised');
+    }
+
+    /**
+     * @todo Revisit this test if the boolean casting behavior changes.
+     * @see  Horde_Db_Adapter_Abstract_ColumnTest
+     */
+    public function testCreateTableWithDefaults()
+    {
+        $table = $this->_conn->createTable('testings');
+            $table->column('one',   'string',  array('default' => 'hello'));
+            $table->column('two',   'boolean', array('default' => true));
+            $table->column('three', 'boolean', array('default' => false));
+            $table->column('four',  'integer', array('default' => 1));
+        $table->end();
+
+        $columns = array();
+        foreach ($this->_conn->columns('testings') as $col) {
+            $columns[$col->getName()] = $col;
+        }
+
+        $this->assertEquals('hello', $columns['one']->getDefault());
+
+        $this->assertTrue($columns['two']->getDefault());
+        $this->assertFalse($columns['three']->getDefault());
+
+        $this->assertEquals('1',     $columns['four']->getDefault());
+    }
+
+    public function testCreateTableWithLimits()
+    {
+        $table = $this->_conn->createTable('testings');
+            $table->column('foo', 'string', array('limit' => 80));
+        $table->end();
+
+        $columns = array();
+        foreach ($this->_conn->columns('testings') as $col) {
+            $columns[$col->getName()] = $col;
+        }
+        $this->assertEquals(80, $columns['foo']->getLimit());
+    }
+
+    public function testAddColumnNotNullWithoutDefault()
+    {
+        $table = $this->_conn->createTable('testings');
+            $table->column('foo', 'string');
+        $table->end();
+        $this->_conn->addColumn('testings', 'bar', 'string', array('null' => false));
+
+        try {
+            $this->_conn->execute("INSERT INTO testings (foo, bar) VALUES ('hello', NULL)");
+        } catch (Exception $e) { return; }
+        $this->fail('Expected exception wasn\'t raised');
+
+    }
+
+    public function testAddColumnNotNullWithDefault()
+    {
+        $table = $this->_conn->createTable('testings');
+            $table->column('foo', 'string');
+        $table->end();
+
+        $this->_conn->execute("INSERT INTO testings (id, foo) VALUES ('1', 'hello')");
+
+        $this->_conn->addColumn('testings', 'bar', 'string', array('null' => false, 'default' => 'default'));
+
+        try {
+            $this->_conn->execute("INSERT INTO testings (id, foo, bar) VALUES (2, 'hello', NULL)");
+        } catch (Exception $e) { return; }
+        $this->fail('Expected exception wasn\'t raised');
+    }
+
+    /**
+     * We specifically do a manual INSERT here, and then test only the SELECT
+     * functionality. This allows us to more easily catch INSERT being broken,
+     * but SELECT actually working fine.
+     */
+    public function testNativeDecimalInsertManualVsAutomatic()
+    {
+        $correctValue = 12345678901234567890.0123456789;
+
+        User::deleteAll();
+        $this->_conn->addColumn("users", "wealth", 'decimal', array('precision' => 30, 'scale' => 10));
+
+        // do a manual insertion
+        $this->_conn->execute("INSERT INTO users (wealth) VALUES ('12345678901234567890.0123456789')");
+
+        // SELECT @todo - type cast attribute values
+        $user = User::find('first');
+        // assert_kind_of BigDecimal, row.wealth
+
+        // If this assert fails, that means the SELECT is broken!
+        $this->assertEquals($correctValue, $user->wealth);
+
+        // Reset to old state
+        User::deleteAll();
+
+        // Now use the Rails insertion
+        User::create(array('wealth' => '12345678901234567890.0123456789'));
+
+        // SELECT @todo - type cast attribute values
+        $user = User::find('first');
+        // assert_kind_of BigDecimal, row.wealth
+
+        // If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
+        $this->assertEquals($correctValue, $user->wealth);
+    }
+
+    public function testNativeTypes()
+    {
+        User::deleteAll();
+
+        $this->_conn->addColumn("users", "last_name",       'string');
+        $this->_conn->addColumn("users", "bio",             'text');
+        $this->_conn->addColumn("users", "age",             'integer');
+        $this->_conn->addColumn("users", "height",          'float');
+        $this->_conn->addColumn("users", "wealth",          'decimal', array('precision' => '30', 'scale' => '10'));
+        $this->_conn->addColumn("users", "birthday",        'datetime');
+        $this->_conn->addColumn("users", "favorite_day",    'date');
+        $this->_conn->addColumn("users", "moment_of_truth", 'datetime');
+        $this->_conn->addColumn("users", "male",            'boolean');
+
+        User::create(array('first_name' => 'bob', 'last_name' => 'bobsen',
+          'bio' => "I was born ....", 'age' => 18, 'height' => 1.78,
+          'wealth' => "12345678901234567890.0123456789",
+          'birthday' => '2005-01-01 12:23:40',
+          'favorite_day' => '1980-03-05',
+          'moment_of_truth' => "1582-10-10 21:40:18",
+          'male' => true, 'company_id' => 1));
+
+        $bob = User::find('first');
+        $this->assertEquals('bob',             $bob->first_name);
+        $this->assertEquals('bobsen',          $bob->last_name);
+        $this->assertEquals('I was born ....', $bob->bio);
+        $this->assertEquals(18,                $bob->age);
+
+        // Test for 30 significent digits (beyond the 16 of float), 10 of them
+        // after the decimal place.
+        $this->assertEquals('12345678901234567890.0123456789', $bob->wealth);
+        $this->assertEquals('1',                               $bob->male);
+
+        // @todo - type casting
+    }
+
+    public function testUnabstractedDatabaseDependentTypes()
+    {
+        User::deleteAll();
+
+        $this->_conn->addColumn('users', 'intelligence_quotient', 'tinyint');
+        User::create(array('intelligence_quotient' => 300));
+
+        $jonnyg = User::find('first');
+        $this->assertEquals('127', $jonnyg->intelligence_quotient);
+        $jonnyg->destroy();
+    }
+
+    public function testAddRemoveSingleField()
+    {
+        $user = new User;
+
+        $this->assertFalse(in_array('last_name', $user->columnNames()));
+
+        $this->_conn->addColumn('users', 'last_name', 'string');
+        $user->resetColumnInformation();
+        $this->assertTrue(in_array('last_name', $user->columnNames()));
+
+        $this->_conn->removeColumn('users', 'last_name');
+        $user->resetColumnInformation();
+        $this->assertFalse(in_array('last_name', $user->columnNames()));
+    }
+
+    public function testAddRename()
+    {
+        User::deleteAll();
+
+        $this->_conn->addColumn('users', 'girlfriend', 'string');
+        User::create(array('girlfriend' => 'bobette'));
+
+        $this->_conn->renameColumn('users', 'girlfriend', 'exgirlfriend');
+
+        $bob = User::find('first');
+        $this->assertEquals('bobette', $bob->exgirlfriend);
+    }
+
+    public function testRenameColumn()
+    {
+        $this->_conn->renameColumn('users', 'first_name', 'nick_name');
+
+        $user = new User;
+        $this->assertTrue(in_array('nick_name', $user->columnNames()));
+    }
+
+    public function testRenameColumnWithSqlReservedWord()
+    {
+        $this->_conn->renameColumn('users', 'first_name', 'group');
+
+        $user = new User;
+        $this->assertTrue(in_array('group', $user->columnNames()));
+    }
+
+    public function testRenameTable()
+    {
+        $table = $this->_conn->createTable('octopuses');
+            $table->column('url', 'string');
+        $table->end();
+
+        $this->_conn->renameTable('octopuses', 'octopi');
+
+        $sql = "INSERT INTO octopi (id, url) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')";
+        $this->_conn->execute($sql);
+
+        $this->assertEquals('http://www.foreverflying.com/octopus-black7.jpg',
+                $this->_conn->selectValue("SELECT url FROM octopi WHERE id=1"));
+    }
+
+    public function testRenameTableWithAnIndex()
+    {
+        $table = $this->_conn->createTable('octopuses');
+            $table->column('url', 'string');
+        $table->end();
+        $this->_conn->addIndex('octopuses', 'url');
+        $this->_conn->renameTable('octopuses', 'octopi');
+
+        $sql = "INSERT INTO octopi (id, url) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')";
+        $this->_conn->execute($sql);
+
+        $this->assertEquals('http://www.foreverflying.com/octopus-black7.jpg',
+                $this->_conn->selectValue("SELECT url FROM octopi WHERE id=1"));
+
+        $indexes = $this->_conn->indexes('octopi');
+        $this->assertEquals('url', $indexes[0]->columns[0]);
+    }
+
+    public function testChangeColumn()
+    {
+        $user = new User;
+
+        $this->_conn->addColumn('users', 'age', 'integer');
+        $oldColumns = $this->_conn->columns($user->tableName(), "User Columns");
+
+        $found = false;
+        foreach ($oldColumns as $c) {
+            if ($c->getName() == 'age' && $c->getType() == 'integer') { $found = true; }
+        }
+        $this->assertTrue($found);
+
+        $this->_conn->changeColumn('users', 'age', 'string');
+
+        $newColumns = $this->_conn->columns($user->tableName(), "User Columns");
+
+        $found = false;
+        foreach ($newColumns as $c) {
+            if ($c->getName() == 'age' && $c->getType() == 'integer') { $found = true; }
+        }
+        $this->assertFalse($found);
+        $found = false;
+        foreach ($newColumns as $c) {
+            if ($c->getName() == 'age' && $c->getType() == 'string') { $found = true; }
+        }
+        $this->assertTrue($found);
+
+        $found = false;
+        foreach ($oldColumns as $c) {
+            if ($c->getName() == 'approved' && $c->getType() == 'boolean' &&
+                $c->getDefault() == true) { $found = true; }
+        }
+        $this->assertTrue($found);
+
+        // changeColumn() throws exception on error
+        $this->_conn->changeColumn('users', 'approved', 'boolean', array('default' => false));
+
+        $newColumns = $this->_conn->columns($user->tableName(), "User Columns");
+
+        $found = false;
+        foreach ($newColumns as $c) {
+            if ($c->getName() == 'approved' && $c->getType() == 'boolean' &&
+                $c->getDefault() == true) { $found = true; }
+        }
+        $this->assertFalse($found);
+
+        $found = false;
+        foreach ($newColumns as $c) {
+            if ($c->getName() == 'approved' && $c->getType() == 'boolean' &&
+                $c->getDefault() == false) { $found = true; }
+        }
+        $this->assertTrue($found);
+
+        // changeColumn() throws exception on error
+        $this->_conn->changeColumn('users', 'approved', 'boolean', array('default' => true));
+    }
+
+    public function testChangeColumnWithNilDefault()
+    {
+        $this->_conn->addColumn('users', 'contributor', 'boolean', array('default' => true));
+        $user = new User;
+        $this->assertTrue($user->contributor);
+
+        // changeColumn() throws exception on error
+        $this->_conn->changeColumn('users', 'contributor', 'boolean', array('default' => null));
+
+        $user = new User;
+        $this->assertNull($user->contributor);
+    }
+
+    public function testChangeColumnWithNewDefault()
+    {
+        $this->_conn->addColumn('users', 'administrator', 'boolean', array('default' => true));
+        $user = new User;
+        $this->assertTrue($user->administrator);
+
+        // changeColumn() throws exception on error
+        $this->_conn->changeColumn('users', 'administrator', 'boolean', array('default' => false));
+
+        $user = new User;
+        $this->assertFalse($user->administrator);
+    }
+
+    public function testChangeColumnDefault()
+    {
+        $this->_conn->changeColumnDefault('users', 'first_name', 'Tester');
+
+        $user = new User;
+        $this->assertEquals('Tester', $user->first_name);
+    }
+
+    public function testChangeColumnDefaultToNull()
+    {
+        $this->_conn->changeColumnDefault('users', 'first_name', null);
+
+        $user = new User;
+        $this->assertNull($user->first_name);
+    }
+
+    public function testAddTable()
+    {
+        $e = null;
+        try {
+            $this->_conn->selectValues("SELECT * FROM reminders");
+        } catch (Exception $e) {}
+        $this->assertType('Horde_Db_Exception', $e);
+
+        $m = new WeNeedReminders1;
+        $m->up();
+
+        $result = Reminder::create(array('content'   => 'hello world',
+                                         'remind_at' => '2005-01-01 11:10:01'));
+        $this->assertType('Reminder', $result);
+
+        $this->assertEquals('hello world', Reminder::find('first')->content);
+
+        $m->down();
+        $e = null;
+        try {
+            $this->_conn->selectValues("SELECT * FROM reminders");
+        } catch (Exception $e) {}
+        $this->assertType('Horde_Db_Exception', $e);
+    }
+
+    public function testAddTableWithDecimals()
+    {
+        $e = null;
+        try {
+            $this->_conn->selectValues("SELECT * FROM big_numbers");
+        } catch (Exception $e) {}
+        $this->assertType('Horde_Db_Exception', $e);
+
+        $m = new GiveMeBigNumbers;
+        $m->up();
+
+        $result = BigNumber::create(array(
+            'bank_balance'        => '1586.43',
+            'big_bank_balance'    => "1000234000567.95",
+            'world_population'    => '6000000000',
+            'my_house_population' => '3',
+            'value_of_e'          => "2.7182818284590452353602875"
+        ));
+        $this->assertType('BigNumber', $result);
+
+        $b = BigNumber::find('first');
+        $this->assertNotNull($b->bank_balance);
+        $this->assertNotNull($b->big_bank_balance);
+        $this->assertNotNull($b->world_population);
+        $this->assertNotNull($b->my_house_population);
+        $this->assertNotNull($b->value_of_e);
+
+        $m->down();
+        $e = null;
+        try {
+            $this->_conn->selectValues("SELECT * FROM big_numbers");
+        } catch (Exception $e) {}
+        $this->assertType('Horde_Db_Exception', $e);
+    }
+
+    public function testCreateTableWithBinaryColumn()
+    {
+        try {
+            $table = $this->_conn->createTable('binary_testings');
+                $table->column('data', 'binary', array('null' => false));
+            $table->end();
+        } catch (Exception $e) { $this->fail('Unexepected exception raised'); }
+
+        $columns = $this->_conn->columns('binary_testings');
+
+        foreach ($columns as $c) {
+            if ($c->getName() == 'data') { $dataColumn = $c; }
+        }
+        $this->assertEquals('', $dataColumn->getDefault());
+    }
+
+}
diff --git a/framework/Db/test/Horde/Db/Migration/MigratorTest.php b/framework/Db/test/Horde/Db/Migration/MigratorTest.php
new file mode 100644 (file)
index 0000000..6e50d5a
--- /dev/null
@@ -0,0 +1,213 @@
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @author     Derek DeVries <derek@maintainable.com>
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php
+ * @category   Horde
+ * @package    Horde_Db
+ * @subpackage UnitTests
+ */
+
+/**
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @author     Derek DeVries <derek@maintainable.com>
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php
+ * @group      horde_db
+ * @category   Horde
+ * @package    Horde_Db
+ * @subpackage UnitTests
+ */
+class Horde_Db_Migration_MigratorTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        Horde_Db_Migration_Base::$verbose = false;
+    }
+
+    public function tearDown()
+    {
+        $this->_conn->initializeSchemaInformation();
+        $this->_conn->update("UPDATE schema_info SET version = 0");
+
+        // drop tables
+        foreach (array('reminders', 'users_reminders', 'testings', 'octopuses',
+                       'octopi', 'binary_testings', 'big_numbers') as $table) {
+            try {
+                $this->_conn->dropTable($table);
+            } catch (Exception $e) {}
+        }
+
+        // drop cols
+        foreach (array('first_name', 'middle_name', 'last_name', 'key', 'male',
+                       'bio', 'age', 'height', 'wealth', 'birthday', 'group',
+                       'favorite_day', 'moment_of_truth', 'administrator',
+                       'exgirlfriend', 'contributor', 'nick_name',
+                       'intelligence_quotient') as $col) {
+            try {
+                $this->_conn->removeColumn('users', $col);
+            } catch (Exception $e) {}
+        }
+        $this->_conn->addColumn('users', 'first_name', 'string', array('limit' => 40));
+        $this->_conn->changeColumn('users', 'approved', 'boolean', array('default' => true));
+    }
+
+    public function testMigrator()
+    {
+        $user = new User;
+        $columns = $user->columnNames();
+
+        $this->assertFalse(in_array('last_name', $columns));
+
+        $e = null;
+        try {
+            $this->_conn->selectValues("SELECT * FROM reminders");
+        } catch (Exception $e) {}
+        $this->assertType('Horde_Db_Exception', $e);
+
+        $dir = dirname(dirname(dirname(dirname(__FILE__)))).'/fixtures/migrations/';
+        Horde_Db_Migration_Migrator::up($dir);
+        $this->assertEquals(3, Horde_Db_Migration_Migrator::getCurrentVersion());
+
+        $user->resetColumnInformation();
+        $columns = $user->columnNames();
+        $this->assertTrue(in_array('last_name', $columns));
+
+        $result = Reminder::create(array('content'   => 'hello world',
+                                         'remind_at' => '2005-01-01 02:22:23'));
+        $reminder = Reminder::find('first');
+        $this->assertEquals('hello world', $reminder->content);
+
+        $dir = dirname(dirname(dirname(dirname(__FILE__)))).'/fixtures/migrations/';
+        Horde_Db_Migration_Migrator::down($dir);
+        $this->assertEquals(0, Horde_Db_Migration_Migrator::getCurrentVersion());
+
+        $user->resetColumnInformation();
+        $columns = $user->columnNames();
+        $this->assertFalse(in_array('last_name', $columns));
+
+        $e = null;
+        try {
+            $this->_conn->selectValues("SELECT * FROM reminders");
+        } catch (Exception $e) {}
+        $this->assertType('Horde_Db_Exception', $e);
+    }
+
+    public function testOneUp()
+    {
+        $e = null;
+        try {
+            $this->_conn->selectValues("SELECT * FROM reminders");
+        } catch (Exception $e) {}
+        $this->assertType('Horde_Db_Exception', $e);
+
+        $dir = dirname(dirname(dirname(dirname(__FILE__)))).'/fixtures/migrations/';
+        Horde_Db_Migration_Migrator::up($dir, 1);
+        $this->assertEquals(1, Horde_Db_Migration_Migrator::getCurrentVersion());
+
+        $user = new User;
+        $columns = $user->columnNames();
+        $this->assertTrue(in_array('last_name', $columns));
+
+        $e = null;
+        try {
+            $this->_conn->selectValues("SELECT * FROM reminders");
+        } catch (Exception $e) {}
+        $this->assertType('Horde_Db_Exception', $e);
+
+        Horde_Db_Migration_Migrator::up($dir, 2);
+        $this->assertEquals(2, Horde_Db_Migration_Migrator::getCurrentVersion());
+
+        $result = Reminder::create(array('content'   => 'hello world',
+                                         'remind_at' => '2005-01-01 02:22:23'));
+        $reminder = Reminder::find('first');
+        $this->assertEquals('hello world', $reminder->content);
+    }
+
+    public function testOneDown()
+    {
+        $dir = dirname(dirname(dirname(dirname(__FILE__)))).'/fixtures/migrations/';
+
+        Horde_Db_Migration_Migrator::up($dir);
+        Horde_Db_Migration_Migrator::down($dir, 1);
+
+        $user = new User;
+        $columns = $user->columnNames();
+        $this->assertTrue(in_array('last_name', $columns));
+    }
+
+    public function testOneUpOneDown()
+    {
+        $dir = dirname(dirname(dirname(dirname(__FILE__)))).'/fixtures/migrations/';
+
+        Horde_Db_Migration_Migrator::up($dir, 1);
+        Horde_Db_Migration_Migrator::down($dir, 0);
+
+        $user = new User;
+        $columns = $user->columnNames();
+        $this->assertFalse(in_array('last_name', $columns));
+    }
+
+    public function testMigratorGoingDownDueToVersionTarget()
+    {
+        $dir = dirname(dirname(dirname(dirname(__FILE__)))).'/fixtures/migrations/';
+
+        Horde_Db_Migration_Migrator::up($dir, 1);
+        Horde_Db_Migration_Migrator::down($dir, 0);
+
+        $user = new User;
+        $columns = $user->columnNames();
+        $this->assertFalse(in_array('last_name', $columns));
+
+        $e = null;
+        try {
+            $this->_conn->selectValues("SELECT * FROM reminders");
+        } catch (Exception $e) {}
+        $this->assertType('Horde_Db_Exception', $e);
+
+
+        Horde_Db_Migration_Migrator::up($dir);
+
+        $user->resetColumnInformation();
+        $columns = $user->columnNames();
+        $this->assertTrue(in_array('last_name', $columns));
+
+        $result = Reminder::create(array('content'   => 'hello world',
+                                         'remind_at' => '2005-01-01 02:22:23'));
+        $reminder = Reminder::find('first');
+        $this->assertEquals('hello world', $reminder->content);
+    }
+
+    public function testWithDuplicates()
+    {
+        try {
+            $dir = dirname(dirname(dirname(dirname(__FILE__)))).'/fixtures/migrations_with_duplicate/';
+            Horde_Db_Migration_Migrator::up($dir);
+        } catch (Exception $e) { return; }
+        $this->fail('Expected exception wasn\'t raised');
+    }
+
+    public function testWithMissingVersionNumbers()
+    {
+        $dir = dirname(dirname(dirname(dirname(__FILE__)))).'/fixtures/migrations_with_missing_versions/';
+        Horde_Db_Migration_Migrator::migrate($dir, 500);
+        $this->assertEquals(4, Horde_Db_Migration_Migrator::getCurrentVersion());
+
+        Horde_Db_Migration_Migrator::migrate($dir, 2);
+        $this->assertEquals(2, Horde_Db_Migration_Migrator::getCurrentVersion());
+
+        $e = null;
+        try {
+            $this->_conn->selectValues("SELECT * FROM reminders");
+        } catch (Exception $e) {}
+        $this->assertType('Horde_Db_Exception', $e);
+
+        $user = new User;
+        $columns = $user->columnNames();
+        $this->assertTrue(in_array('last_name', $columns));
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations/1_users_have_last_names1.php b/framework/Db/test/Horde/Db/fixtures/migrations/1_users_have_last_names1.php
new file mode 100644 (file)
index 0000000..fd389e5
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+class UsersHaveLastNames1 extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $this->addColumn('users', 'last_name', 'string');
+    }
+
+    public function down()
+    {
+        $this->removeColumn('users', 'last_name');
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations/2_we_need_reminders1.php b/framework/Db/test/Horde/Db/fixtures/migrations/2_we_need_reminders1.php
new file mode 100644 (file)
index 0000000..9c54859
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+class WeNeedReminders1 extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $t = $this->createTable('reminders');
+            $t->column('content',   'text');
+            $t->column('remind_at', 'datetime');
+        $t->end();
+    }
+
+    public function down()
+    {
+        $this->dropTable('reminders');
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations/3_innocent_jointable1.php b/framework/Db/test/Horde/Db/fixtures/migrations/3_innocent_jointable1.php
new file mode 100644 (file)
index 0000000..71106db
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+class InnocentJointable1 extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $t = $this->createTable('users_reminders', array('id' => false));
+            $t->column('reminder_id', 'integer');
+            $t->column('user_id',     'integer');
+        $t->end();
+    }
+
+    public function down()
+    {
+        $this->dropTable('users_reminders');
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations_with_decimal/1_give_me_big_numbers.php b/framework/Db/test/Horde/Db/fixtures/migrations_with_decimal/1_give_me_big_numbers.php
new file mode 100644 (file)
index 0000000..dc74170
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+class GiveMeBigNumbers extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $table = $this->createTable('big_numbers');
+            $table->column('bank_balance',        'decimal', array('precision' => 10, 'scale' => 2));
+            $table->column('big_bank_balance',    'decimal', array('precision' => 15, 'scale' => 2));
+            $table->column('world_population',    'decimal', array('precision' => 10));
+            $table->column('my_house_population', 'decimal', array('precision' => 2));
+            $table->column('value_of_e',          'decimal');
+        $table->end();
+    }
+
+    public function down()
+    {
+        $this->dropTable('big_numbers');
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/1_users_have_last_names2.php b/framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/1_users_have_last_names2.php
new file mode 100644 (file)
index 0000000..b35c794
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+class UsersHaveLastNames2 extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $this->addColumn('users', 'last_name', 'string');
+    }
+
+    public function down()
+    {
+        $this->removeColumn('users', 'last_name');
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/2_we_need_reminders2.php b/framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/2_we_need_reminders2.php
new file mode 100644 (file)
index 0000000..94d2811
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+class WeNeedReminders2 extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $t = $this->createTable('reminders');
+            $t->column('content',   'text');
+            $t->column('remind_at', 'datetime');
+        $t->end();
+    }
+
+    public function down()
+    {
+        $this->dropTable('reminders');
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/3_foo.php b/framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/3_foo.php
new file mode 100644 (file)
index 0000000..99eb234
--- /dev/null
@@ -0,0 +1,12 @@
+<?php
+
+class Foo extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+    }
+    
+    public function down()
+    {
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/3_innocent_jointable2.php b/framework/Db/test/Horde/Db/fixtures/migrations_with_duplicate/3_innocent_jointable2.php
new file mode 100644 (file)
index 0000000..d9b01c7
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+class InnocentJointable2 extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $t = $this->createTable('users_reminders', array('id' => false));
+            $t->column('reminder_id', 'integer');
+            $t->column('user_id',     'integer');
+        $t->end();
+    }
+
+    public function down()
+    {
+        $this->dropTable('users_reminders');
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/1000_users_have_middle_names.php b/framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/1000_users_have_middle_names.php
new file mode 100644 (file)
index 0000000..a74310b
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+class UsersHaveMiddleNames extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $this->addColumn('users', 'middle_name', 'string');
+    }
+
+    public function down()
+    {
+        $this->removeColumn('users', 'middle_name');
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/1_users_have_last_names3.php b/framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/1_users_have_last_names3.php
new file mode 100644 (file)
index 0000000..ba494e2
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+class UsersHaveLastNames3 extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $this->addColumn('users', 'last_name', 'string');
+    }
+
+    public function down()
+    {
+        $this->removeColumn('users', 'last_name');
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/3_we_need_reminders.php b/framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/3_we_need_reminders.php
new file mode 100644 (file)
index 0000000..5d0cba0
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+class WeNeedReminders extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $t = $this->createTable('reminders');
+            $t->column('content',   'text');
+            $t->column('remind_at', 'datetime');
+        $t->end();
+    }
+
+    public function down()
+    {
+        $this->dropTable('reminders');
+    }
+}
\ No newline at end of file
diff --git a/framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/4_innocent_jointable3.php b/framework/Db/test/Horde/Db/fixtures/migrations_with_missing_versions/4_innocent_jointable3.php
new file mode 100644 (file)
index 0000000..400a763
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+class InnocentJointable3 extends Mad_Model_Migration_Base 
+{
+    public function up()
+    {
+        $t = $this->createTable('users_reminders', array('id' => false));
+            $t->column('reminder_id', 'integer');
+            $t->column('user_id',     'integer');
+        $t->end();
+    }
+
+    public function down()
+    {
+        $this->dropTable('users_reminders');
+    }
+}
\ No newline at end of file