rhoconnect 4.0.4 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +5 -13
  2. data/CHANGELOG.md +54 -5
  3. data/CREDITS +219 -219
  4. data/Gemfile +2 -2
  5. data/Gemfile.lock +68 -79
  6. data/Rakefile +1 -2
  7. data/bench/benchapp/spec/models/ruby/mock_adapter_spec.rb +17 -17
  8. data/bench/benchapp/spec/models/ruby/queue_mock_adapter_spec.rb +17 -17
  9. data/bench/benchapp/spec/spec_helper.rb +3 -3
  10. data/bench/blobapp/spec/models/ruby/blob_adapter_spec.rb +17 -17
  11. data/bench/blobapp/spec/spec_helper.rb +3 -3
  12. data/bench/lib/bench/runner.rb +1 -69
  13. data/bench/lib/bench.rb +18 -18
  14. data/bench/spec/mock_adapter_spec.rb +54 -55
  15. data/bench/spec/mock_client_spec.rb +47 -48
  16. data/bench/spec/result_spec.rb +41 -44
  17. data/bench/spec/utils_spec.rb +24 -25
  18. data/commands/generators/app.rb +7 -5
  19. data/commands/generators/controller.rb +7 -5
  20. data/commands/generators/model.rb +7 -5
  21. data/commands/generators/source.rb +7 -5
  22. data/commands/parser.rb +1 -1
  23. data/commands/redis/redis_download.rb +1 -1
  24. data/doc/async-jobs.txt +9 -9
  25. data/doc/supported-platforms.txt +0 -2
  26. data/generators/rhoconnect.rb +92 -212
  27. data/generators/templates/application/rcgemfile +3 -3
  28. data/generators/templates/application/spec/application_controller_spec.rb +14 -16
  29. data/generators/templates/application/spec/js_spec.rb +20 -20
  30. data/generators/templates/application/spec/spec_helper.rb +1 -1
  31. data/generators/templates/source/controllers/ruby/controller_spec.rb +18 -19
  32. data/generators/templates/source/models/ruby/model_spec.rb +17 -17
  33. data/install.sh +10 -21
  34. data/installer/unix-like/rho_connect_install_constants.rb +5 -5
  35. data/installer/unix-like/rho_connect_install_installers.rb +4 -4
  36. data/installer/utils/constants.rb +6 -6
  37. data/installer/utils/nix_install_test.rb +29 -29
  38. data/installer/utils/package_upload/repos.rake +16 -26
  39. data/js-adapters/node.rb +4 -4
  40. data/js-adapters/node_channel.rb +4 -8
  41. data/lib/rhoconnect/db_adapter.rb +13 -13
  42. data/lib/rhoconnect/handler/changes/engine.rb +1 -1
  43. data/lib/rhoconnect/jobs/bulk_data_job.rb +29 -29
  44. data/lib/rhoconnect/license.rb +7 -7
  45. data/lib/rhoconnect/model/helpers/find_duplicates_on_update.rb +13 -13
  46. data/lib/rhoconnect/ping/apple.rb +4 -4
  47. data/lib/rhoconnect/server.rb +2 -2
  48. data/lib/rhoconnect/source.rb +2 -2
  49. data/lib/rhoconnect/store.rb +12 -6
  50. data/lib/rhoconnect/utilities.rb +2 -2
  51. data/lib/rhoconnect/version.rb +1 -1
  52. data/lib/rhoconnect.rb +6 -4
  53. data/rhoconnect.gemspec +5 -6
  54. data/spec/api/api_helper.rb +1 -1
  55. data/spec/api/app/fast_delete_spec.rb +22 -22
  56. data/spec/api/app/fast_insert_spec.rb +23 -23
  57. data/spec/api/app/fast_update_spec.rb +63 -63
  58. data/spec/api/app/push_deletes_spec.rb +11 -13
  59. data/spec/api/app/push_objects_spec.rb +39 -39
  60. data/spec/api/client/client_get_db_doc_spec.rb +29 -29
  61. data/spec/api/client/client_set_db_doc_spec.rb +11 -11
  62. data/spec/api/client/get_client_params_spec.rb +29 -29
  63. data/spec/api/client/list_client_docs_spec.rb +32 -34
  64. data/spec/api/client/reset_spec.rb +30 -30
  65. data/spec/api/readstate/set_refresh_time_spec.rb +43 -43
  66. data/spec/api/source/get_source_params_spec.rb +32 -34
  67. data/spec/api/source/list_sources_spec.rb +13 -13
  68. data/spec/api/source/update_source_params_spec.rb +19 -19
  69. data/spec/api/store/get_db_doc_spec.rb +27 -27
  70. data/spec/api/store/set_db_doc_spec.rb +38 -38
  71. data/spec/api/system/adapter_spec.rb +27 -29
  72. data/spec/api/system/get_license_info_spec.rb +11 -11
  73. data/spec/api/system/login_spec.rb +37 -37
  74. data/spec/api/system/reset_spec.rb +15 -15
  75. data/spec/api/system/stats_spec.rb +70 -71
  76. data/spec/api/user/create_user_spec.rb +37 -37
  77. data/spec/api/user/delete_client_spec.rb +7 -7
  78. data/spec/api/user/delete_user_spec.rb +62 -62
  79. data/spec/api/user/list_clients_spec.rb +24 -24
  80. data/spec/api/user/list_source_docs_spec.rb +29 -29
  81. data/spec/api/user/list_users_spec.rb +22 -22
  82. data/spec/api/user/ping_spec.rb +18 -18
  83. data/spec/api/user/show_user_spec.rb +10 -10
  84. data/spec/api/user/update_user_spec.rb +43 -43
  85. data/spec/api/user/user_get_db_doc_spec.rb +12 -12
  86. data/spec/api/user/user_set_db_doc_spec.rb +37 -37
  87. data/spec/api_token_spec.rb +8 -8
  88. data/spec/app_spec.rb +18 -17
  89. data/spec/apps/jstestapp/settings/settings.yml +2 -0
  90. data/spec/async_spec.rb +9 -11
  91. data/spec/bulk_data/bulk_data_spec.rb +120 -120
  92. data/spec/cli/cli_spec.rb +50 -53
  93. data/spec/client_spec.rb +105 -105
  94. data/spec/client_sync_spec.rb +529 -528
  95. data/spec/controllers/js_base_spec.rb +147 -141
  96. data/spec/doc/doc_spec.rb +51 -52
  97. data/spec/document_spec.rb +58 -58
  98. data/spec/dynamic_adapter_spec.rb +33 -36
  99. data/spec/generator/generator_spec.rb +76 -42
  100. data/spec/jobs/bulk_data_job_spec.rb +101 -102
  101. data/spec/jobs/ping_job_spec.rb +176 -177
  102. data/spec/jobs/source_job_spec.rb +24 -25
  103. data/spec/license_spec.rb +54 -55
  104. data/spec/models/js_base_spec.rb +121 -120
  105. data/spec/perf/bulk_data_perf_spec.rb +23 -24
  106. data/spec/perf/perf_spec_helper.rb +7 -7
  107. data/spec/perf/store_perf_spec.rb +139 -140
  108. data/spec/ping/apple_spec.rb +65 -65
  109. data/spec/ping/gcm_spec.rb +83 -84
  110. data/spec/ping/rhoconnect_push_spec.rb +52 -53
  111. data/spec/predefined_adapters/rho_internal_bench_adapter_controller_js_spec.rb +100 -101
  112. data/spec/predefined_adapters/rho_internal_js_bench_adapter_js_spec.rb +29 -31
  113. data/spec/read_state_spec.rb +24 -25
  114. data/spec/rhoconnect_spec.rb +7 -7
  115. data/spec/server/server_spec.rb +664 -662
  116. data/spec/server/stats_spec.rb +12 -12
  117. data/spec/source_adapter_spec.rb +124 -125
  118. data/spec/source_spec.rb +148 -149
  119. data/spec/source_sync_spec.rb +736 -736
  120. data/spec/spec_helper.rb +4 -5
  121. data/spec/stats/record_spec.rb +22 -21
  122. data/spec/store_orm_spec.rb +48 -48
  123. data/spec/store_spec.rb +428 -426
  124. data/spec/support/shared_examples.rb +5 -7
  125. data/spec/sync_states_spec.rb +67 -67
  126. data/spec/test_methods_spec.rb +121 -123
  127. data/spec/testdata/10000-data.txt +0 -0
  128. data/spec/testdata/5000-data.txt +0 -0
  129. data/spec/user_spec.rb +102 -102
  130. data/tasks/redis.rake +3 -3
  131. metadata +154 -195
  132. data/bench/benchapp/tmp/pids/passenger.9292.pid.lock +0 -0
  133. data/bench/benchapp/tmp/restart.txt +0 -0
  134. data/bench/blobapp/settings/license.key.bak +0 -2
  135. data/bench/blobapp/tmp/restart.txt +0 -0
  136. data/bench/lib/testdata/1-data.txt +0 -0
  137. data/bench/lib/testdata/10-data.txt +0 -0
  138. data/bench/lib/testdata/2-data.txt +0 -0
  139. data/bench/lib/testdata/250-data.txt +0 -0
  140. data/bench/lib/testdata/5-blob_data.txt +0 -0
  141. data/bench/lib/testdata/5-data.txt +0 -0
  142. data/bench/lib/testdata/50-data.txt +0 -0
  143. data/bench/lib/testdata/500-data.txt +0 -0
  144. data/doc/protocol.html +0 -1993
  145. data/spec/coverage/rcov/assets/0.2.3/jquery-1.3.2.min.js +0 -19
  146. data/spec/coverage/rcov/assets/0.2.3/jquery.tablesorter.min.js +0 -15
  147. data/spec/coverage/rcov/assets/0.2.3/print.css +0 -12
  148. data/spec/coverage/rcov/assets/0.2.3/rcov.js +0 -42
  149. data/spec/coverage/rcov/assets/0.2.3/screen.css +0 -270
  150. data/spec/coverage/rcov/index.html +0 -88
  151. data/spec/generator/generator_spec_helper.rb +0 -9
@@ -1,819 +1,819 @@
1
1
  require File.join(File.dirname(__FILE__),'spec_helper')
2
2
 
3
3
  describe "SourceSync" do
4
- it_behaves_like "SharedRhoconnectHelper", :rhoconnect_data => true do
5
- before(:each) do
6
- @s = Source.load(@s_fields[:name],@s_params)
4
+ include_examples "SharedRhoconnectHelper", :rhoconnect_data => true
5
+
6
+ before(:each) do
7
+ @s = Source.load(@s_fields[:name],@s_params)
8
+ @model = Rhoconnect::Model::Base.create(@s)
9
+ end
10
+
11
+ let(:mock_schema) { {"property" => { "name" => "string", "brand" => "string" }, "version" => "1.0"} }
12
+
13
+ describe "SourceSync query methods" do
14
+ before (:each) do
15
+ rh = lambda { @model.query(params[:query])}
7
16
  @model = Rhoconnect::Model::Base.create(@s)
17
+ @ssq = Rhoconnect::Handler::Query::Engine.new(@model, rh, {})
8
18
  end
9
19
 
10
- let(:mock_schema) { {"property" => { "name" => "string", "brand" => "string" }, "version" => "1.0"} }
11
-
12
- describe "SourceSync query methods" do
13
- before (:each) do
14
- rh = lambda { @model.query(params[:query])}
15
- @model = Rhoconnect::Model::Base.create(@s)
16
- @ssq = Rhoconnect::Handler::Query::Engine.new(@model, rh, {})
17
- end
20
+ it "should create Rhoconnect::Handler::Query::Engine" do
21
+ @ssq.model.is_a?(SampleAdapter).should == true
22
+ end
18
23
 
