mongo 2.16.0 → 2.17.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +1 -1
  4. data/lib/mongo/auth/aws/request.rb +0 -1
  5. data/lib/mongo/client.rb +4 -0
  6. data/lib/mongo/cluster/reapers/cursor_reaper.rb +26 -14
  7. data/lib/mongo/collection/view/aggregation.rb +62 -17
  8. data/lib/mongo/collection/view/builder/aggregation.rb +11 -13
  9. data/lib/mongo/collection/view/builder/map_reduce.rb +8 -5
  10. data/lib/mongo/collection/view/change_stream.rb +7 -3
  11. data/lib/mongo/collection/view/iterable.rb +2 -3
  12. data/lib/mongo/collection/view/map_reduce.rb +16 -1
  13. data/lib/mongo/collection/view/readable.rb +24 -1
  14. data/lib/mongo/collection/view/writable.rb +23 -0
  15. data/lib/mongo/collection.rb +21 -1
  16. data/lib/mongo/cursor/kill_spec.rb +19 -2
  17. data/lib/mongo/cursor.rb +5 -5
  18. data/lib/mongo/database/view.rb +4 -2
  19. data/lib/mongo/database.rb +6 -6
  20. data/lib/mongo/error/snapshot_session_invalid_server_version.rb +31 -0
  21. data/lib/mongo/error/snapshot_session_transaction_prohibited.rb +30 -0
  22. data/lib/mongo/error.rb +2 -0
  23. data/lib/mongo/operation/delete/op_msg.rb +2 -1
  24. data/lib/mongo/operation/find/builder/command.rb +1 -0
  25. data/lib/mongo/operation/result.rb +6 -0
  26. data/lib/mongo/operation/shared/executable.rb +4 -0
  27. data/lib/mongo/operation/shared/sessions_supported.rb +18 -2
  28. data/lib/mongo/operation/update/op_msg.rb +2 -1
  29. data/lib/mongo/server/description/features.rb +3 -1
  30. data/lib/mongo/server/push_monitor.rb +4 -1
  31. data/lib/mongo/server_selector/base.rb +26 -4
  32. data/lib/mongo/session.rb +19 -0
  33. data/lib/mongo/socket/ocsp_cache.rb +2 -3
  34. data/lib/mongo/socket.rb +1 -5
  35. data/lib/mongo/utils.rb +0 -6
  36. data/lib/mongo/version.rb +1 -1
  37. data/mongo.gemspec +1 -1
  38. data/spec/integration/read_preference_spec.rb +16 -12
  39. data/spec/lite_spec_helper.rb +7 -0
  40. data/spec/mongo/cluster/cursor_reaper_spec.rb +22 -15
  41. data/spec/mongo/collection/view/aggregation_spec.rb +71 -95
  42. data/spec/mongo/collection/view/change_stream_spec.rb +1 -1
  43. data/spec/mongo/collection/view/map_reduce_spec.rb +30 -1
  44. data/spec/mongo/cursor_spec.rb +3 -2
  45. data/spec/mongo/operation/read_preference_op_msg_spec.rb +24 -1
  46. data/spec/mongo/server/monitor/connection_spec.rb +22 -0
  47. data/spec/mongo/server/push_monitor_spec.rb +101 -0
  48. data/spec/mongo/server_selector_spec.rb +136 -15
  49. data/spec/mongo/socket/ssl_spec.rb +26 -58
  50. data/spec/mongo/utils_spec.rb +0 -14
  51. data/spec/runners/auth.rb +1 -1
  52. data/spec/runners/change_streams/spec.rb +1 -1
  53. data/spec/runners/cmap.rb +1 -1
  54. data/spec/runners/command_monitoring.rb +1 -1
  55. data/spec/runners/connection_string.rb +1 -1
  56. data/spec/runners/crud/spec.rb +1 -3
  57. data/spec/runners/crud/verifier.rb +1 -2
  58. data/spec/runners/gridfs.rb +1 -1
  59. data/spec/runners/read_write_concern_document.rb +1 -1
  60. data/spec/runners/sdam.rb +1 -1
  61. data/spec/runners/server_selection.rb +1 -1
  62. data/spec/runners/server_selection_rtt.rb +1 -1
  63. data/spec/runners/unified/assertions.rb +3 -1
  64. data/spec/runners/unified/crud_operations.rb +77 -23
  65. data/spec/runners/unified/ddl_operations.rb +29 -1
  66. data/spec/runners/unified/entity_map.rb +3 -3
  67. data/spec/runners/unified/support_operations.rb +6 -1
  68. data/spec/runners/unified/test.rb +15 -3
  69. data/spec/runners/unified/test_group.rb +1 -1
  70. data/spec/spec_tests/data/crud_unified/aggregate-let.yml +138 -0
  71. data/spec/spec_tests/data/crud_unified/aggregate-write-readPreference.yml +155 -0
  72. data/spec/spec_tests/data/crud_unified/db-aggregate-write-readPreference.yml +151 -0
  73. data/spec/spec_tests/data/crud_unified/deleteMany-let.yml +91 -0
  74. data/spec/spec_tests/data/crud_unified/deleteOne-let.yml +89 -0
  75. data/spec/spec_tests/data/crud_unified/find-let.yml +71 -0
  76. data/spec/spec_tests/data/crud_unified/findOneAndDelete-let.yml +88 -0
  77. data/spec/spec_tests/data/crud_unified/findOneAndReplace-let.yml +94 -0
  78. data/spec/spec_tests/data/crud_unified/findOneAndUpdate-let.yml +96 -0
  79. data/spec/spec_tests/data/crud_unified/updateMany-let.yml +103 -0
  80. data/spec/spec_tests/data/crud_unified/updateOne-let.yml +98 -0
  81. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/DefaultNoMaxStaleness.yml +2 -2
  82. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/LastUpdateTime.yml +3 -3
  83. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Nearest.yml +3 -3
  84. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Nearest2.yml +3 -3
  85. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred.yml +2 -2
  86. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/PrimaryPreferred_tags.yml +2 -2
  87. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/Secondary.yml +4 -4
  88. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred.yml +2 -2
  89. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/SecondaryPreferred_tags.yml +4 -4
  90. data/spec/spec_tests/data/max_staleness/ReplicaSetNoPrimary/ZeroMaxStaleness.yml +2 -2
  91. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/DefaultNoMaxStaleness.yml +2 -2
  92. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LastUpdateTime.yml +3 -3
  93. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LongHeartbeat.yml +2 -2
  94. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/LongHeartbeat2.yml +2 -2
  95. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/MaxStalenessTooSmall.yml +2 -2
  96. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/MaxStalenessWithModePrimary.yml +2 -2
  97. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest.yml +3 -3
  98. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest2.yml +3 -3
  99. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Nearest_tags.yml +2 -2
  100. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/PrimaryPreferred.yml +2 -2
  101. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred.yml +2 -2
  102. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags.yml +5 -5
  103. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/SecondaryPreferred_tags2.yml +3 -3
  104. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Secondary_tags.yml +5 -5
  105. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/Secondary_tags2.yml +3 -3
  106. data/spec/spec_tests/data/max_staleness/ReplicaSetWithPrimary/ZeroMaxStaleness.yml +2 -2
  107. data/spec/spec_tests/data/max_staleness/Sharded/SmallMaxStaleness.yml +2 -2
  108. data/spec/spec_tests/data/max_staleness/Single/SmallMaxStaleness.yml +1 -1
  109. data/spec/spec_tests/data/max_staleness/Unknown/SmallMaxStaleness.yml +1 -1
  110. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-not-supported-client-error.yml +69 -0
  111. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-not-supported-server-error.yml +102 -0
  112. data/spec/spec_tests/data/sessions_unified/snapshot-sessions-unsupported-ops.yml +258 -0
  113. data/spec/spec_tests/data/sessions_unified/snapshot-sessions.yml +482 -0
  114. data/spec/spec_tests/seed_list_discovery_spec.rb +1 -1
  115. data/spec/spec_tests/sessions_unified_spec.rb +13 -0
  116. data/spec/support/utils.rb +31 -0
  117. data.tar.gz.sig +0 -0
  118. metadata +1127 -1090
  119. metadata.gz.sig +1 -2
