mince_migrator 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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