diff options
author | schneems <richard.schneeman@gmail.com> | 2015-08-14 11:31:33 -0500 |
---|---|---|
committer | schneems <richard.schneeman@gmail.com> | 2016-01-07 18:01:05 -0600 |
commit | 900bfd94a9c3c45484d88aa69071b7a52c5b04b4 (patch) | |
tree | 9f28b1f0acf2f74143ec7806bd89081430f6f306 /activerecord/test | |
parent | 89f776402dbaca581ef4bb342bb89db922124c7a (diff) | |
download | rails-900bfd94a9c3c45484d88aa69071b7a52c5b04b4.tar.gz rails-900bfd94a9c3c45484d88aa69071b7a52c5b04b4.tar.bz2 rails-900bfd94a9c3c45484d88aa69071b7a52c5b04b4.zip |
Prevent destructive action on production database
This PR introduces a key/value type store to Active Record that can be used for storing internal values. It is an alternative implementation to #21237 cc @sgrif @matthewd.
It is possible to run your tests against your production database by accident right now. While infrequently, but as an anecdotal data point, Heroku receives a non-trivial number of requests for a database restore due to this happening. In these cases the loss can be large.
To prevent against running tests against production we can store the "environment" version that was used when migrating the database in a new internal table. Before executing tests we can see if the database is a listed in `protected_environments` and abort. There is a manual escape valve to force this check from happening with environment variable `DISABLE_DATABASE_ENVIRONMENT_CHECK=1`.
Diffstat (limited to 'activerecord/test')
-rw-r--r-- | activerecord/test/cases/migration_test.rb | 44 | ||||
-rw-r--r-- | activerecord/test/cases/schema_dumper_test.rb | 4 | ||||
-rw-r--r-- | activerecord/test/cases/tasks/database_tasks_test.rb | 28 |
3 files changed, 76 insertions, 0 deletions
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 19be357b6e..2f7859a7be 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -354,6 +354,50 @@ class MigrationTest < ActiveRecord::TestCase Reminder.reset_table_name end + def test_internal_metadata_table_name + original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name + + assert_equal "active_record_internal_metadatas", ActiveRecord::InternalMetadata.table_name + ActiveRecord::Base.table_name_prefix = "prefix_" + ActiveRecord::Base.table_name_suffix = "_suffix" + Reminder.reset_table_name + assert_equal "prefix_active_record_internal_metadatas_suffix", ActiveRecord::InternalMetadata.table_name + ActiveRecord::Base.internal_metadata_table_name = "changed" + Reminder.reset_table_name + assert_equal "prefix_changed_suffix", ActiveRecord::InternalMetadata.table_name + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + Reminder.reset_table_name + assert_equal "changed", ActiveRecord::InternalMetadata.table_name + ensure + ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name + Reminder.reset_table_name + end + + def test_internal_metadata_stores_environment + current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + migrations_path = MIGRATIONS_ROOT + "/valid" + old_path = ActiveRecord::Migrator.migrations_paths + ActiveRecord::Migrator.migrations_paths = migrations_path + + assert_equal current_env, ActiveRecord::InternalMetadata.value_for("environment") + + original_rails_env = ENV["RAILS_ENV"] + original_rack_env = ENV["RACK_ENV"] + ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo" + new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + + refute_equal current_env, new_env + + sleep 1 # mysql by default does not store fractional seconds in the database + ActiveRecord::Migrator.up(migrations_path) + assert_equal new_env, ActiveRecord::InternalMetadata.value_for("environment") + ensure + ActiveRecord::Migrator.migrations_paths = old_path + ENV["RAILS_ENV"] = original_rails_env + ENV["RACK_ENV"] = original_rack_env + end + def test_proper_table_name_on_migration reminder_class = new_isolated_reminder_class migration = ActiveRecord::Migration.new diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 9a5e4313d8..97c3d664ba 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -38,6 +38,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output + assert_no_match %r{create_table "internal_metadatas"}, output end def test_schema_dump_uses_force_cascade_on_create_table @@ -158,6 +159,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output + assert_no_match %r{create_table "internal_metadatas"}, output end def test_schema_dump_with_regexp_ignored_table @@ -165,6 +167,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output + assert_no_match %r{create_table "internal_metadatas"}, output end def test_schema_dumps_index_columns_in_right_order @@ -342,6 +345,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "foo_.+_bar"}, output assert_no_match %r{add_index "foo_.+_bar"}, output assert_no_match %r{create_table "schema_migrations"}, output + assert_no_match %r{create_table "internal_metadatas"}, output if ActiveRecord::Base.connection.supports_foreign_keys? assert_no_match %r{add_foreign_key "foo_.+_bar"}, output diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index c8f4179313..e91a222534 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -18,6 +18,34 @@ module ActiveRecord sqlite3: :sqlite_tasks } + class DatabaseTasksUtilsTask< ActiveRecord::TestCase + def test_raises_an_error_when_called_with_protected_environment + ActiveRecord::Migrator.stubs(:current_version).returns(1) + + protected_environments = ActiveRecord::Base.protected_environments.dup + current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + assert !protected_environments.include?(current_env) + # Assert no error + ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! + + ActiveRecord::Base.protected_environments << current_env + assert_raise(ActiveRecord::ProtectedEnvironmentError) do + ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! + end + ensure + ActiveRecord::Base.protected_environments = protected_environments + end + + def test_raises_an_error_if_no_migrations_have_been_made + ActiveRecord::InternalMetadata.stubs(:table_exists?).returns(false) + ActiveRecord::Migrator.stubs(:current_version).returns(1) + + assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do + ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! + end + end + end + class DatabaseTasksRegisterTask < ActiveRecord::TestCase def test_register_task klazz = Class.new do |