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,23 @@
1
+ :sources:
2
+ SampleAdapter:
3
+ poll_interval: 300
4
+ SimpleAdapter:
5
+ poll_interval: 600
6
+ partition_type: app
7
+
8
+ :development:
9
+ :licensefile: settings/license.key
10
+ :iphonecertfile: settings/apple_fake_cert.pem
11
+ :iphonepassphrase: certpassword
12
+ :iphoneserver: gateway.sandbox.push.apple.com
13
+ :iphoneport: 2195
14
+ :redis: localhost:6379
15
+ :syncserver: http://localhost:9292/application/
16
+ :test:
17
+ :licensefile: settings/license.key
18
+ :redis: localhost:6379
19
+ :syncserver: http://localhost:9292/application/
20
+ :production:
21
+ :licensefile: settings/license.key
22
+ :redis: localhost:6379
23
+ :syncserver: http://localhost:9292/application/
@@ -0,0 +1,9 @@
1
+ class BaseAdapter < SourceAdapter
2
+ def initialize(source,credential)
3
+ super(source,credential)
4
+ end
5
+
6
+ def query(params=nil)
7
+ @result
8
+ end
9
+ end
@@ -0,0 +1,66 @@
1
+ class SampleAdapter < SourceAdapter
2
+ def initialize(source,credential)
3
+ super(source,credential)
4
+ end
5
+
6
+ def login
7
+ raise SourceAdapterLoginException.new('Error logging in') if _is_empty?(current_user.login)
8
+ true
9
+ end
10
+
11
+ def query(params=nil)
12
+ _read('query',params)
13
+ end
14
+
15
+ def search(params=nil)
16
+ _read('search',params)
17
+ end
18
+
19
+ def sync
20
+ super
21
+ end
22
+
23
+ def create(name_value_list,blob=nil)
24
+ Store.put_data('test_create_storage',{name_value_list['_id']=>name_value_list},true)
25
+ raise SourceAdapterException.new("ID provided in name_value_list") if name_value_list['id']
26
+ _raise_exception(name_value_list)
27
+ 'backend_id' if name_value_list and name_value_list['link']
28
+ end
29
+
30
+ def update(name_value_list)
31
+ raise SourceAdapterException.new("No id provided in name_value_list") unless name_value_list['id']
32
+ Store.put_data('test_update_storage',{name_value_list['id']=>name_value_list},true)
33
+ _raise_exception(name_value_list)
34
+ end
35
+
36
+ def delete(name_value_list)
37
+ raise SourceAdapterException.new("No id provided in name_value_list") unless name_value_list['id']
38
+ raise SourceAdapterServerErrorException.new("Error delete record") if name_value_list['id'] == ERROR
39
+ Store.put_data('test_delete_storage',{name_value_list['id']=>name_value_list},true)
40
+ end
41
+
42
+ def logoff
43
+ @result = Store.get_data('test_db_storage')
44
+ raise SourceAdapterLogoffException.new(@result[ERROR]['an_attribute']) if @result[ERROR] and
45
+ @result[ERROR]['name'] == 'logoff error'
46
+ end
47
+
48
+ private
49
+ def _is_empty?(str)
50
+ str.length <= 0
51
+ end
52
+
53
+ def _raise_exception(name_value_list)
54
+ if name_value_list and name_value_list['name'] == 'wrongname' or name_value_list['id'] == 'error'
55
+ raise SourceAdapterServerErrorException.new(name_value_list['an_attribute'])
56
+ end
57
+ end
58
+
59
+ def _read(operation,params)
60
+ @result = Store.get_data('test_db_storage')
61
+ raise SourceAdapterServerErrorException.new(@result[ERROR]['an_attribute']) if @result[ERROR] and
62
+ @result[ERROR]['name'] == "#{operation} error"
63
+ @result.reject! {|key,value| value['name'] != params['name']} if params
64
+ @result
65
+ end
66
+ end
@@ -0,0 +1,39 @@
1
+ class SimpleAdapter < SourceAdapter
2
+ def initialize(source,credential)
3
+ super(source,credential)
4
+ end
5
+
6
+ def login
7
+ unless _is_empty?(current_user.login)
8
+ true
9
+ else
10
+ raise SourceAdapterLoginException.new('Error logging in')
11
+ end
12
+ end
13
+
14
+ def query(params=nil)
15
+ @result
16
+ end
17
+
18
+ def search(params=nil,txt='')
19
+ params[:foo] = 'bar'
20
+ if params['search'] == 'bar'
21
+ @result = {'obj'=>{'foo'=>'bar'}}
22
+ params['name'] = 'iPhone'
23
+ end
24
+ @result
25
+ end
26
+
27
+ def sync
28
+ super
29
+ end
30
+
31
+ def create(name_value_list,blob=nil)
32
+ 'obj4'
33
+ end
34
+
35
+ private
36
+ def _is_empty?(str)
37
+ str.length <= 0
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ require 'base_adapter'
2
+
3
+ class SubAdapter < BaseAdapter
4
+ def initialize(source,credential)
5
+ super(source,credential)
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Mygem
2
+ VERSION = '0.1.0'
3
+ class Mygem
4
+ def self.version
5
+ VERSION
6
+ end
7
+ end
8
+ end
@@ -0,0 +1 @@
1
+ require 'mygem/mygem'
@@ -0,0 +1,79 @@
1
+ require File.join(File.dirname(__FILE__),'..','spec_helper')
2
+
3
+ describe "BulkData" do
4
+ it_should_behave_like "SpecBootstrapHelper"
5
+ it_should_behave_like "SourceAdapterHelper"
6
+
7
+ after(:each) do
8
+ delete_data_directory
9
+ end
10
+
11
+ it "should return true if bulk data is completed" do
12
+ dbfile = create_datafile(File.join(@a.name,@u.id.to_s),@u.id.to_s)
13
+ data = BulkData.create(:name => bulk_data_docname(@a.id,@u.id),
14
+ :state => :completed,
15
+ :app_id => @a.id,
16
+ :user_id => @u.id,
17
+ :sources => [@s_fields[:name]])
18
+ data.dbfile = dbfile
19
+ data.completed?.should == true
20
+ end
21
+
22
+ it "should return false if bulk data isn't completed" do
23
+ data = BulkData.create(:name => bulk_data_docname(@a.id,@u.id),
24
+ :state => :inprogress,
25
+ :app_id => @a.id,
26
+ :user_id => @u.id,
27
+ :sources => [@s_fields[:name]])
28
+ data.completed?.should == false
29
+ end
30
+
31
+ it "should enqueue sqlite db type" do
32
+ BulkData.enqueue
33
+ Resque.peek(:bulk_data).should == {"args"=>[{}],
34
+ "class"=>"Rhosync::BulkDataJob"}
35
+ end
36
+
37
+ it "should generate correct bulk data name for user partition" do
38
+ BulkData.get_name(:user,@c).should == File.join(@a_fields[:name],@u_fields[:login],@u_fields[:login])
39
+ end
40
+
41
+ it "should generate correct bulk data name for app partition" do
42
+ BulkData.get_name(:app,@c).should ==
43
+ File.join(@a_fields[:name],@a_fields[:name])
44
+ end
45
+
46
+ it "should process_sources for bulk data" do
47
+ current = Time.now.to_i
48
+ @s.read_state.refresh_time = current
49
+ data = BulkData.create(:name => bulk_data_docname(@a.id,@u.id),
50
+ :state => :inprogress,
51
+ :app_id => @a.id,
52
+ :user_id => @u.id,
53
+ :sources => [@s_fields[:name]])
54
+ data.process_sources
55
+ @s.read_state.refresh_time.should >= current + @s_fields[:poll_interval].to_i
56
+ end
57
+
58
+ it "should delete source masterdoc copy on delete" do
59
+ set_state('test_db_storage' => @data)
60
+ data = BulkData.create(:name => bulk_data_docname(@a.id,@u.id),
61
+ :state => :inprogress,
62
+ :app_id => @a.id,
63
+ :user_id => @u.id,
64
+ :sources => [@s_fields[:name]])
65
+ data.process_sources
66
+ verify_result(@s.docname(:md_copy) => @data)
67
+ data.delete
68
+ verify_result(@s.docname(:md_copy) => {},
69
+ @s.docname(:md) => @data)
70
+ end
71
+ end
72
+
73
+ def create_datafile(dir,name)
74
+ dir = File.join(Rhosync.data_directory,dir)
75
+ FileUtils.mkdir_p(dir)
76
+ fname = File.join(dir,name+'.data')
77
+ File.open(fname,'wb') {|f| f.puts ''}
78
+ fname
79
+ end
@@ -0,0 +1,58 @@
1
+ require File.join(File.dirname(__FILE__),'spec_helper')
2
+
3
+ describe "Client" do
4
+ it_should_behave_like "SpecBootstrapHelper"
5
+ it_should_behave_like "SourceAdapterHelper"
6
+
7
+ it "should create client with fields" do
8
+ @c.id.length.should == 32
9
+ @c.device_type.should == @c_fields[:device_type]
10
+ end
11
+
12
+ it "should update_fields for a client" do
13
+ @c.update_fields({:device_type => 'android',:device_port => 100})
14
+ @c.device_type.should == 'android'
15
+ @c.device_port.should == '100'
16
+ end
17
+
18
+ it "should create client with user_id" do
19
+ @c.id.length.should == 32
20
+ @c.user_id.should == @c_fields[:user_id]
21
+ @u.clients.members.should == [@c.id]
22
+ end
23
+
24
+ it "should raise exception if source_name is nil" do
25
+ @c.source_name = nil
26
+ lambda {
27
+ @c.doc_suffix('foo')
28
+ }.should raise_error(InvalidSourceNameError, 'Invalid Source Name For Client')
29
+ end
30
+
31
+ it "should raise exception if license seats exceeded" do
32
+ Store.put_value(License::CLIENT_DOCKEY,100)
33
+ lambda { Client.create(@c_fields,{}) }.should raise_error(
34
+ LicenseSeatsExceededException, "WARNING: Maximum # of clients exceeded for this license."
35
+ )
36
+ end
37
+
38
+ it "should free seat when client is deleted" do
39
+ current = Store.get_value(License::CLIENT_DOCKEY).to_i
40
+ @c.delete
41
+ Store.get_value(License::CLIENT_DOCKEY).to_i.should == current - 1
42
+ end
43
+
44
+ it "should delete client and all associated documents" do
45
+ docname = @c.docname(:cd)
46
+ set_state(docname => @data)
47
+ @c.delete
48
+ verify_result(docname => {})
49
+ end
50
+
51
+ it "should create cd as masterdoc clone" do
52
+ set_state(@s.docname(:md_copy) => @data,
53
+ @c.docname(:cd) => {'foo' => {'bar' => 'abc'}})
54
+ @c.update_clientdoc([@s_fields[:name]])
55
+ verify_result(@c.docname(:cd) => @data,
56
+ @s.docname(:md_copy) => @data)
57
+ end
58
+ end
@@ -0,0 +1,377 @@
1
+ require File.join(File.dirname(__FILE__),'spec_helper')
2
+
3
+ describe "ClientSync" do
4
+ it_should_behave_like "SpecBootstrapHelper"
5
+ it_should_behave_like "SourceAdapterHelper"
6
+
7
+ it "should raise Argument error if no client or source is provided" do
8
+ lambda { ClientSync.new(@s,nil,2) }.should raise_error(ArgumentError,'Missing required attribute client')
9
+ lambda { ClientSync.new(nil,@c,2) }.should raise_error(ArgumentError,'Missing required attribute source')
10
+ end
11
+
12
+ before(:each) do
13
+ @cs = ClientSync.new(@s,@c,2)
14
+ end
15
+
16
+ describe "cud methods" do
17
+ it "should handle receive cud" do
18
+ params = {'create'=>{'1'=>@product1},'update'=>{'2'=>@product2},'delete'=>{'3'=>@product3}}
19
+ @cs.receive_cud(params)
20
+ verify_result(@cs.client.docname(:create) => {},
21
+ @cs.client.docname(:update) => {},
22
+ @cs.client.docname(:delete) => {})
23
+ end
24
+
25
+ it "should handle send cud" do
26
+ data = {'1'=>@product1,'2'=>@product2}
27
+ expected = {'insert'=>data}
28
+ set_test_data('test_db_storage',data)
29
+ @cs.send_cud.should == [{'version'=>ClientSync::VERSION},
30
+ {'token'=>@c.get_value(:page_token)},
31
+ {'count'=>data.size},{'progress_count'=>0},
32
+ {'total_count'=>data.size},expected]
33
+ verify_result(@cs.client.docname(:page) => data,
34
+ @cs.client.docname(:delete_page) => {},
35
+ @cs.client.docname(:cd) => data)
36
+ end
37
+
38
+ it "should return read errors in send cud" do
39
+ msg = "Error during query"
40
+ data = {'1'=>@product1,'2'=>@product2}
41
+ set_test_data('test_db_storage',data,msg,'query error')
42
+ @cs.send_cud.should == [{"version"=>ClientSync::VERSION},
43
+ {"token"=>""}, {"count"=>0}, {"progress_count"=>0},{"total_count"=>0},
44
+ {"source-error"=>{"query-error"=>{"message"=>msg}}}]
45
+ end
46
+
47
+ it "should return login errors in send cud" do
48
+ @u.login = nil
49
+ @cs.send_cud.should == [{"version"=>ClientSync::VERSION},{"token"=>""},
50
+ {"count"=>0}, {"progress_count"=>0}, {"total_count"=>0},
51
+ {'source-error'=>{"login-error"=>{"message"=>"Error logging in"}}}]
52
+ end
53
+
54
+ it "should return logoff errors in send cud" do
55
+ msg = "Error logging off"
56
+ set_test_data('test_db_storage',{},msg,'logoff error')
57
+ @cs.send_cud.should == [{"version"=>ClientSync::VERSION},
58
+ {"token"=>@c.get_value(:page_token)},
59
+ {"count"=>1}, {"progress_count"=>0}, {"total_count"=>1},
60
+ {"source-error"=>{"logoff-error"=>{"message"=>msg}},
61
+ "insert"=>{ERROR=>{"name"=>"logoff error", "an_attribute"=>msg}}}]
62
+ end
63
+
64
+ describe "send errors in send_cud" do
65
+ it "should handle create errors" do
66
+ receive_and_send_cud('create')
67
+ end
68
+
69
+ it "should handle update errors" do
70
+ receive_and_send_cud('update')
71
+ end
72
+
73
+ it "should handle delete errors" do
74
+ msg = "Error delete record"
75
+ error_objs = add_error_object({},"Error delete record")
76
+ op_data = {'delete'=>error_objs}
77
+ @cs.receive_cud(op_data)
78
+ @cs.send_cud.should == [{"version"=>ClientSync::VERSION},
79
+ {"token"=>""}, {"count"=>0}, {"progress_count"=>0}, {"total_count"=>0},
80
+ {"delete-error"=>{"#{ERROR}-error"=>{"message"=>msg},ERROR=>error_objs[ERROR]}}]
81
+ end
82
+
83
+ it "should send cud errors only once" do
84
+ msg = "Error delete record"
85
+ error_objs = add_error_object({},"Error delete record")
86
+ op_data = {'delete'=>error_objs}
87
+ @cs.receive_cud(op_data)
88
+ @cs.send_cud.should == [{"version"=>ClientSync::VERSION},
89
+ {"token"=>""}, {"count"=>0}, {"progress_count"=>0}, {"total_count"=>0},
90
+ {"delete-error"=>{"#{ERROR}-error"=>{"message"=>msg},ERROR=>error_objs[ERROR]}}]
91
+ verify_result(@c.docname(:delete_errors) => {})
92
+ @cs.send_cud.should == [{"version"=>ClientSync::VERSION},
93
+ {"token"=>""}, {"count"=>0}, {"progress_count"=>0}, {"total_count"=>0},{}]
94
+ end
95
+
96
+ def receive_and_send_cud(operation)
97
+ msg = "Error #{operation} record"
98
+ op_data = {operation=>{ERROR=>{'an_attribute'=>msg,'name'=>'wrongname'}}}
99
+ @cs.receive_cud(op_data)
100
+ @cs.send_cud.should == [{"version"=>ClientSync::VERSION},
101
+ {"token"=>""}, {"count"=>0}, {"progress_count"=>0}, {"total_count"=>0},
102
+ {"#{operation}-error"=>{"#{ERROR}-error"=>{"message"=>msg},ERROR=>op_data[operation][ERROR]}}]
103
+ end
104
+ end
105
+
106
+ it "should handle receive_cud" do
107
+ set_state(@s.docname(:md) => {'3'=>@product3},
108
+ @c.docname(:cd) => {'3'=>@product3})
109
+ params = {'create'=>{'1'=>@product1},
110
+ 'update'=>{'2'=>@product2},'delete'=>{'3'=>@product3}}
111
+ @cs.receive_cud(params)
112
+ verify_result(@cs.client.docname(:create) => {},
113
+ @cs.client.docname(:update) => {},
114
+ @cs.client.docname(:delete) => {},
115
+ @s.docname(:md) => {},
116
+ @c.docname(:cd) => {})
117
+ end
118
+
119
+ it "should handle blob upload in receive_cud" do
120
+ pending
121
+ end
122
+
123
+ it "should handle send_cud with query_params" do
124
+ expected = {'1'=>@product1}
125
+ set_state('test_db_storage' => {'1'=>@product1,'2'=>@product2,'4'=>@product4})
126
+ params = {'name' => 'iPhone'}
127
+ @cs.send_cud(nil,params)
128
+ verify_result(@s.docname(:md) => expected,
129
+ @cs.client.docname(:page) => expected)
130
+ end
131
+ end
132
+
133
+ describe "reset" do
134
+ it "should handle reset" do
135
+ set_state(@c.docname(:cd) => @data)
136
+ ClientSync.reset(@c)
137
+ verify_result(@c.docname(:cd) => {})
138
+ Client.load(@c.id,{:source_name => @s.name}).should_not be_nil
139
+ end
140
+ end
141
+
142
+ describe "search" do
143
+ before(:each) do
144
+ @s_fields[:name] = 'SimpleAdapter'
145
+ @c1 = Client.create(@c_fields,{:source_name => @s_fields[:name]})
146
+ @s1 = Source.create(@s_fields,@s_params)
147
+ @cs1 = ClientSync.new(@s1,@c1,2)
148
+ end
149
+
150
+ it "should handle search" do
151
+ params = {:search => {'name' => 'iPhone'}}
152
+ set_state('test_db_storage' => @data)
153
+ res = @cs.search(params)
154
+ token = @c.get_value(:search_token)
155
+ res.should == [{'version'=>ClientSync::VERSION},{'search_token'=>token},
156
+ {'source'=>@s.name},{'count'=>1},{'insert'=>{'1'=>@product1}}]
157
+ verify_result(@c.docname(:search) => {'1'=>@product1},
158
+ @c.docname(:search_errors) => {})
159
+ end
160
+
161
+ it "should handle search with nil result" do
162
+ params = {:search => {'name' => 'foo'}}
163
+ set_state('test_db_storage' => @data)
164
+ @cs.search(params).should == []
165
+ verify_result(@c.docname(:search) => {},
166
+ @c.docname(:search_errors) => {})
167
+ end
168
+
169
+ it "should resend search by search_token" do
170
+ @source = @s
171
+ set_state({@c.docname(:search) => {'1'=>@product1}})
172
+ token = compute_token @cs.client.docname(:search_token)
173
+ @cs.search({:resend => true,:search_token => token}).should == [{'version'=>ClientSync::VERSION},
174
+ {'search_token'=>token},{'source'=>@s.name},{'count'=>1},{'insert'=>{'1'=>@product1}}]
175
+ verify_result(@c.docname(:search) => {'1'=>@product1},
176
+ @c.docname(:search_errors) => {},
177
+ @cs.client.docname(:search_token) => token)
178
+ end
179
+
180
+ it "should handle search ack" do
181
+ @source = @s
182
+ set_state({@c.docname(:search) => {'1'=>@product1}})
183
+ token = compute_token @cs.client.docname(:search_token)
184
+ @cs.search({:search_token => token}).should == []
185
+ verify_result(@c.docname(:search) => {},
186
+ @c.docname(:search_errors) => {},
187
+ @cs.client.docname(:search_token) => nil)
188
+ end
189
+
190
+ it "should handle search all" do
191
+ sources = ['SampleAdapter']
192
+ set_state('test_db_storage' => @data)
193
+ res = ClientSync.search_all(@c,{:sources => sources,:search => {'name' => 'iPhone'}})
194
+ token = Store.get_value(@cs.client.docname(:search_token))
195
+ res.should == [[{'version'=>ClientSync::VERSION},{'search_token'=>token},
196
+ {'source'=>sources[0]},{'count'=>1},{'insert'=>{'1'=>@product1}}]]
197
+ verify_result(@c.docname(:search) => {'1'=>@product1},
198
+ @c.docname(:search_errors) => {})
199
+ end
200
+
201
+ it "should handle search all error" do
202
+ sources = ['SampleAdapter']
203
+ msg = "Error during search"
204
+ error = set_test_data('test_db_storage',@data,msg,'search error')
205
+ res = ClientSync.search_all(@c,{:sources => sources,:search => {'name' => 'iPhone'}})
206
+ token = Store.get_value(@cs.client.docname(:search_token))
207
+ res.should == [[{'version'=>ClientSync::VERSION},
208
+ {'source'=>sources[0]},{'search-error'=>{'search-error'=>{'message'=>msg}}}]]
209
+ verify_result(@c.docname(:search) => {},
210
+ @c.docname(:search_errors) => {'search-error'=>{'message'=>msg}})
211
+ end
212
+
213
+ it "should handle search all login error" do
214
+ @u.login = nil
215
+ sources = ['SampleAdapter']
216
+ msg = "Error logging in"
217
+ error = set_test_data('test_db_storage',@data,msg,'search error')
218
+ ClientSync.search_all(@c,{:sources => sources,:search => {'name' => 'iPhone'}}).should == [
219
+ [{'version'=>ClientSync::VERSION},{'source'=>sources[0]},
220
+ {'search-error'=>{'login-error'=>{'message'=>msg}}}]]
221
+ verify_result(@c.docname(:search) => {},
222
+ @c.docname(:search_errors) => {'login-error'=>{'message'=>msg}},
223
+ @c.docname(:search_token) => nil)
224
+ end
225
+
226
+ it "should handle multiple source search all" do
227
+ set_test_data('test_db_storage',@data)
228
+ sources = ['SampleAdapter','SimpleAdapter']
229
+ res = ClientSync.search_all(@c,{:sources => sources,:search => {'name' => 'iPhone'}})
230
+ @c.source_name = 'SampleAdapter'
231
+ token = Store.get_value(@c.docname(:search_token))
232
+ res.should == [[{"version"=>ClientSync::VERSION},{'search_token'=>token},
233
+ {"source"=>"SampleAdapter"},{"count"=>1},{"insert"=>{'1'=>@product1}}],[]]
234
+ end
235
+
236
+ it "should handle search and accumulate params" do
237
+ set_test_data('test_db_storage',@data)
238
+ sources = ['SimpleAdapter','SampleAdapter']
239
+ res = ClientSync.search_all(@c,{:sources => sources,:search => {'search'=>'bar'}})
240
+ @c.source_name = 'SimpleAdapter'
241
+ token = Store.get_value(@c.docname(:search_token))
242
+ @c.source_name = 'SampleAdapter'
243
+ token1 = Store.get_value(@c.docname(:search_token))
244
+ res.should == [[{"version"=>ClientSync::VERSION}, {'search_token'=>token},
245
+ {"source"=>"SimpleAdapter"},{"count"=>1},{"insert"=>{'obj'=>{'foo'=>'bar'}}}],
246
+ [{"version"=>ClientSync::VERSION},{'search_token'=>token1},{"source"=>"SampleAdapter"},
247
+ {"count"=>1}, {"insert"=>{'1'=>@product1}}]]
248
+ end
249
+ end
250
+
251
+ describe "page methods" do
252
+ it "should return diffs between master documents and client documents limited by page size" do
253
+ Store.put_data(@s.docname(:md),@data).should == true
254
+ Store.get_data(@s.docname(:md)).should == @data
255
+ Store.put_value(@s.docname(:md_size),@data.size)
256
+ @expected = {'1'=>@product1,'2'=>@product2}
257
+ @cs.compute_page.should == [0,3,@expected]
258
+ Store.get_value(@cs.client.docname(:cd_size)).to_i.should == 0
259
+ Store.get_data(@cs.client.docname(:page)).should == @expected
260
+ end
261
+
262
+ it "appends diff to the client document" do
263
+ @cd = {'3'=>@product3}
264
+ Store.put_data(@c.docname(:cd),@cd)
265
+ Store.get_data(@c.docname(:cd)).should == @cd
266
+
267
+ @page = {'1'=>@product1,'2'=>@product2}
268
+ @expected = {'1'=>@product1,'2'=>@product2,'3'=>@product3}
269
+
270
+ Store.put_data(@c.docname(:cd),@page,true).should == true
271
+ Store.get_data(@c.docname(:cd)).should == @expected
272
+ end
273
+
274
+ it "should return deleted objects in the client document" do
275
+ Store.put_data(@s.docname(:md),@data).should == true
276
+ Store.get_data(@s.docname(:md)).should == @data
277
+
278
+ @cd = {'1'=>@product1,'2'=>@product2,'3'=>@product3,'4'=>@product4}
279
+ Store.put_data(@cs.client.docname(:cd),@cd)
280
+ Store.get_data(@cs.client.docname(:cd)).should == @cd
281
+
282
+ @expected = {'4'=>@product4}
283
+ @cs.compute_deleted_page.should == @expected
284
+ Store.get_data(@cs.client.docname(:delete_page)).should == @expected
285
+ end
286
+
287
+ it "should delete objects from client document" do
288
+ Store.put_data(@s.docname(:md),@data).should == true
289
+ Store.get_data(@s.docname(:md)).should == @data
290
+
291
+ @cd = {'1'=>@product1,'2'=>@product2,'3'=>@product3,'4'=>@product4}
292
+ Store.put_data(@cs.client.docname(:cd),@cd)
293
+ Store.get_data(@cs.client.docname(:cd)).should == @cd
294
+
295
+ Store.delete_data(@cs.client.docname(:cd),@cs.compute_deleted_page).should == true
296
+ Store.get_data(@cs.client.docname(:cd)).should == @data
297
+ end
298
+
299
+ it "should resend page if page exists and no token provided" do
300
+ expected = {'1'=>@product1}
301
+ set_test_data('test_db_storage',{'1'=>@product1,'2'=>@product2,'4'=>@product4})
302
+ params = {'name' => 'iPhone'}
303
+ @cs.send_cud(nil,params)
304
+ token = @c.get_value(:page_token)
305
+ @cs.send_cud.should == [{"version"=>ClientSync::VERSION},{"token"=>token},
306
+ {"count"=>1}, {"progress_count"=>0},{"total_count"=>1},{'insert' => expected}]
307
+ @cs.send_cud(token).should == [{"version"=>ClientSync::VERSION},{"token"=>""},
308
+ {"count"=>0}, {"progress_count"=>1}, {"total_count"=>1}, {}]
309
+ Store.get_data(@cs.client.docname(:page)).should == {}
310
+ @c.get_value(:page_token).should be_nil
311
+ end
312
+ end
313
+
314
+ describe "bulk data" do
315
+ after(:each) do
316
+ delete_data_directory
317
+ end
318
+
319
+ it "should create bulk data job user parition if none exists" do
320
+ ClientSync.bulk_data(:user,@c).should == {:result => :wait}
321
+ Resque.peek(:bulk_data).should == {"args"=>
322
+ [{"data_name"=>File.join(@a_fields[:name],@u_fields[:login],@u_fields[:login])}],
323
+ "class"=>"Rhosync::BulkDataJob"}
324
+ end
325
+
326
+ it "should create bulk data job app partition if none exists and no parition sources" do
327
+ ClientSync.bulk_data(:app,@c).should == {:result => :nop}
328
+ Resque.peek(:bulk_data).should == nil
329
+ end
330
+
331
+ it "should create bulk data job app partition with partition sources" do
332
+ @s.partition = :app
333
+ ClientSync.bulk_data(:app,@c).should == {:result => :wait}
334
+ Resque.peek(:bulk_data).should == {"args"=>
335
+ [{"data_name"=>File.join(@a_fields[:name],@a_fields[:name])}],
336
+ "class"=>"Rhosync::BulkDataJob"}
337
+ end
338
+
339
+ it "should return bulk data url for completed bulk data user partition" do
340
+ set_state('test_db_storage' => @data)
341
+ ClientSync.bulk_data(:user,@c)
342
+ BulkDataJob.perform("data_name" => bulk_data_docname(@a.id,@u.id))
343
+ ClientSync.bulk_data(:user,@c).should == {:result => :url,
344
+ :url => BulkData.load(bulk_data_docname(@a.id,@u.id)).dbfile}
345
+ verify_result(
346
+ "client:#{@a_fields[:name]}:#{@u_fields[:login]}:#{@c.id}:#{@s_fields[:name]}:cd" => @data,
347
+ "source:#{@a_fields[:name]}:#{@u_fields[:login]}:#{@s_fields[:name]}:md" => @data,
348
+ "source:#{@a_fields[:name]}:#{@u_fields[:login]}:#{@s_fields[:name]}:md_copy" => @data)
349
+ end
350
+
351
+ it "should return bulk data url for completed bulk data app partition" do
352
+ set_state('test_db_storage' => @data)
353
+ @s.partition = :app
354
+ ClientSync.bulk_data(:app,@c)
355
+ BulkDataJob.perform("data_name" => bulk_data_docname(@a.id,"*"))
356
+ ClientSync.bulk_data(:app,@c).should == {:result => :url,
357
+ :url => BulkData.load(bulk_data_docname(@a.id,"*")).dbfile}
358
+ verify_result(
359
+ "client:#{@a_fields[:name]}:#{@u_fields[:login]}:#{@c.id}:#{@s_fields[:name]}:cd" => @data,
360
+ "source:#{@a_fields[:name]}:__shared__:#{@s_fields[:name]}:md" => @data,
361
+ "source:#{@a_fields[:name]}:__shared__:#{@s_fields[:name]}:md_copy" => @data)
362
+ end
363
+
364
+ it "should return bulk data url for completed bulk data with bulk_sync_only source" do
365
+ set_state('test_db_storage' => @data)
366
+ @s.sync_type = :bulk_sync_only
367
+ ClientSync.bulk_data(:user,@c)
368
+ BulkDataJob.perform("data_name" => bulk_data_docname(@a.id,@u.id))
369
+ ClientSync.bulk_data(:user,@c).should == {:result => :url,
370
+ :url => BulkData.load(bulk_data_docname(@a.id,@u.id)).dbfile}
371
+ verify_result(
372
+ "client:#{@a_fields[:name]}:#{@u_fields[:login]}:#{@c.id}:#{@s_fields[:name]}:cd" => {},
373
+ "source:#{@a_fields[:name]}:#{@u_fields[:login]}:#{@s_fields[:name]}:md" => @data,
374
+ "source:#{@a_fields[:name]}:#{@u_fields[:login]}:#{@s_fields[:name]}:md_copy" => {})
375
+ end
376
+ end
377
+ end