19
- it "should create Rhoconnect::Handler::Query::Engine" do
20
- @ssq.model.is_a?(SampleAdapter).should == true
21
- end
24
+ it "should fail to create Rhoconnect::Handler::Query::Engine with InvalidArgumentError without source" do
25
+ lambda { Rhoconnect::Handler::Query::Engine.new(nil, nil, {}) }.should raise_error(ArgumentError, 'Unknown source')
26
+ end
22
27
 
23
- it "should fail to create Rhoconnect::Handler::Query::Engine with InvalidArgumentError without source" do
24
- lambda { Rhoconnect::Handler::Query::Engine.new(nil, nil, {}) }.should raise_error(ArgumentError, 'Unknown source')
25
- end
28
+ it "should fail to create Rhoconnect::Handler::Query::Engine with InvalidArgumentError without proc handler" do
29
+ lambda { Rhoconnect::Handler::Query::Engine.new(@model, nil) }.should raise_error(ArgumentError, 'Invalid sync handler')
30
+ end
26
31
 
27
- it "should fail to create Rhoconnect::Handler::Query::Engine with InvalidArgumentError without proc handler" do
28
- lambda { Rhoconnect::Handler::Query::Engine.new(@model, nil) }.should raise_error(ArgumentError, 'Invalid sync handler')
29
- end
32
+ it "should raise LoginException if login fails" do
33
+ msg = "Error logging in"
34
+ @u.login = nil
35
+ @ssq.should_receive(:log).with("Model raised login exception: #{msg}")
36
+ @ssq.should_receive(:log).with(anything)
37
+ @ssq.do_sync
38
+ verify_doc_result(@s, :errors => {'login-error'=>{'message'=>msg}})
39
+ end
30
40
 
31
- it "should raise LoginException if login fails" do
32
- msg = "Error logging in"
33
- @u.login = nil
34
- @ssq.should_receive(:log).with("Model raised login exception: #{msg}")
35
- @ssq.should_receive(:log).with(anything)
36
- @ssq.do_sync
37
- verify_doc_result(@s, :errors => {'login-error'=>{'message'=>msg}})
38
- end
41
+ it "should raise LogoffException if logoff fails" do
42
+ msg = "Error logging off"
43
+ @ssq.should_receive(:log).with("Model raised logoff exception: #{msg}")
44
+ @ssq.should_receive(:log).with(anything)
45
+ set_test_data('test_db_storage',{},msg,'logoff error')
46
+ @ssq.do_sync
47
+ verify_doc_result(@s, :errors => {'logoff-error'=>{'message'=>msg}})
48
+ end
39
49
 
40
- it "should raise LogoffException if logoff fails" do
41
- msg = "Error logging off"
42
- @ssq.should_receive(:log).with("Model raised logoff exception: #{msg}")
43
- @ssq.should_receive(:log).with(anything)
44
- set_test_data('test_db_storage',{},msg,'logoff error')
45
- @ssq.do_sync
46
- verify_doc_result(@s, :errors => {'logoff-error'=>{'message'=>msg}})
47
- end
50
+ it "should hold on read on subsequent call of process if default poll interval is not exprired" do
51
+ expected = {'1'=>@product1}
52
+ set_state('test_db_storage' => expected)
53
+ @ssq.do_sync
54
+ set_state('test_db_storage' => {'2'=>@product2})
55
+ @ssq.do_sync
56
+ verify_doc_result(@s, :md => expected)
57
+ end
48
58
 
49
- it "should hold on read on subsequent call of process if default poll interval is not exprired" do
50
- expected = {'1'=>@product1}
59
+ it "should read on every subsequent call of process if poll interval is set to 0" do
60
+ expected = {'2'=>@product2}
61
+ @s.poll_interval = 0
62
+ set_state('test_db_storage' => {'1'=>@product1})
63
+ @ssq.do_sync
64
+ set_state('test_db_storage' => expected)
65
+ @ssq.do_sync
66
+ verify_doc_result(@s, :md => expected)
67
+ end
68
+
69
+ it "should never call read on any call of process if poll interval is set to -1" do
70
+ @s.poll_interval = -1
71
+ set_state('test_db_storage' => {'1'=>@product1})
72
+ @ssq.do_sync
73
+ verify_doc_result(@s, :md => {})
74
+ end
75
+
76
+
77
+ it "should process model metadata" do
78
+ mock_metadata_method([SampleAdapter, SimpleAdapter]) do
79
+ expected = {'1'=>@product1,'2'=>@product2}
51
80
  set_state('test_db_storage' => expected)
52
81
  @ssq.do_sync
53
- set_state('test_db_storage' => {'2'=>@product2})
54
- @ssq.do_sync
55
- verify_doc_result(@s, :md => expected)
82
+ verify_doc_result(@s, {:md => expected,
83
+ :metadata => "{\"foo\":\"bar\"}",
84
+ :metadata_sha1 => "a5e744d0164540d33b1d7ea616c28f2fa97e754a"})
56
85
  end
86
+ end
57
87
 
58
- it "should read on every subsequent call of process if poll interval is set to 0" do
59
- expected = {'2'=>@product2}
60
- @s.poll_interval = 0
61
- set_state('test_db_storage' => {'1'=>@product1})
62
- @ssq.do_sync
88
+ it "should process model schema" do
89
+ mock_schema_method([SampleAdapter]) do
90
+ expected = {'1'=>@product1,'2'=>@product2}
63
91
  set_state('test_db_storage' => expected)
64
92
  @ssq.do_sync
65
- verify_doc_result(@s, :md => expected)
93
+ verify_doc_result(@s, :md => expected)
94
+ JSON.parse(@s.get_value(:schema)).should == mock_schema
95
+ @s.get_value(:schema_sha1).should == get_sha1(mock_schema['version'])
66
96
  end
97
+ end
67
98
 
68
- it "should never call read on any call of process if poll interval is set to -1" do
69
- @s.poll_interval = -1
70
- set_state('test_db_storage' => {'1'=>@product1})
99
+ it "should raise exception if model schema has no version key/value pair" do
100
+ mock_schema_no_version_method([SampleAdapter]) do
101
+ expected = {'1'=>@product1,'2'=>@product2}
102
+ set_state('test_db_storage' => expected)
71
103
  @ssq.do_sync
72
- verify_doc_result(@s, :md => {})
73
- end
74
-
75
-
76
- it "should process model metadata" do
77
- mock_metadata_method([SampleAdapter, SimpleAdapter]) do
78
- expected = {'1'=>@product1,'2'=>@product2}
79
- set_state('test_db_storage' => expected)
80
- @ssq.do_sync
81
- verify_doc_result(@s, {:md => expected,
82
- :metadata => "{\"foo\":\"bar\"}",
83
- :metadata_sha1 => "a5e744d0164540d33b1d7ea616c28f2fa97e754a"})
84
- end
104
+ errors = {}
105
+ @s.lock(:errors) { errors = @s.get_data(:errors) }
106
+ errors.empty?().should == false
107
+ errors["query-error"]["message"].should == "Mandatory version key is not defined in model schema method"
85
108
  end
109
+ end
86
110
 
87
- it "should process model schema" do
88
- mock_schema_method([SampleAdapter]) do
89
- expected = {'1'=>@product1,'2'=>@product2}
90
- set_state('test_db_storage' => expected)
91
- @ssq.do_sync
92
- verify_doc_result(@s, :md => expected)
93
- JSON.parse(@s.get_value(:schema)).should == mock_schema
94
- @s.get_value(:schema_sha1).should == get_sha1(mock_schema['version'])
95
- end
96
- end
111
+ it "should process model with stash" do
112
+ expected = {'1'=>@product1,'2'=>@product2}
113
+ set_state('test_db_storage' => expected)
114
+ @ssq.params[:query] = {'stash_result' => true}
115
+ @ssq.do_sync
116
+ #@ssq.model.should_receive(:stash_result).once
117
+ verify_doc_result(@s, {:md => expected,
118
+ :md_size => expected.size.to_s})
119
+ end
97
120
 
98
- it "should raise exception if model schema has no version key/value pair" do
99
- mock_schema_no_version_method([SampleAdapter]) do
100
- expected = {'1'=>@product1,'2'=>@product2}
101
- set_state('test_db_storage' => expected)
102
- @ssq.do_sync
103
- errors = {}
104
- @s.lock(:errors) { errors = @s.get_data(:errors) }
105
- errors.empty?().should == false
106
- errors["query-error"]["message"].should == "Mandatory version key is not defined in model schema method"
107
- end
108
- end
121
+ it "should process model with pass_through set" do
122
+ expected = {'1'=>@product1,'2'=>@product2}
123
+ set_state('test_db_storage' => expected)
124
+ @s.pass_through = 'true'
125
+ @ssq.do_sync.should == expected
126
+ verify_doc_result(@s, {:md => {},
127
+ :md_size => nil})
128
+ @s.pass_through = nil
129
+ end
109
130
 
110
- it "should process model with stash" do
131
+ it "should call methods in model" do
132
+ mock_metadata_method([SampleAdapter, SimpleAdapter]) do
111
133
  expected = {'1'=>@product1,'2'=>@product2}
112
- set_state('test_db_storage' => expected)
113
- @ssq.params[:query] = {'stash_result' => true}
134
+ metadata = "{\"foo\":\"bar\"}"
135
+ @ssq.model.should_receive(:login).once.with(no_args()).and_return(true)
136
+ @ssq.model.should_receive(:metadata).once.with(no_args()).and_return(metadata)
137
+ @ssq.model.should_receive(:query).once.with(nil).and_return(expected)
138
+ @ssq.model.should_receive(:sync).once.with(no_args()).and_return(true)
139
+ @ssq.model.should_receive(:logoff).once.with(no_args()).and_return(nil)
114
140
  @ssq.do_sync
115
- #@ssq.model.should_receive(:stash_result).once
116
- verify_doc_result(@s, {:md => expected,
117
- :md_size => expected.size.to_s})
118
- end
119
-
120
- it "should process model with pass_through set" do
121
- expected = {'1'=>@product1,'2'=>@product2}
122
- set_state('test_db_storage' => expected)
123
- @s.pass_through = 'true'
124
- @ssq.do_sync.should == expected
125
- verify_doc_result(@s, {:md => {},
126
- :md_size => nil})
127
- @s.pass_through = nil
128
141
  end
142
+ end
129
143
 
130
- it "should call methods in model" do
131
- mock_metadata_method([SampleAdapter, SimpleAdapter]) do
132
- expected = {'1'=>@product1,'2'=>@product2}
133
- metadata = "{\"foo\":\"bar\"}"
134
- @ssq.model.should_receive(:login).once.with(no_args()).and_return(true)
135
- @ssq.model.should_receive(:metadata).once.with(no_args()).and_return(metadata)
136
- @ssq.model.should_receive(:query).once.with(nil).and_return(expected)
137
- @ssq.model.should_receive(:sync).once.with(no_args()).and_return(true)
138
- @ssq.model.should_receive(:logoff).once.with(no_args()).and_return(nil)
139
- @ssq.do_sync
140
- end
141
- end
144
+ it "should do query with no exception" do
145
+ verify_read_operation('query')
146
+ end
142
147
 
143
- it "should do query with no exception" do
144
- verify_read_operation('query')
145
- end
146
-
147
- it "should do query with no exception pass through" do
148
- verify_read_operation_pass_through('query')
149
- end
148
+ it "should do query with no exception pass through" do
149
+ verify_read_operation_pass_through('query')
150
+ end
150
151
 
