rhoconnect 4.0.0.beta.12 → 4.0.0.beta.24

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 (88) hide show
  1. data/CHANGELOG.md +34 -0
  2. data/Gemfile +5 -8
  3. data/Gemfile.lock +34 -32
  4. data/Rakefile +2 -12
  5. data/bench/run_bench.sh +1 -1
  6. data/bench/spec/bench_spec_helper.rb +3 -5
  7. data/bin/rhoconnect +20 -13
  8. data/commands/dtach/dtach_install.rb +3 -3
  9. data/commands/generators/update.rb +1 -0
  10. data/commands/parser.rb +180 -0
  11. data/commands/rhoconnect/config.rb +8 -4
  12. data/commands/rhoconnect/get_token.rb +3 -1
  13. data/commands/rhoconnect/routes.rb +34 -0
  14. data/commands/rhoconnect/secret.rb +1 -1
  15. data/commands/rhoconnect/set_admin_password.rb +2 -1
  16. data/commands/rhoconnect/startbg.rb +4 -4
  17. data/commands/rhoconnect/stop.rb +2 -2
  18. data/commands/rhoconnect_console/console.rb +6 -6
  19. data/commands/rhoconnect_spec/spec.rb +2 -1
  20. data/commands/rhoconnect_war/war.rb +1 -0
  21. data/commands/utilities/redis_runner.rb +2 -2
  22. data/doc/data-partitioning.txt +20 -7
  23. data/doc/extending-rhoconnect-server.txt +36 -7
  24. data/doc/push-client-setup-rps.txt +3 -3
  25. data/doc/source-adapters-js.txt +41 -4
  26. data/doc/source-adapters.txt +3 -0
  27. data/generators/templates/application/rcgemfile +1 -5
  28. data/generators/templates/application/spec/spec_helper.rb +0 -9
  29. data/generators/templates/source/models/js/model.js +8 -0
  30. data/generators/templates/source/models/ruby/model.rb +5 -11
  31. data/install.sh +2 -2
  32. data/installer/unix-like/rho_connect_install_constants.rb +3 -3
  33. data/installer/utils/delete_from_s3.rb +3 -6
  34. data/installer/utils/download_from_s3.rb +5 -9
  35. data/installer/utils/verify_checksum.rb +16 -11
  36. data/js-adapters/ballroom.js +9 -0
  37. data/js-adapters/exceptions.js +60 -0
  38. data/js-adapters/node.rb +14 -5
  39. data/js-adapters/node_channel.rb +68 -48
  40. data/js-adapters/rhoconnect_helpers.js +8 -2
  41. data/js-adapters/router.js +16 -14
  42. data/lib/rhoconnect.rb +5 -5
  43. data/lib/rhoconnect/client.rb +2 -2
  44. data/lib/rhoconnect/condition/add_parameter.rb +21 -0
  45. data/lib/rhoconnect/controller/clients_controller.rb +1 -1
  46. data/lib/rhoconnect/controller/js_base.rb +1 -2
  47. data/lib/rhoconnect/controller/system_controller.rb +33 -10
  48. data/lib/rhoconnect/db_adapter.rb +1 -1
  49. data/lib/rhoconnect/document.rb +11 -3
  50. data/lib/rhoconnect/handler/changes.rb +20 -19
  51. data/lib/rhoconnect/handler/changes/engine.rb +48 -25
  52. data/lib/rhoconnect/handler/changes/hooks.rb +36 -0
  53. data/lib/rhoconnect/handler/changes/runner.rb +12 -2
  54. data/lib/rhoconnect/handler/helpers.rb +4 -4
  55. data/lib/rhoconnect/jobs/source_job.rb +1 -1
  56. data/lib/rhoconnect/model/base.rb +32 -8
  57. data/lib/rhoconnect/model/helpers.rb +15 -0
  58. data/lib/rhoconnect/model/helpers/find_duplicates_on_update.rb +85 -0
  59. data/lib/rhoconnect/model/js_base.rb +23 -28
  60. data/lib/rhoconnect/server.rb +23 -18
  61. data/lib/rhoconnect/source.rb +10 -17
  62. data/lib/rhoconnect/store.rb +36 -57
  63. data/lib/rhoconnect/test_methods.rb +5 -4
  64. data/lib/rhoconnect/utilities.rb +7 -5
  65. data/lib/rhoconnect/version.rb +2 -2
  66. data/lib/rhoconnect/web-console/server.rb +17 -16
  67. data/rhoconnect.gemspec +23 -24
  68. data/spec/apps/rhotestapp/controllers/js/js_sample_controller.js +4 -0
  69. data/spec/apps/rhotestapp/models/js/js_sample.js +36 -0
  70. data/spec/apps/rhotestapp/models/ruby/sample_adapter.rb +34 -19
  71. data/spec/async_spec.rb +1 -1
  72. data/spec/cli/cli_spec.rb +69 -31
  73. data/spec/client_sync_spec.rb +30 -13
  74. data/spec/controllers/js_base_spec.rb +10 -4
  75. data/spec/jobs/source_job_spec.rb +1 -1
  76. data/spec/models/{js_model_spec.rb → js_base_spec.rb} +63 -13
  77. data/spec/server/server_spec.rb +20 -0
  78. data/spec/server/stats_spec.rb +7 -17
  79. data/spec/source_adapter_spec.rb +6 -0
  80. data/spec/source_sync_spec.rb +219 -54
  81. data/spec/spec_helper.rb +8 -13
  82. data/spec/store_spec.rb +6 -4
  83. data/spec/test_methods_spec.rb +4 -4
  84. metadata +14 -27
  85. data/commands/execute.rb +0 -48
  86. data/commands/utilities/utilities.rb +0 -6
  87. data/generators/templates/application/controllers/application_controller.rb +0 -17
  88. data/lib/rhoconnect/js_adapter.rb +0 -79
