mince_migrator 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +20 -58
  3. data/bin/mince_migrator +125 -0
  4. data/lib/mince_migrator/cli_helper.rb +72 -0
  5. data/lib/mince_migrator/config.rb +30 -0
  6. data/lib/mince_migrator/creator.rb +48 -0
  7. data/lib/mince_migrator/deleter.rb +42 -0
  8. data/lib/mince_migrator/list.rb +46 -0
  9. data/lib/mince_migrator/list_report.rb +67 -0
  10. data/lib/mince_migrator/migration.rb +66 -0
  11. data/lib/mince_migrator/migrations/file.rb +74 -0
  12. data/lib/mince_migrator/migrations/loader.rb +52 -0
  13. data/lib/mince_migrator/migrations/name.rb +48 -0
  14. data/lib/mince_migrator/migrations/runner.rb +39 -0
  15. data/lib/mince_migrator/migrations/runner_validator.rb +47 -0
  16. data/lib/mince_migrator/migrations/template.mustache +24 -0
  17. data/lib/mince_migrator/migrations/template.rb +16 -0
  18. data/lib/mince_migrator/migrations/versioned_file.rb +38 -0
  19. data/lib/mince_migrator/ran_migration.rb +27 -0
  20. data/lib/mince_migrator/reverter.rb +57 -0
  21. data/lib/mince_migrator/status_report.rb +41 -0
  22. data/lib/mince_migrator/version.rb +18 -1
  23. data/lib/mince_migrator.rb +5 -4
  24. data/spec/integration/create_a_migration_spec.rb +57 -0
  25. data/spec/integration/deleting_a_migration_spec.rb +40 -0
  26. data/spec/integration/list_all_migrations_spec.rb +57 -0
  27. data/spec/integration/reverting_a_migration_spec.rb +59 -0
  28. data/spec/integration/running_a_migration_spec.rb +66 -0
  29. data/spec/integration_helper.rb +17 -0
  30. data/spec/support/db/a_second_migration.rb +24 -0
  31. data/spec/support/db/a_second_migration_2.rb +24 -0
  32. data/spec/support/db/create_seeded_admin_users.rb +24 -0
  33. data/spec/support/db/first_migration.rb +24 -0
  34. data/spec/support/db/first_migration_2.rb +24 -0
  35. data/spec/support/db/first_migration_3.rb +24 -0
  36. data/spec/support/db/first_migration_4.rb +24 -0
  37. data/spec/support/db/name_of_migration.rb +24 -0
  38. data/spec/support/invalid_interface_migration.rb +7 -0
  39. data/spec/support/not_a_migration.rb +2 -0
  40. data/spec/support/test_migration.rb +30 -0
  41. data/spec/units/mince_migrator/cli_helper_spec.rb +147 -0
  42. data/spec/units/mince_migrator/creator_spec.rb +80 -0
  43. data/spec/units/mince_migrator/deleter_spec.rb +52 -0
  44. data/spec/units/mince_migrator/list_spec.rb +53 -0
  45. data/spec/units/mince_migrator/migration_spec.rb +119 -0
  46. data/spec/units/mince_migrator/migrations/file_spec.rb +85 -0
  47. data/spec/units/mince_migrator/migrations/loader_spec.rb +47 -0
  48. data/spec/units/mince_migrator/migrations/name_spec.rb +47 -0
  49. data/spec/units/mince_migrator/migrations/runner_spec.rb +70 -0
  50. data/spec/units/mince_migrator/migrations/runner_validator_spec.rb +46 -0
  51. data/spec/units/mince_migrator/migrations/template_spec.rb +42 -0
  52. data/spec/units/mince_migrator/migrations/versioned_file_spec.rb +48 -0
  53. data/spec/units/mince_migrator/ran_migration_spec.rb +60 -0
  54. data/spec/units/mince_migrator/reverter_spec.rb +78 -0
  55. metadata +298 -26
  56. data/.gitignore +0 -17
  57. data/Gemfile +0 -4
  58. data/Rakefile +0 -12
  59. data/mince_migrator.gemspec +0 -19