151
- it "should do query with exception raised" do
152
- verify_read_operation_with_error('query')
153
- end
154
-
155
- it "should do query with exception raised and update refresh time only after retries limit is exceeded" do
156
- @s.retry_limit = 1
157
- msg = "Error during query"
158
- set_test_data('test_db_storage',{},msg,"query error")
159
- res = @ssq.do_sync
160
- verify_doc_result(@s, {:md => {},
161
- :errors => {'query-error'=>{'message'=>msg}}})
162
- # 1) if retry_limit is set to N - then, first N retries should not update refresh_time
163
- @s.read_state.retry_counter.should == 1
164
- @s.read_state.refresh_time.should <= Time.now.to_i
165
-
166
- # try once more and fail again
167
- set_test_data('test_db_storage',{},msg,"query error")
168
- res = @ssq.do_sync
169
- verify_doc_result(@s, {:md => {},
170
- :errors => {'query-error'=>{'message'=>msg}}})
152
+ it "should do query with exception raised" do
153
+ verify_read_operation_with_error('query')
154
+ end
171
155
 
172
- # 2) if retry_limit is set to N and number of retries exceeded it - update refresh_time
173
- @s.read_state.retry_counter.should == 0
174
- @s.read_state.refresh_time.should > Time.now.to_i
175
- end
156
+ it "should do query with exception raised and update refresh time only after retries limit is exceeded" do
157
+ @s.retry_limit = 1
158
+ msg = "Error during query"
159
+ set_test_data('test_db_storage',{},msg,"query error")
160
+ res = @ssq.do_sync
161
+ verify_doc_result(@s, {:md => {},
162
+ :errors => {'query-error'=>{'message'=>msg}}})
163
+ # 1) if retry_limit is set to N - then, first N retries should not update refresh_time
164
+ @s.read_state.retry_counter.should == 1
165
+ @s.read_state.refresh_time.should <= Time.now.to_i
166
+
167
+ # try once more and fail again
168
+ set_test_data('test_db_storage',{},msg,"query error")
169
+ res = @ssq.do_sync
170
+ verify_doc_result(@s, {:md => {},
171
+ :errors => {'query-error'=>{'message'=>msg}}})
172
+
173
+ # 2) if retry_limit is set to N and number of retries exceeded it - update refresh_time
174
+ @s.read_state.retry_counter.should == 0
175
+ @s.read_state.refresh_time.should > Time.now.to_i
176
+ end
176
177
 
177
- it "should do query with exception raised and restore state with succesfull retry" do
178
- @s.retry_limit = 1
179
- msg = "Error during query"
180
- set_test_data('test_db_storage',{},msg,"query error")
181
- res = @ssq.do_sync
182
- verify_doc_result(@s, {:md => {},
183
- :errors => {'query-error'=>{'message'=>msg}}})
184
- # 1) if retry_limit is set to N - then, first N retries should not update refresh_time
185
- @s.read_state.retry_counter.should == 1
186
- @s.read_state.refresh_time.should <= Time.now.to_i
187
-
188
- # try once more (with success)
189
- expected = {'1'=>@product1,'2'=>@product2}
190
- set_test_data('test_db_storage',expected)
191
- @ssq.do_sync
192
- verify_doc_result(@s, {:md => expected,
193
- :errors => {}})
194
- @s.read_state.retry_counter.should == 0
195
- @s.read_state.refresh_time.should > Time.now.to_i
196
- end
178
+ it "should do query with exception raised and restore state with succesfull retry" do
179
+ @s.retry_limit = 1
180
+ msg = "Error during query"
181
+ set_test_data('test_db_storage',{},msg,"query error")
182
+ res = @ssq.do_sync
183
+ verify_doc_result(@s, {:md => {},
184
+ :errors => {'query-error'=>{'message'=>msg}}})
185
+ # 1) if retry_limit is set to N - then, first N retries should not update refresh_time
186
+ @s.read_state.retry_counter.should == 1
187
+ @s.read_state.refresh_time.should <= Time.now.to_i
188
+
189
+ # try once more (with success)
190
+ expected = {'1'=>@product1,'2'=>@product2}
191
+ set_test_data('test_db_storage',expected)
192
+ @ssq.do_sync
193
+ verify_doc_result(@s, {:md => expected,
194
+ :errors => {}})
195
+ @s.read_state.retry_counter.should == 0
196
+ @s.read_state.refresh_time.should > Time.now.to_i
197
+ end
197
198
 
