yolk-memento 0.2.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.
@@ -0,0 +1,55 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ class FooController < ActionController::Base
4
+
5
+ private
6
+
7
+ end
8
+
9
+ describe Memento::ActionControllerMethods do
10
+
11
+ before do
12
+ setup_db
13
+ setup_data
14
+ @controller = FooController.new
15
+ @controller.stub!(:current_user).and_return(@user)
16
+ @headers = {}
17
+ @controller.stub!(:response).and_return(mock("response", :headers => @headers))
18
+ end
19
+
20
+ after do
21
+ shutdown_db
22
+ end
23
+
24
+ it "should add memento-method to ActionController::Base" do
25
+ FooController.private_instance_methods.should include("memento")
26
+ end
27
+
28
+ it "should call memento#memento with user and block" do
29
+ project = Project.create!
30
+ @controller.send(:memento) do
31
+ project.update_attribute(:name, "P7")
32
+ end
33
+ project.reload.name.should eql("P7")
34
+ project.memento_states.count.should eql(1)
35
+ Memento::Session.count.should eql(1)
36
+ end
37
+
38
+ it "should set header X-MementoSessionId" do
39
+ @controller.send(:memento) { Project.create!.update_attribute(:name, "P7") }
40
+ @headers.should == {'X-Memento-Session-Id' => Memento::Session.last.id.to_s }
41
+ end
42
+
43
+ it "should return result of given block" do
44
+ @controller.send(:memento) do
45
+ 1 + 2
46
+ end.should eql(3)
47
+ end
48
+
49
+ it "should not set header when no session stored" do
50
+ @controller.send(:memento) { Customer.create! } # not stored
51
+ @headers['X-MementoSessionId'].should be_nil
52
+ @headers.should_not have_key('X-Memento-Session-Id')
53
+ end
54
+
55
+ end
@@ -0,0 +1,65 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Memento::ActiveRecordMethods do
4
+
5
+ before do
6
+ setup_db
7
+ setup_data
8
+ end
9
+
10
+ it "should declare private methods on Project" do
11
+ Project.private_instance_methods.should include("record_destroy", "record_update", "record_create")
12
+ end
13
+
14
+ it "should set hook on create to call Memento" do
15
+ project = Project.new(:name => "Project X")
16
+ Memento.instance.should_receive(:add_state).once().with("create", project)
17
+ project.save!
18
+ end
19
+
20
+ it "should set hook on update to call Memento" do
21
+ project = Project.create!(:name => "Project X")
22
+ Memento.instance.should_receive(:add_state).once().with("update", project)
23
+ project.update_attribute(:name, "Project XY")
24
+ end
25
+
26
+ it "should set hook on destroy to call Memento" do
27
+ project = Project.create!(:name => "Project X")
28
+ Memento.instance.should_receive(:add_state).once().with("destroy", project)
29
+ project.destroy
30
+ end
31
+
32
+ it "should define attributes_for_memento and ignore attributes given by options" do
33
+ Project.create!(:name => "Project X").attributes_for_memento.should == {
34
+ "id"=>1, "name"=>"Project X", "notes"=>nil, "customer_id"=>nil, "closed_at"=>nil
35
+ }
36
+ end
37
+
38
+ it "should define changes_for_memento and ignore attributes given by options" do
39
+ project = Project.create!(:name => "Project X")
40
+ project.name = "A Project"
41
+ project.updated_at = 5.minutes.ago
42
+ project.notes = "new"
43
+ project.ignore_this = 2
44
+ project.changes_for_memento.should_not == project.changes
45
+ project.changes_for_memento.should == {"name"=>["Project X", "A Project"], "notes"=>[nil, "new"]}
46
+ end
47
+
48
+ it "should define has_many association to memento_states" do
49
+ project = Project.create!(:name => "Project X")
50
+ project.memento_states.should be_empty
51
+ Memento.instance.memento(@user) { project.update_attribute(:name, "Project Y") }
52
+ project.memento_states.count.should eql(1)
53
+ Memento.instance.memento(@user) { project.update_attribute(:name, "Project Y") }
54
+ project.memento_states.count.should eql(1)
55
+ Memento.instance.memento(@user) { Project.create!.update_attribute(:name, "Project X") }
56
+ project.memento_states.count.should eql(1)
57
+ Project.last.memento_states.count.should eql(2)
58
+ Memento::State.count.should eql(3)
59
+ end
60
+
61
+ after do
62
+ shutdown_db
63
+ end
64
+
65
+ end
@@ -0,0 +1,89 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Memento::Result do
4
+
5
+ describe "when initalized with valid object" do
6
+ before do
7
+ @object = mock("object", :errors => {})
8
+ @state = mock("state1")
9
+ @result = Memento::Result.new(@object, @state)
10
+ end
11
+
12
+ it "should have an object attribute" do
13
+ @result.object.should eql(@object)
14
+ end
15
+
16
+ it "should have an state attribute" do
17
+ @result.state.should eql(@state)
18
+ end
19
+
20
+ it "should have an error attribute" do
21
+ @result.error.should be_nil
22
+ end
23
+
24
+ it "should be valid" do
25
+ @result.should be_success
26
+ @result.should_not be_failed
27
+ end
28
+ end
29
+
30
+ describe "when initalized with object with errors" do
31
+ before do
32
+ @object = mock("object", :errors => {:memento_undo => "123"})
33
+ @result = Memento::Result.new(@object, mock("state1"))
34
+ end
35
+
36
+ it "should have an object attribute" do
37
+ @result.object.should eql(@object)
38
+ end
39
+
40
+ it "should return error" do
41
+ @result.error.should eql("123")
42
+ end
43
+
44
+ it "should be invalid" do
45
+ @result.should be_failed
46
+ @result.should_not be_success
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ describe Memento::ResultArray do
53
+
54
+ before do
55
+ @results = Memento::ResultArray.new()
56
+ end
57
+
58
+ it "should have an empty errors array" do
59
+ @results.errors.should eql([])
60
+ end
61
+
62
+ it "should have no errors" do
63
+ @results.should be_success
64
+ @results.should_not be_failed
65
+ end
66
+
67
+ describe "when Memento::Result without errors added" do
68
+ before do
69
+ @object = mock("object", :errors => {:memento_undo => "123"})
70
+ @results << Memento::Result.new(mock("object2", :errors => {}), mock("state1"))
71
+ @results << (@with_error = Memento::Result.new(@object, mock("state2")))
72
+ end
73
+
74
+ it "should have two entrys" do
75
+ @results.size.should eql(2)
76
+ end
77
+
78
+ it "should have one error" do
79
+ @results.errors.size.should eql(1)
80
+ @results.errors.should eql([@with_error])
81
+ end
82
+
83
+ it "should have an error" do
84
+ @results.should_not be_success
85
+ @results.should be_failed
86
+ end
87
+ end
88
+
89
+ end
@@ -0,0 +1,181 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Memento::Session do
4
+
5
+ before do
6
+ setup_db
7
+ setup_data
8
+ @session = Memento::Session.create(:user => @user)
9
+ end
10
+
11
+ it "should belong to user" do
12
+ @session.user.should eql(@user)
13
+ end
14
+
15
+ it "should require user" do
16
+ Memento::Session.create.errors[:user].should eql("can't be blank")
17
+ end
18
+
19
+ it "should have_many states" do
20
+ @session.states.should eql([])
21
+ @session.states.create!(:action_type => "destroy", :record => Project.create!)
22
+ @session.states.count.should eql(1)
23
+ end
24
+
25
+ describe "on undo" do
26
+ before do
27
+ @states = [@t1 = mock("t1"), @t2 = mock("t2")]
28
+ @session.stub!(:states).and_return(@states)
29
+ end
30
+
31
+ describe "and all states fail" do
32
+ before do
33
+ @t1.stub!(:undo).once().and_return(mock("r1", :success? => false))
34
+ @t2.stub!(:undo).once().and_return(mock("r2", :success? => false))
35
+ @states.stub!(:count).and_return(2)
36
+ end
37
+
38
+ it "should call undo on all states when undo is called" do
39
+ @t1.should_receive(:undo).once().and_return(mock("r1", :success? => false))
40
+ @t2.should_receive(:undo).once().and_return(mock("r2", :success? => false))
41
+ @session.undo
42
+ end
43
+
44
+ it "should kepp itself" do
45
+ @session.undo
46
+ @session.reload
47
+ end
48
+ end
49
+
50
+ describe "and all states succeed" do
51
+ before do
52
+ @t1.stub!(:undo).once().and_return(mock("r1", :success? => true, :state => @t1))
53
+ @t2.stub!(:undo).once().and_return(mock("r2", :success? => true, :state => @t2))
54
+ @t1.stub!(:destroy).once()
55
+ @t2.stub!(:destroy).once()
56
+ @states.stub!(:count).and_return(0)
57
+ end
58
+
59
+ it "should destroy itself" do
60
+ @session.undo
61
+ Memento::Session.find_by_id(@session.id).should be_nil
62
+ end
63
+
64
+ it "should destroy all states" do
65
+ @t1.should_receive(:destroy).once()
66
+ @t2.should_receive(:destroy).once()
67
+ @session.undo
68
+ end
69
+ end
70
+
71
+ describe "and some states succeed, some fail" do
72
+ before do
73
+ @t1.stub!(:undo).once().and_return(mock("r1", :success? => true, :state => @t1))
74
+ @t2.stub!(:undo).once().and_return(mock("r2", :success? => false, :state => @t2))
75
+ @t1.stub!(:destroy).once()
76
+ @states.stub!(:count).and_return(1)
77
+ end
78
+
79
+ it "should kepp itself" do
80
+ @session.undo
81
+ @session.reload
82
+ end
83
+
84
+ it "should destroy only successful states" do
85
+ @t1.should_receive(:destroy).once()
86
+ @t2.should_receive(:destroy).never()
87
+ @session.undo
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "on undo!" do
93
+ before do
94
+ @state1 = @session.states.create!(:action_type => "update", :record => @p1 = Project.create!)
95
+ Memento::Session.create!(:user => @user).states.create!(:action_type => "destroy", :record => Project.create!)
96
+ @state2 = @session.states.create!(:action_type => "update", :record => @p2 = Project.create!)
97
+ end
98
+
99
+ describe "and all states succeed" do
100
+ it "should return ResultsArray" do
101
+ @session.undo!.should be_a(Memento::ResultArray)
102
+ end
103
+
104
+ it "should remove all states" do
105
+ @session.undo!
106
+ Memento::State.count.should eql(1)
107
+ end
108
+
109
+ it "should remove itself" do
110
+ @session.undo!
111
+ Memento::Session.find_by_id(@session.id).should be_nil
112
+ end
113
+ end
114
+
115
+ describe "and all states fail" do
116
+ before do
117
+ @state1.update_attribute(:record_data, {:name => ["A", "B"]})
118
+ @p1.update_attribute(:name, "C")
119
+ @state2.update_attribute(:record_data, {:name => ["A", "B"]})
120
+ @p2.update_attribute(:name, "C")
121
+ end
122
+
123
+ it "should keep all states" do
124
+ @session.undo! rescue
125
+ Memento::State.count.should eql(3)
126
+ end
127
+
128
+ it "should keep itself" do
129
+ @session.undo! rescue
130
+ @session.reload
131
+ end
132
+
133
+ it "should raise Memento::ErrorOnRewind" do
134
+ lambda{ @session.undo! }.should raise_error(Memento::ErrorOnRewind)
135
+ end
136
+ end
137
+
138
+ describe "and some states succeed, some fail" do
139
+ before do
140
+ @state1.update_attribute(:record_data, {:name => ["A", "B"]})
141
+ @p1.update_attribute(:name, "C")
142
+ end
143
+
144
+ it "should keep all states" do
145
+ @session.undo! rescue nil
146
+ Memento::State.count.should eql(3)
147
+ end
148
+
149
+ it "should keep itself" do
150
+ @session.undo! rescue nil
151
+ @session.reload
152
+ end
153
+
154
+ it "should raise Memento::ErrorOnRewind" do
155
+ lambda{ @session.undo! }.should raise_error(Memento::ErrorOnRewind)
156
+ end
157
+ end
158
+ end
159
+
160
+ describe "with states" do
161
+ before do
162
+ @session.states.create!(:action_type => "destroy", :record => Project.create!)
163
+ Memento::Session.create!(:user => @user).states.create!(:action_type => "destroy", :record => Project.create!)
164
+ @state2 = @session.states.create!(:action_type => "update", :record => Project.create!)
165
+ end
166
+
167
+ it "should destroy all states when destroyed" do
168
+ Memento::State.count.should eql(3)
169
+ @session.destroy
170
+ Memento::State.count.should eql(1)
171
+ end
172
+
173
+ end
174
+
175
+
176
+
177
+ after do
178
+ shutdown_db
179
+ end
180
+
181
+ end
@@ -0,0 +1,105 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ describe Memento::State do
4
+
5
+ before do
6
+ setup_db
7
+ setup_data
8
+ @session = Memento::Session.create(:user => @user)
9
+ end
10
+
11
+ it "should belong to session" do
12
+ Memento::State.new(:session => @session).session.should eql(@session)
13
+ end
14
+
15
+ it "should require session" do
16
+ Memento::State.create.errors[:session].should eql("can't be blank")
17
+ end
18
+
19
+ it "should require action_type to be one of Memento::State::RECORD_CAUSES" do
20
+ Memento::State.create.errors[:action_type].should eql("can't be blank")
21
+ Memento::State.create(:action_type => "move").errors[:action_type].should eql("is not included in the list")
22
+ end
23
+
24
+ it "should belong to polymorphic record" do
25
+ Memento::State.new(:record => @user).record.should eql(@user)
26
+ Memento::State.new(:record => @session).record.should eql(@session)
27
+ end
28
+
29
+ it "should require record" do
30
+ Memento::State.create.errors[:record].should eql("can't be blank")
31
+ end
32
+
33
+
34
+ describe "valid State" do
35
+ before do
36
+ @state = @session.states.create!(:action_type => "destroy", :record => @project = Project.create(:name => "A") )
37
+ end
38
+
39
+ it "should give back Memento::Result on undo" do
40
+ result = @state.undo
41
+ result.should be_a(Memento::Result)
42
+ result.object.should be_a(Project)
43
+ result.state.should eql(@state)
44
+ end
45
+
46
+ it "should give back old data on record_data" do
47
+ @state.record_data.should == (@project.attributes_for_memento)
48
+ end
49
+
50
+ it "should give back new unsaved copy of object on new_object" do
51
+ @state.new_object.should be_kind_of(Project)
52
+ @state.new_object.name.should be_nil
53
+ @state.new_object do |object|
54
+ object.name = "B"
55
+ end.name.should eql("B")
56
+ end
57
+
58
+ it "should give back new unsaved copy filled with old data of object on rebuild_object" do
59
+ @state.rebuild_object.should be_kind_of(Project)
60
+ @state.rebuild_object.name.should eql("A")
61
+ @state.rebuild_object(:id).id.should be_nil # skip id
62
+ @state.rebuild_object.id.should eql(@project.id)
63
+ @state.rebuild_object do |object|
64
+ object.name = "B"
65
+ end.name.should eql("B")
66
+ end
67
+
68
+ describe "on later_states_on_record_for" do
69
+
70
+ it "should return empty array" do
71
+ @state.later_states_on_record_for("destroy").should eql([])
72
+ end
73
+
74
+ it "should return filled array when other record of the given action_type exists" do
75
+ Memento::Session.create!(:user => @user).states.create!(:action_type => "destroy", :record => @project )
76
+ @state.later_states_on_record_for("destroy").map(&:class).should eql([Memento::State])
77
+ @state.later_states_on_record_for(:"destroy").map(&:id).should eql([2])
78
+ end
79
+
80
+ it "should return empty array when only records of another action_type exists" do
81
+ Memento::Session.create!(:user => @user).states.create!(:action_type => "update", :record => @project )
82
+ @state.later_states_on_record_for("destroy").should eql([])
83
+ end
84
+
85
+ it "should return empty array when only destroy records of another record exists" do
86
+ Memento::Session.create!(:user => @user).states.create!(:action_type => "destroy", :record => Project.create(:name => "B") )
87
+ @state.later_states_on_record_for("destroy").should eql([])
88
+ end
89
+
90
+ it "should return empty array when only destroy records older than @tack exist" do
91
+ state2 = Memento::Session.create!(:user => @user).states.create!(:action_type => "destroy", :record => @project )
92
+ state2.update_attribute(:created_at, 3.minutes.ago)
93
+ @state.later_states_on_record_for("destroy").should eql([])
94
+ end
95
+ end
96
+
97
+
98
+
99
+ end
100
+
101
+ after do
102
+ shutdown_db
103
+ end
104
+
105
+ end