rhosync 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (200) hide show
  1. data/CHANGELOG +5 -0
  2. data/LICENSE +674 -0
  3. data/README.md +26 -0
  4. data/Rakefile +109 -0
  5. data/bench/bench +6 -0
  6. data/bench/benchapp/Rakefile +14 -0
  7. data/bench/benchapp/application.rb +13 -0
  8. data/bench/benchapp/config.ru +32 -0
  9. data/bench/benchapp/settings/license.key +1 -0
  10. data/bench/benchapp/settings/settings.yml +18 -0
  11. data/bench/benchapp/sources/mock_adapter.rb +55 -0
  12. data/bench/benchapp/sources/queue_mock_adapter.rb +2 -0
  13. data/bench/benchapp/vendor/rhosync/lib/rhosync.rb +7 -0
  14. data/bench/lib/bench/cli.rb +16 -0
  15. data/bench/lib/bench/logging.rb +18 -0
  16. data/bench/lib/bench/mock_client.rb +41 -0
  17. data/bench/lib/bench/result.rb +50 -0
  18. data/bench/lib/bench/runner.rb +44 -0
  19. data/bench/lib/bench/session.rb +65 -0
  20. data/bench/lib/bench/statistics.rb +56 -0
  21. data/bench/lib/bench/test_data.rb +55 -0
  22. data/bench/lib/bench/timer.rb +10 -0
  23. data/bench/lib/bench/utils.rb +49 -0
  24. data/bench/lib/bench.rb +128 -0
  25. data/bench/lib/testdata/100-data.txt +148 -0
  26. data/bench/lib/testdata/5-data.txt +11 -0
  27. data/bench/scripts/cud_script.rb +77 -0
  28. data/bench/scripts/helpers.rb +101 -0
  29. data/bench/scripts/query_md_script.rb +46 -0
  30. data/bench/scripts/query_script.rb +46 -0
  31. data/bench/spec/bench_spec_helper.rb +65 -0
  32. data/bench/spec/logging_spec.rb +19 -0
  33. data/bench/spec/mock_adapter_spec.rb +61 -0
  34. data/bench/spec/mock_client_spec.rb +64 -0
  35. data/bench/spec/result_spec.rb +59 -0
  36. data/bench/spec/utils_spec.rb +35 -0
  37. data/bin/rhosync +34 -0
  38. data/doc/protocol.html +1901 -0
  39. data/doc/public/css/print.css +29 -0
  40. data/doc/public/css/screen.css +257 -0
  41. data/doc/public/css/style.css +20 -0
  42. data/examples/simple/application.rb +13 -0
  43. data/examples/simple/sources/sample_adapter.rb +5 -0
  44. data/examples/simple/sources/simple_adapter.rb +5 -0
  45. data/examples/simple/vendor/rhosync/lib/rhosync.rb +7 -0
  46. data/generators/rhosync.rb +98 -0
  47. data/generators/templates/application/Rakefile +19 -0
  48. data/generators/templates/application/application.rb +27 -0
  49. data/generators/templates/application/config.ru +33 -0
  50. data/generators/templates/application/settings/license.key +1 -0
  51. data/generators/templates/application/settings/settings.yml +14 -0
  52. data/generators/templates/source/source_adapter.rb +49 -0
  53. data/lib/rhosync/api/create_client.rb +3 -0
  54. data/lib/rhosync/api/create_user.rb +7 -0
  55. data/lib/rhosync/api/delete_client.rb +5 -0
  56. data/lib/rhosync/api/delete_user.rb +5 -0
  57. data/lib/rhosync/api/get_api_token.rb +7 -0
  58. data/lib/rhosync/api/get_client_params.rb +3 -0
  59. data/lib/rhosync/api/get_db_doc.rb +7 -0
  60. data/lib/rhosync/api/get_license_info.rb +7 -0
  61. data/lib/rhosync/api/get_source_params.rb +3 -0
  62. data/lib/rhosync/api/list_client_docs.rb +12 -0
  63. data/lib/rhosync/api/list_clients.rb +3 -0
  64. data/lib/rhosync/api/list_source_docs.rb +10 -0
  65. data/lib/rhosync/api/list_sources.rb +15 -0
  66. data/lib/rhosync/api/list_users.rb +3 -0
  67. data/lib/rhosync/api/ping.rb +7 -0
  68. data/lib/rhosync/api/push_deletes.rb +6 -0
  69. data/lib/rhosync/api/push_objects.rb +6 -0
  70. data/lib/rhosync/api/reset.rb +10 -0
  71. data/lib/rhosync/api/set_db_doc.rb +8 -0
  72. data/lib/rhosync/api/set_refresh_time.rb +8 -0
  73. data/lib/rhosync/api/update_user.rb +4 -0
  74. data/lib/rhosync/api/upload_file.rb +4 -0
  75. data/lib/rhosync/api_token.rb +19 -0
  76. data/lib/rhosync/app.rb +69 -0
  77. data/lib/rhosync/bulk_data/bulk_data.rb +75 -0
  78. data/lib/rhosync/bulk_data/syncdb.index.schema +3 -0
  79. data/lib/rhosync/bulk_data/syncdb.schema +37 -0
  80. data/lib/rhosync/bulk_data.rb +2 -0
  81. data/lib/rhosync/client.rb +74 -0
  82. data/lib/rhosync/client_sync.rb +296 -0
  83. data/lib/rhosync/console/app/helpers/auth_helper.rb +18 -0
  84. data/lib/rhosync/console/app/helpers/extensions.rb +19 -0
  85. data/lib/rhosync/console/app/helpers/helpers.rb +52 -0
  86. data/lib/rhosync/console/app/public/main.css +7 -0
  87. data/lib/rhosync/console/app/public/text.txt +0 -0
  88. data/lib/rhosync/console/app/routes/auth.rb +29 -0
  89. data/lib/rhosync/console/app/routes/client.rb +32 -0
  90. data/lib/rhosync/console/app/routes/docs.rb +84 -0
  91. data/lib/rhosync/console/app/routes/home.rb +22 -0
  92. data/lib/rhosync/console/app/routes/user.rb +63 -0
  93. data/lib/rhosync/console/app/views/client.erb +30 -0
  94. data/lib/rhosync/console/app/views/doc.erb +56 -0
  95. data/lib/rhosync/console/app/views/docs.erb +29 -0
  96. data/lib/rhosync/console/app/views/index.erb +50 -0
  97. data/lib/rhosync/console/app/views/layout.erb +12 -0
  98. data/lib/rhosync/console/app/views/newuser.erb +17 -0
  99. data/lib/rhosync/console/app/views/ping.erb +28 -0
  100. data/lib/rhosync/console/app/views/result.erb +11 -0
  101. data/lib/rhosync/console/app/views/user.erb +32 -0
  102. data/lib/rhosync/console/app/views/users.erb +14 -0
  103. data/lib/rhosync/console/rhosync_api.rb +102 -0
  104. data/lib/rhosync/console/server.rb +27 -0
  105. data/lib/rhosync/credential.rb +9 -0
  106. data/lib/rhosync/document.rb +43 -0
  107. data/lib/rhosync/indifferent_access.rb +132 -0
  108. data/lib/rhosync/jobs/bulk_data_job.rb +104 -0
  109. data/lib/rhosync/jobs/ping_job.rb +19 -0
  110. data/lib/rhosync/jobs/source_job.rb +16 -0
  111. data/lib/rhosync/license.rb +79 -0
  112. data/lib/rhosync/lock_ops.rb +11 -0
  113. data/lib/rhosync/model.rb +410 -0
  114. data/lib/rhosync/ping/blackberry.rb +55 -0
  115. data/lib/rhosync/ping/iphone.rb +44 -0
  116. data/lib/rhosync/ping.rb +2 -0
  117. data/lib/rhosync/read_state.rb +27 -0
  118. data/lib/rhosync/server/views/index.erb +12 -0
  119. data/lib/rhosync/server.rb +242 -0
  120. data/lib/rhosync/source.rb +112 -0
  121. data/lib/rhosync/source_adapter.rb +95 -0
  122. data/lib/rhosync/source_sync.rb +245 -0
  123. data/lib/rhosync/store.rb +199 -0
  124. data/lib/rhosync/tasks.rb +151 -0
  125. data/lib/rhosync/user.rb +83 -0
  126. data/lib/rhosync/version.rb +3 -0
  127. data/lib/rhosync.rb +251 -0
  128. data/spec/api/api_helper.rb +44 -0
  129. data/spec/api/create_client_spec.rb +13 -0
  130. data/spec/api/create_user_spec.rb +16 -0
  131. data/spec/api/delete_client_spec.rb +13 -0
  132. data/spec/api/delete_user_spec.rb +18 -0
  133. data/spec/api/get_api_token_spec.rb +25 -0
  134. data/spec/api/get_client_params_spec.rb +18 -0
  135. data/spec/api/get_db_doc_spec.rb +21 -0
  136. data/spec/api/get_license_info_spec.rb +16 -0
  137. data/spec/api/get_source_params_spec.rb +26 -0
  138. data/spec/api/list_client_docs_spec.rb +33 -0
  139. data/spec/api/list_clients_spec.rb +23 -0
  140. data/spec/api/list_source_docs_spec.rb +26 -0
  141. data/spec/api/list_sources_spec.rb +27 -0
  142. data/spec/api/list_users_spec.rb +21 -0
  143. data/spec/api/ping_spec.rb +24 -0
  144. data/spec/api/push_deletes_spec.rb +16 -0
  145. data/spec/api/push_objects_spec.rb +27 -0
  146. data/spec/api/reset_spec.rb +22 -0
  147. data/spec/api/set_db_doc_spec.rb +20 -0
  148. data/spec/api/set_refresh_time_spec.rb +43 -0
  149. data/spec/api/update_user_spec.rb +31 -0
  150. data/spec/api/upload_file_spec.rb +26 -0
  151. data/spec/api_token_spec.rb +13 -0
  152. data/spec/app_spec.rb +20 -0
  153. data/spec/apps/rhotestapp/Rakefile +1 -0
  154. data/spec/apps/rhotestapp/application.rb +16 -0
  155. data/spec/apps/rhotestapp/config.ru +1 -0
  156. data/spec/apps/rhotestapp/settings/apple_fake_cert.pem +1 -0
  157. data/spec/apps/rhotestapp/settings/license.key +1 -0
  158. data/spec/apps/rhotestapp/settings/settings.yml +23 -0
  159. data/spec/apps/rhotestapp/sources/base_adapter.rb +9 -0
  160. data/spec/apps/rhotestapp/sources/sample_adapter.rb +66 -0
  161. data/spec/apps/rhotestapp/sources/simple_adapter.rb +39 -0
  162. data/spec/apps/rhotestapp/sources/sub_adapter.rb +7 -0
  163. data/spec/apps/rhotestapp/vendor/mygem-0.1.0/lib/mygem/mygem.rb +8 -0
  164. data/spec/apps/rhotestapp/vendor/mygem-0.1.0/lib/mygem.rb +1 -0
  165. data/spec/bulk_data/bulk_data_spec.rb +79 -0
  166. data/spec/client_spec.rb +58 -0
  167. data/spec/client_sync_spec.rb +377 -0
  168. data/spec/doc/base.html +72 -0
  169. data/spec/doc/doc_spec.rb +303 -0
  170. data/spec/doc/footer.html +4 -0
  171. data/spec/doc/header.html +30 -0
  172. data/spec/document_spec.rb +27 -0
  173. data/spec/generator/generator_spec.rb +53 -0
  174. data/spec/generator/generator_spec_helper.rb +8 -0
  175. data/spec/jobs/bulk_data_job_spec.rb +76 -0
  176. data/spec/jobs/ping_job_spec.rb +26 -0
  177. data/spec/jobs/source_job_spec.rb +25 -0
  178. data/spec/license_spec.rb +48 -0
  179. data/spec/model_spec.rb +269 -0
  180. data/spec/perf/bulk_data_perf_spec.rb +33 -0
  181. data/spec/perf/perf_spec_helper.rb +51 -0
  182. data/spec/perf/store_perf_spec.rb +28 -0
  183. data/spec/ping/blackberry_spec.rb +62 -0
  184. data/spec/ping/iphone_spec.rb +50 -0
  185. data/spec/read_state_spec.rb +25 -0
  186. data/spec/rhosync_spec.rb +43 -0
  187. data/spec/server/server_spec.rb +341 -0
  188. data/spec/source_adapter_spec.rb +114 -0
  189. data/spec/source_spec.rb +77 -0
  190. data/spec/source_sync_spec.rb +248 -0
  191. data/spec/spec_helper.rb +240 -0
  192. data/spec/store_spec.rb +149 -0
  193. data/spec/sync_states_spec.rb +101 -0
  194. data/spec/testdata/1000-data.txt +1414 -0
  195. data/spec/testdata/compressed/compress-data.txt +1 -0
  196. data/spec/testdata/upload1.txt +1 -0
  197. data/spec/testdata/upload2.txt +1 -0
  198. data/spec/user_spec.rb +79 -0
  199. data/tasks/redis.rake +134 -0
  200. metadata +545 -0
