mongo 2.16.0 → 2.17.1

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