@@ -145,6 +145,26 @@ describe "Server" do
145
145
  last_response.status.should == 204
146
146
  end
147
147
  end
148
+
149
+ context "rps app authenticate" do
150
+ it "should authenticate rhoconnect app with correct push server credentials" do
151
+ # Test app push server settings are
152
+ # :push_server: http://user:pwd@localhost:8675
153
+ authorize 'user', 'pwd'
154
+ get "/rc/#{Rhoconnect::API_VERSION}/system/rps_login", {}
155
+ last_response.status.should == 204
156
+ end
157
+ it "should not authenticate rhoconnect app with invalid rhoconnect push server credentials" do
158
+ authorize 'someappname', ''
159
+ get "/rc/#{Rhoconnect::API_VERSION}/system/rps_login", {}
160
+ last_response.status.should == 401
161
+ end
162
+ it "should not authenticate rhoconnect app with invalid basic Authorization header in request" do
163
+ # Basic Authorization header is missing
164
+ get "/rc/#{Rhoconnect::API_VERSION}/system/rps_login", {}
165
+ last_response.status.should == 401
166
+ end
167
+ end
148
168
  end
149
169
 
150
170
  describe "controller custom routes" do
@@ -46,22 +46,12 @@ describe "Middleware" do
46
46
  Rhoconnect::Stats::Record.key(metric).should == "stat:#{metric}"
47
47
 
48
48
  # The conversion algorithm (float to string) currently checks two precisions.
49
- # In Ruby 1.9, it tries 16 digits and if that's not enough it then uses 17.
50
- # In 1.8, it's the same but with 15 and 16.
51
- if RUBY_VERSION =~ /1.9/
52
- Rhoconnect::Stats::Record.range(metric, 0, -1).should == [
53
- "2.0,0.6000000000000014:12",
54
- "2.0,0.6000000000000014:14",
55
- "2.0,0.6000000000000014:16",
56
- "2.0,0.6000000000000014:18"
57
- ]
58
- else
59
- Rhoconnect::Stats::Record.range(metric, 0, -1).should == [
60
- "2.0,0.600000000000002:12",
61
- "2.0,0.600000000000002:14",
62
- "2.0,0.600000000000002:16",
63
- "2.0,0.600000000000002:18"
64
- ]
65
- end
49
+ # it tries 16 digits and if that's not enough it then uses 17.
50
+ Rhoconnect::Stats::Record.range(metric, 0, -1).should == [
51
+ "2.0,0.6000000000000014:12",
52
+ "2.0,0.6000000000000014:14",
53
+ "2.0,0.6000000000000014:16",
54
+ "2.0,0.6000000000000014:18"
55
+ ]
66
56
  end