198
- it "should reset the retry counter if prev_refresh_time was set more than poll_interval secs ago" do
199
- @s.retry_limit = 3
200
- @s.poll_interval = 2
201
- msg = "Error during query"
202
- set_test_data('test_db_storage',{},msg,"query error")
203
- res = @ssq.do_sync
204
- verify_doc_result(@s, {:md => {},
199
+ it "should reset the retry counter if prev_refresh_time was set more than poll_interval secs ago" do
200
+ @s.retry_limit = 3
201
+ @s.poll_interval = 2
202
+ msg = "Error during query"
203
+ set_test_data('test_db_storage',{},msg,"query error")
204
+ res = @ssq.do_sync
205
+ verify_doc_result(@s, {:md => {},
206
+ :errors => {'query-error'=>{'message'=>msg}}})
207
+ # 1) if retry_limit is set to N - then, first N retries should not update refresh_time
208
+ @s.read_state.retry_counter.should == 1
209
+ @s.read_state.refresh_time.should <= Time.now.to_i
210
+
211
+ # 2) Make another error - results are the same
212
+ set_test_data('test_db_storage',{},msg,"query error")
213
+ res = @ssq.do_sync
214
+ verify_doc_result(@s, {:md => {},
215
+ :errors => {'query-error'=>{'message'=>msg}}})
216
+ @s.read_state.retry_counter.should == 2
217
+ @s.read_state.refresh_time.should <= Time.now.to_i
218
+
219
+ # wait until time interval exprires and prev_refresh_time is too old -
220
+ # this should reset the counter on next request with error
221
+ # and do not update refresh_time
222
+ sleep(3)
223
+ set_test_data('test_db_storage',{},msg,"query error")
224
+ res = @ssq.do_sync
225
+ verify_doc_result(@s, {:md => {},
205
226
  :errors => {'query-error'=>{'message'=>msg}}})
206
- # 1) if retry_limit is set to N - then, first N retries should not update refresh_time
207
- @s.read_state.retry_counter.should == 1
208
- @s.read_state.refresh_time.should <= Time.now.to_i
209
-
210
- # 2) Make another error - results are the same
211
- set_test_data('test_db_storage',{},msg,"query error")
212
- res = @ssq.do_sync
213
- verify_doc_result(@s, {:md => {},
214
- :errors => {'query-error'=>{'message'=>msg}}})
215
- @s.read_state.retry_counter.should == 2
216
- @s.read_state.refresh_time.should <= Time.now.to_i
217
-
218
- # wait until time interval exprires and prev_refresh_time is too old -
219
- # this should reset the counter on next request with error
220
- # and do not update refresh_time
221
- sleep(3)
222
- set_test_data('test_db_storage',{},msg,"query error")
223
- res = @ssq.do_sync
224
- verify_doc_result(@s, {:md => {},
225
- :errors => {'query-error'=>{'message'=>msg}}})
226
- @s.read_state.retry_counter.should == 1
227
- @s.read_state.refresh_time.should <= Time.now.to_i
228
- end
229
-
230
- it "should do query with exception raised and update refresh time if retry_limit is 0" do
231
- @s.retry_limit = 0
232
- msg = "Error during query"
233
- set_test_data('test_db_storage',{},msg,"query error")
234
- res = @ssq.do_sync
235
- verify_doc_result(@s, {:md => {},
236
- :errors => {'query-error'=>{'message'=>msg}}})
237
- # if poll_interval is set to 0 - refresh time should be updated
238
- @s.read_state.retry_counter.should == 0
239
- @s.read_state.refresh_time.should > Time.now.to_i
240
- end
227
+ @s.read_state.retry_counter.should == 1
228
+ @s.read_state.refresh_time.should <= Time.now.to_i
229
+ end
241
230
 
242
- it "should do query with exception raised and update refresh time if poll_interval == 0" do
243
- @s.retry_limit = 1
244
- @s.poll_interval = 0
245
- msg = "Error during query"
246
- set_test_data('test_db_storage',{},msg,"query error")
247
- prev_refresh_time = @s.read_state.refresh_time
248
- # make sure refresh time is expired
249
- sleep(1)
250
- res = @ssq.do_sync
251
- verify_doc_result(@s, {:md => {},
252
- :errors => {'query-error'=>{'message'=>msg}}})
253
- # if poll_interval is set to 0 - refresh time should be updated
254
- @s.read_state.retry_counter.should == 0
255
- @s.read_state.refresh_time.should > prev_refresh_time
256
- end
231
+ it "should do query with exception raised and update refresh time if retry_limit is 0" do
232
+ @s.retry_limit = 0
233
+ msg = "Error during query"
234
+ set_test_data('test_db_storage',{},msg,"query error")
235
+ res = @ssq.do_sync
236
+ verify_doc_result(@s, {:md => {},
237
+ :errors => {'query-error'=>{'message'=>msg}}})
238
+ # if poll_interval is set to 0 - refresh time should be updated
239
+ @s.read_state.retry_counter.should == 0
240
+ @s.read_state.refresh_time.should > Time.now.to_i
257
241
  end
258
-
259
- describe "push_notify" do
260
- it "should do push_notify for source after push_objects if enabled" do
261
- @s.push_notify = 'true'
262
- data = {'1' => @product1, '2' => @product2, '3' => @product3}
263
- ping_params = {'user_id'=>["testuser"], 'sources'=>["SampleAdapter"]}
264
- PingJob.should_receive(:perform).once.with(ping_params)
265
- po_handler = lambda { @model.push_objects(params) }
266
- push_objects_handler = Rhoconnect::Handler::PluginCallbacks::Runner.new(po_handler, @model, {'objects' => data})
267
- push_objects_handler.run
268
- end
269
-
270
- it "should do push_notify for source after push_deletes if enabled" do
271
- @s.push_notify = 'true'
272
- u2 = User.create(:login => 'user2')
273
- @a.users << u2.id
274
- data = {'1' => @product1, '2' => @product2, '3' => @product3}
275
- ping_params = {'user_id'=>["testuser"], 'sources'=>["SampleAdapter"]}
276
- PingJob.should_receive(:perform).once.with(ping_params)
277
- pd_handler = lambda { @model.push_deletes(params) }
278
- push_deletes_handler = Rhoconnect::Handler::PluginCallbacks::Runner.new(pd_handler, @model, {'objects' => data})
279
- push_deletes_handler.run
280
- end
281
-
282
- it "should not do push_notify for user source if not enabled (default)" do
283
- u2 = User.create(:login => 'user2')
284
- @a.users << u2.id
285
- data = {'1' => @product1}
286
- PingJob.should_receive(:perform).never
287
- po_handler = lambda { @model.push_objects(params) }
288
- push_objects_handler = Rhoconnect::Handler::PluginCallbacks::Runner.new(po_handler, @model, {'objects' => data})
289
- push_objects_handler.run
290
- end
291
242
 
292
- it "should not do push_notify for app source" do
293
- @s.partition = :app
294
- u2 = User.create(:login => 'user2')
295
- @a.users << u2.id
296
- data = {'1' => @product1}
297
- PingJob.should_receive(:perform).never
298
- po_handler = lambda { @model.push_objects(params) }
299
- push_objects_handler = Rhoconnect::Handler::PluginCallbacks::Runner.new(po_handler, @model, {'objects' => data})
300
- push_objects_handler.run
301
- end
243
+ it "should do query with exception raised and update refresh time if poll_interval == 0" do
244
+ @s.retry_limit = 1
245
+ @s.poll_interval = 0
246
+ msg = "Error during query"
247
+ set_test_data('test_db_storage',{},msg,"query error")
248
+ prev_refresh_time = @s.read_state.refresh_time
249
+ # make sure refresh time is expired
250
+ sleep(1)
251
+ res = @ssq.do_sync
252
+ verify_doc_result(@s, {:md => {},
253
+ :errors => {'query-error'=>{'message'=>msg}}})
254
+ # if poll_interval is set to 0 - refresh time should be updated
255
+ @s.read_state.retry_counter.should == 0
256
+ @s.read_state.refresh_time.should > prev_refresh_time
302
257
  end
258
+ end
303
259
 
304
- describe "create" do
305
- before (:each) do
306
- rh = lambda { @model.create(params[:create_object])}
307
- @ssc = Rhoconnect::Handler::Changes::Engine.new(['create'], @model, rh, {})
308
- @queue_name = "create"
309
- @u2_fields = {:login => 'anotheruser'}
310
- @u2 = User.create(@u2_fields)
311
- @u2.password = 'testpass'
312
- @c2_fields = {
313
- :device_type => 'Android',
314
- :device_pin => 'efgh',
315
- :device_port => '4444',
316
- :user_id => @u2.id,
317
- :app_id => @a.id
318
- }
319
- @c2 = Client.create(@c2_fields,{:source_name => @s_fields[:name]})
320
- @a.users << @u2.id
321
- end
260
+ describe "push_notify" do
261
+ it "should do push_notify for source after push_objects if enabled" do
262
+ @s.push_notify = 'true'
263
+ data = {'1' => @product1, '2' => @product2, '3' => @product3}
264
+ ping_params = {'user_id'=>["testuser"], 'sources'=>["SampleAdapter"]}
265
+ PingJob.should_receive(:perform).once.with(ping_params)
266
+ po_handler = lambda { @model.push_objects(params) }
267
+ push_objects_handler = Rhoconnect::Handler::PluginCallbacks::Runner.new(po_handler, @model, {'objects' => data})
268
+ push_objects_handler.run
269
+ end
322
270
 
323
- it "should do create where adapter.create returns nil" do
324
- set_source_queue_state(@s, {@queue_name => [[@s.name, [['2', @product2]]]]}, @c.id, true)
325
- @ssc.create
326
- verify_source_queue_data(@s, @queue_name => [])
327
- verify_doc_result(@c, {:create_errors => {},
328
- :create_links => {}})
329
- end
271
+ it "should do push_notify for source after push_deletes if enabled" do
272
+ @s.push_notify = 'true'
273
+ u2 = User.create(:login => 'user2')
274
+ @a.users << u2.id
275
+ data = {'1' => @product1, '2' => @product2, '3' => @product3}
276
+ ping_params = {'user_id'=>["testuser"], 'sources'=>["SampleAdapter"]}
277
+ PingJob.should_receive(:perform).once.with(ping_params)
278
+ pd_handler = lambda { @model.push_deletes(params) }
279
+ push_deletes_handler = Rhoconnect::Handler::PluginCallbacks::Runner.new(pd_handler, @model, {'objects' => data})
280
+ push_deletes_handler.run
281
+ end
330
282
 
331
- it "should do create where adapter.create returns object link" do
332
- @product4['link'] = 'test link'
333
- set_source_queue_state(@s, {@queue_name => [[@s.name, [['4', @product4]]]]},@c.id,true)
334
- @ssc.create
335
- verify_source_queue_data(@s, @queue_name => [])
336
- verify_doc_result(@c, {:create_errors => {},
337
- :create_links => {'4'=>{'l'=>'backend_id'}}})
338
- end
283
+ it "should not do push_notify for user source if not enabled (default)" do
284
+ u2 = User.create(:login => 'user2')
285
+ @a.users << u2.id
286
+ data = {'1' => @product1}
287
+ PingJob.should_receive(:perform).never
288
+ po_handler = lambda { @model.push_objects(params) }
289
+ push_objects_handler = Rhoconnect::Handler::PluginCallbacks::Runner.new(po_handler, @model, {'objects' => data})
290
+ push_objects_handler.run
291
+ end
339
292
 
340
- it "should raise exception on adapter.create" do
341
- msg = "Error creating record"
342
- data = add_error_object({'4'=>@product4,'2'=>@product2},msg)
343
- source_queue_data = []
344
- data.each do |key, value|
345
- source_queue_data << [key, value]
346
- end
347
- set_source_queue_state(@s, {@queue_name => [[@s.name, source_queue_data]]},@c.id, true)
348
- @ssc.create
349
- verify_doc_result(@c, :create_errors =>
350
- {"#{ERROR}-error"=>{"message"=>msg},ERROR=>data[ERROR]})
351
- end
293
+ it "should not do push_notify for app source" do
294
+ @s.partition = :app
295
+ u2 = User.create(:login => 'user2')
296
+ @a.users << u2.id
297
+ data = {'1' => @product1}
298
+ PingJob.should_receive(:perform).never
299
+ po_handler = lambda { @model.push_objects(params) }
300
+ push_objects_handler = Rhoconnect::Handler::PluginCallbacks::Runner.new(po_handler, @model, {'objects' => data})
301
+ push_objects_handler.run
302
+ end
303
+ end
352
304
 
353
- it "should properly process creates for 2 users using same queue" do
354
- @product3['link'] = 'test link'
355
- @product4['link'] = 'test link'
356
- set_source_queue_state(@s, {@queue_name => [[@s.name, [['temp_id1', @product3]]]]},@c.id,true)
357
- set_source_queue_state(@s, {@queue_name => [[@s.name, [['temp_id2', @product4]]]]},@c2.id,true)
358
- @s.queue_docname(:create).should == "source:application:#{@s.name}:create"
359
- @ssc.create
360
- verify_source_queue_data(@s, @queue_name => [])
361
- creates_source1 = Source.load(@s.name,
362
- {:user_id => @u.id,:app_id => @a.id})
363
- creates_source2 = Source.load(@s.name,
364
- {:user_id => @u2.id,:app_id => @a.id})
365
- verify_doc_result(creates_source1, {:md => {'backend_id' => @product3}})
366
- verify_doc_result(creates_source2, {:md => {'backend_id' => @product4}})
367
- verify_doc_result(@c, {:create_errors => {},
368
- :create_links => {'temp_id1'=>{'l'=>'backend_id'}}})
369
- verify_doc_result(@c2, {:create_errors => {},
370
- :create_links => {'temp_id2'=>{'l'=>'backend_id'}}})
371
- end
305
+ describe "create" do
306
+ before (:each) do
307
+ rh = lambda { @model.create(params[:create_object])}
308
+ @ssc = Rhoconnect::Handler::Changes::Engine.new(['create'], @model, rh, {})
309
+ @queue_name = "create"
310
+ @u2_fields = {:login => 'anotheruser'}
311
+ @u2 = User.create(@u2_fields)
312
+ @u2.password = 'testpass'
313
+ @c2_fields = {
314
+ :device_type => 'Android',
315
+ :device_pin => 'efgh',
316
+ :device_port => '4444',
317
+ :user_id => @u2.id,
318
+ :app_id => @a.id
319
+ }
320
+ @c2 = Client.create(@c2_fields,{:source_name => @s_fields[:name]})
321
+ @a.users << @u2.id
372
322
  end
373
323
 
374
- describe "update" do
375
- before (:each) do
376
- rh = lambda { @model.update(params[:update_object])}
377
- @ssu = Rhoconnect::Handler::Changes::Engine.new(['update'], @model, rh, {})
378
- @queue_name = "update"
379
- end
324
+ it "should do create where adapter.create returns nil" do
325
+ set_source_queue_state(@s, {@queue_name => [[@s.name, [['2', @product2]]]]}, @c.id, true)
326
+ @ssc.create
327
+ verify_source_queue_data(@s, @queue_name => [])
328
+ verify_doc_result(@c, {:create_errors => {},
329
+ :create_links => {}})
330
+ end
380
331
 
381
- it "should do update with no errors" do
382
- set_source_queue_state(@s, {@queue_name => [[@s.name, [['4', { 'price' => '199.99' }]]]]},@c.id,true)
383
- @ssu.update
384
- verify_source_queue_data(@s, @queue_name => [])
385
- verify_doc_result(@c, :update_errors => {})
386
- end
332
+ it "should do create where adapter.create returns object link" do
333
+ @product4['link'] = 'test link'
334
+ set_source_queue_state(@s, {@queue_name => [[@s.name, [['4', @product4]]]]},@c.id,true)
335
+ @ssc.create
336
+ verify_source_queue_data(@s, @queue_name => [])
337
+ verify_doc_result(@c, {:create_errors => {},
338
+ :create_links => {'4'=>{'l'=>'backend_id'}}})
339
+ end
387
340
 
388
- it "should do update with errors" do
389
- msg = "Error updating record"
390
- data = add_error_object({},msg)
391
- source_queue_data = []
392
- data.each do |key, value|
393
- source_queue_data << [key, value]
394
- end
395
- # this one will be after the error record - and should remain in the queue
396
- source_queue_data << ['4', { 'price' => '199.99' }]
397
- set_source_queue_state(@s, {@queue_name => [[@s.name, source_queue_data]]},@c.id,true)
398
- set_doc_state(@c, :cd => { ERROR => { 'price' => '99.99' } }
399
- )
400
- @ssu.update
401
- update_data,client_ids = @s.get_queue(@queue_name)
402
- update_data.should == [[[@s.name, [['4', { 'price' => '199.99'}]]]]]
403
- client_ids.should == [@c.id]
404
- verify_doc_result(@c, {:update_errors =>
405
- {"#{ERROR}-error"=>{"message"=>msg}, ERROR=>data[ERROR]},
406
- :update_rollback => {ERROR=>{"price"=>"99.99"}}})
407
- end
341
+ it "should raise exception on adapter.create" do
342
+ msg = "Error creating record"
343
+ data = add_error_object({'4'=>@product4,'2'=>@product2},msg)
344
+ source_queue_data = []
345
+ data.each do |key, value|
346
+ source_queue_data << [key, value]
347
+ end
348
+ set_source_queue_state(@s, {@queue_name => [[@s.name, source_queue_data]]},@c.id, true)
349
+ @ssc.create
350
+ verify_doc_result(@c, :create_errors =>
351
+ {"#{ERROR}-error"=>{"message"=>msg},ERROR=>data[ERROR]})
408
352
  end
409
353
 
410
- describe "delete" do
411
- before (:each) do
412
- rh = lambda { @model.update(params[:delete_object])}
413
- @ssd = Rhoconnect::Handler::Changes::Engine.new(['delete'], @model, rh, {})
414
- @queue_name = "delete"
415
- @u2_fields = {:login => 'anotheruser'}
416
- @u2 = User.create(@u2_fields)
417
- @u2.password = 'testpass'
418
- @c2_fields = {
419
- :device_type => 'Android',
420
- :device_pin => 'efgh',
421
- :device_port => '4444',
422
- :user_id => @u2.id,
423
- :app_id => @a.id
424
- }
425
- @c2 = Client.create(@c2_fields,{:source_name => @s_fields[:name]})
426
- @a.users << @u2.id
427
- end
354
+ it "should properly process creates for 2 users using same queue" do
355
+ @product3['link'] = 'test link'
356
+ @product4['link'] = 'test link'
357
+ set_source_queue_state(@s, {@queue_name => [[@s.name, [['temp_id1', @product3]]]]},@c.id,true)
358
+ set_source_queue_state(@s, {@queue_name => [[@s.name, [['temp_id2', @product4]]]]},@c2.id,true)
359
+ @s.queue_docname(:create).should == "source:application:#{@s.name}:create"
360
+ @ssc.create
361
+ verify_source_queue_data(@s, @queue_name => [])
362
+ creates_source1 = Source.load(@s.name,
363
+ {:user_id => @u.id,:app_id => @a.id})
364
+ creates_source2 = Source.load(@s.name,
365
+ {:user_id => @u2.id,:app_id => @a.id})
366
+ verify_doc_result(creates_source1, {:md => {'backend_id' => @product3}})
367
+ verify_doc_result(creates_source2, {:md => {'backend_id' => @product4}})
368
+ verify_doc_result(@c, {:create_errors => {},
369
+ :create_links => {'temp_id1'=>{'l'=>'backend_id'}}})
370
+ verify_doc_result(@c2, {:create_errors => {},
371
+ :create_links => {'temp_id2'=>{'l'=>'backend_id'}}})
372
+ end
373
+ end
428
374
 
429
- it "should do delete with no errors" do
430
- set_source_queue_state(@s, {@queue_name => [[@s.name, [['4', @product4]]]]}, @c.id, true)
431
- set_doc_state(@s, :md => {'4'=>@product4,'3'=>@product3})
432
- set_doc_state(@c, :cd => {'4'=>@product4,'3'=>@product3})
433
- @ssd.delete
434
- verify_source_queue_data(@s, @queue_name => [])
435
- verify_doc_result(@c, :delete_errors => {})
436
- verify_doc_result(@s, :md => {'3'=>@product3})
437
- verify_doc_result(@c, :cd => {'3'=>@product3})
438
- end
375
+ describe "update" do
376
+ before (:each) do
377
+ rh = lambda { @model.update(params[:update_object])}
378
+ @ssu = Rhoconnect::Handler::Changes::Engine.new(['update'], @model, rh, {})
379
+ @queue_name = "update"
380
+ end
439
381
 
440
- it "should do delete with errors" do
441
- msg = "Error delete record"
442
- data = add_error_object({},msg)
443
- source_queue_data = []
444
- data.each do |key, value|
445
- source_queue_data << [key, value]
446
- end
447
- # this one will be after the error and should remain in the queue
448
- source_queue_data << ['2', @product2]
449
- set_source_queue_state(@s, {@queue_name => [[@s.name, source_queue_data]]}, @c.id, true)
450
- @ssd.delete
451
- verify_doc_result(@c, :delete_errors => {"#{ERROR}-error"=>{"message"=>msg}, ERROR=>data[ERROR]})
452
- verify_source_queue_data(@s, @queue_name => [[[@s.name, [['2', @product2]]]]])
453
- end
382
+ it "should do update with no errors" do
383
+ set_source_queue_state(@s, {@queue_name => [[@s.name, [['4', { 'price' => '199.99' }]]]]},@c.id,true)
384
+ @ssu.update
385
+ verify_source_queue_data(@s, @queue_name => [])
386
+ verify_doc_result(@c, :update_errors => {})
387
+ end
454
388
 
455
- it "should properly process deletes for 2 users using same queue" do
456
- deletes_source1 = Source.load(@s.name,
457
- {:user_id => @u.id,:app_id => @a.id})
458
- deletes_source2 = Source.load(@s.name,
459
- {:user_id => @u2.id,:app_id => @a.id})
460
- set_source_queue_state(deletes_source1, {@queue_name => [[@s.name, [['4', @product4]]]]},@c.id,true)
461
- set_source_queue_state(deletes_source2, {@queue_name => [[@s.name, [['3', @product3]]]]},@c2.id,true)
462
- deletes_source1.queue_docname(:delete).should == "source:application:#{@s.name}:delete"
463
- deletes_source1.queue_docname(:delete).should == deletes_source2.queue_docname(:delete)
464
- set_doc_state(deletes_source1, :md => {'4'=>@product4,'2'=>@product2,'3'=>@product3})
465
- set_doc_state(deletes_source2, :md => {'4'=>@product4,'3'=>@product3, '1'=>@product1})
466
- set_doc_state(@c, :cd => {'4'=>@product4,'2'=>@product2,'3'=>@product3})
467
- set_doc_state(@c2, :cd => {'4'=>@product4,'3'=>@product3, '1'=>@product1})
468
- @ssd.delete
469
- verify_source_queue_data(@s, @queue_name => [])
470
- verify_doc_result(@c, :delete_errors => {})
471
- verify_doc_result(@c2, :delete_errors => {})
472
- verify_doc_result(deletes_source1, :md => {'2'=>@product2,'3'=>@product3})
473
- verify_doc_result(deletes_source2, :md => {'4'=>@product4, '1'=>@product1})
474
- verify_doc_result(@c, :cd => {'2'=>@product2,'3'=>@product3})
475
- verify_doc_result(@c2, :cd => {'4'=>@product4, '1'=>@product1})
476
- end
389
+ it "should do update with errors" do
390
+ msg = "Error updating record"
391
+ data = add_error_object({},msg)
392
+ source_queue_data = []
393
+ data.each do |key, value|
394
+ source_queue_data << [key, value]
395
+ end
396
+ # this one will be after the error record - and should remain in the queue
397
+ source_queue_data << ['4', { 'price' => '199.99' }]
398
+ set_source_queue_state(@s, {@queue_name => [[@s.name, source_queue_data]]},@c.id,true)
399
+ set_doc_state(@c, :cd => { ERROR => { 'price' => '99.99' } }
400
+ )
401
+ @ssu.update
402
+ update_data,client_ids = @s.get_queue(@queue_name)
403
+ update_data.should == [[[@s.name, [['4', { 'price' => '199.99'}]]]]]
404
+ client_ids.should == [@c.id]
405
+ verify_doc_result(@c, {:update_errors =>
406
+ {"#{ERROR}-error"=>{"message"=>msg}, ERROR=>data[ERROR]},
407
+ :update_rollback => {ERROR=>{"price"=>"99.99"}}})
477
408
  end
409
+ end
478
410
 
479
- describe "cud" do
480
- before (:each) do
481
- rh = lambda { @model.send(params[:operation].to_sym, params["#{params[:operation]}_object".to_sym]) }
482
- @sscud = Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], @model, rh, {})
483
- end
411
+ describe "delete" do
412
+ before (:each) do
413
+ rh = lambda { @model.update(params[:delete_object])}
414
+ @ssd = Rhoconnect::Handler::Changes::Engine.new(['delete'], @model, rh, {})
415
+ @queue_name = "delete"
416
+ @u2_fields = {:login => 'anotheruser'}
417
+ @u2 = User.create(@u2_fields)
418
+ @u2.password = 'testpass'
419
+ @c2_fields = {
420
+ :device_type => 'Android',
421
+ :device_pin => 'efgh',
422
+ :device_port => '4444',
423
+ :user_id => @u2.id,
424
+ :app_id => @a.id
425
+ }
426
+ @c2 = Client.create(@c2_fields,{:source_name => @s_fields[:name]})
427
+ @a.users << @u2.id
428
+ end
484
429
 