@@ -0,0 +1,248 @@
1
+ require File.join(File.dirname(__FILE__),'spec_helper')
2
+
3
+ describe "SourceSync" do
4
+ it_should_behave_like "SpecBootstrapHelper"
5
+ it_should_behave_like "SourceAdapterHelper"
6
+
7
+ before(:each) do
8
+ @ss = SourceSync.new(@s)
9
+ end
10
+
11
+ it "should create SourceSync" do
12
+ @ss.adapter.is_a?(SampleAdapter).should == true
13
+ end
14
+
15
+ it "should fail to create SourceSync with InvalidArgumentError" do
16
+ lambda { SourceSync.new(nil) }.should raise_error(InvalidArgumentError, 'Invalid source')
17
+ end
18
+
19
+ it "should raise SourceAdapterLoginException if login fails" do
20
+ msg = "Error logging in"
21
+ @u.login = nil
22
+ @ss = SourceSync.new(@s)
23
+ @ss.should_receive(:log).with("SourceAdapter raised login exception: #{msg}")
24
+ @ss.process_query
25
+ verify_result(@s.docname(:errors) => {'login-error'=>{'message'=>msg}})
26
+ end
27
+
28
+ it "should raise SourceAdapterLogoffException if logoff fails" do
29
+ msg = "Error logging off"
30
+ @ss.should_receive(:log).with("SourceAdapter raised logoff exception: #{msg}")
31
+ set_test_data('test_db_storage',{},msg,'logoff error')
32
+ @ss.process_query
33
+ verify_result(@s.docname(:errors) => {'logoff-error'=>{'message'=>msg}})
34
+ end
35
+
36
+ it "should hold on read on subsequent call of process" do
37
+ expected = {'1'=>@product1}
38
+ Store.put_data('test_db_storage',expected)
39
+ @ss.process_query
40
+ Store.put_data('test_db_storage',{'2'=>@product2})
41
+ @ss.process_query
42
+ verify_result(@s.docname(:md) => expected)
43
+ end
44
+
45
+ it "should read on every subsequent call of process" do
46
+ expected = {'2'=>@product2}
47
+ @s.poll_interval = 0
48
+ Store.put_data('test_db_storage',{'1'=>@product1})
49
+ @ss.process_query
50
+ Store.put_data('test_db_storage',expected)
51
+ @ss.process_query
52
+ verify_result(@s.docname(:md) => expected)
53
+ end
54
+
55
+ it "should never call read on any call of process" do
56
+ @s.poll_interval = -1
57
+ Store.put_data('test_db_storage',{'1'=>@product1})
58
+ @ss.process_query
59
+ verify_result(@s.docname(:md) => {})
60
+ end
61
+
62
+ describe "methods" do
63
+
64
+ it "should process source adapter" do
65
+ mock_metadata_method([SampleAdapter, SimpleAdapter]) do
66
+ expected = {'1'=>@product1,'2'=>@product2}
67
+ set_state('test_db_storage' => expected)
68
+ @ss.process_query
69
+ verify_result(@s.docname(:md) => expected,
70
+ @s.docname(:metadata) => "{\"foo\":\"bar\"}",
71
+ @s.docname(:metadata_sha1) => "a5e744d0164540d33b1d7ea616c28f2fa97e754a")
72
+ end
73
+ end
74
+
75
+ it "should call methods in source adapter" do
76
+ mock_metadata_method([SampleAdapter, SimpleAdapter]) do
77
+ expected = {'1'=>@product1,'2'=>@product2}
78
+ metadata = "{\"foo\":\"bar\"}"
79
+ @ss.adapter.should_receive(:login).once.with(no_args()).and_return(true)
80
+ @ss.adapter.should_receive(:metadata).once.with(no_args()).and_return(metadata)
81
+ @ss.adapter.should_receive(:query).once.with(no_args()).and_return(expected)
82
+ @ss.adapter.should_receive(:sync).once.with(no_args()).and_return(true)
83
+ @ss.adapter.should_receive(:logoff).once.with(no_args()).and_return(nil)
84
+ @ss.process_query
85
+ end
86
+ end
87
+
88
+ describe "create" do
89
+ it "should do create where adapter.create returns nil" do
90
+ set_state(@c.docname(:create) => {'2'=>@product2})
91
+ @ss.create(@c.id)
92
+ verify_result(@c.docname(:create_errors) => {},
93
+ @c.docname(:create_links) => {},
94
+ @c.docname(:create) => {})
95
+ end
96
+
97
+ it "should do create where adapter.create returns object link" do
98
+ @product4['link'] = 'test link'
99
+ set_state(@c.docname(:create) => {'4'=>@product4})
100
+ @ss.create(@c.id)
101
+ verify_result(@c.docname(:create_errors) => {},
102
+ @c.docname(:create_links) => {'4'=>{'l'=>'backend_id'}},
103
+ @c.docname(:create) => {})
104
+ end
105
+
106
+ it "should raise exception on adapter.create" do
107
+ msg = "Error creating record"
108
+ data = add_error_object({'4'=>@product4,'2'=>@product2},msg)
109
+ set_state(@c.docname(:create) => data)
110
+ @ss.create(@c.id)
111
+ verify_result(@c.docname(:create_errors) =>
112
+ {"#{ERROR}-error"=>{"message"=>msg},ERROR=>data[ERROR]})
113
+ end
114
+ end
115
+
116
+ describe "update" do
117
+ it "should do update with no errors" do
118
+ set_state(@c.docname(:update) => {'4'=> { 'price' => '199.99' }})
119
+ @ss.update(@c.id)
120
+ verify_result(@c.docname(:update_errors) => {},
121
+ @c.docname(:update) => {})
122
+ end
123
+
124
+ it "should do update with errors" do
125
+ msg = "Error updating record"
126
+ data = add_error_object({'4'=> { 'price' => '199.99' }},msg)
127
+ set_state(@c.docname(:update) => data)
128
+ @ss.update(@c.id)
129
+ verify_result(@c.docname(:update_errors) =>
130
+ {"#{ERROR}-error"=>{"message"=>msg}, ERROR=>data[ERROR]},
131
+ @c.docname(:update) => {'4'=> { 'price' => '199.99'}})
132
+ end
133
+ end
134
+
135
+ describe "delete" do
136
+ it "should do delete with no errors" do
137
+ set_state(@c.docname(:delete) => {'4'=>@product4},
138
+ @s.docname(:md) => {'4'=>@product4,'3'=>@product3},
139
+ @c.docname(:cd) => {'4'=>@product4,'3'=>@product3})
140
+ @ss.delete(@c.id)
141
+ verify_result(@c.docname(:delete_errors) => {},
142
+ @s.docname(:md) => {'3'=>@product3},
143
+ @c.docname(:cd) => {'3'=>@product3},
144
+ @c.docname(:delete) => {})
145
+ end
146
+
147
+ it "should do delete with errors" do
148
+ msg = "Error delete record"
149
+ data = add_error_object({'2'=>@product2},msg)
150
+ set_state(@c.docname(:delete) => data)
151
+ @ss.delete(@c.id)
152
+ verify_result(@c.docname(:delete_errors) =>
153
+ {"#{ERROR}-error"=>{"message"=>msg}, ERROR=>data[ERROR]},
154
+ @c.docname(:delete) => {'2'=>@product2})
155
+ end
156
+ end
157
+
158
+ describe "cud" do
159
+ it "should do process_cud" do
160
+ @ss.should_receive(:_auth_op).twice.and_return(true)
161
+ @ss.should_receive(:create).once.with(@c.id)
162
+ @ss.should_receive(:update).once.with(@c.id)
163
+ @ss.should_receive(:delete).once.with(@c.id)
164
+ @ss.process_cud(@c.id)
165
+ end
166
+ end
167
+
168
+ describe "query" do
169
+ it "should do query with no exception" do
170
+ verify_read_operation('query')
171
+ end
172
+
173
+ it "should do query with exception raised" do
174
+ verify_read_operation_with_error('query')
175
+ end
176
+ end
177
+
178
+ describe "search" do
179
+ it "should do search with no exception" do
180
+ verify_read_operation('search')
181
+ end
182
+
183
+ it "should do search with exception raised" do
184
+ verify_read_operation_with_error('search')
185
+ end
186
+ end
187
+
188
+ describe "app-level partitioning" do
189
+ it "should create app-level masterdoc with '__shared__' docname" do
190
+ @s1 = Source.load(@s_fields[:name],@s_params)
191
+ @s1.partition = :app
192
+ @ss1 = SourceSync.new(@s1)
193
+ expected = {'1'=>@product1,'2'=>@product2}
194
+ set_state('test_db_storage' => expected)
195
+ @ss1.process_query
196
+ verify_result("source:#{@test_app_name}:__shared__:#{@s_fields[:name]}:md" => expected)
197
+ Store.db.keys("read_state:#{@test_app_name}:__shared__*").sort.should ==
198
+ [ "read_state:#{@test_app_name}:__shared__:SampleAdapter:refresh_time",
199
+ "read_state:#{@test_app_name}:__shared__:SampleAdapter:rho__id"]
200
+ end
201
+ end
202
+
203
+ def verify_read_operation(operation)
204
+ expected = {'1'=>@product1,'2'=>@product2}
205
+ set_test_data('test_db_storage',expected)
206
+ Store.put_data(@s.docname(:errors),
207
+ {"#{operation}-error"=>{'message'=>'failed'}},true)
208
+ if operation == 'query'
209
+ @ss.read.should == true
210
+ verify_result(@s.docname(:md) => expected,
211
+ @s.docname(:errors) => {})
212
+ else
213
+ @ss.search(@c.id).should == true
214
+ verify_result(@c.docname(:search) => expected,
215
+ @c.docname(:search_errors) => {})
216
+ end
217
+ end
218
+
219
+ def verify_read_operation_with_error(operation)
220
+ msg = "Error during #{operation}"
221
+ @ss.should_receive(:log).with("SourceAdapter raised #{operation} exception: #{msg}")
222
+ set_test_data('test_db_storage',{},msg,"#{operation} error")
223
+ if operation == 'query'
224
+ @ss.read.should == true
225
+ verify_result(@s.docname(:md) => {},
226
+ @s.docname(:errors) => {'query-error'=>{'message'=>msg}})
227
+ else
228
+ @ss.search(@c.id).should == true
229
+ verify_result(@c.docname(:search) => {},
230
+ @c.docname(:search_errors) => {'search-error'=>{'message'=>msg}})
231
+ end
232
+ end
233
+ end
234
+
235
+ it "should enqueue process_cud SourceJob" do
236
+ @s.cud_queue = :cud
237
+ @ss.process_cud(@c.id)
238
+ Resque.peek(:cud).should == {"args"=>
239
+ ["cud", @s.name, @a.name, @u.login, @c.id, nil], "class"=>"Rhosync::SourceJob"}
240
+ end
241
+
242
+ it "should enqueue process_query SourceJob" do
243
+ @s.query_queue = :abc
244
+ @ss.process_query({'foo'=>'bar'})
245
+ Resque.peek(:abc).should == {"args"=>
246
+ ["query", @s.name, @a.name, @u.login, nil, {'foo'=>'bar'}], "class"=>"Rhosync::SourceJob"}
247
+ end
248
+ end
@@ -0,0 +1,240 @@
1
+ require 'rubygems'
2
+ require 'rhosync'
3
+ include Rhosync
4
+
5
+ describe "RhosyncHelper", :shared => true do
6
+ before(:each) do
7
+ Store.create
8
+ Store.db.flushdb
9
+ end
10
+ end
11
+
12
+ describe "TestappHelper", :shared => true do
13
+ before(:all) do
14
+ @test_app_name = 'application'
15
+ end
16
+ def get_testapp_path
17
+ File.expand_path(File.join(File.dirname(__FILE__),'apps','rhotestapp'))
18
+ end
19
+ end
20
+
21
+ describe "RhosyncDataHelper", :shared => true do
22
+ it_should_behave_like "RhosyncHelper"
23
+ it_should_behave_like "TestappHelper"
24
+
25
+ before(:each) do
26
+ @source = 'Product'
27
+ @user_id = 5
28
+ @client_id = 1
29
+
30
+ @product1 = {
31
+ 'name' => 'iPhone',
32
+ 'brand' => 'Apple',
33
+ 'price' => '199.99'
34
+ }
35
+
36
+ @product2 = {
37
+ 'name' => 'G2',
38
+ 'brand' => 'Android',
39
+ 'price' => '99.99'
40
+ }
41
+
42
+ @product3 = {
43
+ 'name' => 'Fuze',
44
+ 'brand' => 'HTC',
45
+ 'price' => '299.99'
46
+ }
47
+
48
+ @product4 = {
49
+ 'name' => 'Droid',
50
+ 'brand' => 'Android',
51
+ 'price' => '249.99'
52
+ }
53
+
54
+ @data = {'1'=>@product1,'2'=>@product2,'3'=>@product3}
55
+ end
56
+ end
57
+
58
+ describe "DBObjectsHelper", :shared => true do
59
+
60
+ ERROR = '0_broken_object_id' unless defined? ERROR
61
+
62
+ before(:each) do
63
+ @a_fields = { :name => @test_app_name }
64
+ # @a = App.create(@a_fields)
65
+ @a = (App.load(@test_app_name) || App.create(@a_fields))
66
+ @u_fields = {:login => 'testuser'}
67
+ @u = User.create(@u_fields)
68
+ @u.password = 'testpass'
69
+ @c_fields = {
70
+ :device_type => 'iPhone',
71
+ :device_pin => 'abcd',
72
+ :device_port => '3333',
73
+ :user_id => @u.id,
74
+ :app_id => @a.id
75
+ }
76
+ @s_fields = {
77
+ :name => 'SampleAdapter',
78
+ :url => 'http://example.com',
79
+ :login => 'testuser',
80
+ :password => 'testpass',
81
+ }
82
+ @s_params = {
83
+ :user_id => @u.id,
84
+ :app_id => @a.id
85
+ }
86
+ @c = Client.create(@c_fields,{:source_name => @s_fields[:name]})
87
+ @s = Source.load(@s_fields[:name],@s_params)
88
+ @s = Source.create(@s_fields,@s_params) if @s.nil?
89
+ @r = @s.read_state
90
+ @a.sources << @s.id
91
+ @a.users << @u.id
92
+ end
93
+
94
+ def do_post(url,params)
95
+ post url, params.to_json, {'CONTENT_TYPE'=>'application/json'}
96
+ end
97
+
98
+ def bulk_data_docname(app_id,user_id)
99
+ if user_id == "*"
100
+ File.join(app_id,app_id)
101
+ else
102
+ File.join(app_id,user_id,user_id)
103
+ end
104
+ end
105
+
106
+ def dump_db_data(store)
107
+ puts "*"*50
108
+ puts "DATA DUMP"
109
+ puts "*"*50
110
+ store.db.keys('*').sort.each do |key|
111
+ next if not key =~ /md|cd/
112
+ line = ""
113
+ line << "#{key}: "
114
+ type = store.db.type key
115
+ if type == 'set'
116
+ if not key =~ /sources|clients|users/
117
+ line << "#{store.get_data(key).inspect}"
118
+ else
119
+ line << "#{store.db.smembers(key).inspect}"
120
+ end
121
+ else
122
+ line << "#{store.db.get key}"
123
+ end
124
+ puts line
125
+ end
126
+ puts "*"*50
127
+ end
128
+
129
+ def add_client_id(data)
130
+ res = Marshal.load(Marshal.dump(data))
131
+ res.each { |key,value| value['rhomobile.rhoclient'] = @c.id.to_s }
132
+ end
133
+
134
+ def add_error_object(data,error_message,error_name='wrongname')
135
+ error = {'an_attribute'=>error_message,'name'=>error_name}
136
+ data.merge!({ERROR=>error})
137
+ data
138
+ end
139
+
140
+ def delete_data_directory
141
+ FileUtils.rm_rf(Rhosync.data_directory)
142
+ end
143
+
144
+ def set_state(state)
145
+ state.each do |dockey,data|
146
+ if data.is_a?(Hash) or data.is_a?(Array)
147
+ Store.put_data(dockey,data)
148
+ else
149
+ Store.put_value(dockey,data)
150
+ end
151
+ end
152
+ end
153
+
154
+ def set_test_data(dockey,data,error_message=nil,error_name='wrongname')
155
+ if error_message
156
+ error = {'an_attribute'=>error_message,'name'=>error_name}
157
+ data.merge!({ERROR=>error})
158
+ end
159
+ Store.put_data(dockey,data)
160
+ data
161
+ end
162
+
163
+ def verify_result(result)
164
+ result.keys.sort.each do |dockey|
165
+ expected = result[dockey]
166
+ begin
167
+ if expected.is_a?(Hash)
168
+ Store.get_data(dockey).should == expected
169
+ elsif expected.is_a?(Array)
170
+ Store.get_data(dockey,Array).should == expected
171
+ else
172
+ Store.get_value(dockey).should == expected
173
+ end
174
+ rescue Spec::Expectations::ExpectationNotMetError => e
175
+ message = "\nVerifying `#{dockey}`\n\n" + e.to_s
176
+ Kernel::raise(Spec::Expectations::ExpectationNotMetError.new(message))
177
+ end
178
+ end
179
+ end
180
+
181
+ def validate_db(bulk_data,data)
182
+ validate_db_by_name(bulk_data.dbfile,data)
183
+ end
184
+
185
+ def validate_db_by_name(name,data)
186
+ db = SQLite3::Database.new(name)
187
+ db.execute("select * from sources").each do |row|
188
+ return false if row.last != get_attrib_counter(data)
189
+ end
190
+ db.execute("select * from object_values").each do |row|
191
+ object = data[row[2]]
192
+ return false if object.nil? or object[row[1]] != row[3] or row[0] != @s.source_id.to_s
193
+ object.delete(row[1])
194
+ data.delete(row[2]) if object.empty?
195
+ end
196
+ data.empty?
197
+ end
198
+
199
+ def get_attrib_counter(data)
200
+ counter = {}
201
+ data.each do |object_name,object|
202
+ object.each do |attrib,value|
203
+ counter[attrib] = counter[attrib] ? counter[attrib] + 1 : 1
204
+ end
205
+ end
206
+ BulkDataJob.refs_to_s(counter)
207
+ end
208
+
209
+ def mock_metadata_method(adapters, &block)
210
+ adapters.each do |klass|
211
+ klass.class_eval "def metadata; {'foo'=>'bar'}.to_json; end"
212
+ end
213
+ yield
214
+ adapters.each do |klass|
215
+ klass.class_eval "def metadata; end"
216
+ end
217
+ end
218
+ end
219
+
220
+ describe "SourceAdapterHelper", :shared => true do
221
+ it_should_behave_like "RhosyncDataHelper"
222
+ it_should_behave_like "DBObjectsHelper"
223
+ end
224
+
225
+ describe "StorageStateHelper", :shared => true do
226
+ it_should_behave_like "SourceAdapterHelper"
227
+
228
+ before(:each) do
229
+ @s.name = 'StorageStateAdapter'
230
+ end
231
+ end
232
+
233
+ describe "SpecBootstrapHelper", :shared => true do
234
+ it_should_behave_like "TestappHelper"
235
+ before(:all) do
236
+ Rhosync.bootstrap(get_testapp_path) do |rhosync|
237
+ rhosync.vendor_directory = File.join(File.dirname(__FILE__),'..','vendor')
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,149 @@
1
+ require File.join(File.dirname(__FILE__),'spec_helper')
2
+
3
+ describe "Store" do
4
+
5
+ it_should_behave_like "SourceAdapterHelper"
6
+
7
+ describe "store methods" do
8
+ it "should create db class method" do
9
+ Store.db.class.name.should match(/Redis/)
10
+ end
11
+
12
+ it "should set redis connection" do
13
+ begin
14
+ Store.db = 'localhost:5555'
15
+ Store.db.server.should == 'localhost:5555'
16
+ ensure
17
+ Store.db = ''
18
+ end
19
+ end
20
+
21
+ it "should add simple data to new set" do
22
+ Store.put_data(@s.docname(:md),@data).should == true
23
+ Store.get_data(@s.docname(:md)).should == @data
24
+ end
25
+
26
+ it "should add simple array data to new set" do
27
+ @data = ['1','2','3']
28
+ Store.put_data(@s.docname(:md),@data).should == true
29
+ Store.get_data(@s.docname(:md),Array).sort.should == @data
30
+ end
31
+
32
+ it "should replace simple data to existing set" do
33
+ new_data,new_data['3'] = {},{'name' => 'Droid','brand' => 'Google'}
34
+ Store.put_data(@s.docname(:md),@data).should == true
35
+ Store.put_data(@s.docname(:md),new_data)
36
+ Store.get_data(@s.docname(:md)).should == new_data
37
+ end
38
+
39
+ it "should put_value and get_value" do
40
+ Store.put_value('foo','bar')
41
+ Store.get_value('foo').should == 'bar'
42
+ end
43
+
44
+ it "should return true/false if element ismember of a set" do
45
+ Store.put_data('foo',['a'])
46
+ Store.ismember?('foo','a').should == true
47
+
48
+ Store.ismember?('foo','b').should == false
49
+ end
50
+
51
+ it "should return attributes modified in doc2" do
52
+ Store.put_data(@s.docname(:md),@data).should == true
53
+ Store.get_data(@s.docname(:md)).should == @data
54
+
55
+ @product3['price'] = '59.99'
56
+ expected = { '3' => { 'price' => '59.99' } }
57
+ @data1,@data1['1'],@data1['2'],@data1['3'] = {},@product1,@product2,@product3
58
+
59
+ Store.put_data(@c.docname(:cd),@data1)
60
+ Store.get_data(@c.docname(:cd)).should == @data1
61
+ Store.get_diff_data(@s.docname(:md),@c.docname(:cd)).should == [expected,1]
62
+ end
63
+
64
+ it "should return attributes modified and missed in doc2" do
65
+ Store.put_data(@s.docname(:md),@data).should == true
66
+ Store.get_data(@s.docname(:md)).should == @data
67
+
68
+ @product2['price'] = '59.99'
69
+ expected = { '2' => { 'price' => '99.99' },'3' => @product3 }
70
+ @data1,@data1['1'],@data1['2'] = {},@product1,@product2
71
+
72
+ Store.put_data(@c.docname(:cd),@data1)
73
+ Store.get_data(@c.docname(:cd)).should == @data1
74
+ Store.get_diff_data(@c.docname(:cd),@s.docname(:md)).should == [expected,2]
75
+ end
76
+
77
+ it "should ignore reserved attributes" do
78
+ @newproduct = {
79
+ 'name' => 'iPhone',
80
+ 'brand' => 'Apple',
81
+ 'price' => '199.99',
82
+ 'id' => 1234,
83
+ 'attrib_type' => 'someblob'
84
+ }
85
+
86
+ @data1 = {'1'=>@newproduct,'2'=>@product2,'3'=>@product3}
87
+
88
+ Store.put_data(@s.docname(:md),@data1).should == true
89
+ Store.get_data(@s.docname(:md)).should == @data
90
+ end
91
+
92
+ it "should flash_data" do
93
+ Store.put_data(@s.docname(:md),@data)
94
+ Store.flash_data(@s.docname(:md))
95
+ Store.get_data(@s.docname(:md)).should == {}
96
+ end
97
+
98
+ it "should get_keys" do
99
+ expected = ["doc1:1:1:1:source1", "doc1:1:1:1:source2"]
100
+ Store.put_data(expected[0],@data)
101
+ Store.put_data(expected[1],@data)
102
+ Store.get_keys('doc1:1:1:1:*').sort.should == expected
103
+ end
104
+
105
+ it "should lock document" do
106
+ doc = "locked_data"
107
+ m_lock = Store.get_lock(doc)
108
+ th = Thread.new do
109
+ t_lock = Store.get_lock(doc)
110
+ Store.put_data(doc,{'1'=>@product1},true)
111
+ Store.release_lock(doc,t_lock)
112
+ end
113
+ Store.put_data(doc,{'2'=>@product2},true)
114
+ Store.get_data(doc).should == {'2'=>@product2}
115
+ th.alive?.should == true
116
+ Store.release_lock(doc,m_lock)
117
+ sleep(2)
118
+ m_lock = Store.get_lock(doc)
119
+ Store.get_data(doc).should == {'1'=>@product1,'2'=>@product2}
120
+ th.alive?.should == false
121
+ end
122
+
123
+ it "should lock document in block" do
124
+ doc = "locked_data"
125
+ Store.lock(doc,0) do
126
+ Store.put_data(doc,{'2'=>@product2})
127
+ Store.get_data(doc).should == {'2'=>@product2}
128
+ end
129
+ end
130
+
131
+ it "should create clone of set" do
132
+ set_state('abc' => @data)
133
+ Store.clone('abc','def')
134
+ verify_result('abc' => @data,'def' => @data)
135
+ end
136
+
137
+ it "should rename a key" do
138
+ set_state('key1' => @data)
139
+ Store.rename('key1','key2')
140
+ verify_result('key1' => {}, 'key2' => @data)
141
+ end
142
+
143
+ it "should not fail to rename if key doesn't exist" do
144
+ Store.rename('key1','key2')
145
+ Store.db.exists('key1').should be_false
146
+ Store.db.exists('key2').should be_false
147
+ end
148
+ end
149
+ end