nsync 0.0.2

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,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