485
- it "should fail to create Rhoconnect::Handler::Changes::Engine with InvalidArgumentError without source" do
486
- lambda { Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], nil, nil, {}) }.should raise_error(ArgumentError, 'Unknown source')
487
- end
430
+ it "should do delete with no errors" do
431
+ set_source_queue_state(@s, {@queue_name => [[@s.name, [['4', @product4]]]]}, @c.id, true)
432
+ set_doc_state(@s, :md => {'4'=>@product4,'3'=>@product3})
433
+ set_doc_state(@c, :cd => {'4'=>@product4,'3'=>@product3})
434
+ @ssd.delete
435
+ verify_source_queue_data(@s, @queue_name => [])
436
+ verify_doc_result(@c, :delete_errors => {})
437
+ verify_doc_result(@s, :md => {'3'=>@product3})
438
+ verify_doc_result(@c, :cd => {'3'=>@product3})
439
+ end
488
440
 
489
- it "should fail to create Rhoconnect::Handler::Changes::Engine with InvalidArgumentError without proc handler" do
490
- lambda { Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], @model, nil) }.should raise_error(ArgumentError, 'Invalid CUD handler')
491
- end
441
+ it "should do delete with errors" do
442
+ msg = "Error delete record"
443
+ data = add_error_object({},msg)
444
+ source_queue_data = []
445
+ data.each do |key, value|
446
+ source_queue_data << [key, value]
447
+ end
448
+ # this one will be after the error and should remain in the queue
449
+ source_queue_data << ['2', @product2]
450
+ set_source_queue_state(@s, {@queue_name => [[@s.name, source_queue_data]]}, @c.id, true)
451
+ @ssd.delete
452
+ verify_doc_result(@c, :delete_errors => {"#{ERROR}-error"=>{"message"=>msg}, ERROR=>data[ERROR]})
453
+ verify_source_queue_data(@s, @queue_name => [[[@s.name, [['2', @product2]]]]])
454
+ end
492
455
 
493
- it "should create Rhoconnect::Handler::Changes::Engine" do
494
- @sscud.model.is_a?(SampleAdapter).should == true
495
- end
456
+ it "should properly process deletes for 2 users using same queue" do
457
+ deletes_source1 = Source.load(@s.name,
458
+ {:user_id => @u.id,:app_id => @a.id})
459
+ deletes_source2 = Source.load(@s.name,
460
+ {:user_id => @u2.id,:app_id => @a.id})
461
+ set_source_queue_state(deletes_source1, {@queue_name => [[@s.name, [['4', @product4]]]]},@c.id,true)
462
+ set_source_queue_state(deletes_source2, {@queue_name => [[@s.name, [['3', @product3]]]]},@c2.id,true)
463
+ deletes_source1.queue_docname(:delete).should == "source:application:#{@s.name}:delete"
464
+ deletes_source1.queue_docname(:delete).should == deletes_source2.queue_docname(:delete)
465
+ set_doc_state(deletes_source1, :md => {'4'=>@product4,'2'=>@product2,'3'=>@product3})
466
+ set_doc_state(deletes_source2, :md => {'4'=>@product4,'3'=>@product3, '1'=>@product1})
467
+ set_doc_state(@c, :cd => {'4'=>@product4,'2'=>@product2,'3'=>@product3})
468
+ set_doc_state(@c2, :cd => {'4'=>@product4,'3'=>@product3, '1'=>@product1})
469
+ @ssd.delete
470
+ verify_source_queue_data(@s, @queue_name => [])
471
+ verify_doc_result(@c, :delete_errors => {})
472
+ verify_doc_result(@c2, :delete_errors => {})
473
+ verify_doc_result(deletes_source1, :md => {'2'=>@product2,'3'=>@product3})
474
+ verify_doc_result(deletes_source2, :md => {'4'=>@product4, '1'=>@product1})
475
+ verify_doc_result(@c, :cd => {'2'=>@product2,'3'=>@product3})
476
+ verify_doc_result(@c2, :cd => {'4'=>@product4, '1'=>@product1})
477
+ end
478
+ end
496
479
 