67
57
  end
@@ -109,6 +109,12 @@ describe "Model" do
109
109
  Store.get_value(@s.docname(:md_size)).to_i.should == 2
110
110
  end
111
111
 
112
+ it "should execute Model sync method with nil result" do
113
+ @sa.inject_result nil
114
+ @sa.do_query
115
+ Store.get_data(@s.docname(:md)).should == {}
116
+ end
117
+
112
118
  it "should fail gracefully if @result is missing" do
113
119
  @sa.inject_result nil
114
120
  lambda { @sa.query }.should_not raise_error
@@ -305,21 +305,34 @@ describe "SourceSync" do
305
305
  before (:each) do
306
306
  rh = lambda { @model.create(params[:create_object])}
307
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
308
321
  end
309
322
 
310
323
  it "should do create where adapter.create returns nil" do
311
- set_source_queue_state(@s, {:create => {'2'=>@product2}}, @c.id, true)
324
+ set_source_queue_state(@s, {@queue_name => [[@s.name, [['2', @product2]]]]}, @c.id, true)
312
325
  @ssc.create
313
- verify_source_queue_data(@s, :create => [])
326
+ verify_source_queue_data(@s, @queue_name => [])
314
327
  verify_doc_result(@c, {:create_errors => {},
315
328
  :create_links => {}})
316
329
  end
317
330
 
318
331
  it "should do create where adapter.create returns object link" do
319
332
  @product4['link'] = 'test link'
320
- set_source_queue_state(@s, {:create => {'4'=>@product4}},@c.id,true)
333
+ set_source_queue_state(@s, {@queue_name => [[@s.name, [['4', @product4]]]]},@c.id,true)
321
334
  @ssc.create
322
- verify_source_queue_data(@s, :create => [])
335
+ verify_source_queue_data(@s, @queue_name => [])
323
336
  verify_doc_result(@c, {:create_errors => {},
324
337
  :create_links => {'4'=>{'l'=>'backend_id'}}})
325
338
  end
@@ -327,35 +340,66 @@ describe "SourceSync" do
327
340
  it "should raise exception on adapter.create" do
328
341
  msg = "Error creating record"
329
342
  data = add_error_object({'4'=>@product4,'2'=>@product2},msg)
330
- set_source_queue_state(@s, {:create => data},@c.id, true)
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)
331
348
  @ssc.create
332
349
  verify_doc_result(@c, :create_errors =>
333
350
  {"#{ERROR}-error"=>{"message"=>msg},ERROR=>data[ERROR]})
334
351
  end
352
+
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
335
372
  end
336
373
 
337
374
  describe "update" do
338
375
  before (:each) do
339
376
  rh = lambda { @model.update(params[:update_object])}
340
377
  @ssu = Rhoconnect::Handler::Changes::Engine.new(['update'], @model, rh, {})
378
+ @queue_name = "update"
341
379
  end
342
380
 
343
381
  it "should do update with no errors" do
344
- set_source_queue_state(@s, {:update => {'4'=> { 'price' => '199.99' }}},@c.id,true)
382
+ set_source_queue_state(@s, {@queue_name => [[@s.name, [['4', { 'price' => '199.99' }]]]]},@c.id,true)
345
383
  @ssu.update
346
- verify_source_queue_data(@s, :update => [])
384
+ verify_source_queue_data(@s, @queue_name => [])
347
385
  verify_doc_result(@c, :update_errors => {})
348
386
  end
349
387
 
350
388
  it "should do update with errors" do
351
389
  msg = "Error updating record"
352
- data = add_error_object({'4'=> { 'price' => '199.99' }},msg)
353
- set_source_queue_state(@s, {:update => data},@c.id,true)
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)
354
398
  set_doc_state(@c, :cd => { ERROR => { 'price' => '99.99' } }
355
399
  )
356
400
  @ssu.update
357
- update_data,client_ids = @s.get_queue(:update)
358
- update_data.should == [{'4'=> { 'price' => '199.99'}}]
401
+ update_data,client_ids = @s.get_queue(@queue_name)
402
+ update_data.should == [[[@s.name, [['4', { 'price' => '199.99'}]]]]]
359
403
  client_ids.should == [@c.id]