@@ -0,0 +1,119 @@
1
+ require_relative '../../../lib/mince_migrator/migration'
2
+
3
+ require 'time'
4
+
5
+ describe MinceMigrator::Migration do
6
+ subject { described_class.new(options) }
7
+
8
+ let(:options) { { klass: klass, name: name, relative_path: relative_path, path: path } }
9
+ let(:klass) { mock time_created: Time.now.utc - 550000 }
10
+ let(:name) { "name_of_migration" }
11
+ let(:relative_path) { mock }
12
+ let(:path) { mock }
13
+
14
+ its(:time_created) { should == klass.time_created }
15
+ its(:relative_path) { should == relative_path }
16
+ its(:path) { should == path }
17
+ its(:age) { should == '6d' }
18
+
19
+ it 'can run the migration' do
20
+ return_value = mock
21
+ klass.should_receive(:run).and_return(return_value)
22
+ subject.run.should == return_value
23
+ end
24
+
25
+ it 'can revert the migration' do
26
+ return_value = mock
27
+ klass.should_receive(:revert).and_return(return_value)
28
+ subject.revert.should == return_value
29
+ end
30
+
31
+ context 'when there is a record of the migration being ran' do
32
+ let(:ran_migration) { mock }
33
+
34
+ before do
35
+ MinceMigrator::RanMigration.stub(:find_by_name).with(subject.name).and_return(ran_migration)
36
+ end
37
+
38
+ its(:ran?) { should be_true }
39
+ its(:status) { should == 'ran' }
40
+ end
41
+
42
+ context 'when there is not a record of the migration being ran' do
43
+ before do
44
+ MinceMigrator::RanMigration.stub(:find_by_name).with(subject.name).and_return(nil)
45
+ end
46
+
47
+ its(:ran?) { should be_false }
48
+ end
49
+ end
50
+
51
+ describe MinceMigrator::Migration, 'class methods:' do
52
+ describe 'Loading from a file' do
53
+ subject { described_class.load_from_file(path_to_file) }
54
+
55
+ let(:path_to_file) { mock }
56
+ let(:migration_file) { mock klass: mock, name: mock, full_relative_path: mock, full_path: mock }
57
+ let(:migration) { mock }
58
+
59
+ before do
60
+ described_class.stub(:new).with(klass: migration_file.klass, name: migration_file.name, relative_path: migration_file.full_relative_path, path: migration_file.full_path).and_return(migration)
61
+ MinceMigrator::Migrations::File.stub(:load_from_file).with(path_to_file).and_return(migration_file)
62
+ end
63
+
64
+ it 'returns a migration for the given migration file' do
65
+ subject.should == migration
66
+ end
67
+
68
+ it 'loads the migration file into memory' do
69
+ MinceMigrator::Migrations::File.should_receive(:load_from_file).with(path_to_file).and_return(migration_file)
70
+
71
+ subject
72
+ end
73
+ end
74
+
75
+ describe 'Finding a migration for a given name' do
76
+ subject { described_class.find(name) }
77
+
78
+ let(:name) { mock }
79
+
80
+ context 'when the migration exists' do
81
+ let(:migration) { mock }
82
+ let(:migration_file) { mock }
83
+
84
+ before do
85
+ MinceMigrator::Migrations::File.stub(:find).with(name).and_return(migration_file)
86
+ MinceMigrator::Migration.stub(:new_from_file).with(migration_file).and_return(migration)
87
+ end
88
+
89
+ it 'returns the migration' do
90
+ subject.should == migration
91
+ end
92
+ end
93
+
94
+ context 'when the migration does not exist' do
95
+ before do
96
+ MinceMigrator::Migrations::File.stub(:find).with(name).and_return(nil)
97
+ end
98
+
99
+ it 'returns nothing' do
100
+ subject.should be_nil
101
+ end
102
+ end
103
+ end
104
+
105
+ describe 'initializing with a migration file' do
106
+ subject { described_class.new_from_file(file) }
107
+
108
+ let(:file) { mock klass: mock, name: mock, full_relative_path: mock, full_path: mock }
109
+ let(:migration) { mock }
110
+
111
+ before do
112
+ described_class.stub(:new).with(klass: file.klass, name: file.name, relative_path: file.full_relative_path, path: file.full_path).and_return(migration)
113
+ end
114
+
115
+ it 'returns the migration' do
116
+ subject.should == migration
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,85 @@
1
+ require_relative '../../../../lib/mince_migrator/migrations/file'
2
+
3
+ describe MinceMigrator::Migrations::File do
4
+ subject { described_class.new(name) }
5
+
6
+ let(:expected_class_name) { 'ChangeSpacesToUnderscores' }
7
+ let(:name) { "Change spaces to underscores" }
8
+ let(:migration_template) { mock render: mock }
9
+ let(:config) { MinceMigrator::Config }
10
+ let(:loader) { mock klass: mock, load: nil }
11
+
12
+ before do
13
+ MinceMigrator::Migrations::Template.stub(:new).with(expected_class_name).and_return(migration_template)
14
+ File.stub(:exists?).with(subject.full_path).and_return(false)
15
+ MinceMigrator::Migrations::Loader.stub(:new).with(full_path: subject.full_path, klass_name: subject.klass_name).and_return(loader)
16
+ end
17
+
18
+ its(:name) { should == "change_spaces_to_underscores" }
19
+ its(:filename) { should == "#{subject.name}.rb" }
20
+ its(:full_path) { should == File.join(config.migration_dir, subject.filename) }
21
+ its(:full_relative_path) { should == File.join(config.migration_relative_dir, subject.filename) }
22
+ its(:body) { should == migration_template.render }
23
+ its(:klass) { should == loader.klass }
24
+
25
+ it 'can load the migration file' do
26
+ loader.should_receive(:call)
27
+
28
+ subject.load
29
+ end
30
+
31
+ context 'when it has been written to the file system' do
32
+ before do
33
+ ::File.stub(:exists?).with(subject.full_path).and_return(true)
34
+ end
35
+
36
+ its(:persisted?) { should be_true }
37
+ end
38
+
39
+ context 'when it has not been written to the file system' do
40
+ before do
41
+ ::File.stub(:exists?).with(subject.full_path).and_return(false)
42
+ end
43
+
44
+ its(:persisted?) { should be_false }
45
+ end
46
+ end
47
+
48
+ describe MinceMigrator::Migrations::File, 'Class methods:' do
49
+ describe 'finding a file' do
50
+ subject { described_class.find(name) }
51
+
52
+ let(:name) { mock }
53
+ let(:file) { mock }
54
+
55
+ before do
56
+ described_class.stub(:new).with(name).and_return(file)
57
+ end
58
+
59
+ context 'when one exists' do
60
+ before do
61
+ file.stub(persisted?: true, load: nil)
62
+ end
63
+
64
+ it 'returns the file' do
65
+ subject.should == file
66
+ end
67
+
68
+ it 'loads the migration class' do
69
+ file.should_receive(:load)
70
+
71
+ subject
72
+ end
73
+ end
74
+
75
+ context 'when one does not exist' do
76
+ before do
77
+ file.stub(persisted?: false)
78
+ end
79
+
80
+ it 'returns nothing' do
81
+ subject.should be_nil
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../../../../lib/mince_migrator/migrations/loader'
2
+
3
+ describe MinceMigrator::Migrations::Loader do
4
+ subject { described_class.new full_path: full_path, klass_name: klass_name }
5
+
6
+ context 'when the file exists and is a mince migrator migration file' do
7
+ let(:full_path) { File.expand_path('../../../../support/test_migration.rb', __FILE__) }
8
+ let(:klass_name) { 'TestMigration' }
9
+ let(:expected_klass) { eval "::MinceMigrator::Migrations::#{klass_name}" }
10
+
11
+ its(:klass) { should == expected_klass }
12
+
13
+ it 'loads the migration into memory' do
14
+ subject.call
15
+
16
+ expected_klass.should == expected_klass
17
+ end
18
+ end
19
+
20
+ context 'when the file exists but is not a mince migrator migration file' do
21
+ let(:full_path) { File.expand_path('../../../../support/not_a_migration.rb', __FILE__) }
22
+ let(:klass_name) { 'Foo' }
23
+
24
+ it 'raises an exception' do
25
+ expect { subject.call }.to raise_exception('invalid migration')
26
+ end
27
+ end
28
+
29
+ context 'when the file does not exist' do
30
+ let(:full_path) { File.expand_path('../../../../support/does_not_exist_migration.rb', __FILE__) }
31
+ let(:klass_name) { 'Foo' }
32
+
33
+ it 'raises an exception' do
34
+ expect { subject.call }.to raise_exception('migration does not exist')
35
+ end
36
+ end
37
+
38
+ context 'when the migration does not have the required interface' do
39
+ let(:full_path) { File.expand_path('../../../../support/invalid_interface_migration.rb', __FILE__) }
40
+ let(:klass_name) { 'InvalidInterfaceMigration' }
41
+
42
+ it 'raises an exception' do
43
+ expect { subject.call }.to raise_exception('migration does not have all required methods (:run, :revert, and :time_created)')
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,47 @@
1
+ require_relative '../../../../lib/mince_migrator/migrations/name'
2
+
3
+ module MinceMigrator
4
+ module Migrations
5
+ describe Name do
6
+ subject { described_class.new(name) }
7
+
8
+ invalid_names = [
9
+ { in: '!@#$%^&*()', out: '' },
10
+ { in: nil, out: nil },
11
+ { in: '', out: '' }
12
+ ]
13
+
14
+ valid_names = [
15
+ { in: 'name', out: 'Name', filename: 'name.rb' },
16
+ { in: 'name_of_migration', out: 'Name of migration', filename: 'name_of_migration.rb' },
17
+ { in: 'name of migration', out: 'Name of migration', filename: 'name_of_migration.rb' },
18
+ { in: '1Name Of Migration', out: 'Name of migration', filename: 'name_of_migration.rb' },
19
+ { in: 'Name Of Migration 1!@#$%^&*()', out: 'Name of migration 1', filename: 'name_of_migration_1.rb' }
20
+ ]
21
+
22
+ valid_names.each do |name_group|
23
+ context "when the name is '#{name_group[:in]}'" do
24
+ let(:name) { name_group[:in] }
25
+
26
+ its(:value) { should == name_group[:out] }
27
+ its(:filename) { should == name_group[:filename] }
28
+ its(:valid?) { should be_true }
29
+ end
30
+ end
31
+
32
+ invalid_names.each do |name_group|
33
+ context "when the name is '#{name_group[:in]}'" do
34
+ let(:name) { name_group[:in] }
35
+
36
+ before do
37
+ subject.valid?
38
+ end
39
+
40
+ its(:value) { should == name_group[:out] }
41
+ its(:valid?) { should be_false }
42
+ its(:reasons_for_failure) { should == "Name is invalid, it must start with a character from A-Z or a-z" }
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,70 @@
1
+ require_relative '../../../../lib/mince_migrator/migrations/runner'
2
+
3
+ describe MinceMigrator::Migrations::Runner do
4
+ subject { described_class.new(name: name) }
5
+
6
+ let(:name) { mock }
7
+ let(:validator) { mock }
8
+
9
+ before do
10
+ Mince::Config.interface = mock
11
+ end
12
+
13
+ describe 'initializing with a migration' do
14
+ subject { described_class.new(migration: migration) }
15
+
16
+ let(:migration) { mock name: 'asdf' }
17
+
18
+ its(:migration) { should == migration }
19
+ its(:name) { should == migration.name }
20
+ end
21
+
22
+ context 'when the migration exists' do
23
+ let(:migration) { mock ran?: false, name: mock }
24
+
25
+ before do
26
+ MinceMigrator::Migrations::RunnerValidator.stub(:new).with(migration).and_return(validator)
27
+ MinceMigrator::Migration.stub(:find).with(name).and_return(migration)
28
+ validator.stub(call: true, errors: [])
29
+ end
30
+
31
+ its(:can_run_migration?) { should be_true }
32
+
33
+ context 'when it is ran' do
34
+ before do
35
+ migration.stub(:run)
36
+ MinceMigrator::RanMigration.stub(:create)
37
+ end
38
+
39
+ it 'returns true' do
40
+ subject.run_migration.should be_true
41
+ end
42
+
43
+ it 'runs the migration' do
44
+ migration.should_receive(:run)
45
+
46
+ subject.run_migration
47
+ end
48
+
49
+ it 'stores the record that it has been ran' do
50
+ MinceMigrator::RanMigration.should_receive(:create).with(name: migration.name)
51
+
52
+ subject.run_migration
53
+ end
54
+ end
55
+ end
56
+
57
+ context 'when the runner validator has errors' do
58
+ let(:errors) { [mock] }
59
+ let(:name) { mock }
60
+
61
+ before do
62
+ MinceMigrator::Migrations::RunnerValidator.stub(:new).with(nil).and_return(validator)
63
+ validator.stub(call: false, errors: errors)
64
+ MinceMigrator::Migration.stub(:find).with(name).and_return(nil)
65
+ end
66
+
67
+ its(:can_run_migration?) { should be_false }
68
+ its(:reasons_for_failure) { should == errors.join(' ') }
69
+ end
70
+ end
@@ -0,0 +1,46 @@
1
+ require_relative '../../../../lib/mince_migrator/migrations/runner_validator'
2
+
3
+ describe MinceMigrator::Migrations::RunnerValidator do
4
+ subject { described_class.new(migration) }
5
+
6
+ before do
7
+ Mince::Config.interface = mock
8
+ end
9
+
10
+ context 'when the migration exists' do
11
+ let(:migration) { mock }
12
+
13
+ context 'when it has already ran' do
14
+ before do
15
+ migration.stub(ran?: true)
16
+ subject.call
17
+ end
18
+
19
+ its(:call) { should be_false }
20
+ its(:errors) { should == ['Migration has already ran'] }
21
+ end
22
+ end
23
+
24
+ context 'when the migration does not exist' do
25
+ let(:migration) { nil }
26
+
27
+ before do
28
+ subject.call
29
+ end
30
+
31
+ its(:call) { should be_false }
32
+ its(:errors) { should == ['Migration does not exist'] }
33
+ end
34
+
35
+ context 'when the mince interface is not set' do
36
+ let(:migration) { mock }
37
+
38
+ before do
39
+ Mince::Config.interface = nil
40
+ subject.call
41
+ end
42
+
43
+ its(:call) { should be_false }
44
+ its(:errors) { should == ['Mince interface is not set'] }
45
+ end
46
+ end
@@ -0,0 +1,42 @@
1
+ require_relative '../../../../lib/mince_migrator/migrations/template'
2
+
3
+ describe MinceMigrator::Migrations::Template do
4
+ subject { described_class.new klass_name }
5
+
6
+ let(:klass_name) { 'NameOfMigration' }
7
+ let(:now) { mock 'time', to_s: "2013-02-23 19:03:27 UTC" }
8
+
9
+ before do
10
+ Time.stub_chain('now.utc' => now)
11
+ end
12
+
13
+ it 'renders the template' do
14
+ expected_content = <<-eos
15
+ module MinceMigrator
16
+ module Migrations
17
+ require 'time'
18
+
19
+ module #{klass_name}
20
+ def self.run
21
+ # Actual migration goes here
22
+ end
23
+
24
+ def self.revert
25
+ # In case you need to revert this one migration
26
+ end
27
+
28
+ # So you can change the order to run more easily
29
+ def self.time_created
30
+ Time.parse "#{now.to_s}"
31
+ end
32
+
33
+ module Temporary
34
+ # Migration dependent classes go here
35
+ end
36
+ end
37
+ end
38
+ end
39
+ eos
40
+ subject.render.should == expected_content
41
+ end
42
+ end
@@ -0,0 +1,48 @@
1
+ require_relative '../../../../lib/mince_migrator/migrations/versioned_file'
2
+
3
+ describe MinceMigrator::Migrations::VersionedFile do
4
+ describe 'Getting the next version of a migration for a given name' do
5
+ subject { described_class.new(name).next_unused_version }
6
+
7
+ let(:migration_file) { mock full_path: mock, persisted?: false }
8
+ let(:name) { mock }
9
+
10
+ context 'when no migrations contain the same name' do
11
+ before do
12
+ MinceMigrator::Migrations::File.stub(:new).with(name).and_return(migration_file)
13
+ end
14
+
15
+ it 'returns the migration file' do
16
+ subject.should == migration_file
17
+ end
18
+ end
19
+
20
+ context 'when a single migration exists with the same name' do
21
+ let(:other_migration_file) { mock full_path: mock, persisted?: true }
22
+
23
+ before do
24
+ MinceMigrator::Migrations::File.stub(:new).with(name).and_return(other_migration_file)
25
+ MinceMigrator::Migrations::File.stub(:new).with("#{name}_2").and_return(migration_file)
26
+ end
27
+
28
+ it 'returns a migration file the second version' do
29
+ subject.should == migration_file
30
+ end
31
+ end
32
+
33
+ context 'when multiple migrations exist with the same name' do
34
+ let(:other_migration_file) { mock full_path: mock, persisted?: true }
35
+ let(:other_migration_file2) { mock full_path: mock, persisted?: true }
36
+
37
+ before do
38
+ MinceMigrator::Migrations::File.stub(:new).with(name).and_return(other_migration_file)
39
+ MinceMigrator::Migrations::File.stub(:new).with("#{name}_2").and_return(other_migration_file2)
40
+ MinceMigrator::Migrations::File.stub(:new).with("#{name}_3").and_return(migration_file)
41
+ end
42
+
43
+ it 'returns a migration file for the first unused version' do
44
+ subject.should == migration_file
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,60 @@
1
+ require_relative '../../../lib/mince_migrator/ran_migration'
2
+
3
+ describe MinceMigrator::RanMigration do
4
+ let(:name) { "Name of migration" }
5
+
6
+ subject { described_class.new(name: name) }
7
+
8
+ its(:data_model) { should == MinceMigrator::RanMigrationDataModel }
9
+ its(:fields){ should == [:name] }
10
+ its(:name) { should == name }
11
+
12
+ it 'can be deleted' do
13
+ described_class.data_model.should_receive(:delete_by_params).with(name: name)
14
+
15
+ subject.delete
16
+ end
17
+ end
18
+
19
+ describe MinceMigrator::RanMigration, 'Class methods:' do
20
+ describe 'finding by name' do
21
+ subject { described_class.find_by_name(name) }
22
+
23
+ let(:name) { mock }
24
+
25
+ before do
26
+ described_class.data_model.stub(:find_by_field).with(:name, name).and_return(data)
27
+ end
28
+
29
+ context 'when it exists' do
30
+ let(:data) { mock }
31
+ let(:model) { mock }
32
+
33
+ before do
34
+ described_class.stub(:new).with(data).and_return(model)
35
+ end
36
+
37
+ it 'returns the model 'do
38
+ subject.should == model
39
+ end
40
+ end
41
+
42
+ context 'when it does not exist 'do
43
+ let(:data) { nil }
44
+
45
+ it 'returns nil' do
46
+ subject.should be_nil
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ describe MinceMigrator::RanMigrationDataModel do
53
+ it 'stores everything in the "migrations" collection' do
54
+ described_class.data_collection.should == :migrations
55
+ end
56
+
57
+ it 'has the name field' do
58
+ described_class.data_fields.should == [:name]
59
+ end
60
+ end
@@ -0,0 +1,78 @@
1
+ require_relative '../../../lib/mince_migrator/reverter'
2
+
3
+ describe MinceMigrator::Reverter do
4
+ subject { described_class.new(name: name) }
5
+
6
+ let(:name) { mock }
7
+ let(:migration_name) { mock value: mock }
8
+
9
+ before do
10
+ MinceMigrator::Migrations::Name.stub(:new).with(name).and_return(migration_name)
11
+ end
12
+
13
+ describe 'initializing with a migration' do
14
+ subject { described_class.new(migration: migration) }
15
+
16
+ let(:migration) { mock name: name }
17
+
18
+ its(:migration) { should == migration }
19
+ its(:name) { should == migration_name.value }
20
+ end
21
+
22
+ context 'when the migration does not exist' do
23
+ before do
24
+ MinceMigrator::Migration.stub(:find).with(migration_name.value).and_return(nil)
25
+ subject.can_revert_migration?
26
+ end
27
+
28
+ its(:can_revert_migration?) { should be_false }
29
+ its(:reasons_for_failure) { should == "Migration does not exist with name '#{migration_name.value}'" }
30
+ end
31
+
32
+ context 'when the migration exists' do
33
+ let(:migration) { mock name: mock }
34
+
35
+ before do
36
+ MinceMigrator::Migration.stub(:find).with(migration_name.value).and_return(migration)
37
+ end
38
+
39
+ context 'and has been ran' do
40
+ let(:ran_migration) { mock }
41
+
42
+ before do
43
+ migration.stub(ran?: true, ran_migration: ran_migration)
44
+ migration.stub(:revert)
45
+ ran_migration.stub(:delete)
46
+ end
47
+
48
+ its(:can_revert_migration?) { should be_true }
49
+
50
+ it 'returns true' do
51
+ subject.revert_migration.should be_true
52
+ end
53
+
54
+ it 'reverts the migration' do
55
+ migration.should_receive(:revert)
56
+
57
+ subject.revert_migration
58
+ end
59
+
60
+ it 'deletes the ran migration' do
61
+ ran_migration.should_receive(:delete)
62
+
63
+ subject.revert_migration
64
+ end
65
+ end
66
+
67
+ context 'but has not been ran' do
68
+ before do
69
+ migration.stub(ran?: false)
70
+
71
+ subject.can_revert_migration?
72
+ end
73
+
74
+ its(:can_revert_migration?) { should be_false }
75
+ its(:reasons_for_failure) { should == "Migration has not ran" }
76
+ end
77
+ end
78
+ end