497
- it "should do process_cud" do
498
- @create_queue_name = :create
499
- @update_queue_name = :update
500
- @u2_fields = {:login => 'anotheruser'}
501
- @u2 = User.create(@u2_fields)
502
- @u2.password = 'testpass'
503
- @c2_fields = {
504
- :device_type => 'Android',
505
- :device_pin => 'efgh',
506
- :device_port => '4444',
507
- :user_id => @u2.id,
508
- :app_id => @a.id
509
- }
510
- @c2 = Client.create(@c2_fields,{:source_name => @s_fields[:name]})
511
- @a.users << @u2.id
512
- create_doc1 = { 'name' => 'abc', 'link' => '1', 'an_attribute' => "My attrib 2" }
513
- create_doc2 = { 'name' => 'name0', 'link' => '1' }
514
- create_doc3 = { 'name' => 'name7', 'link' => '7' }
515
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', create_doc1]]]]},@c.id,true)
516
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', create_doc2]]]]},@c2.id,true)
517
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['6', create_doc3]]]]},@c.id,true)
518
- set_doc_state(@c, :cd => {'10'=> {'name' => 'Apple'}})
519
- set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['10', { 'name' => 'Android' }]]]]},@c.id,true)
520
- # should receive login/logoff pair twice for create and once for update
521
- @sscud.should_receive(:auth_method).exactly(6).times.and_return(true)
522
- @sscud.should_receive(:_process_create).exactly(3).times
523
- @sscud.should_receive(:_process_update).once
524
- @sscud.should_not_receive(:_process_delete)
525
- @sscud.do_cud
526
- end
480
+ describe "cud" do
481
+ before (:each) do
482
+ rh = lambda { @model.send(params[:operation].to_sym, params["#{params[:operation]}_object".to_sym]) }
483
+ @sscud = Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], @model, rh, {})
527
484
  end
528
-
529
- describe "cud conflicts" do
530
- before (:each) do
531
- @create_queue_name = :create
532
- @update_queue_name = :update
533
- @delete_queue_name = :delete
534
- rh = lambda { @model.send(params[:operation].to_sym, params["#{params[:operation]}_object".to_sym]) }
535
- @sscud = Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], @model, rh, {})
536
- @u2_fields = {:login => 'anotheruser'}
537
- @u2 = User.create(@u2_fields)
538
- @u2.password = 'testpass'
539
- @c2_fields = {
540
- :device_type => 'Android',
541
- :device_pin => 'efgh',
542
- :device_port => '4444',
543
- :user_id => @u2.id,
544
- :app_id => @a.id
545
- }
546
- @c2 = Client.create(@c2_fields,{:source_name => @s_fields[:name]})
547
- @a.users << @u2.id
548
- end
549
485
 
550
- it "should detect create conflict and skip the duplicate record creation, but properly update the links" do
551
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', { 'name' => 'Android', 'link' => '1' }]]]]},@c.id,true)
552
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', { 'name' => 'Android', 'link' => '1', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '0', 'duplicate_of_queue_index' => '0' }]]]]},@c.id,true)
553
- @sscud.do_cud
554
-
555
- verify_source_queue_data(@s, @create_queue_name => [])
556
- verify_doc_result(@c, :create_errors => {})
557
- verify_doc_result(@c, :create_links => {'4'=> { 'l' => 'backend_id' }, '5' => { 'l' => 'backend_id'}})
558
- end
559
-
560
- it "should detect create conflict and skip the duplicate record creation, but properly update the errors page" do
561
- create_doc1 = { 'name' => 'wrongname', 'link' => '1', 'an_attribute' => "Create Sample Adapter Error" }
562
- create_doc2 = { 'name' => 'wrongname', 'link' => '1', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '0', 'duplicate_of_queue_index' => '0'}
563
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', create_doc1]]]]},@c.id,true)
564
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', create_doc2]]]]},@c.id,true)
565
- @sscud.do_cud
566
-
567
- verify_source_queue_data(@s, @create_queue_name => [])
568
- verify_doc_result(@c, :create_errors => {"4-error"=>{"message"=>"Create Sample Adapter Error"},
569
- '4' => create_doc1,
570
- "5-error"=>{"message"=>"Create Sample Adapter Error"},
571
- '5' => create_doc2})
572
- verify_doc_result(@c, :create_links => {})
573
- end
574
-
575
- it "should detect create conflict and force and error" do
576
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', { 'name' => 'Android', 'link' => true }]]]]},@c.id,true)
577
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', { 'name' => 'Android', 'link' => '1', 'force_duplicate_error' => '1' }]]]]},@c.id,true)
578
- @sscud.do_cud
579
- verify_source_queue_data(@s, @create_queue_name => [])
580
- verify_doc_result(@c, :create_errors => {"5-error"=>{"message"=>"Error during create: object confict detected"}, "5"=>{"name"=>"Android", "link"=>"1", 'force_duplicate_error' => '1'}} )
581
- end
486
+ it "should fail to create Rhoconnect::Handler::Changes::Engine with InvalidArgumentError without source" do
487
+ lambda { Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], nil, nil, {}) }.should raise_error(ArgumentError, 'Unknown source')
488
+ end
582
489
 
583
- it "should detect create conflict in the intermediate state create and skip the duplicate record create" do
584
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', { 'name' => 'InvalidName', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '1', 'duplicate_of_queue_index' => '0'}], ['4', { 'name' => 'Android' , 'link' => true}]]]]},@c.id,true)
585
- set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['6', { 'name' => 'iPhone', 'link' => true }], ['7', { 'name' => 'InvalidName', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '1', 'duplicate_of_queue_index' => '0'}]]]]},@c.id,true)
586
- @sscud.do_cud
587
-
588
- verify_source_queue_data(@s, @create_queue_name => [])
589
- verify_doc_result(@c, :create_errors => {})
590
- verify_doc_result(@c, :create_links => {'4'=> { 'l' => 'backend_id' }, '5'=> { 'l' => 'backend_id' }, '6' => { 'l' => 'backend_id' }, '7'=> { 'l' => 'backend_id' }})
591
- end
592
-
593
- it "should detect update conflict and skip the duplicate record update" do
594
- set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
595
- set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
596
- set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'InvalidName', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '0', 'duplicate_of_queue_index' => '0'}]]]]},@c.id,true)
597
- @sscud.do_cud
598
-
599
- verify_source_queue_data(@s, @update_queue_name => [])
600
- verify_doc_result(@c, :update_errors => {})
601
- verify_doc_result(@c, :update_rollback => {})
602
- end
603
-
604
- it "should detect update conflict and force an error on duplicate record update" do
605
- set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
606
- set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
607
- set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'ErrorName', 'force_duplicate_error' => '1' }]]]]},@c.id,true)
608
- @sscud.do_cud
609
-
610
- verify_source_queue_data(@s, @update_queue_name => [])
611
- verify_doc_result(@c, :update_errors => {"4-error"=>{"message"=>"Error during update: object confict detected"}, "4"=>{"name"=>"ErrorName", 'force_duplicate_error' => '1'}})
612
- verify_doc_result(@c, :update_rollback => {'4'=> {'name' => 'Apple'}})
613
- end
490
+ it "should fail to create Rhoconnect::Handler::Changes::Engine with InvalidArgumentError without proc handler" do
491
+ lambda { Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], @model, nil) }.should raise_error(ArgumentError, 'Invalid CUD handler')
492
+ end
614
493
 
615
- it "should install find_duplicates_on_update , detect equal objects conflict and skip the duplicate record update" do
616
- SampleAdapter.enable :find_duplicates_on_update
617
- set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
618
- set_doc_state(@c2, :cd => {'4'=> {'name' => 'Apple'}})
619
- update_source1 = Source.load(@s.name,
620
- {:user_id => @u.id,:app_id => @a.id})
621
- update_source2 = Source.load(@s.name,
622
- {:user_id => @u2.id,:app_id => @a.id})
623
- set_source_queue_state(update_source1, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
624
- set_source_queue_state(update_source2, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c2.id, true)
625
- operation_data,client_ids = update_source2.get_queue(:update)
626
- invalid_meta = @sscud.model.run_validators(:update,operation_data,client_ids)
627
- invalid_meta.should == { 1=> {@s.name => { 0 => { :duplicate_of=>true }}},
628
- 0=>{@s.name => { 0 => { :duplicates=>[{:client_id=>@c2.id, :key=>"4", :value=>{"name"=>"Android"}}]}}}}
629
-
630
- @sscud.model.should_receive(:find_duplicates_on_update).once.and_return(invalid_meta)
631
- @sscud.do_cud
632
-
633
- verify_source_queue_data(@s, @update_queue_name => [])
634
- verify_doc_result(@c, :update_errors => {})
635
- verify_doc_result(@c, :update_rollback => {})
636
- SampleAdapter.validators.delete(:find_duplicates_on_update)
637
- end
638
-
639
- it "should detect update conflict and force an error on duplicate record update" do
640
- set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
641
- set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
642
- set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'ErrorName', 'force_duplicate_error' => '1' }]]]]},@c.id,true)
643
- @sscud.do_cud
644
-
645
- verify_source_queue_data(@s, @update_queue_name => [])
646
- verify_doc_result(@c, :update_errors => {"4-error"=>{"message"=>"Error during update: object confict detected"}, "4"=>{"name"=>"ErrorName", 'force_duplicate_error' => '1'}})
647
- verify_doc_result(@c, :update_rollback => {'4'=> {'name' => 'Apple'}})
648
- end
649
-
650
- it "should install find_duplicates_on_update , detect equal objects conflict and raise a custom error" do
651
- SampleAdapter.enable :find_duplicates_on_update, :raise_error => true do |options, invalid_meta, operation, operation_data, client_ids|
652
- invalid_meta.each do |index, index_data|
653
- index_data.each do |source_id, objindex_data|
654
- objindex_data.each do |objindex, objmeta|
655
- if objmeta.has_key?(:error)
656
- objmeta[:error] = "My custom error"
657
- end
658
- end if objindex_data
659
- end if index_data
660
- end if invalid_meta
661
- invalid_meta
662
- end
663
- set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
664
- set_doc_state(@c2, :cd => {'4'=> {'name' => 'Apple'}})
665
- update_source1 = Source.load(@s.name,
666
- {:user_id => @u.id,:app_id => @a.id})
667
- update_source2 = Source.load(@s.name,
668
- {:user_id => @u2.id,:app_id => @a.id})
669
- set_source_queue_state(update_source1, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
670
- set_source_queue_state(update_source2, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c2.id, true)
671
- operation_data,client_ids = update_source2.get_queue(:update)
672
- invalid_meta = @sscud.model.run_validators(:update,operation_data,client_ids)
673
- invalid_meta.should == {1=>{@s.name=>{0=>{:error=>"My custom error"}}}}
674
-
675
- @sscud.do_cud
676
- verify_source_queue_data(@s, @update_queue_name => [])
677
- verify_doc_result(@c, :update_errors => {})
678
- verify_doc_result(@c, :update_rollback => {})
679
- verify_doc_result(@c2, :update_errors => {"4-error"=>{"message"=>"My custom error"}, "4"=>{"name"=>"Android"}})
680
- verify_doc_result(@c2, :update_rollback => {'4'=> {'name' => 'Apple'}})
681
- SampleAdapter.validators.delete(:find_duplicates_on_update)
682
- end
683
-
684
-
685
- it "should detect delete conflict and skip the duplicate record delete" do
686
- set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
687
- set_doc_state(@c, :cd_size => 1)
688
- set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple' }]]]]},@c.id,true)
689
- set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '0', 'duplicate_of_queue_index' => '0'}]]]]},@c.id,true)
690
- @sscud.do_cud
691
-
692
- verify_source_queue_data(@s, @delete_queue_name => [])
693
- verify_doc_result(@c, :cd => {})
694
- verify_doc_result(@c, :cd_size => '0')
695
- verify_doc_result(@c, :delete_errors => {})
696
- end
697
-
698
- it "should detect delete conflict and force an error on duplicate record delete" do
699
- set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
700
- set_doc_state(@c, :cd_size => 1)
701
- set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple' }]]]]},@c.id,true)
702
- set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple', 'force_duplicate_error' => '1'}]]]]},@c.id,true)
703
- @sscud.do_cud
704
-
705
- verify_source_queue_data(@s, @delete_queue_name => [])
706
- verify_doc_result(@c, :delete_errors => {"4-error"=>{"message"=>"Error during delete: object confict detected"}, "4"=>{"name"=>"Apple", 'force_duplicate_error' => '1'}})
707
- end
494
+ it "should create Rhoconnect::Handler::Changes::Engine" do
495
+ @sscud.model.is_a?(SampleAdapter).should == true
708
496
  end
