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,341 @@
1
+ require File.join(File.dirname(__FILE__),'..','spec_helper')
2
+ require 'rack/test'
3
+ require 'spec'
4
+ require 'spec/autorun'
5
+ require 'spec/interop/test'
6
+
7
+ require File.join(File.dirname(__FILE__),'..','..','lib','rhosync','server.rb')
8
+
9
+ describe "Server" do
10
+ it_should_behave_like "RhosyncDataHelper"
11
+ it_should_behave_like "TestappHelper"
12
+
13
+ include Rack::Test::Methods
14
+ include Rhosync
15
+
16
+ before(:each) do
17
+ require File.join(get_testapp_path,@test_app_name)
18
+ Rhosync.bootstrap(get_testapp_path) do |rhosync|
19
+ rhosync.vendor_directory = File.join(rhosync.base_directory,'..','..','..','vendor')
20
+ end
21
+ Server.set(
22
+ :environment => :test,
23
+ :run => false,
24
+ :secret => "secure!"
25
+ )
26
+ Server.use Rack::Static, :urls => ["/data"],
27
+ :root => File.join(File.dirname(__FILE__),'..','apps','rhotestapp')
28
+ end
29
+
30
+ def app
31
+ @app ||= Server.new
32
+ end
33
+
34
+ it_should_behave_like "DBObjectsHelper"
35
+
36
+ it "should show status page" do
37
+ get '/'
38
+ last_response.body.match(Rhosync::VERSION)[0].should == Rhosync::VERSION
39
+ end
40
+
41
+ it "should login without app_name" do
42
+ post "/login", "login" => @u_fields[:login], "password" => 'testpass'
43
+ last_response.should be_ok
44
+ end
45
+
46
+ it "should respond with 401 to /:app_name" do
47
+ get "/application"
48
+ last_response.status.should == 401
49
+ end
50
+
51
+ it "should have default session secret" do
52
+ Server.secret.should == "secure!"
53
+ end
54
+
55
+ it "should update session secret to default" do
56
+ Server.set :secret, "<changeme>"
57
+ Server.secret.should == "<changeme>"
58
+ Server.should_receive(:log).any_number_of_times.with(any_args())
59
+ check_default_secret!("<changeme>")
60
+ Server.set :secret, "secure!"
61
+ end
62
+
63
+ it "should complain about hsqldata.jar missing" do
64
+ Rhosync.vendor_directory = 'missing'
65
+ Server.should_receive(:log).any_number_of_times.with(any_args())
66
+ check_hsql_lib!
67
+ end
68
+
69
+ describe "helpers" do
70
+ before(:each) do
71
+ do_post "/application/clientlogin", "login" => @u.login, "password" => 'testpass'
72
+ end
73
+
74
+ it "should return nil if params[:source_name] is missing" do
75
+ get "/application"
76
+ last_response.status.should == 500
77
+ end
78
+ end
79
+
80
+ describe "auth routes" do
81
+ it "should login user with correct username,password" do
82
+ do_post "/application/clientlogin", "login" => @u.login, "password" => 'testpass'
83
+ last_response.should be_ok
84
+ end
85
+
86
+ it "should respond 401 for incorrect username or password" do
87
+ do_post "/application/clientlogin", "login" => @u.login, "password" => 'wrongpass'
88
+ last_response.status.should == 401
89
+ end
90
+
91
+ it "should create unknown user through delegated authentication" do
92
+ do_post "/application/clientlogin", "login" => 'newuser', "password" => 'testpass'
93
+ User.is_exist?('newuser').should == true
94
+ @a.users.members.sort.should == ['newuser','testuser']
95
+ end
96
+ end
97
+
98
+ describe "client management routes" do
99
+ before(:each) do
100
+ do_post "/application/clientlogin", "login" => @u.login, "password" => 'testpass'
101
+ @source_config = {"sources"=>{"SampleAdapter"=>{"poll_interval"=>300},
102
+ "SimpleAdapter"=>{"partition_type"=>"app","poll_interval"=>600}}}
103
+ end
104
+
105
+ it "should respond to clientcreate" do
106
+ get "/application/clientcreate?device_type=blackberry"
107
+ last_response.should be_ok
108
+ last_response.content_type.should == 'application/json'
109
+ id = JSON.parse(last_response.body)['client']['client_id']
110
+ id.length.should == 32
111
+ JSON.parse(last_response.body).should ==
112
+ {"client"=>{"client_id"=>id}}.merge!(@source_config)
113
+ c = Client.load(id,{:source_name => '*'})
114
+ c.user_id.should == 'testuser'
115
+ c.device_type.should == 'blackberry'
116
+ end
117
+
118
+ it "should respond to clientregister" do
119
+ do_post "/application/clientregister",
120
+ "device_type" => "iPhone", "device_pin" => 'abcd', "client_id" => @c.id
121
+ last_response.should be_ok
122
+ JSON.parse(last_response.body).should == @source_config
123
+ @c.device_type.should == 'iPhone'
124
+ @c.device_pin.should == 'abcd'
125
+ @c.id.length.should == 32
126
+ end
127
+
128
+ it "should respond to clientreset" do
129
+ set_state(@c.docname(:cd) => @data)
130
+ get "/application/clientreset", :client_id => @c.id,:version => ClientSync::VERSION
131
+ JSON.parse(last_response.body).should == @source_config
132
+ verify_result(@c.docname(:cd) => {})
133
+ end
134
+ end
135
+
136
+ describe "source routes" do
137
+ before(:each) do
138
+ do_post "/application/clientlogin", "login" => @u.login, "password" => 'testpass'
139
+ end
140
+
141
+ it "should return 404 message with version < 3" do
142
+ get "/application",:source_name => @s.name,:version => 2
143
+ last_response.status.should == 404
144
+ last_response.body.should == "Server supports version 3 or higher of the protocol."
145
+ end
146
+
147
+ it "should post records for create" do
148
+ @product1['_id'] = '1'
149
+ params = {'create'=>{'1'=>@product1},:client_id => @c.id,:source_name => @s.name,
150
+ :version => ClientSync::VERSION}
151
+ do_post "/application", params
152
+ last_response.should be_ok
153
+ last_response.body.should == ''
154
+ verify_result("test_create_storage" => {'1'=>@product1})
155
+ end
156
+
157
+ it "should post records for update" do
158
+ params = {'update'=>{'1'=>@product1},:client_id => @c.id,:source_name => @s.name,
159
+ :version => ClientSync::VERSION}
160
+ do_post "/application", params
161
+ last_response.should be_ok
162
+ last_response.body.should == ''
163
+ verify_result("test_update_storage" => {'1'=>@product1})
164
+ end
165
+
166
+ it "should post records for delete" do
167
+ params = {'delete'=>{'1'=>@product1},:client_id => @c.id,:source_name => @s.name,
168
+ :version => ClientSync::VERSION}
169
+ do_post "/application", params
170
+ last_response.should be_ok
171
+ last_response.body.should == ''
172
+ verify_result("test_delete_storage" => {'1'=>@product1})
173
+ end
174
+
175
+ it "should get inserts json" do
176
+ cs = ClientSync.new(@s,@c,1)
177
+ data = {'1'=>@product1,'2'=>@product2}
178
+ set_test_data('test_db_storage',data)
179
+ get "/application",:client_id => @c.id,:source_name => @s.name,:version => ClientSync::VERSION
180
+ last_response.should be_ok
181
+ last_response.content_type.should == 'application/json'
182
+ token = @c.get_value(:page_token)
183
+ JSON.parse(last_response.body).should == [{"version"=>ClientSync::VERSION},{"token"=>token},
184
+ {"count"=>2}, {"progress_count"=>0},{"total_count"=>2},{'insert'=>data}]
185
+ end
186
+
187
+ it "should get inserts json and confirm token" do
188
+ cs = ClientSync.new(@s,@c,1)
189
+ data = {'1'=>@product1,'2'=>@product2}
190
+ set_test_data('test_db_storage',data)
191
+ get "/application",:client_id => @c.id,:source_name => @s.name,:version => ClientSync::VERSION
192
+ last_response.should be_ok
193
+ token = @c.get_value(:page_token)
194
+ JSON.parse(last_response.body).should == [{"version"=>ClientSync::VERSION},{"token"=>token},
195
+ {"count"=>2}, {"progress_count"=>0}, {"total_count"=>2},{'insert'=>data}]
196
+ get "/application",:client_id => @c.id,:source_name => @s.name,:token => token,
197
+ :version => ClientSync::VERSION
198
+ last_response.should be_ok
199
+ JSON.parse(last_response.body).should == [{"version"=>ClientSync::VERSION},{"token"=>''},
200
+ {"count"=>0}, {"progress_count"=>2}, {"total_count"=>2},{}]
201
+ end
202
+
203
+ it "should get deletes json" do
204
+ cs = ClientSync.new(@s,@c,1)
205
+ data = {'1'=>@product1,'2'=>@product2}
206
+ set_test_data('test_db_storage',data)
207
+
208
+ get "/application",:client_id => @c.id,:source_name => @s.name,:version => ClientSync::VERSION
209
+ last_response.should be_ok
210
+ token = @c.get_value(:page_token)
211
+ JSON.parse(last_response.body).should == [{"version"=>ClientSync::VERSION},{"token"=>token},
212
+ {"count"=>2}, {"progress_count"=>0}, {"total_count"=>2},{'insert'=>data}]
213
+
214
+ Store.flash_data('test_db_storage')
215
+ @s.read_state.refresh_time = Time.now.to_i
216
+
217
+ get "/application",:client_id => @c.id,:source_name => @s.name,:token => token,
218
+ :version => ClientSync::VERSION
219
+ last_response.should be_ok
220
+ token = @c.get_value(:page_token)
221
+ JSON.parse(last_response.body).should == [{"version"=>ClientSync::VERSION},{"token"=>token},
222
+ {"count"=>2}, {"progress_count"=>0}, {"total_count"=>0},{'delete'=>data}]
223
+ end
224
+
225
+ it "should get search results" do
226
+ sources = ['SampleAdapter']
227
+ cs = ClientSync.new(@s,@c,1)
228
+ Store.put_data('test_db_storage',@data)
229
+ params = {:client_id => @c.id,:sources => sources,:search => {'name' => 'iPhone'},
230
+ :version => ClientSync::VERSION}
231
+ get "/application/search",params
232
+ last_response.content_type.should == 'application/json'
233
+ token = @c.get_value(:search_token)
234
+ JSON.parse(last_response.body).should == [[{'version'=>ClientSync::VERSION},{'search_token'=>token},
235
+ {'source'=>sources[0]},{'count'=>1},{'insert'=>{'1'=>@product1}}]]
236
+ end
237
+
238
+ it "should get search results with error" do
239
+ sources = ['SampleAdapter']
240
+ msg = "Error during search"
241
+ error = set_test_data('test_db_storage',@data,msg,'search error')
242
+ params = {:client_id => @c.id,:sources => sources,:search => {'name' => 'iPhone'},
243
+ :version => ClientSync::VERSION}
244
+ get "/application/search",params
245
+ JSON.parse(last_response.body).should == [[{'version'=>ClientSync::VERSION},
246
+ {'source'=>sources[0]},{'search-error'=>{'search-error'=>{'message'=>msg}}}]]
247
+ end
248
+
249
+ it "should get multiple source search results" do
250
+ @s_fields[:name] = 'SimpleAdapter'
251
+ @s1 = Source.load(@s_fields,@s_params)
252
+ Store.put_data('test_db_storage',@data)
253
+ sources = ['SimpleAdapter','SampleAdapter']
254
+ params = {:client_id => @c.id,:sources => sources,:search => {'search' => 'bar'},
255
+ :version => ClientSync::VERSION}
256
+ get "/application/search",params
257
+ @c.source_name = 'SimpleAdapter'
258
+ token1 = @c.get_value(:search_token)
259
+ @c.source_name = 'SampleAdapter'
260
+ token = @c.get_value(:search_token)
261
+ JSON.parse(last_response.body).should == [
262
+ [{"version"=>ClientSync::VERSION},{'search_token'=>token1},{"source"=>"SimpleAdapter"},
263
+ {"count"=>1}, {"insert"=>{'obj'=>{'foo'=>'bar'}}}],
264
+ [{"version"=>ClientSync::VERSION},{'search_token'=>token},{"source"=>"SampleAdapter"},
265
+ {"count"=>1}, {"insert"=>{'1'=>@product1}}]]
266
+ end
267
+ end
268
+
269
+ describe "bulk data routes" do
270
+ before(:each) do
271
+ do_post "/application/clientlogin", "login" => @u.login, "password" => 'testpass'
272
+ end
273
+
274
+ after(:each) do
275
+ delete_data_directory
276
+ end
277
+
278
+ it "should make initial bulk data request and receive wait" do
279
+ set_state('test_db_storage' => @data)
280
+ get "/application/bulk_data", :partition => :user, :client_id => @c.id
281
+ last_response.should be_ok
282
+ last_response.body.should == {:result => :wait}.to_json
283
+ end
284
+
285
+ it "should receive url when bulk data is available" do
286
+ set_state('test_db_storage' => @data)
287
+ get "/application/bulk_data", :partition => :user, :client_id => @c.id
288
+ BulkDataJob.perform("data_name" => bulk_data_docname(@a.id,@u.id))
289
+ get "/application/bulk_data", :partition => :user, :client_id => @c.id
290
+ last_response.should be_ok
291
+ last_response.body.should == {:result => :url,
292
+ :url => BulkData.load(bulk_data_docname(@a.id,@u.id)).dbfile}.to_json
293
+ validate_db_by_name(JSON.parse(last_response.body)["url"],@data)
294
+ end
295
+
296
+ it "should download bulk data file" do
297
+ set_state('test_db_storage' => @data)
298
+ get "/application/bulk_data", :partition => :user, :client_id => @c.id
299
+ BulkDataJob.perform("data_name" => bulk_data_docname(@a.id,@u.id))
300
+ get "/application/bulk_data", :partition => :user, :client_id => @c.id
301
+ get "/data/application/#{@u.id}/#{JSON.parse(last_response.body)["url"].split('/').last}"
302
+ last_response.should be_ok
303
+ File.open('test.data','wb') {|f| f.puts last_response.body}
304
+ validate_db_by_name('test.data',@data)
305
+ File.delete('test.data')
306
+ end
307
+
308
+ it "should receive nop when no sources are available for partition" do
309
+ set_state('test_db_storage' => @data)
310
+ Source.load('SimpleAdapter',@s_params).partition = :user
311
+ get "/application/bulk_data", :partition => :app, :client_id => @c.id
312
+ last_response.should be_ok
313
+ last_response.body.should == {:result => :nop}.to_json
314
+ end
315
+ end
316
+
317
+ describe "blob sync" do
318
+ before(:each) do
319
+ do_post "/application/clientlogin", "login" => @u.login, "password" => 'testpass'
320
+ end
321
+ it "should upload blob in multipart post" do
322
+ file1,file2 = 'upload1.txt','upload2.txt'
323
+ @product1['txtfile-rhoblob'] = file1
324
+ @product1['_id'] = 'tempobj1'
325
+ @product2['txtfile-rhoblob'] = file2
326
+ @product2['_id'] = 'tempobj2'
327
+ cud = {'create'=>{'1'=>@product1,'2'=>@product2},
328
+ :client_id => @c.id,:source_name => @s.name,
329
+ :version => ClientSync::VERSION,
330
+ :blob_fields => ['txtfile-rhoblob']}.to_json
331
+ post "/application",
332
+ {:cud => cud,'txtfile-rhoblob-1' =>
333
+ Rack::Test::UploadedFile.new(File.join(File.dirname(__FILE__),'..','testdata',file1), "application/octet-stream"),
334
+ 'txtfile-rhoblob-2' =>
335
+ Rack::Test::UploadedFile.new(File.join(File.dirname(__FILE__),'..','testdata',file2), "application/octet-stream")}
336
+ Store.get_data('test_create_storage').each do |id,obj|
337
+ File.exists?(obj['txtfile-rhoblob']).should == true
338
+ end
339
+ end
340
+ end
341
+ end
@@ -0,0 +1,114 @@
1
+ require File.join(File.dirname(__FILE__),'spec_helper')
2
+
3
+ class Rhosync::SourceAdapter
4
+ def inject_result(result)
5
+ @result = result
6
+ end
7
+ end
8
+
9
+ describe "SourceAdapter" do
10
+ it_should_behave_like "SpecBootstrapHelper"
11
+ it_should_behave_like "SourceAdapterHelper"
12
+
13
+ before(:each) do
14
+ @s.name = 'SimpleAdapter'
15
+ @sa = SourceAdapter.create(@s,nil)
16
+ end
17
+
18
+ it "should create SourceAdapter with source" do
19
+ @sa.class.name.should == @s.name
20
+ end
21
+
22
+ it "should create and execute SubAdapter that extends BaseAdapter" do
23
+ @s.name = 'SubAdapter'
24
+ @sa = SourceAdapter.create(@s,nil)
25
+ @sa.class.name.should == 'SubAdapter'
26
+ expected = {'1'=>@product1,'2'=>@product2}
27
+ @sa.inject_result expected
28
+ @sa.query.should == expected
29
+ end
30
+
31
+ it "should fail to create SourceAdapter" do
32
+ @s_fields[:name] = 'Broken'
33
+ broken_source = Source.create(@s_fields,@s_params)
34
+ lambda { SourceAdapter.create(broken_source) }.should raise_error(Exception)
35
+ end
36
+
37
+ it "should create SourceAdapter with trailing spaces" do
38
+ @s.name = 'SimpleAdapter '
39
+ SourceAdapter.create(@s,nil).is_a?(SimpleAdapter).should be_true
40
+ end
41
+
42
+ describe "SourceAdapter methods" do
43
+ it "should execute SourceAdapter login method with source vars" do
44
+ @sa.login.should == true
45
+ end
46
+
47
+ it "should execute SourceAdapter query method" do
48
+ expected = {'1'=>@product1,'2'=>@product2}
49
+ @sa.inject_result expected
50
+ @sa.query.should == expected
51
+ end
52
+
53
+ it "should execute SourceAdapter query method" do
54
+ expected = {'1'=>@product1,'2'=>@product2}
55
+ @sa.inject_result expected
56
+ @sa.query.should == expected
57
+ end
58
+
59
+ it "should execute SourceAdapter search method and modify params" do
60
+ params = {:hello => 'world'}
61
+ expected = {'1'=>@product1,'2'=>@product2}
62
+ @sa.inject_result expected
63
+ @sa.search(params).should == expected
64
+ params.should == {:hello => 'world', :foo => 'bar'}
65
+ end
66
+
67
+ it "should execute SourceAdapter login with current_user" do
68
+ @sa.should_receive(:current_user).with(no_args()).and_return(@u)
69
+ @sa.login
70
+ end
71
+
72
+ it "should execute SourceAdapter sync method" do
73
+ expected = {'1'=>@product1,'2'=>@product2}
74
+ @sa.inject_result expected
75
+ @sa.query.should == expected
76
+ @sa.sync
77
+ Store.get_data(@s.docname(:md)).should == expected
78
+ Store.get_value(@s.docname(:md_size)).to_i.should == 2
79
+ end
80
+
81
+ it "should fail gracefully if @result is missing" do
82
+ @sa.inject_result nil
83
+ lambda { @sa.query }.should_not raise_error
84
+ end
85
+
86
+ it "should reset count if @result is empty" do
87
+ @sa.inject_result({'1'=>@product1,'2'=>@product2})
88
+ @sa.query; @sa.sync
89
+ Store.get_value(@s.docname(:md_size)).to_i.should == 2
90
+ @sa.inject_result({})
91
+ @sa.query; @sa.sync
92
+ Store.get_value(@s.docname(:md_size)).to_i.should == 0
93
+ end
94
+
95
+ it "should execute SourceAdapter create method" do
96
+ @sa.create(@product4).should == 'obj4'
97
+ end
98
+
99
+ it "should log warning if @result is missing" do
100
+ @sa.should_receive(:log).with(SourceAdapter::MSG_NIL_RESULT_ATTRIB)
101
+ @sa.inject_result nil
102
+ @sa.sync
103
+ end
104
+
105
+ describe "SourceAdapter metadata method" do
106
+
107
+ it "should execute SourceAdapter metadata method" do
108
+ mock_metadata_method([SimpleAdapter]) do
109
+ @sa.metadata.should == "{\"foo\":\"bar\"}"
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,77 @@
1
+ require File.join(File.dirname(__FILE__),'spec_helper')
2
+
3
+ describe "Source" do
4
+ it_should_behave_like "SpecBootstrapHelper"
5
+ it_should_behave_like "SourceAdapterHelper"
6
+
7
+ it "should create and load source with @s_fields and @s_params" do
8
+ @s.name.should == @s_fields[:name]
9
+ @s.url.should == @s_fields[:url]
10
+ @s.login.should == @s_fields[:login]
11
+ @s.app.name.should == @a_fields[:name]
12
+ @s.priority.should == 3
13
+ @s.callback_url.should be_nil
14
+ @s.queue.should be_nil
15
+ @s.query_queue.should be_nil
16
+ @s.cud_queue.should be_nil
17
+ @s.app_id.should == @s_params[:app_id]
18
+ @s.user_id.should == @s_params[:user_id]
19
+ @s.sync_type.should == :incremental
20
+ @s.partition_type.should == :user
21
+ @s.poll_interval.should == 300
22
+
23
+ @s1 = Source.load(@s.id,@s_params)
24
+ @s1.name.should == @s_fields[:name]
25
+ @s1.url.should == @s_fields[:url]
26
+ @s1.login.should == @s_fields[:login]
27
+ @s1.app.name.should == @a_fields[:name]
28
+ @s1.priority.should == 3
29
+ @s1.callback_url.should be_nil
30
+ @s1.poll_interval.should == 300
31
+ @s1.app_id.should == @s_params[:app_id]
32
+ @s1.user_id.should == @s_params[:user_id]
33
+ end
34
+
35
+ it "should create source with user" do
36
+ @s.user.login.should == @u_fields[:login]
37
+ end
38
+
39
+ it "should create source with app and document" do
40
+ @s.app.name.should == @a_fields[:name]
41
+ @s.docname(:md).should == "source:#{@s.app.id}:#{@u.id}:#{@s_fields[:name]}:md"
42
+ end
43
+
44
+ it "should delete source" do
45
+ @s.delete
46
+ Source.is_exist?(@s_fields[:name]).should == false
47
+ end
48
+
49
+ it "should delete master and all documents associated with source" do
50
+ set_state(@s.docname(:md) => @data)
51
+ @s.delete
52
+ verify_result(@s.docname(:md) => {})
53
+ Store.db.keys(@s.docname('*')).should == []
54
+ end
55
+
56
+ it "should create source with default partition user" do
57
+ @s1 = Source.load(@s_fields[:name],{:app_id => @a.id,:user_id => '*'})
58
+ @s1.partition.should == :user
59
+ end
60
+
61
+ it "should create correct docname based on partition scheme" do
62
+ @s.partition = :app
63
+ @s.docname(:md).should == "source:#{@s.app.id}:__shared__:#{@s_fields[:name]}:md"
64
+ end
65
+
66
+ it "should create source with default read/write queue" do
67
+ @s.delete
68
+ @s_fields[:queue] = :default
69
+ @s_fields[:query_queue] = :query
70
+ @s_fields[:cud_queue] = :cud
71
+ Source.create(@s_fields,@s_params)
72
+ s = Source.load(@s_fields[:name],@s_params)
73
+ s.queue.should == 'default'
74
+ s.query_queue.should == 'query'
75
+ s.cud_queue.should == 'cud'
76
+ end
77
+ end