360
404
  verify_doc_result(@c, {:update_errors =>
361
405
  {"#{ERROR}-error"=>{"message"=>msg}, ERROR=>data[ERROR]},
@@ -367,14 +411,27 @@ describe "SourceSync" do
367
411
  before (:each) do
368
412
  rh = lambda { @model.update(params[:delete_object])}
369
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
370
427
  end
371
428
 
372
429
  it "should do delete with no errors" do
373
- set_source_queue_state(@s, {:delete => {'4'=>@product4}}, @c.id, true)
430
+ set_source_queue_state(@s, {@queue_name => [[@s.name, [['4', @product4]]]]}, @c.id, true)
374
431
  set_doc_state(@s, :md => {'4'=>@product4,'3'=>@product3})
375
432
  set_doc_state(@c, :cd => {'4'=>@product4,'3'=>@product3})
376
433
  @ssd.delete
377
- verify_source_queue_data(@s, :delete => [])
434
+ verify_source_queue_data(@s, @queue_name => [])
378
435
  verify_doc_result(@c, :delete_errors => {})
379
436
  verify_doc_result(@s, :md => {'3'=>@product3})
380
437
  verify_doc_result(@c, :cd => {'3'=>@product3})
@@ -382,27 +439,40 @@ describe "SourceSync" do
382
439
 
383
440
  it "should do delete with errors" do
384
441
  msg = "Error delete record"
385
- data = add_error_object({'2'=>@product2},msg)
386
- set_source_queue_state(@s, {:delete => data}, @c.id, true)
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)
387
450
  @ssd.delete
388
-
389
- # FIXME: Failed for jruby, ruby 1.9.2
390
- # verify_result(@c.docname(:delete_errors) =>
391
- # {"#{ERROR}-error"=>{"message"=>msg}, ERROR=>data[ERROR]},
392
- # @c.docname(:delete) => {'2'=>@product2})
393
-
394
- # Failure/Error: {"#{ERROR}-error"=>{"message"=>msg}, ERROR=>data[ERROR]},
395
- # Verifying `client:application:testuser:b020a633ac2c43f7b0d30ef92dd43886:SampleAdapter:delete`
396
- # expected: {"2"=>{"name"=>"G2", "brand"=>"Android", "price"=>"99.99"}}
397
- # got: {} (using ==)
398
-
399
- # Failure/Error: @c.docname(:delete) => {'2'=>@product2})
400
- # Verifying `client:application:testuser:26a072a1da0d4bc18f69376e3229ab30:SampleAdapter:delete`
401
- # expected: {"2"=>{"name"=>"G2", "brand"=>"Android", "price"=>"99.99"}}
402
- # got: {} (using ==)
403
-
404
- # But this one works everywhere !!!
405
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
454
+
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})
406
476
  end
407
477
  end
408
478
 
@@ -435,28 +505,43 @@ describe "SourceSync" do
435
505
 
436
506
  describe "cud conflicts" do
437
507
  before (:each) do
508
+ @create_queue_name = :create
509
+ @update_queue_name = :update
510
+ @delete_queue_name = :delete
438
511
  rh = lambda { @model.send(params[:operation].to_sym, params["#{params[:operation]}_object".to_sym]) }
439
512
  @sscud = Rhoconnect::Handler::Changes::Engine.new(['create', 'update', 'delete'], @model, rh, {})
513
+ @u2_fields = {:login => 'anotheruser'}
514
+ @u2 = User.create(@u2_fields)
515
+ @u2.password = 'testpass'
516
+ @c2_fields = {
517
+ :device_type => 'Android',
518
+ :device_pin => 'efgh',
519
+ :device_port => '4444',
520
+ :user_id => @u2.id,
521
+ :app_id => @a.id
522
+ }
523
+ @c2 = Client.create(@c2_fields,{:source_name => @s_fields[:name]})
524
+ @a.users << @u2.id
440
525
  end
441
526
 
442
527
  it "should detect create conflict and skip the duplicate record creation, but properly update the links" do