709
497
 
710
- describe "search" do
711
- before (:each) do
712
- rh = lambda { @model.search(params[:search]) }
713
- @sss = Rhoconnect::Handler::Search::Engine.new(@model, @c, rh, {})
714
- end
498
+ it "should do process_cud" do
499
+ @create_queue_name = :create
500
+ @update_queue_name = :update
501
+ @u2_fields = {:login => 'anotheruser'}
502
+ @u2 = User.create(@u2_fields)
503
+ @u2.password = 'testpass'
504
+ @c2_fields = {
505
+ :device_type => 'Android',
506
+ :device_pin => 'efgh',
507
+ :device_port => '4444',
508
+ :user_id => @u2.id,
509
+ :app_id => @a.id
510
+ }
511
+ @c2 = Client.create(@c2_fields,{:source_name => @s_fields[:name]})
512
+ @a.users << @u2.id
513
+ create_doc1 = { 'name' => 'abc', 'link' => '1', 'an_attribute' => "My attrib 2" }
514
+ create_doc2 = { 'name' => 'name0', 'link' => '1' }
515
+ create_doc3 = { 'name' => 'name7', 'link' => '7' }
516
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', create_doc1]]]]},@c.id,true)
517
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', create_doc2]]]]},@c2.id,true)
518
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['6', create_doc3]]]]},@c.id,true)
519
+ set_doc_state(@c, :cd => {'10'=> {'name' => 'Apple'}})
520
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['10', { 'name' => 'Android' }]]]]},@c.id,true)
521
+ # should receive login/logoff pair twice for create and once for update
522
+ @sscud.should_receive(:auth_method).exactly(6).times.and_return(true)
523
+ @sscud.should_receive(:_process_create).exactly(3).times
524
+ @sscud.should_receive(:_process_update).once
525
+ @sscud.should_not_receive(:_process_delete)
526
+ @sscud.do_cud
527
+ end
528
+ end
715
529
 
716
- it "should do search with no exception" do
717
- verify_read_operation('search')
718
- end
719
-
720
- it "should do search with no exception pass through" do
721
- verify_read_operation_pass_through('search')
722
- end
530
+ describe "cud conflicts" do
531
+ before (:each) do
532
+ @create_queue_name = :create
533
+ @update_queue_name = :update
534
+ @delete_queue_name = :delete
535
+ rh = lambda { @model.send(params[:operation].to_sym, params["#{params[:operation]}_object".to_sym]) }
536
+ @sscud = Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], @model, rh, {})
537
+ @u2_fields = {:login => 'anotheruser'}
538
+ @u2 = User.create(@u2_fields)
539
+ @u2.password = 'testpass'
540
+ @c2_fields = {
541
+ :device_type => 'Android',
542
+ :device_pin => 'efgh',
543
+ :device_port => '4444',
544
+ :user_id => @u2.id,
545
+ :app_id => @a.id
546
+ }
547
+ @c2 = Client.create(@c2_fields,{:source_name => @s_fields[:name]})
548
+ @a.users << @u2.id
549
+ end
723
550
 
724
- it "should do search with exception raised" do
725
- verify_read_operation_with_error('search')
726
- end
551
+ it "should detect create conflict and skip the duplicate record creation, but properly update the links" do
552
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', { 'name' => 'Android', 'link' => '1' }]]]]},@c.id,true)
553
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', { 'name' => 'Android', 'link' => '1', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '0', 'duplicate_of_queue_index' => '0' }]]]]},@c.id,true)
554
+ @sscud.do_cud
555
+
556
+ verify_source_queue_data(@s, @create_queue_name => [])
557
+ verify_doc_result(@c, :create_errors => {})
558
+ verify_doc_result(@c, :create_links => {'4'=> { 'l' => 'backend_id' }, '5' => { 'l' => 'backend_id'}})
727
559
  end
728
560
 
729
- describe "app-level partitioning" do
730
- it "should create app-level masterdoc with '__shared__' docname" do
731
- @s1 = Source.load(@s_fields[:name],@s_params)
732
- @s1.partition = :app
733
- rh = lambda { @model.query(params[:query]) }
734
- @model1 = Rhoconnect::Model::Base.create(@s1)
735
- @ssq = Rhoconnect::Handler::Query::Engine.new(@model1, rh, {})
736
- expected = {'1'=>@product1,'2'=>@product2}
737
- set_state('test_db_storage' => expected)
738
- @ssq.do_sync
739
- verify_doc_result(@s1, :md => expected)
740
- Store.get_store(0).keys("read_state:#{test_app_name}:__shared__*").sort.should ==
741
- [ "read_state:#{test_app_name}:__shared__:SampleAdapter:refresh_time",
742
- "read_state:#{test_app_name}:__shared__:SampleAdapter:prev_refresh_time",
743
- "read_state:#{test_app_name}:__shared__:SampleAdapter:rho__id",
744
- "read_state:#{test_app_name}:__shared__:SampleAdapter:retry_counter"].sort
745
- end
561
+ it "should detect create conflict and skip the duplicate record creation, but properly update the errors page" do
562
+ create_doc1 = { 'name' => 'wrongname', 'link' => '1', 'an_attribute' => "Create Sample Adapter Error" }
563
+ create_doc2 = { 'name' => 'wrongname', 'link' => '1', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '0', 'duplicate_of_queue_index' => '0'}
564
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', create_doc1]]]]},@c.id,true)
565
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', create_doc2]]]]},@c.id,true)
566
+ @sscud.do_cud
567
+
568
+ verify_source_queue_data(@s, @create_queue_name => [])
569
+ verify_doc_result(@c, :create_errors => {"4-error"=>{"message"=>"Create Sample Adapter Error"},
570
+ '4' => create_doc1,
571
+ "5-error"=>{"message"=>"Create Sample Adapter Error"},
572
+ '5' => create_doc2})
573
+ verify_doc_result(@c, :create_links => {})
746
574
  end
747
575
 
748
- def verify_read_operation(operation)
749
- expected = {'1'=>@product1,'2'=>@product2}
750
- set_test_data('test_db_storage',expected)
751
- @s.put_data(:errors,
752
- {"#{operation}-error"=>{'message'=>'failed'}},true)
753
- if operation == 'query'
754
- @ssq.run_query.should == true
755
- verify_doc_result(@s, {:md => expected,
756
- :errors => {}})
757
- else
758
- @sss.run_search.should == true
759
- verify_doc_result(@c, {:search => expected,
760
- :search_errors => {}})
576
+ it "should detect create conflict and force and error" do
577
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', { 'name' => 'Android', 'link' => true }]]]]},@c.id,true)
578
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', { 'name' => 'Android', 'link' => '1', 'force_duplicate_error' => '1' }]]]]},@c.id,true)
579
+ @sscud.do_cud
580
+ verify_source_queue_data(@s, @create_queue_name => [])
581
+ verify_doc_result(@c, :create_errors => {"5-error"=>{"message"=>"Error during create: object confict detected"}, "5"=>{"name"=>"Android", "link"=>"1", 'force_duplicate_error' => '1'}} )
582
+ end
583
+
584
+ it "should detect create conflict in the intermediate state create and skip the duplicate record create" do
585
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', { 'name' => 'InvalidName', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '1', 'duplicate_of_queue_index' => '0'}], ['4', { 'name' => 'Android' , 'link' => true}]]]]},@c.id,true)
586
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['6', { 'name' => 'iPhone', 'link' => true }], ['7', { 'name' => 'InvalidName', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '1', 'duplicate_of_queue_index' => '0'}]]]]},@c.id,true)
587
+ @sscud.do_cud
588
+
589
+ verify_source_queue_data(@s, @create_queue_name => [])
590
+ verify_doc_result(@c, :create_errors => {})
591
+ verify_doc_result(@c, :create_links => {'4'=> { 'l' => 'backend_id' }, '5'=> { 'l' => 'backend_id' }, '6' => { 'l' => 'backend_id' }, '7'=> { 'l' => 'backend_id' }})
592
+ end
593
+
594
+ it "should detect update conflict and skip the duplicate record update" do
595
+ set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
596
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
597
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'InvalidName', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '0', 'duplicate_of_queue_index' => '0'}]]]]},@c.id,true)
598
+ @sscud.do_cud
599
+
600
+ verify_source_queue_data(@s, @update_queue_name => [])
601
+ verify_doc_result(@c, :update_errors => {})
602
+ verify_doc_result(@c, :update_rollback => {})
603
+ end
604
+
605
+ it "should detect update conflict and force an error on duplicate record update" do
606
+ set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
607
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
608
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'ErrorName', 'force_duplicate_error' => '1' }]]]]},@c.id,true)
609
+ @sscud.do_cud
610
+
611
+ verify_source_queue_data(@s, @update_queue_name => [])
612
+ verify_doc_result(@c, :update_errors => {"4-error"=>{"message"=>"Error during update: object confict detected"}, "4"=>{"name"=>"ErrorName", 'force_duplicate_error' => '1'}})
613
+ verify_doc_result(@c, :update_rollback => {'4'=> {'name' => 'Apple'}})
614
+ end
615
+
616
+ it "should install find_duplicates_on_update , detect equal objects conflict and skip the duplicate record update" do
617
+ SampleAdapter.enable :find_duplicates_on_update
618
+ set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
619
+ set_doc_state(@c2, :cd => {'4'=> {'name' => 'Apple'}})
620
+ update_source1 = Source.load(@s.name,
621
+ {:user_id => @u.id,:app_id => @a.id})
622
+ update_source2 = Source.load(@s.name,
623
+ {:user_id => @u2.id,:app_id => @a.id})
624
+ set_source_queue_state(update_source1, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
625
+ set_source_queue_state(update_source2, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c2.id, true)
626
+ operation_data,client_ids = update_source2.get_queue(:update)
627
+ invalid_meta = @sscud.model.run_validators(:update,operation_data,client_ids)
628
+ invalid_meta.should == { 1=> {@s.name => { 0 => { :duplicate_of=>true }}},
629
+ 0=>{@s.name => { 0 => { :duplicates=>[{:client_id=>@c2.id, :key=>"4", :value=>{"name"=>"Android"}}]}}}}
630
+
631
+ @sscud.model.should_receive(:find_duplicates_on_update).once.and_return(invalid_meta)
632
+ @sscud.do_cud
633
+
634
+ verify_source_queue_data(@s, @update_queue_name => [])
635
+ verify_doc_result(@c, :update_errors => {})
636
+ verify_doc_result(@c, :update_rollback => {})
637
+ SampleAdapter.validators.delete(:find_duplicates_on_update)
638
+ end
639
+
640
+ it "should detect update conflict and force an error on duplicate record update" do
641
+ set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
642
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
643
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'ErrorName', 'force_duplicate_error' => '1' }]]]]},@c.id,true)
644
+ @sscud.do_cud
645
+
646
+ verify_source_queue_data(@s, @update_queue_name => [])
647
+ verify_doc_result(@c, :update_errors => {"4-error"=>{"message"=>"Error during update: object confict detected"}, "4"=>{"name"=>"ErrorName", 'force_duplicate_error' => '1'}})
648
+ verify_doc_result(@c, :update_rollback => {'4'=> {'name' => 'Apple'}})
649
+ end
650
+
651
+ it "should install find_duplicates_on_update , detect equal objects conflict and raise a custom error" do
652
+ SampleAdapter.enable :find_duplicates_on_update, :raise_error => true do |options, invalid_meta, operation, operation_data, client_ids|
653
+ invalid_meta.each do |index, index_data|
654
+ index_data.each do |source_id, objindex_data|
655
+ objindex_data.each do |objindex, objmeta|
656
+ if objmeta.has_key?(:error)
657
+ objmeta[:error] = "My custom error"
658
+ end
659
+ end if objindex_data
660
+ end if index_data
661
+ end if invalid_meta
662
+ invalid_meta
663
+ end
664
+ set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
665
+ set_doc_state(@c2, :cd => {'4'=> {'name' => 'Apple'}})
666
+ update_source1 = Source.load(@s.name,
667
+ {:user_id => @u.id,:app_id => @a.id})
668
+ update_source2 = Source.load(@s.name,
669
+ {:user_id => @u2.id,:app_id => @a.id})
670
+ set_source_queue_state(update_source1, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
671
+ set_source_queue_state(update_source2, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c2.id, true)
672
+ operation_data,client_ids = update_source2.get_queue(:update)
673
+ invalid_meta = @sscud.model.run_validators(:update,operation_data,client_ids)
674
+ invalid_meta.should == {1=>{@s.name=>{0=>{:error=>"My custom error"}}}}
675
+
676
+ @sscud.do_cud
677
+ verify_source_queue_data(@s, @update_queue_name => [])
678
+ verify_doc_result(@c, :update_errors => {})
679
+ verify_doc_result(@c, :update_rollback => {})
680
+ verify_doc_result(@c2, :update_errors => {"4-error"=>{"message"=>"My custom error"}, "4"=>{"name"=>"Android"}})
681
+ verify_doc_result(@c2, :update_rollback => {'4'=> {'name' => 'Apple'}})
682
+ SampleAdapter.validators.delete(:find_duplicates_on_update)
683
+ end
684
+
685
+
686
+ it "should detect delete conflict and skip the duplicate record delete" do
687
+ set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
688
+ set_doc_state(@c, :cd_size => 1)
689
+ set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple' }]]]]},@c.id,true)
690
+ set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '0', 'duplicate_of_queue_index' => '0'}]]]]},@c.id,true)
691
+ @sscud.do_cud
692
+
693
+ verify_source_queue_data(@s, @delete_queue_name => [])
694
+ verify_doc_result(@c, :cd => {})
695
+ verify_doc_result(@c, :cd_size => '0')
696
+ verify_doc_result(@c, :delete_errors => {})
697
+ end
698
+
699
+ it "should detect delete conflict and force an error on duplicate record delete" do
700
+ set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
701
+ set_doc_state(@c, :cd_size => 1)
702
+ set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple' }]]]]},@c.id,true)
703
+ set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple', 'force_duplicate_error' => '1'}]]]]},@c.id,true)
704
+ @sscud.do_cud
705
+
706
+ verify_source_queue_data(@s, @delete_queue_name => [])
707
+ verify_doc_result(@c, :delete_errors => {"4-error"=>{"message"=>"Error during delete: object confict detected"}, "4"=>{"name"=>"Apple", 'force_duplicate_error' => '1'}})
708
+ end
709
+ end
710
+
711
+ describe "search" do
712
+ before (:each) do
713
+ rh = lambda { @model.search(params[:search]) }
714
+ @sss = Rhoconnect::Handler::Search::Engine.new(@model, @c, rh, {})
715
+ end
716
+
717
+ it "should do search with no exception" do
718
+ verify_read_operation('search')
719
+ end
720
+
721
+ it "should do search with no exception pass through" do
722
+ verify_read_operation_pass_through('search')
761
723
  end
