nsync 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
1
+ require File.join(File.dirname(__FILE__), "helper")
2
+
3
+ gem "activerecord", "~> 2.3.5"
4
+ require 'active_record'
5
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
6
+ ActiveRecord::Base.send(:extend, Nsync::ActiveRecord::ClassMethods)
7
+
8
+ class TestFoo < ActiveRecord::Base
9
+ nsync_producer :if => lambda{|o| o.should_save }
10
+ end
11
+
12
+ class TestBar < ActiveRecord::Base
13
+ nsync_consumer
14
+
15
+ def should_save=(val)
16
+ val
17
+ end
18
+ end
19
+
20
+ class TestVersion < ActiveRecord::Base
21
+ def self.version
22
+ scoped(:order => "id DESC").first.try(:commit_id) || "0"*40
23
+ end
24
+
25
+ def self.version=(commit_id)
26
+ create(:commit_id => commit_id)
27
+ end
28
+
29
+ def self.previous_version
30
+ previous_versions_raw.first.commit_id
31
+ end
32
+
33
+ def self.previous_versions
34
+ previous_versions_raw.map(&:commit_id)
35
+ end
36
+
37
+ def self.previous_versions_raw
38
+ scoped(:order => "id DESC", :offset => 1)
39
+ end
40
+ end
41
+
42
+
43
+ class ActiveRecordTest < Test::Unit::TestCase
44
+ class MyConsumer < Nsync::Consumer
45
+ def self.config
46
+ @config ||= Nsync::Config.new
47
+ end
48
+ def config
49
+ self.class.config
50
+ end
51
+ end
52
+
53
+ class MyProducer < Nsync::Producer
54
+ def self.config
55
+ @config ||= Nsync::Config.new
56
+ end
57
+ def config
58
+ self.class.config
59
+ end
60
+ end
61
+
62
+ def setup
63
+ #Grit.debug = true
64
+ ActiveRecord::Base.connection.create_table :test_versions, :force => true do |t|
65
+ t.string :commit_id
66
+ t.timestamps
67
+ end
68
+ ActiveRecord::Base.connection.create_table :test_foos, :force => true do |t|
69
+ t.string :val
70
+ t.boolean :should_save
71
+ t.timestamps
72
+ end
73
+
74
+ ActiveRecord::Base.connection.create_table :test_bars, :force => true do |t|
75
+ t.integer :source_id
76
+ t.string :val
77
+ t.timestamps
78
+ end
79
+
80
+ FileUtils.rm_rf TestRepo.repo_path
81
+ MyProducer.config.repo_path = TestRepo.repo_path
82
+ FileUtils.rm_rf TestRepo.repo_push_path
83
+ Grit::Repo.init_bare(TestRepo.repo_push_path)
84
+ MyProducer.config.repo_push_url = TestRepo.repo_push_path
85
+ MyProducer.config.version_manager = Nsync::GitVersionManager.new(TestRepo.repo_path)
86
+
87
+ MyConsumer.config.repo_url = MyProducer.config.repo_push_url
88
+ FileUtils.rm_rf TestRepo.bare_consumer_repo_path
89
+ MyConsumer.config.repo_path = TestRepo.bare_consumer_repo_path
90
+ MyConsumer.config.version_manager = TestVersion
91
+ MyConsumer.config.map_class "TestFoo", "TestBar"
92
+
93
+ FileUtils.rm(MyProducer.config.lock_file, :force => true)
94
+
95
+ end
96
+
97
+ def test_integration
98
+ @producer = MyProducer.new
99
+ #dirty, dirty, dirty hack
100
+ Nsync.config.producer_instance = @producer
101
+ @consumer = MyConsumer.new
102
+
103
+ first_foo = TestFoo.create(:val => "This should be consumed", :should_save => true)
104
+ unsaved_foo = TestFoo.create(:val => "This should not be", :should_save => false)
105
+
106
+ assert File.exists?(File.join(MyProducer.config.repo_path, first_foo.nsync_filename))
107
+ assert !File.exists?(File.join(MyProducer.config.repo_path, unsaved_foo.nsync_filename))
108
+
109
+ @producer.commit("First objects")
110
+
111
+ @consumer.update
112
+
113
+ assert_equal 1, TestBar.count
114
+ assert_equal "This should be consumed", TestBar.nsync_find(first_foo.id).first.val
115
+
116
+ unsaved_foo.update_attributes(:should_save => true, :val => "consumption time")
117
+
118
+ assert File.exists?(File.join(MyProducer.config.repo_path, unsaved_foo.nsync_filename))
119
+
120
+ @producer.commit("oh yeah")
121
+
122
+ @consumer.update
123
+
124
+ assert_equal 2, TestBar.count
125
+
126
+ first_foo.update_attributes(:should_save => false, :val => "this is now gone")
127
+
128
+ assert !File.exists?(File.join(MyProducer.config.repo_path, first_foo.nsync_filename))
129
+
130
+ @producer.commit("we got rid of one")
131
+
132
+ @consumer.update
133
+
134
+ assert_equal 1, TestBar.count
135
+ assert_equal unsaved_foo.id, TestBar.first.source_id
136
+
137
+ @producer.rollback
138
+
139
+ assert File.exists?(File.join(MyProducer.config.repo_path, first_foo.nsync_filename))
140
+
141
+ @consumer.update
142
+
143
+ assert_equal 2, TestBar.count
144
+
145
+ @consumer.rollback
146
+
147
+ assert_equal 1, TestBar.count
148
+
149
+ @consumer.update
150
+
151
+ assert_equal 2, TestBar.count
152
+ end
153
+ end
data/test/classes.rb ADDED
@@ -0,0 +1,22 @@
1
+ class NsyncTestFoo; end
2
+ class NsyncTestBar; end
3
+
4
+ class NsyncTestVersion
5
+ class << self
6
+ def version
7
+ puts "version: #{@version}"
8
+ @version
9
+ end
10
+
11
+ def previous_version
12
+ @previous_version
13
+ end
14
+
15
+ def version=(id)
16
+ puts "updated version: #{id}"
17
+ @previous_version = @version
18
+ @version = id
19
+ end
20
+ end
21
+ end
22
+
data/test/helper.rb ADDED
@@ -0,0 +1,12 @@
1
+ require File.join(File.dirname(__FILE__), "..", "lib", "nsync")
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'mocha'
6
+ require 'shoulda'
7
+ require 'redgreen'
8
+
9
+ require File.join(File.dirname(__FILE__), "repo")
10
+ require File.join(File.dirname(__FILE__), "classes")
11
+
12
+
@@ -0,0 +1,142 @@
1
+ require File.join(File.dirname(__FILE__), "helper")
2
+
3
+ class NsyncConfigTest < Test::Unit::TestCase
4
+ def setup
5
+ Nsync.reset_config
6
+ @repo_path = "/foo/bar"
7
+ @repo_url = "http://example.com/foo/bar.git"
8
+ end
9
+
10
+ context "Basic Configuration" do
11
+ context "for local configuration" do
12
+ setup do
13
+ Nsync::Config.run do |config|
14
+ config.repo_path = @repo_path
15
+ end
16
+ end
17
+
18
+ should "set repo_path" do
19
+ assert_equal @repo_path, Nsync.config.repo_path
20
+ end
21
+
22
+ should "set local?" do
23
+ assert_equal true, Nsync.config.local?
24
+ end
25
+
26
+ should "set remote?" do
27
+ assert_equal false, Nsync.config.remote?
28
+ end
29
+ end
30
+
31
+ context "for remote configuration" do
32
+ setup do
33
+ Nsync::Config.run do |config|
34
+ config.repo_path = @repo_path
35
+ config.repo_url = @repo_url
36
+ end
37
+ end
38
+
39
+ should "set repo_path" do
40
+ assert_equal @repo_path, Nsync.config.repo_path
41
+ end
42
+
43
+ should "set repo_url" do
44
+ assert_equal @repo_url, Nsync.config.repo_url
45
+ end
46
+
47
+ should "set local?" do
48
+ assert_equal false, Nsync.config.local?
49
+ end
50
+
51
+ should "set remote?" do
52
+ assert_equal true, Nsync.config.remote?
53
+ end
54
+ end
55
+ end
56
+
57
+ context "Class Mapping" do
58
+ setup do
59
+ Nsync.config.map_class "NsyncTestFoo", "NsyncTestBar"
60
+ Nsync.config.map_class "NsyncTestFoo", "NsyncTestFoo"
61
+ Nsync.config.map_class "NsyncTestBar", "NsyncTestBar"
62
+ end
63
+
64
+ should "map classes according to the defined mapping in order" do
65
+ assert_equal [NsyncTestBar, NsyncTestFoo], Nsync.config.consumer_classes_for("NsyncTestFoo")
66
+ assert_equal [NsyncTestBar], Nsync.config.consumer_classes_for("NsyncTestBar")
67
+ end
68
+
69
+ should "return empty list for undefined mapping" do
70
+ assert_equal [], Nsync.config.consumer_classes_for("NsyncTestBaz")
71
+ end
72
+ end
73
+
74
+ context "Lock" do
75
+ setup do
76
+ @lock_file = "/private/tmp/nsync_test_#{Process.pid}.lock"
77
+ FileUtils.rm @lock_file, :force => true
78
+ Nsync.config.lock_file = @lock_file
79
+ end
80
+
81
+ context "unlocked" do
82
+ should "return the value of the block" do
83
+ ret = Nsync.config.lock do
84
+ "Success"
85
+ end
86
+ assert_equal "Success", ret
87
+ end
88
+
89
+ should "remove the lock file" do
90
+ assert(Nsync.config.lock do
91
+ true
92
+ end)
93
+ assert !File.exists?(@lock_file)
94
+ end
95
+ end
96
+
97
+ context "locked" do
98
+ setup do
99
+ FileUtils.touch @lock_file
100
+ #silence warnings
101
+ Nsync.config.log = ::Logger.new("/dev/null")
102
+ end
103
+
104
+ should "return false" do
105
+ ret = Nsync.config.lock do
106
+ true
107
+ end
108
+ assert_equal false, ret
109
+ end
110
+
111
+ should "not remove the lock file" do
112
+ assert(!Nsync.config.lock do
113
+ true
114
+ end)
115
+ assert File.exists? @lock_file
116
+ end
117
+ end
118
+ end
119
+
120
+ context "Version Manager" do
121
+ context "when unassigned" do
122
+ setup do
123
+ Nsync.config.version_manager = nil
124
+ end
125
+ should "raise an error" do
126
+ assert_raise RuntimeError do
127
+ Nsync.config.version_manager
128
+ end
129
+ end
130
+ end
131
+
132
+ context "when assigned" do
133
+ setup do
134
+ Nsync.config.version_manager = true
135
+ end
136
+
137
+ should "return it" do
138
+ assert Nsync.config.version_manager
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,260 @@
1
+ require File.join(File.dirname(__FILE__), "helper")
2
+
3
+ class NsyncConsumerTest < Test::Unit::TestCase
4
+ def setup
5
+ Nsync.reset_config
6
+ #Grit.debug = true
7
+ @lock_file = "/private/tmp/nsync_test_#{Process.pid}.lock"
8
+ FileUtils.rm @lock_file, :force => true
9
+ Nsync.config.lock_file = @lock_file
10
+
11
+ Nsync.config.version_manager = stub(:version => "foo", :previous_version => "bar")
12
+ Nsync.config.version_manager.stubs(:version=).with(is_a(String))
13
+
14
+ # I think mocha is doing something evil and redefing clone
15
+ if Grit::Git.instance_methods.include?("clone")
16
+ Grit::Git.send(:undef_method, :clone)
17
+ end
18
+ end
19
+
20
+ context "Initializing Consumer" do
21
+ context "when the repo already exists" do
22
+ setup do
23
+ @repo = TestRepo.new
24
+ FileUtils.rm_rf @repo.bare_consumer_repo_path
25
+
26
+ Nsync.config.repo_url = @repo.repo_path
27
+ Nsync.config.repo_path = @repo.bare_consumer_repo_path
28
+
29
+ @consumer = Nsync::Consumer.new
30
+ end
31
+
32
+ should "not reclone it" do
33
+ Grit::Git.any_instance.expects(:clone).never
34
+ @consumer = Nsync::Consumer.new
35
+ end
36
+ end
37
+
38
+ context "for a local repo" do
39
+ setup do
40
+ Nsync.config.repo_url = nil
41
+ end
42
+
43
+ context "that doesn't exist" do
44
+ setup do
45
+ @repo_path = "/private/tmp/nsync_non_existent_test_repo"
46
+ FileUtils.rm_rf @repo_path
47
+
48
+ Nsync.config.repo_path = @repo_path
49
+ end
50
+
51
+ should "raise a NoSuchPath error" do
52
+ assert_raise Grit::NoSuchPathError do
53
+ Nsync::Consumer.new
54
+ end
55
+ end
56
+ end
57
+
58
+ context "that does exist" do
59
+ setup do
60
+ @repo = TestRepo.new
61
+ Nsync.config.repo_path = @repo.repo_path
62
+ @consumer = Nsync::Consumer.new
63
+ end
64
+
65
+ should "assign it to repo" do
66
+ assert @consumer.repo.is_a? Grit::Repo
67
+ end
68
+ end
69
+ end
70
+
71
+ context "for a remote repo" do
72
+ context "where the origin does not exist" do
73
+ setup do
74
+ @repo_url = "/private/tmp/nsync_non_existent_test_repo"
75
+ @repo_path = "/private/tmp/nsync_non_existent_test_repo_consumer.git"
76
+ FileUtils.rm_rf @repo_url
77
+ FileUtils.rm_rf @repo_path
78
+
79
+ Nsync.config.repo_url = @repo_url
80
+ Nsync.config.repo_path = @repo_path
81
+ end
82
+
83
+ should "raise a NoSuchPath error" do
84
+ assert_raise Grit::NoSuchPathError do
85
+ Nsync::Consumer.new
86
+ end
87
+ end
88
+ end
89
+
90
+ context "where the origin does exist" do
91
+ setup do
92
+ @repo = TestRepo.new
93
+ FileUtils.rm_rf @repo.bare_consumer_repo_path
94
+
95
+ Nsync.config.repo_url = @repo.repo_path
96
+ Nsync.config.repo_path = @repo.bare_consumer_repo_path
97
+
98
+ @consumer = Nsync::Consumer.new
99
+ end
100
+
101
+ should "assign repo to a new bare repo" do
102
+ assert @consumer.repo.is_a? Grit::Repo
103
+ assert @consumer.repo.bare
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ context "Updating the Consumer side" do
110
+ setup do
111
+ @repo = TestRepo.new
112
+ Nsync.config.version_manager.stubs(:version => "HEAD^1")
113
+ FileUtils.rm_rf @repo.bare_consumer_repo_path
114
+ end
115
+
116
+ context "when local" do
117
+ setup do
118
+ Nsync.config.repo_url = nil
119
+ Nsync.config.repo_path = @repo.repo_path
120
+
121
+ @consumer = Nsync::Consumer.new
122
+ @consumer.expects(:apply_changes).once
123
+ end
124
+
125
+ should "not try to fetch in changes" do
126
+ @consumer.repo.expects(:remote_fetch).never
127
+ Grit::Git.any_instance.expects(:reset).never
128
+ @consumer.update
129
+ end
130
+ end
131
+
132
+ context "when remote" do
133
+ setup do
134
+ Nsync.config.repo_url = @repo.repo_path
135
+ Nsync.config.repo_path = @repo.bare_consumer_repo_path
136
+
137
+ @consumer = Nsync::Consumer.new
138
+ end
139
+
140
+ context "when updating the repo" do
141
+ should "fetch from origin" do
142
+ @consumer.expects(:apply_changes).once
143
+ @consumer.repo.expects(:remote_fetch).once
144
+ Grit::Git.any_instance.expects(:reset).once
145
+ @consumer.update
146
+ end
147
+ end
148
+
149
+ context "with ordering" do
150
+ setup do
151
+ changeset = {NsyncTestFoo => ["changes"], NsyncTestBar => ["changes"]}
152
+
153
+ order = states('order').starts_as('bar')
154
+
155
+ Nsync.config.ordering = ["NsyncTestBar", "NsyncTestFoo"]
156
+ @consumer.repo.expects(:diff).returns(:diffs).once
157
+ @consumer.expects(:changeset_from_diffs).with(:diffs).returns(changeset).once
158
+
159
+ @consumer.expects(:apply_changes_for_class).when(order.is('bar')).with(NsyncTestBar, ["changes"]).then(order.is('foo')).once
160
+ @consumer.expects(:apply_changes_for_class).when(order.is('foo')).with(NsyncTestFoo, ["changes"]).then(order.is('final')).once
161
+ end
162
+
163
+ should "execute in specified order" do
164
+ @consumer.update
165
+ end
166
+ end
167
+
168
+ context "with callbacks" do
169
+ setup do
170
+ Nsync.config.clear_class_mappings
171
+ Nsync.config.map_class "Foo", "NsyncTestFoo"
172
+ Nsync.config.map_class "Bar", "NsyncTestBar"
173
+ Nsync.config.version_manager = NsyncTestVersion
174
+ NsyncTestVersion.version = @repo.repo.head.commit.id
175
+
176
+ Nsync.config.ordering = ["NsyncTestBar", "NsyncTestFoo"]
177
+ @consumer.send(:clear_queues)
178
+ @consumer.expects(:clear_queues).twice
179
+ end
180
+
181
+ should "work" do
182
+ @repo.add_file("foo/1.json", {:id => 1, :val => "Party"})
183
+ NsyncTestFoo.expects(:nsync_find).with('1').returns([]).once
184
+ NsyncTestFoo.expects(:nsync_add_data).once.with(@consumer, :added, is_a(String), has_entry("val", "Party"))
185
+ @repo.add_file("bar/2.json", {:id => 2, :val => "Hardy"})
186
+ @repo.commit("Added some objects")
187
+ NsyncTestFoo.expects(:nsync_find).never
188
+ mock_bar = mock
189
+ mock_bar.expects(:nsync_update).once.with(@consumer, :added, is_a(String), has_entry("val", "Hardy"))
190
+ NsyncTestBar.expects(:nsync_find).with('2').returns([mock_bar]).once
191
+
192
+
193
+ order = states('order').starts_as('bar')
194
+
195
+ NsyncTestFoo.expects(:from_callback).once.when(order.is('foo')).then(order.is('final'))
196
+ mock_bar.expects(:from_callback).once.when(order.is('bar')).then(order.is('foo'))
197
+
198
+ mock_final = mock
199
+ mock_final.expects(:from_callback).once.when(order.is('final'))
200
+
201
+ @consumer.after_class_finished(NsyncTestFoo, lambda { NsyncTestFoo.from_callback })
202
+ @consumer.after_class_finished(NsyncTestBar, lambda { mock_bar.from_callback })
203
+
204
+ @consumer.after_finished(lambda { mock_final.from_callback })
205
+
206
+ @consumer.update
207
+ end
208
+ end
209
+
210
+ context "basic flow" do
211
+ setup do
212
+ Nsync.config.clear_class_mappings
213
+ Nsync.config.map_class "Foo", "NsyncTestFoo"
214
+ Nsync.config.map_class "Bar", "NsyncTestBar"
215
+ Nsync.config.version_manager = NsyncTestVersion
216
+ NsyncTestVersion.version = @repo.repo.head.commit.id
217
+ end
218
+
219
+ should "work" do
220
+ @repo.add_file("foo/1.json", {:id => 1, :val => "Party"})
221
+ @repo.commit("Added one object")
222
+ NsyncTestFoo.expects(:nsync_find).with('1').returns([]).once
223
+ NsyncTestFoo.expects(:nsync_add_data).once.with(@consumer, :added, is_a(String), has_entry("val", "Party"))
224
+ @consumer.update
225
+
226
+ @repo.add_file("bar/2.json", {:id => 2, :val => "Hardy"})
227
+ @repo.commit("And now for the bar")
228
+ NsyncTestFoo.expects(:nsync_find).never
229
+ mock_bar = mock
230
+ mock_bar.expects(:nsync_update).once.with(@consumer, :added, is_a(String), has_entry("val", "Hardy"))
231
+ NsyncTestBar.expects(:nsync_find).with('2').returns([mock_bar]).once
232
+ @consumer.update
233
+
234
+ @repo.add_file("foo/1.json", {:id => 1, :val => "Party Hardest"})
235
+ @repo.add_file("bar/3.json", {:id => 3, :val => "Moooooo"})
236
+ @repo.commit("I changed the foo and added a bar")
237
+ mock_foo = mock
238
+ mock_foo.expects(:nsync_update).once.with(@consumer, :modified, is_a(String), has_entry("val", "Party Hardest"))
239
+ NsyncTestFoo.expects(:nsync_find).with('1').returns([mock_foo]).once
240
+ NsyncTestBar.expects(:nsync_find).with('3').returns([]).once
241
+ NsyncTestBar.expects(:nsync_add_data).with(@consumer, :added, is_a(String), has_entry("val", "Moooooo")).once
242
+ @consumer.update
243
+
244
+ @repo.remove_file("foo/1.json")
245
+ @repo.commit("No more party")
246
+ mock_foo2 = mock
247
+ mock_foo2.expects(:nsync_update).with(@consumer, :deleted, is_a(String), {}).once
248
+ NsyncTestFoo.expects(:nsync_find).with('1').returns([mock_foo2]).once
249
+ @consumer.update
250
+
251
+ NsyncTestFoo.expects(:nsync_find).with('1').returns([]).once
252
+ NsyncTestFoo.expects(:nsync_add_data).once.with(@consumer, :added, is_a(String), has_entry("val", "Party Hardest"))
253
+
254
+ #bring the party back
255
+ @consumer.rollback
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end