443
- set_source_queue_state(@s, {:create => {'4'=> { 'name' => 'Android', 'link' => '1' }}},@c.id,true)
444
- set_source_queue_state(@s, {:create => {'5'=> { 'name' => 'Android', 'link' => '1', 'duplicate_of_cid' => @c.id, 'duplicate_of_key' => '4', 'duplicate_of_queue_index' => '0' }}},@c.id,true)
528
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', { 'name' => 'Android', 'link' => '1' }]]]]},@c.id,true)
529
+ 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)
445
530
  @sscud.do_cud
446
531
 
447
- verify_source_queue_data(@s, :create => [])
532
+ verify_source_queue_data(@s, @create_queue_name => [])
448
533
  verify_doc_result(@c, :create_errors => {})
449
534
  verify_doc_result(@c, :create_links => {'4'=> { 'l' => 'backend_id' }, '5' => { 'l' => 'backend_id'}})
450
535
  end
451
536
 
452
537
  it "should detect create conflict and skip the duplicate record creation, but properly update the errors page" do
453
538
  create_doc1 = { 'name' => 'wrongname', 'link' => '1', 'an_attribute' => "Create Sample Adapter Error" }
454
- create_doc2 = { 'name' => 'wrongname', 'link' => '1', 'duplicate_of_cid' => @c.id, 'duplicate_of_key' => '4', 'duplicate_of_queue_index' => '0'}
455
- set_source_queue_state(@s, {:create => {'4' => create_doc1}},@c.id,true)
456
- set_source_queue_state(@s, {:create => {'5' => create_doc2}},@c.id,true)
539
+ create_doc2 = { 'name' => 'wrongname', 'link' => '1', 'duplicate_of_cid' => @c.id, 'duplicate_of_entry_index' => '0', 'duplicate_of_queue_index' => '0'}
540
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', create_doc1]]]]},@c.id,true)
541
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', create_doc2]]]]},@c.id,true)
457
542
  @sscud.do_cud
458
543
 
459
- verify_source_queue_data(@s, :create => [])
544
+ verify_source_queue_data(@s, @create_queue_name => [])
460
545
  verify_doc_result(@c, :create_errors => {"4-error"=>{"message"=>"Create Sample Adapter Error"},
461
546
  '4' => create_doc1,
462
547
  "5-error"=>{"message"=>"Create Sample Adapter Error"},
@@ -465,43 +550,123 @@ describe "SourceSync" do
465
550
  end
466
551
 
467
552
  it "should detect create conflict and force and error" do
468
- set_source_queue_state(@s, {:create => {'4'=> { 'name' => 'Android', 'link' => true }}},@c.id,true)
469
- set_source_queue_state(@s, {:create => {'5'=> { 'name' => 'Android', 'link' => '1', 'force_duplicate_error' => '1' }}},@c.id,true)
553
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['4', { 'name' => 'Android', 'link' => true }]]]]},@c.id,true)
554
+ set_source_queue_state(@s, {@create_queue_name => [[@s.name, [['5', { 'name' => 'Android', 'link' => '1', 'force_duplicate_error' => '1' }]]]]},@c.id,true)
470
555
  @sscud.do_cud
471
- verify_source_queue_data(@s, :create => [])
556
+ verify_source_queue_data(@s, @create_queue_name => [])
472
557
  verify_doc_result(@c, :create_errors => {"5-error"=>{"message"=>"Error during create: object confict detected"}, "5"=>{"name"=>"Android", "link"=>"1", 'force_duplicate_error' => '1'}} )
473
558
  end
559
+
560
+ it "should detect create conflict in the intermediate state create and skip the duplicate record create" do
561
+ 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)
562
+ 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)
563
+ @sscud.do_cud
564
+
565
+ verify_source_queue_data(@s, @create_queue_name => [])
566
+ verify_doc_result(@c, :create_errors => {})
567
+ verify_doc_result(@c, :create_links => {'4'=> { 'l' => 'backend_id' }, '5'=> { 'l' => 'backend_id' }, '6' => { 'l' => 'backend_id' }, '7'=> { 'l' => 'backend_id' }})
568
+ end
474
569
 
475
570
  it "should detect update conflict and skip the duplicate record update" do
476
571
  set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