@@ -29,8 +29,16 @@ describe Mongo::Collection::View::Aggregation do
29
29
  described_class.new(view, pipeline, options)
30
30
  end
31
31
 
32
+ let(:server) do
33
+ double('server')
34
+ end
35
+
36
+ let(:session) do
37
+ double('session')
38
+ end
39
+
32
40
  let(:aggregation_spec) do
33
- aggregation.send(:aggregate_spec, double('session'))
41
+ aggregation.send(:aggregate_spec, server, session, nil)
34
42
  end
35
43
 
36
44
  before do
@@ -351,15 +359,15 @@ describe Mongo::Collection::View::Aggregation do
351
359
 
352
360
  describe '#aggregate_spec' do
353
361
 
354
- context 'when the collection has a read preference' do
362
+ context 'when a read preference is given' do
355
363
 
356
364
  let(:read_preference) do
357
- {mode: :secondary}
365
+ BSON::Document.new({mode: :secondary})
358
366
  end
359
367
 
360
368
  it 'includes the read preference in the spec' do
361
- allow(authorized_collection).to receive(:read_preference).and_return(read_preference)
362
- expect(aggregation_spec[:read]).to eq(read_preference)
369
+ spec = aggregation.send(:aggregate_spec, server, session, read_preference)
370
+ expect(spec[:read]).to eq(read_preference)
363
371
  end