724
+
725
+ it "should do search with exception raised" do
726
+ verify_read_operation_with_error('search')
762
727
  end
763
-
764
- def verify_read_operation_pass_through(operation)
728
+ end
729
+
730
+ describe "app-level partitioning" do
731
+ it "should create app-level masterdoc with '__shared__' docname" do
732
+ @s1 = Source.load(@s_fields[:name],@s_params)
733
+ @s1.partition = :app
734
+ rh = lambda { @model.query(params[:query]) }
735
+ @model1 = Rhoconnect::Model::Base.create(@s1)
736
+ @ssq = Rhoconnect::Handler::Query::Engine.new(@model1, rh, {})
765
737
  expected = {'1'=>@product1,'2'=>@product2}
766
- set_test_data('test_db_storage',expected)
767
- @s.put_data(:errors,
768
- {"#{operation}-error"=>{'message'=>'failed'}},true)
769
- @s.pass_through = 'true'
770
- if operation == 'query'
771
- @ssq.run_query.should == expected
772
- verify_doc_result(@s, {:md => {},
738
+ set_state('test_db_storage' => expected)
739
+ @ssq.do_sync
740
+ verify_doc_result(@s1, :md => expected)
741
+ Store.get_store(0).keys("read_state:#{test_app_name}:__shared__*").sort.should ==
742
+ [ "read_state:#{test_app_name}:__shared__:SampleAdapter:refresh_time",
743
+ "read_state:#{test_app_name}:__shared__:SampleAdapter:prev_refresh_time",
744
+ "read_state:#{test_app_name}:__shared__:SampleAdapter:rho__id",
745
+ "read_state:#{test_app_name}:__shared__:SampleAdapter:retry_counter"].sort
746
+ end
747
+ end
748
+
749
+ def verify_read_operation(operation)
750
+ expected = {'1'=>@product1,'2'=>@product2}
751
+ set_test_data('test_db_storage',expected)
752
+ @s.put_data(:errors,
753
+ {"#{operation}-error"=>{'message'=>'failed'}},true)
754
+ if operation == 'query'
755
+ @ssq.run_query.should == true
756
+ verify_doc_result(@s, {:md => expected,
773
757
  :errors => {}})
774
- else
775
- @sss.run_search.should == expected
776
- verify_doc_result(@c, {:search => {},
777
- :search_errors => {}})
778
- end
758
+ else
759
+ @sss.run_search.should == true
760
+ verify_doc_result(@c, {:search => expected,
761
+ :search_errors => {}})
779
762
  end
763
+ end
780
764
 
781
- def verify_read_operation_with_error(operation)
782
- msg = "Error during #{operation}"
783
- set_test_data('test_db_storage',{},msg,"#{operation} error")
784
- if operation == 'query'
785
- @ssq.should_receive(:log).with("Model raised #{operation} exception: #{msg}")
786
- @ssq.should_receive(:log).with(anything)
787
- @ssq.run_query.should == true
788
- verify_doc_result(@s, {:md => {},
789
- :errors => {'query-error'=>{'message'=>msg}}})
790
- else
791
- @sss.should_receive(:log).with("Model raised #{operation} exception: #{msg}")
792
- @sss.should_receive(:log).with(anything)
793
- @sss.run_search.should == true
794
- verify_doc_result(@c, {:search => {},
795
- :search_errors => {'search-error'=>{'message'=>msg}}})
796
- end
765
+ def verify_read_operation_pass_through(operation)
766
+ expected = {'1'=>@product1,'2'=>@product2}
767
+ set_test_data('test_db_storage',expected)
768
+ @s.put_data(:errors,
769
+ {"#{operation}-error"=>{'message'=>'failed'}},true)
770
+ @s.pass_through = 'true'
771
+ if operation == 'query'
772
+ @ssq.run_query.should == expected
773
+ verify_doc_result(@s, {:md => {},
774
+ :errors => {}})
775
+ else
776
+ @sss.run_search.should == expected
777
+ verify_doc_result(@c, {:search => {},
778
+ :search_errors => {}})
797
779
  end
780
+ end
798
781
 
799
- describe "Jobs" do
800
- it "should enqueue process_cud SourceJob" do
801
- @s.cud_queue = :cud
802
- rh = lambda { @model.send(params[:operation].to_sym, params["#{params[:operation]}_object".to_sym]) }
803
- @sscud = Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], @model, rh, {})
804
- @sscud.do_cud
805
- Resque.peek(:cud).should == {"args"=>
806
- ["cud", @s.name, @a.name, @u.login, nil], "class"=>"Rhoconnect::SourceJob"}
807
- end
782
+ def verify_read_operation_with_error(operation)
783
+ msg = "Error during #{operation}"
784
+ set_test_data('test_db_storage',{},msg,"#{operation} error")
785
+ if operation == 'query'
786
+ @ssq.should_receive(:log).with("Model raised #{operation} exception: #{msg}")
787
+ @ssq.should_receive(:log).with(anything)
788
+ @ssq.run_query.should == true
789
+ verify_doc_result(@s, {:md => {},
790
+ :errors => {'query-error'=>{'message'=>msg}}})
791
+ else
792
+ @sss.should_receive(:log).with("Model raised #{operation} exception: #{msg}")
793
+ @sss.should_receive(:log).with(anything)
794
+ @sss.run_search.should == true
795
+ verify_doc_result(@c, {:search => {},
796
+ :search_errors => {'search-error'=>{'message'=>msg}}})
797
+ end
798
+ end
808
799
 
809
- it "should enqueue process_query SourceJob" do
810
- @s.query_queue = :abc
811
- rh = lambda { @model.query(params[:query]) }
812
- @ssq = Rhoconnect::Handler::Query::Engine.new(@model, rh, { :query => {'foo'=>'bar'} })
813
- @ssq.do_sync
814
- Resque.peek(:abc).should == {"args"=>
815
- ["query", @s.name, @a.name, @u.login, {'foo'=>'bar'}], "class"=>"Rhoconnect::SourceJob"}
816
- end
800
+ describe "Jobs" do
801
+ it "should enqueue process_cud SourceJob" do
802
+ @s.cud_queue = :cud
803
+ rh = lambda { @model.send(params[:operation].to_sym, params["#{params[:operation]}_object".to_sym]) }
804
+ @sscud = Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], @model, rh, {})
805
+ @sscud.do_cud
806
+ Resque.peek(:cud).should == {"args"=>
807
+ ["cud", @s.name, @a.name, @u.login, nil], "class"=>"Rhoconnect::SourceJob"}
808
+ end
809
+
810
+ it "should enqueue process_query SourceJob" do
811
+ @s.query_queue = :abc
812
+ rh = lambda { @model.query(params[:query]) }
813
+ @ssq = Rhoconnect::Handler::Query::Engine.new(@model, rh, { :query => {'foo'=>'bar'} })
814
+ @ssq.do_sync
815
+ Resque.peek(:abc).should == {"args"=>
816
+ ["query", @s.name, @a.name, @u.login, {'foo'=>'bar'}], "class"=>"Rhoconnect::SourceJob"}
817
817
  end
818
818
  end
819
819
  end