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

Sign up to get free protection for your applications and to get access to all the features.
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