364
372
  end
365
373
 
@@ -570,109 +578,77 @@ describe Mongo::Collection::View::Aggregation do
570
578
  end
571
579
 
572
580
  context 'when $out is in the pipeline' do
573
-
574
- let(:pipeline) do
575
- [{
576
- "$group" => {
577
- "_id" => "$city",
578
- "totalpop" => { "$sum" => "$pop" }
581
+ [['$out', 'string'], [:$out, 'symbol']].each do |op, type|
582
+ context "when #{op} is a #{type}" do
583
+ let(:pipeline) do
584
+ [{
585
+ "$group" => {
586
+ "_id" => "$city",
587
+ "totalpop" => { "$sum" => "$pop" }
588
+ }
589
+ },
590
+ {
591
+ op => 'output_collection'
579
592
  }
580
- },
581
- {
582
- '$out' => 'output_collection'
583
- }
584
- ]
585
- end
586
-
587
- before do
588
- authorized_client['output_collection'].delete_many
589
- end
590
-
591
- context 'when $out is a string' do
592
-
593
- it 'does not allow the operation on a secondary' do
594
- expect(aggregation.send(:secondary_ok?)).to be(false)
595
- end
596
- end
597
-
598
- context 'when $out is a symbol' do
599
-
600
- let(:pipeline) do
601
- [{
602
- "$group" => {
603
- "_id" => "$city",
604
- "totalpop" => { "$sum" => "$pop" }
605
- }
606
- },
607
- {
608
- :$out => 'output_collection'
609
- }
610
- ]
611
- end
612
-
613
- it 'does not allow the operation on a secondary' do
614
- expect(aggregation.send(:secondary_ok?)).to be(false)
615
- end
616
- end
593
+ ]
594
+ end
617
595
 
596
+ before do
597
+ authorized_client['output_collection'].delete_many
598
+ end
618
599
 
619
- context 'when the server is not a valid for writing' do
600
+ let(:features) do
601
+ double()
602
+ end
620
603
 
621
- it 'reroutes the operation to a primary' do
622
- allow(aggregation).to receive(:valid_server?).and_return(false)
623
- expect(Mongo::Logger.logger).to receive(:warn).and_call_original
624
- aggregation.to_a
625
- end
626
- end
604
+ let(:server) do
605
+ double().tap do |server|
606
+ allow(server).to receive(:features).and_return(features)
607
+ end
608
+ end
627
609
 
628
- context 'when the server is a valid for writing' do
610
+ context 'when the view has a write concern' do
629
611
 
630
- it 'does not reroute the operation to a primary' do
631
- expect(Mongo::Logger.logger).not_to receive(:warn)
632
- aggregation.to_a
633
- end
612
+ let(:collection) do
613
+ authorized_collection.with(write: INVALID_WRITE_CONCERN)
614
+ end
634
615
 
635
- context 'when the view has a write concern' do
616
+ let(:view) do
617
+ Mongo::Collection::View.new(collection, selector, view_options)
618
+ end
636
619
 
637
- let(:collection) do
638
- authorized_collection.with(write: INVALID_WRITE_CONCERN)
639
- end
620
+ context 'when the server supports write concern on the aggregate command' do
621
+ min_server_fcv '3.4'
640
622
 
641
- let(:view) do
642
- Mongo::Collection::View.new(collection, selector, view_options)
643
- end
623
+ it 'uses the write concern' do
624
+ expect {
625
+ aggregation.to_a
626
+ }.to raise_exception(Mongo::Error::OperationFailure)
627
+ end
628
+ end
644
629
 
645
- context 'when the server supports write concern on the aggregate command' do
646
- min_server_fcv '3.4'
630
+ context 'when the server does not support write concern on the aggregation command' do
631
+ max_server_version '3.2'
647
632
 
648
- it 'uses the write concern' do
649
- expect {
650
- aggregation.to_a
651
- }.to raise_exception(Mongo::Error::OperationFailure)
652
- end
653
- end
633
+ let(:documents) do
634
+ [
635
+ { city: "Berlin", pop: 18913, neighborhood: "Kreuzberg" },
636
+ { city: "Berlin", pop: 84143, neighborhood: "Mitte" },
637
+ { city: "New York", pop: 40270, neighborhood: "Brooklyn" }
638
+ ]
639
+ end
654
640
 
655
- context 'when the server does not support write concern on the aggregation command' do
656
- max_server_version '3.2'
641
+ before do
642
+ authorized_collection.insert_many(documents)
643
+ aggregation.to_a
644
+ end
657
645
 
658
- let(:documents) do
659
- [
660
- { city: "Berlin", pop: 18913, neighborhood: "Kreuzberg" },
661
- { city: "Berlin", pop: 84143, neighborhood: "Mitte" },
662
- { city: "New York", pop: 40270, neighborhood: "Brooklyn" }
663
- ]
664
- end
665
-
666
- before do
667
- authorized_collection.insert_many(documents)
668
- aggregation.to_a
669
- end
670
-
671
- it 'does not apply the write concern' do
672
- expect(authorized_client['output_collection'].find.count).to eq(2)
673
- end
674
- end
675
- end
646
+ it 'does not apply the write concern' do
647
+ expect(authorized_client['output_collection'].find.count).to eq(2)
648
+ end
649
+ end
650
+ end
651
+ end
676
652
  end
677
653
  end
678
654
  end
@@ -56,7 +56,7 @@ describe Mongo::Collection::View::ChangeStream do
56
56
 
57
57
  let(:command_spec) do
58
58
  change_stream.send(:instance_variable_set, '@resuming', false)
59
- change_stream.send(:aggregate_spec, double('session'))
59
+ change_stream.send(:aggregate_spec, double('server'), double('session'), nil)
60
60
  end
61
61
 
62
62
  let(:cursor) do
@@ -60,6 +60,14 @@ describe Mongo::Collection::View::MapReduce do
60
60
  described_class.new(view, map, reduce, options)
61
61
  end
62
62
 
63
+ describe '#initialize' do
64
+ it 'warns of deprecation' do
65
+ Mongo::Logger.logger.should receive(:warn).with('MONGODB | The map_reduce operation is deprecated, please use the aggregation pipeline instead')
66
+
67
+ map_reduce
68
+ end
69
+ end
70
+
63
71
  describe '#map_function' do
64
72
 
65
73
  it 'returns the map function' do
@@ -672,7 +680,12 @@ describe Mongo::Collection::View::MapReduce do
672
680
  end
673
681
 
674
682
  it 'does not reroute the operation to a primary' do
675
- expect(Mongo::Logger.logger).not_to receive(:warn)
683
+ # We produce a deprecation warning, but there shouldn't be
684
+ # the reroute warning.
685
+ expect(Mongo::Logger.logger).to receive(:warn).once do |msg|
686
+ expect(msg).not_to include('Rerouting the MapReduce operation to the primary server')
687
+ end
688
+
676
689
  map_reduce.to_a
677
690
  end
678
691
  end
@@ -865,4 +878,20 @@ describe Mongo::Collection::View::MapReduce do
865
878
  end
866
879
  end
867
880
  end
881
+
882
+ describe '#map_reduce_spec' do
883
+ context 'when read preference is given' do
884
+ let(:view_options) do
885
+ { read: {mode: :secondary} }
886
+ end
887
+
888
+ context 'selector' do
889
+ # For compatibility with released versions of Mongoid, this method
890
+ # must return read preference under the :read key.
891
+ it 'contains read preference' do
892
+ map_reduce_spec[:selector][:read].should == {'mode' => :secondary}
893
+ end
894
+ end
895
+ end
896
+ end
868
897
  end
@@ -331,8 +331,9 @@ describe Mongo::Cursor do
331
331
 
332
332
  before do
333
333
  authorized_collection.insert_many(documents)
334
- cluster.schedule_kill_cursor(cursor.kill_spec,
335
- cursor.instance_variable_get(:@server))
334
+ cluster.schedule_kill_cursor(
335
+ cursor.kill_spec(cursor.instance_variable_get(:@server))
336
+ )
336
337
  end
337
338
 
338
339
  let(:view) do
@@ -99,6 +99,29 @@ describe Mongo::Operation::SessionsSupported do
99
99
  end
100
100
  end
101
101
 
102
+ shared_examples_for 'sends read preference correctly for replica set' do
103
+ context "when read preference mode is primary" do
104
+ let(:mode) { :primary}
105
+
106
+ it_behaves_like 'does not modify selector'
107
+ end
108
+ %i(primary_preferred secondary secondary_preferred nearest).each do |_mode|
109
+ active_mode = _mode
110
+
111
+ context "when read preference mode is #{active_mode}" do
112
+ let(:mode) { active_mode }
113
+
114
+ let(:expected) do
115
+ selector.merge(:$readPreference => expected_read_preference)
116
+ end
117
+
118
+ it 'adds read preference' do
119
+ expect(actual).to eq(expected)
120
+ end
121
+ end
122
+ end
123
+ end
124
+
102
125
  shared_examples_for 'sends user-specified read preference' do
103
126
  %i(primary primary_preferred secondary secondary_preferred nearest).each do |_mode|
104
127
  active_mode = _mode
@@ -302,7 +325,7 @@ describe Mongo::Operation::SessionsSupported do
302
325
  let(:standalone?) { false }
303
326
  let(:mongos?) { false }
304
327
 
305
- it_behaves_like 'sends user-specified read preference'
328
+ it_behaves_like 'sends read preference correctly for replica set'
306
329
  end
307
330
  end
308
331
  end
@@ -136,6 +136,28 @@ describe Mongo::Server::Monitor::Connection do
136
136
  end
137
137
  end
138
138
 
139
+ describe '#connect!' do
140
+
141
+ let(:options) do
142
+ SpecConfig.instance.test_options.merge(
143
+ app_metadata: monitor_app_metadata,
144
+ )
145
+ end
146
+
147
+ context 'when address resolution fails' do
148
+ let(:connection) { described_class.new(server.address, options) }
149
+
150
+ it 'propagates the exception' do
151
+ connection
152
+
153
+ expect(Socket).to receive(:getaddrinfo).and_raise(SocketError.new('Test exception'))
154
+ lambda do
155
+ connection.connect!
156
+ end.should raise_error(SocketError, 'Test exception')
157
+ end
158
+ end
159
+ end
160
+
139
161
  describe '#check_document' do
140
162
  context 'with API version' do
141
163
  let(:meta) do
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require 'spec_helper'
5
+
6
+ describe Mongo::Server::PushMonitor do
7
+ before(:all) do
8
+ ClientRegistry.instance.close_all_clients
9
+ end
10
+
11
+ let(:address) do
12
+ default_address
13
+ end
14
+
15
+ let(:listeners) do
16
+ Mongo::Event::Listeners.new
17
+ end
18
+
19
+ let(:monitor_options) do
20
+ {}
21
+ end
22
+
23
+ let(:monitor_app_metadata) do
24
+ Mongo::Server::Monitor::AppMetadata.new(
25
+ server_api: SpecConfig.instance.ruby_options[:server_api],
26
+ )
27
+ end
28
+
29
+ let(:cluster) do
30
+ double('cluster').tap do |cluster|
31
+ allow(cluster).to receive(:run_sdam_flow)
32
+ allow(cluster).to receive(:heartbeat_interval).and_return(1000)
33
+ end
34
+ end
35
+
36
+ let(:server) do
37
+ Mongo::Server.new(address, cluster, Mongo::Monitoring.new, listeners,
38
+ monitoring_io: false)
39
+ end
40
+
41
+ let(:monitor) do
42
+ register_background_thread_object(
43
+ Mongo::Server::Monitor.new(server, listeners, Mongo::Monitoring.new,
44
+ SpecConfig.instance.test_options.merge(cluster: cluster).merge(monitor_options).update(
45
+ app_metadata: monitor_app_metadata,
46
+ push_monitor_app_metadata: monitor_app_metadata))
47
+ )
48
+ end
49
+
50
+ let(:topology_version) do
51
+ Mongo::TopologyVersion.new('processId' => BSON::ObjectId.new, 'counter' => 1)
52
+ end
53
+
54
+ let(:check_document) do
55
+ {hello: 1}
56
+ end
57
+
58
+ let(:push_monitor) do
59
+ described_class.new(monitor, topology_version, monitor.monitoring,
60
+ **monitor.options.merge(check_document: check_document))
61
+ end
62
+
63
+ describe '#do_work' do
64
+ it 'works' do
65
+ lambda do
66
+ push_monitor.do_work
67
+ end.should_not raise_error
68
+ end
69
+
70
+ context 'network error during check' do
71
+ it 'does not propagate the exception' do
72
+ push_monitor
73
+
74
+ expect(Socket).to receive(:getaddrinfo).and_raise(SocketError.new('Test exception'))
75
+ lambda do
76
+ push_monitor.do_work
77
+ end.should_not raise_error
78
+ end
79
+
80
+ it 'throttles checks' do
81
+ push_monitor
82
+
83
+ start = Mongo::Utils.monotonic_time
84
+
85
+ expect(Socket).to receive(:getaddrinfo).and_raise(SocketError.new('Test exception'))
86
+ lambda do
87
+ push_monitor.do_work
88
+ end.should_not raise_error
89
+
90
+ expect(Socket).to receive(:getaddrinfo).and_raise(SocketError.new('Test exception'))
91
+ lambda do
92
+ push_monitor.do_work
93
+ end.should_not raise_error
94
+
95
+ elapsed = Mongo::Utils.monotonic_time - start
96
+ elapsed.should > 0.5
97
+ end
98
+ end
99
+ end
100
+
101
+ end
@@ -202,12 +202,7 @@ describe Mongo::ServerSelector do
202
202
  describe "#select_server" do
203
203
  require_no_linting
204
204
 
205
- context 'when #select_in_replica_set returns a list of nils' do
206
-
207
- let(:servers) do
208
- [ make_server(:primary) ]
209
- end
210
-
205
+ context 'replica set topology' do
211
206
  let(:cluster) do
212
207
  double('cluster').tap do |c|
213
208
  allow(c).to receive(:connected?).and_return(true)
@@ -223,19 +218,141 @@ describe Mongo::ServerSelector do
223
218
  allow(c).to receive(:scan!).and_return(true)
224
219
  allow(c).to receive(:options).and_return(server_selection_timeout: 0.1)
225
220
  allow(c).to receive(:server_selection_semaphore).and_return(nil)
221
+ allow(topology).to receive(:compatible?).and_return(true)
226
222
  end
227
223
  end
228
224
 
229
- let(:read_pref) do
230
- described_class.get(mode: :primary).tap do |pref|
231
- allow(pref).to receive(:select_in_replica_set).and_return([ nil, nil ])
225
+ let(:primary) do
226
+ make_server(:primary).tap do |server|
227
+ allow(server).to receive(:features).and_return(double("primary features"))
232
228
  end
233
229
  end
234
230
 
235
- it 'raises a NoServerAvailable error' do
236
- expect do
237
- read_pref.select_server(cluster)
238
- end.to raise_exception(Mongo::Error::NoServerAvailable)
231
+ let(:secondary) do
232
+ make_server(:secondary).tap do |server|
233
+ allow(server).to receive(:features).and_return(double("secondary features"))
234
+ end
235
+ end
236
+
237
+ context "when #select_in_replica_set returns a list of nils" do
238
+ let(:servers) do
239
+ [ primary ]
240
+ end
241
+
242
+ let(:read_pref) do
243
+ described_class.get(mode: :primary).tap do |pref|
244
+ allow(pref).to receive(:select_in_replica_set).and_return([ nil, nil ])
245
+ end
246
+ end
247
+
248
+ it 'raises a NoServerAvailable error' do
249
+ expect do
250
+ read_pref.select_server(cluster)
251
+ end.to raise_exception(Mongo::Error::NoServerAvailable)
252
+ end
253
+ end
254
+
255
+ context "write_aggregation is true" do
256
+
257
+ before do
258
+ # It does not matter for this context whether primary supports secondary wites or not,
259
+ # but we need to mock out this call.
260
+ allow(primary.features).to receive(:merge_out_on_secondary_enabled?).and_return(false)
261
+ end
262
+
263
+ context "read preference is primary" do
264
+ let(:selector) { Mongo::ServerSelector::Primary.new }
265
+
266
+ let(:servers) do
267
+ [ primary, secondary ]
268
+ end
269
+
270
+ [true, false].each do |secondary_support_writes|
271
+ context "secondary #{secondary_support_writes ? 'supports' : 'does not support' } writes" do
272
+ it "selects a primary" do
273
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(secondary_support_writes)
274
+
275
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(primary)
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ context "read preference is primary preferred" do
282
+ let(:selector) { Mongo::ServerSelector::PrimaryPreferred.new }
283
+
284
+ let(:servers) do
285
+ [ primary, secondary ]
286
+ end
287
+
288
+ [true, false].each do |secondary_support_writes|
289
+ context "secondary #{secondary_support_writes ? 'supports' : 'does not support' } writes" do
290
+ it "selects a primary" do
291
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(secondary_support_writes)
292
+
293
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(primary)
294
+ end
295
+ end
296
+ end
297
+ end
298
+
299
+ context "read preference is secondary preferred" do
300
+ let(:selector) { Mongo::ServerSelector::SecondaryPreferred.new }
301
+
302
+ let(:servers) do
303
+ [ primary, secondary ]
304
+ end
305
+
306
+ context "secondary supports writes" do
307
+ it "selects a secondary" do
308
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(true)
309
+
310
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(secondary)
311
+ end
312
+ end
313
+
314
+ context "secondary does not support writes" do
315
+ it "selects a primary" do
316
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(false)
317
+
318
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(primary)
319
+ end
320
+ end
321
+ end
322
+
323
+ context "read preference is secondary" do
324
+ let(:selector) { Mongo::ServerSelector::Secondary.new }
325
+
326
+ let(:servers) do
327
+ [ primary, secondary ]
328
+ end
329
+
330
+ context "secondary supports writes" do
331
+ it "selects a secondary" do
332
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(true)
333
+
334
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(secondary)
335
+ end
336
+ end
337
+
338
+ context "secondary does not support writes" do
339
+ it "selects a primary" do
340
+ allow(secondary.features).to receive(:merge_out_on_secondary_enabled?).and_return(false)
341
+
342
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(primary)
343
+ end
344
+ end
345
+
346
+ context "no secondaries in cluster" do
347
+ let(:servers) do
348
+ [ primary ]
349
+ end
350
+
351
+ it "selects a primary" do
352
+ expect(selector.select_server(cluster, write_aggregation: true)).to eq(primary)
353
+ end
354
+ end
355
+ end
239
356
  end
240
357
  end
241
358
 
@@ -381,8 +498,12 @@ describe Mongo::ServerSelector do
381
498
  let(:servers) { [unknown, mongos] }
382
499
  let(:selector) { described_class.primary }
383
500
 
384
- it 'returns the mongos' do
385
- expect(selector.select_server(cluster)).to eq(mongos)
501
+ [true, false].each do |write_aggregation|
502
+ context "write_aggregation is #{write_aggregation}" do
503
+ it 'returns the mongos' do
504
+ expect(selector.select_server(cluster, write_aggregation: write_aggregation)).to eq(mongos)
505
+ end
506
+ end
386
507
  end
387
508
  end
388
509
  end