477
- set_source_queue_state(@s, {:update => {'4'=> { 'name' => 'Android' }}},@c.id,true)
478
- set_source_queue_state(@s, {:update => {'4'=> { 'name' => 'InvalidName', 'duplicate_of_cid' => @c.id, 'duplicate_of_key' => '4', 'duplicate_of_queue_index' => '0'}}},@c.id,true)
572
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
573
+ 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)
574
+ @sscud.do_cud
575
+
576
+ verify_source_queue_data(@s, @update_queue_name => [])
577
+ verify_doc_result(@c, :update_errors => {})
578
+ verify_doc_result(@c, :update_rollback => {})
579
+ end
580
+
581
+ it "should detect update conflict and force an error on duplicate record update" do
582
+ set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
583
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
584
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'ErrorName', 'force_duplicate_error' => '1' }]]]]},@c.id,true)
479
585
  @sscud.do_cud
480
586
 
481
- verify_source_queue_data(@s, :update => [])
587
+ verify_source_queue_data(@s, @update_queue_name => [])
588
+ verify_doc_result(@c, :update_errors => {"4-error"=>{"message"=>"Error during update: object confict detected"}, "4"=>{"name"=>"ErrorName", 'force_duplicate_error' => '1'}})
589
+ verify_doc_result(@c, :update_rollback => {'4'=> {'name' => 'Apple'}})
590
+ end
591
+
592
+ it "should install find_duplicates_on_update , detect equal objects conflict and skip the duplicate record update" do
593
+ SampleAdapter.enable :find_duplicates_on_update
594
+ set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
595
+ set_doc_state(@c2, :cd => {'4'=> {'name' => 'Apple'}})
596
+ update_source1 = Source.load(@s.name,
597
+ {:user_id => @u.id,:app_id => @a.id})
598
+ update_source2 = Source.load(@s.name,
599
+ {:user_id => @u2.id,:app_id => @a.id})
600
+ set_source_queue_state(update_source1, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
601
+ set_source_queue_state(update_source2, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c2.id, true)
602
+ operation_data,client_ids = update_source2.get_queue(:update)
603
+ invalid_meta = @sscud.model.run_validators(:update,operation_data,client_ids)
604
+ invalid_meta.should == { 1=> {@s.name => { 0 => { :duplicate_of=>true }}},
605
+ 0=>{@s.name => { 0 => { :duplicates=>[{:client_id=>@c2.id, :key=>"4", :value=>{"name"=>"Android"}}]}}}}
606
+
607
+ @sscud.model.should_receive(:find_duplicates_on_update).once.and_return(invalid_meta)
608
+ @sscud.do_cud
609
+
610
+ verify_source_queue_data(@s, @update_queue_name => [])
482
611
  verify_doc_result(@c, :update_errors => {})
483
612
  verify_doc_result(@c, :update_rollback => {})
613
+ SampleAdapter.validators.delete(:find_duplicates_on_update)
484
614
  end
485
615
 
486
616
  it "should detect update conflict and force an error on duplicate record update" do
487
617
  set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
488
- set_source_queue_state(@s, {:update => {'4'=> { 'name' => 'Android' }}},@c.id,true)
489
- set_source_queue_state(@s, {:update => {'4'=> { 'name' => 'ErrorName', 'force_duplicate_error' => '1' }}},@c.id,true)
618
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
619
+ set_source_queue_state(@s, {@update_queue_name => [[@s.name, [['4', { 'name' => 'ErrorName', 'force_duplicate_error' => '1' }]]]]},@c.id,true)
490
620
  @sscud.do_cud
491
621
 
492
- verify_source_queue_data(@s, :update => [])
622
+ verify_source_queue_data(@s, @update_queue_name => [])
493
623
  verify_doc_result(@c, :update_errors => {"4-error"=>{"message"=>"Error during update: object confict detected"}, "4"=>{"name"=>"ErrorName", 'force_duplicate_error' => '1'}})
494
624
  verify_doc_result(@c, :update_rollback => {'4'=> {'name' => 'Apple'}})
495
625
  end
496
626
 
627
+ it "should install find_duplicates_on_update , detect equal objects conflict and raise a custom error" do
628
+ SampleAdapter.enable :find_duplicates_on_update, :raise_error => true do |options, invalid_meta, operation, operation_data, client_ids|
629
+ invalid_meta.each do |index, index_data|
630
+ index_data.each do |source_id, objindex_data|
631
+ objindex_data.each do |objindex, objmeta|
632
+ if objmeta.has_key?(:error)
633
+ objmeta[:error] = "My custom error"
634
+ end
635
+ end if objindex_data
636
+ end if index_data
637
+ end if invalid_meta
638
+ invalid_meta
639
+ end
640
+ set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
641
+ set_doc_state(@c2, :cd => {'4'=> {'name' => 'Apple'}})
642
+ update_source1 = Source.load(@s.name,
643
+ {:user_id => @u.id,:app_id => @a.id})
644
+ update_source2 = Source.load(@s.name,
645
+ {:user_id => @u2.id,:app_id => @a.id})
646
+ set_source_queue_state(update_source1, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c.id,true)
647
+ set_source_queue_state(update_source2, {@update_queue_name => [[@s.name, [['4', { 'name' => 'Android' }]]]]},@c2.id, true)
648
+ operation_data,client_ids = update_source2.get_queue(:update)
649
+ invalid_meta = @sscud.model.run_validators(:update,operation_data,client_ids)
650
+ invalid_meta.should == {1=>{@s.name=>{0=>{:error=>"My custom error"}}}}
651
+
652
+ @sscud.do_cud
653
+ verify_source_queue_data(@s, @update_queue_name => [])
654
+ verify_doc_result(@c, :update_errors => {})
655
+ verify_doc_result(@c, :update_rollback => {})
656
+ verify_doc_result(@c2, :update_errors => {"4-error"=>{"message"=>"My custom error"}, "4"=>{"name"=>"Android"}})
657
+ verify_doc_result(@c2, :update_rollback => {'4'=> {'name' => 'Apple'}})
658
+ SampleAdapter.validators.delete(:find_duplicates_on_update)
659
+ end
660
+
661
+
497
662
  it "should detect delete conflict and skip the duplicate record delete" do
498
663
  set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
499
664
  set_doc_state(@c, :cd_size => 1)
500
- set_source_queue_state(@s, {:delete => {'4'=> { 'name' => 'Apple' }}},@c.id,true)
501
- set_source_queue_state(@s, {:delete => {'4'=> { 'name' => 'Apple', 'duplicate_of_cid' => @c.id, 'duplicate_of_key' => '4', 'duplicate_of_queue_index' => '0'}}},@c.id,true)
665
+ set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple' }]]]]},@c.id,true)
666
+ 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)
502
667
  @sscud.do_cud
