rhosync 2.0.0.beta1

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