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