503
668
 
504
- verify_source_queue_data(@s, :delete => [])
669
+ verify_source_queue_data(@s, @delete_queue_name => [])
505
670
  verify_doc_result(@c, :cd => {})
506
671
  verify_doc_result(@c, :cd_size => '0')
507
672
  verify_doc_result(@c, :delete_errors => {})
@@ -510,11 +675,11 @@ describe "SourceSync" do
510
675
  it "should detect delete conflict and force an error on duplicate record delete" do
511
676
  set_doc_state(@c, :cd => {'4'=> {'name' => 'Apple'}})
512
677
  set_doc_state(@c, :cd_size => 1)
513
- set_source_queue_state(@s, {:delete => {'4'=> { 'name' => 'Apple' }}},@c.id,true)
514
- set_source_queue_state(@s, {:delete => {'4'=> { 'name' => 'Apple', 'force_duplicate_error' => '1'}}},@c.id,true)
678
+ set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple' }]]]]},@c.id,true)
679
+ set_source_queue_state(@s, {@delete_queue_name => [[@s.name, [['4', { 'name' => 'Apple', 'force_duplicate_error' => '1'}]]]]},@c.id,true)
515
680
  @sscud.do_cud
516
681
 
517
- verify_source_queue_data(@s, :delete => [])
682
+ verify_source_queue_data(@s, @delete_queue_name => [])
518
683
  verify_doc_result(@c, :delete_errors => {"4-error"=>{"message"=>"Error during delete: object confict detected"}, "4"=>{"name"=>"Apple", 'force_duplicate_error' => '1'}})
519
684
  end
520
685
  end