mongo 2.4.3 → 2.5.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (235) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +3 -2
  3. data.tar.gz.sig +0 -0
  4. data/lib/mongo.rb +3 -2
  5. data/lib/mongo/auth/cr.rb +6 -4
  6. data/lib/mongo/auth/cr/conversation.rb +33 -17
  7. data/lib/mongo/auth/ldap.rb +4 -2
  8. data/lib/mongo/auth/ldap/conversation.rb +19 -9
  9. data/lib/mongo/auth/scram.rb +7 -4
  10. data/lib/mongo/auth/scram/conversation.rb +62 -24
  11. data/lib/mongo/auth/user.rb +10 -0
  12. data/lib/mongo/auth/user/view.rb +44 -22
  13. data/lib/mongo/auth/x509.rb +4 -2
  14. data/lib/mongo/auth/x509/conversation.rb +19 -9
  15. data/lib/mongo/bulk_write.rb +33 -27
  16. data/lib/mongo/bulk_write/combineable.rb +5 -0
  17. data/lib/mongo/bulk_write/transformable.rb +2 -0
  18. data/lib/mongo/bulk_write/validatable.rb +4 -0
  19. data/lib/mongo/client.rb +123 -12
  20. data/lib/mongo/cluster.rb +52 -11
  21. data/lib/mongo/cluster/app_metadata.rb +8 -2
  22. data/lib/mongo/cluster/cursor_reaper.rb +0 -1
  23. data/lib/mongo/cluster/topology.rb +1 -1
  24. data/lib/mongo/collection.rb +114 -27
  25. data/lib/mongo/collection/view.rb +8 -2
  26. data/lib/mongo/collection/view/aggregation.rb +11 -7
  27. data/lib/mongo/collection/view/builder/aggregation.rb +5 -1
  28. data/lib/mongo/collection/view/builder/find_command.rb +5 -3
  29. data/lib/mongo/collection/view/builder/map_reduce.rb +11 -3
  30. data/lib/mongo/collection/view/builder/op_query.rb +1 -1
  31. data/lib/mongo/collection/view/change_stream.rb +160 -0
  32. data/lib/mongo/collection/view/change_stream/retryable.rb +57 -0
  33. data/lib/mongo/collection/view/iterable.rb +11 -10
  34. data/lib/mongo/collection/view/map_reduce.rb +22 -18
  35. data/lib/mongo/collection/view/readable.rb +51 -37
  36. data/lib/mongo/collection/view/writable.rb +72 -40
  37. data/lib/mongo/cursor.rb +25 -4
  38. data/lib/mongo/cursor/builder/get_more_command.rb +4 -2
  39. data/lib/mongo/database.rb +22 -11
  40. data/lib/mongo/database/view.rb +16 -12
  41. data/lib/mongo/error.rb +5 -0
  42. data/lib/mongo/error/invalid_session.rb +36 -0
  43. data/lib/mongo/error/missing_resume_token.rb +39 -0
  44. data/lib/mongo/error/operation_failure.rb +17 -0
  45. data/lib/mongo/error/parser.rb +3 -2
  46. data/lib/mongo/error/unknown_payload_type.rb +41 -0
  47. data/lib/mongo/error/unsupported_array_filters.rb +51 -0
  48. data/lib/mongo/error/unsupported_message_type.rb +23 -0
  49. data/lib/mongo/grid/fs_bucket.rb +5 -4
  50. data/lib/mongo/grid/stream/read.rb +3 -2
  51. data/lib/mongo/grid/stream/write.rb +2 -2
  52. data/lib/mongo/index/view.rb +35 -25
  53. data/lib/mongo/monitoring/event/secure.rb +14 -0
  54. data/lib/mongo/operation.rb +16 -0
  55. data/lib/mongo/operation/commands.rb +1 -0
  56. data/lib/mongo/operation/commands/aggregate.rb +9 -5
  57. data/lib/mongo/operation/commands/aggregate/result.rb +1 -1
  58. data/lib/mongo/operation/commands/collections_info.rb +6 -6
  59. data/lib/mongo/operation/commands/command.rb +2 -1
  60. data/lib/mongo/operation/commands/create.rb +6 -2
  61. data/lib/mongo/operation/commands/drop.rb +6 -2
  62. data/lib/mongo/operation/commands/drop_database.rb +6 -2
  63. data/lib/mongo/operation/commands/explain.rb +27 -0
  64. data/lib/mongo/operation/commands/explain/result.rb +52 -0
  65. data/lib/mongo/operation/commands/indexes.rb +1 -1
  66. data/lib/mongo/operation/commands/list_collections.rb +1 -1
  67. data/lib/mongo/operation/commands/list_collections/result.rb +1 -1
  68. data/lib/mongo/operation/commands/list_indexes.rb +1 -1
  69. data/lib/mongo/operation/commands/list_indexes/result.rb +1 -1
  70. data/lib/mongo/operation/commands/map_reduce.rb +8 -4
  71. data/lib/mongo/operation/commands/map_reduce/result.rb +13 -1
  72. data/lib/mongo/operation/commands/user_query.rb +1 -1
  73. data/lib/mongo/operation/commands/users_info.rb +6 -2
  74. data/lib/mongo/operation/executable.rb +4 -1
  75. data/lib/mongo/operation/read_preference.rb +10 -5
  76. data/lib/mongo/operation/result.rb +26 -2
  77. data/lib/mongo/operation/specifiable.rb +13 -1
  78. data/lib/mongo/operation/uses_command_op_msg.rb +47 -0
  79. data/lib/mongo/operation/write/bulk/bulkable.rb +4 -1
  80. data/lib/mongo/operation/write/bulk/insert/result.rb +4 -4
  81. data/lib/mongo/operation/write/command/create_index.rb +6 -1
  82. data/lib/mongo/operation/write/command/delete.rb +28 -4
  83. data/lib/mongo/operation/write/command/drop_index.rb +6 -1
  84. data/lib/mongo/operation/write/command/insert.rb +22 -18
  85. data/lib/mongo/operation/write/command/update.rb +24 -9
  86. data/lib/mongo/operation/write/command/writable.rb +14 -1
  87. data/lib/mongo/operation/write/insert.rb +4 -1
  88. data/lib/mongo/operation/write/insert/result.rb +2 -2
  89. data/lib/mongo/operation/write/update.rb +7 -1
  90. data/lib/mongo/operation/write/write_command_enabled.rb +20 -3
  91. data/lib/mongo/protocol.rb +3 -0
  92. data/lib/mongo/protocol/bit_vector.rb +2 -2
  93. data/lib/mongo/protocol/compressed.rb +135 -0
  94. data/lib/mongo/protocol/delete.rb +8 -6
  95. data/lib/mongo/protocol/get_more.rb +8 -6
  96. data/lib/mongo/protocol/insert.rb +8 -6
  97. data/lib/mongo/protocol/kill_cursors.rb +8 -6
  98. data/lib/mongo/protocol/message.rb +31 -3
  99. data/lib/mongo/protocol/msg.rb +172 -0
  100. data/lib/mongo/protocol/query.rb +26 -6
  101. data/lib/mongo/protocol/registry.rb +76 -0
  102. data/lib/mongo/protocol/reply.rb +10 -5
  103. data/lib/mongo/protocol/serializers.rb +224 -0
  104. data/lib/mongo/protocol/update.rb +8 -6
  105. data/lib/mongo/retryable.rb +4 -2
  106. data/lib/mongo/server.rb +6 -3
  107. data/lib/mongo/server/connectable.rb +1 -1
  108. data/lib/mongo/server/connection.rb +30 -8
  109. data/lib/mongo/server/description.rb +25 -1
  110. data/lib/mongo/server/description/features.rb +4 -1
  111. data/lib/mongo/server/monitor.rb +5 -0
  112. data/lib/mongo/server/monitor/connection.rb +50 -2
  113. data/lib/mongo/server_selector/nearest.rb +10 -4
  114. data/lib/mongo/server_selector/primary.rb +20 -0
  115. data/lib/mongo/server_selector/primary_preferred.rb +10 -4
  116. data/lib/mongo/server_selector/secondary.rb +10 -4
  117. data/lib/mongo/server_selector/secondary_preferred.rb +24 -4
  118. data/lib/mongo/session.rb +180 -0
  119. data/lib/mongo/session/server_session.rb +73 -0
  120. data/lib/mongo/session/session_pool.rb +161 -0
  121. data/lib/mongo/uri.rb +11 -0
  122. data/lib/mongo/version.rb +1 -1
  123. data/mongo.gemspec +2 -1
  124. data/spec/mongo/auth/cr_spec.rb +12 -0
  125. data/spec/mongo/auth/ldap_spec.rb +2 -0
  126. data/spec/mongo/auth/scram/conversation_spec.rb +6 -6
  127. data/spec/mongo/auth/scram_spec.rb +25 -1
  128. data/spec/mongo/auth/user/view_spec.rb +268 -76
  129. data/spec/mongo/auth/x509_spec.rb +2 -0
  130. data/spec/mongo/bulk_write_spec.rb +435 -5
  131. data/spec/mongo/client_spec.rb +356 -39
  132. data/spec/mongo/cluster/app_metadata_spec.rb +2 -2
  133. data/spec/mongo/cluster_spec.rb +176 -0
  134. data/spec/mongo/collection/view/aggregation_spec.rb +33 -12
  135. data/spec/mongo/collection/view/builder/find_command_spec.rb +46 -6
  136. data/spec/mongo/collection/view/change_stream_spec.rb +814 -0
  137. data/spec/mongo/collection/view/map_reduce_spec.rb +94 -17
  138. data/spec/mongo/collection/view/readable_spec.rb +3 -12
  139. data/spec/mongo/collection_spec.rb +1048 -42
  140. data/spec/mongo/cursor/builder/get_more_command_spec.rb +19 -0
  141. data/spec/mongo/cursor_spec.rb +2 -2
  142. data/spec/mongo/database_spec.rb +50 -1
  143. data/spec/mongo/grid/fs_bucket_spec.rb +225 -137
  144. data/spec/mongo/grid/stream/read_spec.rb +2 -2
  145. data/spec/mongo/index/view_spec.rb +146 -8
  146. data/spec/mongo/monitoring/event/secure_spec.rb +42 -0
  147. data/spec/mongo/operation/read/query_spec.rb +2 -1
  148. data/spec/mongo/operation/specifiable_spec.rb +2 -2
  149. data/spec/mongo/operation/write/command/delete_spec.rb +96 -13
  150. data/spec/mongo/operation/write/command/insert_spec.rb +111 -12
  151. data/spec/mongo/operation/write/command/update_spec.rb +93 -10
  152. data/spec/mongo/operation/write/delete_spec.rb +1 -1
  153. data/spec/mongo/operation/write/insert_spec.rb +1 -1
  154. data/spec/mongo/operation/write/update_spec.rb +1 -1
  155. data/spec/mongo/protocol/compressed_spec.rb +66 -0
  156. data/spec/mongo/protocol/delete_spec.rb +14 -0
  157. data/spec/mongo/protocol/get_more_spec.rb +14 -0
  158. data/spec/mongo/protocol/insert_spec.rb +14 -0
  159. data/spec/mongo/protocol/kill_cursors_spec.rb +14 -0
  160. data/spec/mongo/protocol/msg_spec.rb +499 -0
  161. data/spec/mongo/protocol/query_spec.rb +45 -0
  162. data/spec/mongo/protocol/registry_spec.rb +31 -0
  163. data/spec/mongo/protocol/reply_spec.rb +14 -0
  164. data/spec/mongo/protocol/update_spec.rb +14 -0
  165. data/spec/mongo/retryable_spec.rb +6 -2
  166. data/spec/mongo/sdam_spec.rb +4 -0
  167. data/spec/mongo/server/connection_spec.rb +4 -2
  168. data/spec/mongo/server/description_spec.rb +28 -1
  169. data/spec/mongo/session/server_session_spec.rb +16 -0
  170. data/spec/mongo/session/session_pool_spec.rb +194 -0
  171. data/spec/mongo/uri_spec.rb +31 -2
  172. data/spec/spec_helper.rb +104 -0
  173. data/spec/support/authorization.rb +6 -1
  174. data/spec/support/crud.rb +3 -1
  175. data/spec/support/crud/write.rb +6 -1
  176. data/spec/support/crud_tests/write/findOneAndUpdate-arrayFilters.yml +69 -0
  177. data/spec/support/crud_tests/write/updateMany-arrayFilters.yml +63 -0
  178. data/spec/support/crud_tests/write/updateOne-arrayFilters.yml +109 -0
  179. data/spec/support/sdam/rs/discover_arbiters.yml +1 -1
  180. data/spec/support/sdam/rs/discover_passives.yml +2 -2
  181. data/spec/support/sdam/rs/discover_primary.yml +1 -1
  182. data/spec/support/sdam/rs/discover_secondary.yml +1 -1
  183. data/spec/support/sdam/rs/discovery.yml +4 -4
  184. data/spec/support/sdam/rs/equal_electionids.yml +1 -0
  185. data/spec/support/sdam/rs/ghost_discovered.yml +1 -1
  186. data/spec/support/sdam/rs/hosts_differ_from_seeds.yml +1 -1
  187. data/spec/support/sdam/rs/ls_timeout.yml +88 -0
  188. data/spec/support/sdam/rs/member_reconfig.yml +2 -2
  189. data/spec/support/sdam/rs/member_standalone.yml +2 -2
  190. data/spec/support/sdam/rs/new_primary.yml +2 -2
  191. data/spec/support/sdam/rs/new_primary_new_electionid.yml +3 -0
  192. data/spec/support/sdam/rs/new_primary_new_setversion.yml +3 -0
  193. data/spec/support/sdam/rs/new_primary_wrong_set_name.yml +2 -2
  194. data/spec/support/sdam/rs/non_rs_member.yml +1 -1
  195. data/spec/support/sdam/rs/normalize_case.yml +1 -1
  196. data/spec/support/sdam/rs/null_election_id.yml +4 -0
  197. data/spec/support/sdam/rs/primary_becomes_standalone.yml +2 -2
  198. data/spec/support/sdam/rs/primary_changes_set_name.yml +2 -2
  199. data/spec/support/sdam/rs/primary_disconnect.yml +2 -2
  200. data/spec/support/sdam/rs/primary_disconnect_electionid.yml +5 -0
  201. data/spec/support/sdam/rs/primary_disconnect_setversion.yml +5 -0
  202. data/spec/support/sdam/rs/primary_hint_from_secondary_with_mismatched_me.yml +58 -0
  203. data/spec/support/sdam/rs/primary_reports_new_member.yml +4 -4
  204. data/spec/support/sdam/rs/primary_to_no_primary_mismatched_me.yml +2 -2
  205. data/spec/support/sdam/rs/primary_wrong_set_name.yml +1 -1
  206. data/spec/support/sdam/rs/response_from_removed.yml +2 -2
  207. data/spec/support/sdam/rs/rsother_discovered.yml +1 -1
  208. data/spec/support/sdam/rs/sec_not_auth.yml +1 -1
  209. data/spec/support/sdam/rs/secondary_wrong_set_name.yml +1 -1
  210. data/spec/support/sdam/rs/secondary_wrong_set_name_with_primary.yml +2 -2
  211. data/spec/support/sdam/rs/setversion_without_electionid.yml +2 -0
  212. data/spec/support/sdam/rs/stepdown_change_set_name.yml +2 -2
  213. data/spec/support/sdam/rs/unexpected_mongos.yml +1 -1
  214. data/spec/support/sdam/rs/use_setversion_without_electionid.yml +3 -0
  215. data/spec/support/sdam/rs/wrong_set_name.yml +1 -1
  216. data/spec/support/sdam/sharded/ls_timeout_mongos.yml +97 -0
  217. data/spec/support/sdam/sharded/mongos_disconnect.yml +3 -3
  218. data/spec/support/sdam/sharded/multiple_mongoses.yml +1 -1
  219. data/spec/support/sdam/sharded/non_mongos_removed.yml +1 -1
  220. data/spec/support/sdam/sharded/normalize_uri_case.yml +1 -1
  221. data/spec/support/sdam/single/direct_connection_external_ip.yml +1 -1
  222. data/spec/support/sdam/single/direct_connection_mongos.yml +1 -1
  223. data/spec/support/sdam/single/direct_connection_rsarbiter.yml +1 -1
  224. data/spec/support/sdam/single/direct_connection_rsprimary.yml +1 -1
  225. data/spec/support/sdam/single/direct_connection_rssecondary.yml +1 -1
  226. data/spec/support/sdam/single/direct_connection_slave.yml +1 -1
  227. data/spec/support/sdam/single/direct_connection_standalone.yml +1 -1
  228. data/spec/support/sdam/single/ls_timeout_standalone.yml +35 -0
  229. data/spec/support/sdam/single/not_ok_response.yml +1 -1
  230. data/spec/support/sdam/single/standalone_removed.yml +1 -1
  231. data/spec/support/sdam/single/unavailable_seed.yml +1 -1
  232. data/spec/support/server_discovery_and_monitoring.rb +4 -0
  233. data/spec/support/shared/session.rb +236 -0
  234. metadata +53 -15
  235. metadata.gz.sig +0 -0
@@ -95,8 +95,8 @@ describe Mongo::Cluster::AppMetadata do
95
95
  allow(app_metadata).to receive(:driver_doc).and_return('x'*500)
96
96
  end
97
97
 
98
- it 'truncates the document to be just an ismaster command' do
99
- expect(app_metadata.ismaster_bytes.length).to eq(Mongo::Server::Monitor::Connection::ISMASTER_BYTES.length)
98
+ it 'truncates the document to be just an ismaster command and the compressors', unless: compression_enabled? do
99
+ expect(app_metadata.ismaster_bytes.length).to eq(Mongo::Server::Monitor::Connection::ISMASTER_BYTES.length + 26)
100
100
  end
101
101
  end
102
102
  end
@@ -510,4 +510,180 @@ describe Mongo::Cluster do
510
510
  end
511
511
  end
512
512
  end
513
+
514
+ describe '#logical_session_timeout' do
515
+
516
+ let(:listeners) do
517
+ Mongo::Event::Listeners.new
518
+ end
519
+
520
+ let(:monitoring) do
521
+ Mongo::Monitoring.new(monitoring: false)
522
+ end
523
+
524
+ let(:server_one) do
525
+ Mongo::Server.new(default_address, cluster, monitoring, listeners)
526
+ end
527
+
528
+ let(:server_two) do
529
+ Mongo::Server.new(default_address, cluster, monitoring, listeners)
530
+ end
531
+
532
+ let(:servers) do
533
+ [ server_one, server_two ]
534
+ end
535
+
536
+ before do
537
+ allow(cluster).to receive(:servers).and_return(servers)
538
+ end
539
+
540
+ context 'when one server has a nil logical session timeout value' do
541
+
542
+ before do
543
+ allow(server_one).to receive(:logical_session_timeout).and_return(7)
544
+ allow(server_two).to receive(:logical_session_timeout).and_return(nil)
545
+ end
546
+
547
+ it 'returns nil' do
548
+ expect(cluster.logical_session_timeout).to be(nil)
549
+ end
550
+ end
551
+
552
+ context 'when all servers have a logical session timeout value' do
553
+
554
+ before do
555
+ allow(server_one).to receive(:logical_session_timeout).and_return(7)
556
+ allow(server_two).to receive(:logical_session_timeout).and_return(3)
557
+ end
558
+
559
+ it 'returns the minimum' do
560
+ expect(cluster.logical_session_timeout).to be(3)
561
+ end
562
+ end
563
+
564
+ context 'when no servers have a logical session timeout value' do
565
+
566
+ before do
567
+ allow(server_one).to receive(:logical_session_timeout).and_return(nil)
568
+ allow(server_two).to receive(:logical_session_timeout).and_return(nil)
569
+ end
570
+
571
+ it 'returns nil' do
572
+ expect(cluster.logical_session_timeout).to be(nil)
573
+ end
574
+ end
575
+ end
576
+
577
+ describe '#cluster_time' do
578
+
579
+ let(:operation) do
580
+ client.command(ping: 1)
581
+ end
582
+
583
+ let(:second_operation) do
584
+ client.command(ping: 1)
585
+ end
586
+
587
+ it_behaves_like 'an operation updating cluster time'
588
+ end
589
+
590
+ describe '#update_cluster_time' do
591
+
592
+ let(:cluster) do
593
+ described_class.new(ADDRESSES, monitoring, TEST_OPTIONS.merge(heartbeat_frequency: 1000))
594
+ end
595
+
596
+ let(:result) do
597
+ double('result', cluster_time: cluster_time_doc)
598
+ end
599
+
600
+ context 'when the cluster_time variable is nil' do
601
+
602
+ before do
603
+ cluster.instance_variable_set(:@cluster_time, nil)
604
+ cluster.update_cluster_time(result)
605
+ end
606
+
607
+ context 'when the cluster time received is nil' do
608
+
609
+ let(:cluster_time_doc) do
610
+ nil
611
+ end
612
+
613
+ it 'does not set the cluster_time variable' do
614
+ expect(cluster.cluster_time).to be_nil
615
+ end
616
+ end
617
+
618
+ context 'when the cluster time received is not nil' do
619
+
620
+ let(:cluster_time_doc) do
621
+ BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1))
622
+ end
623
+
624
+ it 'sets the cluster_time variable to the cluster time doc' do
625
+ expect(cluster.cluster_time).to eq(cluster_time_doc)
626
+ end
627
+ end
628
+ end
629
+
630
+ context 'when the cluster_time variable has a value' do
631
+
632
+ before do
633
+ cluster.instance_variable_set(:@cluster_time, BSON::Document.new(
634
+ Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1)))
635
+ cluster.update_cluster_time(result)
636
+ end
637
+
638
+ context 'when the cluster time received is nil' do
639
+
640
+ let(:cluster_time_doc) do
641
+ nil
642
+ end
643
+
644
+ it 'does not update the cluster_time variable' do
645
+ expect(cluster.cluster_time).to eq(BSON::Document.new(
646
+ Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1)))
647
+ end
648
+ end
649
+
650
+ context 'when the cluster time received is not nil' do
651
+
652
+ context 'when the cluster time received is greater than the cluster_time variable' do
653
+
654
+ let(:cluster_time_doc) do
655
+ BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 2))
656
+ end
657
+
658
+ it 'sets the cluster_time variable to the cluster time' do
659
+ expect(cluster.cluster_time).to eq(cluster_time_doc)
660
+ end
661
+ end
662
+
663
+ context 'when the cluster time received is less than the cluster_time variable' do
664
+
665
+ let(:cluster_time_doc) do
666
+ BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(0, 1))
667
+ end
668
+
669
+ it 'does not set the cluster_time variable to the cluster time' do
670
+ expect(cluster.cluster_time).to eq(BSON::Document.new(
671
+ Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1)))
672
+ end
673
+ end
674
+
675
+ context 'when the cluster time received is equal to the cluster_time variable' do
676
+
677
+ let(:cluster_time_doc) do
678
+ BSON::Document.new(Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1))
679
+ end
680
+
681
+ it 'does not change the cluster_time variable' do
682
+ expect(cluster.cluster_time).to eq(BSON::Document.new(
683
+ Mongo::Cluster::CLUSTER_TIME => BSON::Timestamp.new(1, 1)))
684
+ end
685
+ end
686
+ end
687
+ end
688
+ end
513
689
  end
@@ -26,6 +26,10 @@ describe Mongo::Collection::View::Aggregation do
26
26
  described_class.new(view, pipeline, options)
27
27
  end
28
28
 
29
+ let(:aggregation_spec) do
30
+ aggregation.send(:aggregate_spec, double('session'))
31
+ end
32
+
29
33
  after do
30
34
  authorized_collection.delete_many
31
35
  end
@@ -68,6 +72,23 @@ describe Mongo::Collection::View::Aggregation do
68
72
  authorized_collection.delete_many
69
73
  end
70
74
 
75
+ context 'when provided a session' do
76
+
77
+ let(:options) do
78
+ { session: session }
79
+ end
80
+
81
+ let(:operation) do
82
+ aggregation.to_a
83
+ end
84
+
85
+ let(:client) do
86
+ authorized_client
87
+ end
88
+
89
+ it_behaves_like 'an operation using a session'
90
+ end
91
+
71
92
  context 'when a block is provided' do
72
93
 
73
94
  context 'when no batch size is provided' do
@@ -280,7 +301,7 @@ describe Mongo::Collection::View::Aggregation do
280
301
 
281
302
  it 'includes the read preference in the spec' do
282
303
  allow(authorized_collection).to receive(:read_preference).and_return(read_preference)
283
- expect(aggregation.send(:aggregate_spec)[:read]).to eq(read_preference)
304
+ expect(aggregation_spec[:read]).to eq(read_preference)
284
305
  end
285
306
  end
286
307
 
@@ -291,7 +312,7 @@ describe Mongo::Collection::View::Aggregation do
291
312
  end
292
313
 
293
314
  it 'includes the option in the spec' do
294
- expect(aggregation.send(:aggregate_spec)[:selector][:allowDiskUse]).to eq(true)
315
+ expect(aggregation_spec[:selector][:allowDiskUse]).to eq(true)
295
316
  end
296
317
 
297
318
  context 'when allow_disk_use is specified as an option' do
@@ -305,7 +326,7 @@ describe Mongo::Collection::View::Aggregation do
305
326
  end
306
327
 
307
328
  it 'includes the option in the spec' do
308
- expect(aggregation.send(:aggregate_spec)[:selector][:allowDiskUse]).to eq(true)
329
+ expect(aggregation_spec[:selector][:allowDiskUse]).to eq(true)
309
330
  end
310
331
 
311
332
  context 'when #allow_disk_use is also called' do
@@ -319,7 +340,7 @@ describe Mongo::Collection::View::Aggregation do
319
340
  end
320
341
 
321
342
  it 'overrides the first option with the second' do
322
- expect(aggregation.send(:aggregate_spec)[:selector][:allowDiskUse]).to eq(false)
343
+ expect(aggregation_spec[:selector][:allowDiskUse]).to eq(false)
323
344
  end
324
345
  end
325
346
  end
@@ -332,7 +353,7 @@ describe Mongo::Collection::View::Aggregation do
332
353
  end
333
354
 
334
355
  it 'includes the option in the spec' do
335
- expect(aggregation.send(:aggregate_spec)[:selector][:maxTimeMS]).to eq(options[:max_time_ms])
356
+ expect(aggregation_spec[:selector][:maxTimeMS]).to eq(options[:max_time_ms])
336
357
  end
337
358
  end
338
359
 
@@ -345,7 +366,7 @@ describe Mongo::Collection::View::Aggregation do
345
366
  end
346
367
 
347
368
  it 'uses the batch_size on the view' do
348
- expect(aggregation.send(:aggregate_spec)[:selector][:cursor][:batchSize]).to eq(view_options[:batch_size])
369
+ expect(aggregation_spec[:selector][:cursor][:batchSize]).to eq(view_options[:batch_size])
349
370
  end
350
371
  end
351
372
 
@@ -356,7 +377,7 @@ describe Mongo::Collection::View::Aggregation do
356
377
  end
357
378
 
358
379
  it 'includes the option in the spec' do
359
- expect(aggregation.send(:aggregate_spec)[:selector][:cursor][:batchSize]).to eq(options[:batch_size])
380
+ expect(aggregation_spec[:selector][:cursor][:batchSize]).to eq(options[:batch_size])
360
381
  end
361
382
 
362
383
  context 'when batch_size is also set on the view' do
@@ -366,7 +387,7 @@ describe Mongo::Collection::View::Aggregation do
366
387
  end
367
388
 
368
389
  it 'overrides the view batch_size with the option batch_size' do
369
- expect(aggregation.send(:aggregate_spec)[:selector][:cursor][:batchSize]).to eq(options[:batch_size])
390
+ expect(aggregation_spec[:selector][:cursor][:batchSize]).to eq(options[:batch_size])
370
391
  end
371
392
  end
372
393
  end
@@ -379,7 +400,7 @@ describe Mongo::Collection::View::Aggregation do
379
400
  end
380
401
 
381
402
  it 'includes the option in the spec' do
382
- expect(aggregation.send(:aggregate_spec)[:selector][:hint]).to eq(options['hint'])
403
+ expect(aggregation_spec[:selector][:hint]).to eq(options['hint'])
383
404
  end
384
405
  end
385
406
 
@@ -396,7 +417,7 @@ describe Mongo::Collection::View::Aggregation do
396
417
  end
397
418
 
398
419
  it 'sets a batch size document in the spec' do
399
- expect(aggregation.send(:aggregate_spec)[:selector][:cursor][:batchSize]).to eq(options[:batch_size])
420
+ expect(aggregation_spec[:selector][:cursor][:batchSize]).to eq(options[:batch_size])
400
421
  end
401
422
  end
402
423
 
@@ -407,7 +428,7 @@ describe Mongo::Collection::View::Aggregation do
407
428
  end
408
429
 
409
430
  it 'sets an empty document in the spec' do
410
- expect(aggregation.send(:aggregate_spec)[:selector][:cursor]).to eq({})
431
+ expect(aggregation_spec[:selector][:cursor]).to eq({})
411
432
  end
412
433
  end
413
434
 
@@ -422,7 +443,7 @@ describe Mongo::Collection::View::Aggregation do
422
443
  context 'when batch_size is set' do
423
444
 
424
445
  it 'does not set the cursor option in the spec' do
425
- expect(aggregation.send(:aggregate_spec)[:selector][:cursor]).to be_nil
446
+ expect(aggregation_spec[:selector][:cursor]).to be_nil
426
447
  end
427
448
  end
428
449
  end
@@ -9,7 +9,7 @@ describe Mongo::Collection::View::Builder::FindCommand do
9
9
  end
10
10
 
11
11
  let(:builder) do
12
- described_class.new(view)
12
+ described_class.new(view, nil)
13
13
  end
14
14
 
15
15
  let(:specification) do
@@ -48,11 +48,25 @@ describe Mongo::Collection::View::Builder::FindCommand do
48
48
  no_cursor_timeout: true,
49
49
  await_data: true,
50
50
  allow_partial_results: true,
51
- read_concern: { level: 'local' },
52
51
  collation: { locale: 'en_US' }
53
52
  }
54
53
  end
55
54
 
55
+ context 'when the operation has a session' do
56
+
57
+ let(:session) do
58
+ double('session')
59
+ end
60
+
61
+ let(:builder) do
62
+ described_class.new(view, session)
63
+ end
64
+
65
+ it 'adds the session to the specification' do
66
+ expect(builder.specification[:session]).to be(session)
67
+ end
68
+ end
69
+
56
70
  it 'maps the collection name' do
57
71
  expect(selector['find']).to eq(authorized_collection.name)
58
72
  end
@@ -109,10 +123,6 @@ describe Mongo::Collection::View::Builder::FindCommand do
109
123
  expect(selector['min']).to eq('name' => 'albert')
110
124
  end
111
125
 
112
- it 'maps read concern' do
113
- expect(selector['readConcern']).to eq('level' => 'local')
114
- end
115
-
116
126
  it 'maps return key' do
117
127
  expect(selector['returnKey']).to be true
118
128
  end
@@ -451,5 +461,35 @@ describe Mongo::Collection::View::Builder::FindCommand do
451
461
  end
452
462
  end
453
463
  end
464
+
465
+ context 'when the collection has a read concern defined' do
466
+
467
+ let(:collection) do
468
+ authorized_collection.with(read_concern: { level: 'invalid' })
469
+ end
470
+
471
+ let(:view) do
472
+ Mongo::Collection::View.new(collection, {})
473
+ end
474
+
475
+ it 'applies the read concern of the collection' do
476
+ expect(selector['readConcern']).to eq(BSON::Document.new(level: 'invalid'))
477
+ end
478
+ end
479
+
480
+ context 'when the collection does not have a read concern defined' do
481
+
482
+ let(:filter) do
483
+ {}
484
+ end
485
+
486
+ let(:options) do
487
+ {}
488
+ end
489
+
490
+ it 'does not apply a read concern' do
491
+ expect(selector['readConcern']).to be_nil
492
+ end
493
+ end
454
494
  end
455
495
  end
@@ -0,0 +1,814 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongo::Collection::View::ChangeStream, if: test_change_streams? do
4
+
5
+ let(:pipeline) do
6
+ []
7
+ end
8
+
9
+ let(:options) do
10
+ {}
11
+ end
12
+
13
+ let(:view_options) do
14
+ {}
15
+ end
16
+
17
+ let(:view) do
18
+ Mongo::Collection::View.new(authorized_collection, {}, view_options)
19
+ end
20
+
21
+ let(:change_stream) do
22
+ described_class.new(view, pipeline, options)
23
+ end
24
+
25
+ let(:change_stream_document) do
26
+ change_stream.send(:pipeline)[0]['$changeStream']
27
+ end
28
+
29
+ let!(:sample_resume_token) do
30
+ stream = authorized_collection.watch
31
+ authorized_collection.insert_one(a: 1)
32
+ doc = stream.to_enum.next
33
+ stream.close
34
+ doc[:_id]
35
+ end
36
+
37
+ let(:command_selector) do
38
+ change_stream.send(:aggregate_spec, double('session'))[:selector]
39
+ end
40
+
41
+ let(:cursor) do
42
+ change_stream.instance_variable_get(:@cursor)
43
+ end
44
+
45
+ let(:error) do
46
+ begin
47
+ change_stream
48
+ rescue => e
49
+ e
50
+ end
51
+ end
52
+
53
+ after do
54
+ authorized_collection.delete_many
55
+ begin; change_stream.close; rescue; end
56
+ end
57
+
58
+ describe '#initialize' do
59
+
60
+ it 'sets the view' do
61
+ expect(change_stream.view).to be(view)
62
+ end
63
+
64
+ it 'sets the options' do
65
+ expect(change_stream.options).to eq(options)
66
+ end
67
+
68
+ context 'when full_document is provided' do
69
+
70
+ context "when the value is 'default'" do
71
+
72
+ let(:options) do
73
+ { full_document: 'default' }
74
+ end
75
+
76
+ it 'sets the fullDocument value to default' do
77
+ expect(change_stream_document[:fullDocument]).to eq('default')
78
+ end
79
+ end
80
+
81
+ context "when the value is 'updateLookup'" do
82
+
83
+ let(:options) do
84
+ { full_document: 'updateLookup' }
85
+ end
86
+
87
+ it 'sets the fullDocument value to updateLookup' do
88
+ expect(change_stream_document[:fullDocument]).to eq('updateLookup')
89
+ end
90
+ end
91
+ end
92
+
93
+ context 'when full_document is not provided' do
94
+
95
+ it "defaults to use the 'default' value" do
96
+ expect(change_stream_document[:fullDocument]).to eq('default')
97
+ end
98
+ end
99
+
100
+ context 'when resume_after is provided' do
101
+
102
+ let(:options) do
103
+ { resume_after: sample_resume_token }
104
+ end
105
+
106
+ it 'sets the resumeAfter value to the provided document' do
107
+ expect(change_stream_document[:resumeAfter]).to eq(sample_resume_token)
108
+ end
109
+ end
110
+
111
+ context 'when max_await_time_ms is provided' do
112
+
113
+ let(:options) do
114
+ { max_await_time_ms: 10 }
115
+ end
116
+
117
+ it 'sets the maxTimeMS value to the provided document' do
118
+ expect(command_selector[:maxTimeMS]).to eq(10)
119
+ end
120
+ end
121
+
122
+ context 'when batch_size is provided' do
123
+
124
+ let(:options) do
125
+ { batch_size: 5 }
126
+ end
127
+
128
+ it 'sets the batchSize value to the provided document' do
129
+ expect(command_selector[:cursor][:batchSize]).to eq(5)
130
+ end
131
+ end
132
+
133
+ context 'when collation is provided' do
134
+
135
+ let(:options) do
136
+ { 'collation' => { locale: 'en_US', strength: 2 } }
137
+ end
138
+
139
+ it 'sets the collation value to the provided document' do
140
+ expect(error).to be_a(Mongo::Error::OperationFailure)
141
+ expect(error.message).to include('Only default collation is allowed')
142
+ end
143
+ end
144
+
145
+ context 'when a changeStream operator is provided by the user as well' do
146
+
147
+ let(:pipeline) do
148
+ [ { '$changeStream' => { fullDocument: 'default' } }]
149
+ end
150
+
151
+ it 'raises the error from the server' do
152
+ expect(error).to be_a(Mongo::Error::OperationFailure)
153
+ expect(error.message).to include('$changeStream is only valid as the first stage in a pipeline')
154
+ end
155
+ end
156
+
157
+ context 'when the collection has a readConcern' do
158
+
159
+ let(:collection) do
160
+ authorized_collection.with(read_concern: { level: 'majority' })
161
+ end
162
+
163
+ let(:view) do
164
+ Mongo::Collection::View.new(collection, {}, options)
165
+ end
166
+
167
+ it 'uses the read concern of the collection' do
168
+ expect(command_selector[:readConcern]).to eq('level' => 'majority')
169
+ end
170
+ end
171
+
172
+ context 'when no pipeline is supplied' do
173
+
174
+ it 'uses an empty pipeline' do
175
+ expect(command_selector[:pipeline][0].keys).to eq(['$changeStream'])
176
+ end
177
+ end
178
+
179
+ context 'when other pipeline operators are supplied' do
180
+
181
+ context 'when the other pipeline operators are supported' do
182
+
183
+ let(:pipeline) do
184
+ [ { '$project' => { '_id' => 0 }}]
185
+ end
186
+
187
+ it 'uses the pipeline operators' do
188
+ expect(command_selector[:pipeline][1]).to eq(pipeline[0])
189
+ end
190
+ end
191
+
192
+ context 'when the other pipeline operators are not supported' do
193
+
194
+ let(:pipeline) do
195
+ [ { '$unwind' => '$test' }]
196
+ end
197
+
198
+ it 'sends the pipeline to the server without a custom error' do
199
+ expect(change_stream).to be_a(Mongo::Collection::View::ChangeStream)
200
+ end
201
+
202
+ context 'when the operation fails', if: sessions_enabled? && test_change_streams? do
203
+
204
+ let!(:before_last_use) do
205
+ session.instance_variable_get(:@server_session).last_use
206
+ end
207
+
208
+ let!(:before_operation_time) do
209
+ (session.instance_variable_get(:@operation_time) || 0)
210
+ end
211
+
212
+ let(:pipeline) do
213
+ [ { '$invalid' => '$test' }]
214
+ end
215
+
216
+ let(:options) do
217
+ { session: session }
218
+ end
219
+
220
+ let!(:operation_result) do
221
+ begin; change_stream; rescue => e; e; end
222
+ end
223
+
224
+ let(:session) do
225
+ client.start_session
226
+ end
227
+
228
+ let(:client) do
229
+ authorized_client
230
+ end
231
+
232
+ it 'raises an error' do
233
+ expect(operation_result.class).to eq(Mongo::Error::OperationFailure)
234
+ end
235
+
236
+ it 'updates the last use value' do
237
+ expect(session.instance_variable_get(:@server_session).last_use).not_to eq(before_last_use)
238
+ end
239
+
240
+ it 'updates the operation time value' do
241
+ expect(session.instance_variable_get(:@operation_time)).not_to eq(before_operation_time)
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ context 'when the initial batch is empty' do
248
+
249
+ before do
250
+ change_stream
251
+ end
252
+
253
+ it 'does not close the cursor' do
254
+ expect(cursor).to be_a(Mongo::Cursor)
255
+ end
256
+ end
257
+
258
+ context 'when provided a session', if: sessions_enabled? && test_change_streams? do
259
+
260
+ let(:options) do
261
+ { session: session }
262
+ end
263
+
264
+ let(:operation) do
265
+ change_stream
266
+ authorized_collection.insert_one(a: 1)
267
+ change_stream.to_enum.next
268
+ end
269
+
270
+ let(:client) do
271
+ authorized_client
272
+ end
273
+
274
+ context 'when the session is created from the same client used for the operation' do
275
+
276
+ let(:session) do
277
+ client.start_session
278
+ end
279
+
280
+ let(:server_session) do
281
+ session.instance_variable_get(:@server_session)
282
+ end
283
+
284
+ let!(:before_last_use) do
285
+ server_session.last_use
286
+ end
287
+
288
+ let!(:before_operation_time) do
289
+ (session.instance_variable_get(:@operation_time) || 0)
290
+ end
291
+
292
+ let!(:operation_result) do
293
+ operation
294
+ end
295
+
296
+ it 'updates the last use value' do
297
+ expect(server_session.last_use).not_to eq(before_last_use)
298
+ end
299
+
300
+ it 'updates the operation time value' do
301
+ expect(session.instance_variable_get(:@operation_time)).not_to eq(before_operation_time)
302
+ end
303
+
304
+ it 'does not close the session when the operation completes' do
305
+ expect(session.ended?).to be(false)
306
+ end
307
+ end
308
+
309
+ context 'when a session from another client is provided' do
310
+
311
+ let(:session) do
312
+ client.start_session
313
+ end
314
+
315
+ let(:client) do
316
+ authorized_client.with(read: { mode: :secondary })
317
+ end
318
+
319
+ let(:operation_result) do
320
+ operation
321
+ end
322
+
323
+ it 'raises an exception' do
324
+ expect {
325
+ operation_result
326
+ }.to raise_exception(Mongo::Error::InvalidSession)
327
+ end
328
+ end
329
+
330
+ context 'when the session is ended before it is used' do
331
+
332
+ let(:session) do
333
+ client.start_session
334
+ end
335
+
336
+ before do
337
+ session.end_session
338
+ end
339
+
340
+ let(:operation_result) do
341
+ operation
342
+ end
343
+
344
+ it 'raises an exception' do
345
+ expect {
346
+ operation_result
347
+ }.to raise_exception(Mongo::Error::InvalidSession)
348
+ end
349
+ end
350
+ end
351
+ end
352
+
353
+ describe '#close' do
354
+
355
+ context 'when documents have not been retrieved and the stream is closed' do
356
+
357
+ before do
358
+ expect(cursor).to receive(:kill_cursors)
359
+ change_stream.close
360
+ end
361
+
362
+ it 'closes the cursor' do
363
+ expect(change_stream.instance_variable_get(:@cursor)).to be(nil)
364
+ expect(change_stream.closed?).to be(true)
365
+ end
366
+
367
+ it 'raises an error when the stream is attempted to be iterated' do
368
+ expect {
369
+ change_stream.to_enum.next
370
+ }.to raise_exception(StopIteration)
371
+ end
372
+ end
373
+
374
+ context 'when some documents have been retrieved and the stream is closed before sending getmore' do
375
+
376
+ before do
377
+ change_stream
378
+ authorized_collection.insert_one(a: 1)
379
+ enum.next
380
+ change_stream.close
381
+ end
382
+
383
+ let(:enum) do
384
+ change_stream.to_enum
385
+ end
386
+
387
+ it 'raises an error' do
388
+ expect {
389
+ enum.next
390
+ }.to raise_exception(StopIteration)
391
+ end
392
+ end
393
+ end
394
+
395
+ describe '#closed?' do
396
+
397
+ context 'when the change stream has not been closed' do
398
+
399
+ it 'returns false' do
400
+ expect(change_stream.closed?).to be(false)
401
+ end
402
+ end
403
+
404
+ context 'when the change stream has been closed' do
405
+
406
+ before do
407
+ change_stream.close
408
+ end
409
+
410
+ it 'returns false' do
411
+ expect(change_stream.closed?).to be(true)
412
+ end
413
+ end
414
+ end
415
+
416
+ context 'when the first response does not contain the resume token' do
417
+
418
+ let(:pipeline) do
419
+ [ { '$project' => { _id: 0 } }]
420
+ end
421
+
422
+ before do
423
+ change_stream
424
+ authorized_collection.insert_one(a: 1)
425
+ end
426
+
427
+ it 'raises an exception and closes the cursor' do
428
+ expect(cursor).to receive(:kill_cursors).and_call_original
429
+ expect {
430
+ change_stream.to_enum.next
431
+ }.to raise_exception(Mongo::Error::MissingResumeToken)
432
+ end
433
+ end
434
+
435
+ context 'when an error is encountered the first time the command is run' do
436
+
437
+ let(:primary_socket) do
438
+ primary = authorized_collection.client.cluster.servers.find { |s| s.primary? }
439
+ connection = primary.pool.checkout
440
+ primary.pool.checkin(connection)
441
+ connection.send(:socket)
442
+ end
443
+
444
+ context 'when the error is a resumable error' do
445
+
446
+ shared_examples_for 'a resumable change stream' do
447
+
448
+ before do
449
+ expect(primary_socket).to receive(:write).and_raise(error).once
450
+ expect(view.send(:server_selector)).to receive(:select_server).twice.and_call_original
451
+ change_stream
452
+ authorized_collection.insert_one(a: 1)
453
+ end
454
+
455
+ let(:document) do
456
+ change_stream.to_enum.next
457
+ end
458
+
459
+ it 'runs the command again while using the same read preference and caches the resume token' do
460
+ expect(document[:fullDocument][:a]).to eq(1)
461
+ expect(change_stream_document[:resumeAfter]).to eq(document[:_id])
462
+ end
463
+
464
+ context 'when provided a session' do
465
+
466
+ let(:options) do
467
+ { session: session}
468
+ end
469
+
470
+ let(:session) do
471
+ authorized_client.start_session
472
+ end
473
+
474
+ before do
475
+ change_stream.to_enum.next
476
+ end
477
+
478
+ it 'does not close the session' do
479
+ expect(session.ended?).to be(false)
480
+ end
481
+ end
482
+ end
483
+
484
+ context 'when the error is a SocketError' do
485
+
486
+ let(:error) do
487
+ Mongo::Error::SocketError
488
+ end
489
+
490
+ it_behaves_like 'a resumable change stream'
491
+ end
492
+
493
+ context 'when the error is a SocketTimeoutError' do
494
+
495
+ let(:error) do
496
+ Mongo::Error::SocketTimeoutError
497
+ end
498
+
499
+ it_behaves_like 'a resumable change stream'
500
+ end
501
+
502
+ context "when the error is a 'not master' error" do
503
+
504
+ let(:error) do
505
+ Mongo::Error::OperationFailure.new('not master')
506
+ end
507
+
508
+ it_behaves_like 'a resumable change stream'
509
+ end
510
+
511
+ context "when the error is a 'cursor not found (43)' error" do
512
+
513
+ let(:error) do
514
+ Mongo::Error::OperationFailure.new('cursor not found (43)')
515
+ end
516
+
517
+ it_behaves_like 'a resumable change stream'
518
+ end
519
+ end
520
+
521
+ context 'when the error is another server error' do
522
+
523
+ before do
524
+ expect(primary_socket).to receive(:write).and_raise(Mongo::Error::OperationFailure)
525
+ #expect twice because of kill_cursors in after block
526
+ expect(view.send(:server_selector)).to receive(:select_server).twice.and_call_original
527
+ end
528
+
529
+ it 'does not run the command again and instead raises the error' do
530
+ expect {
531
+ change_stream
532
+ }.to raise_exception(Mongo::Error::OperationFailure)
533
+ end
534
+
535
+ context 'when provided a session' do
536
+
537
+ let(:options) do
538
+ { session: session}
539
+ end
540
+
541
+ let(:session) do
542
+ authorized_client.start_session
543
+ end
544
+
545
+ before do
546
+ begin; change_stream; rescue; end
547
+ end
548
+
549
+ it 'does not close the session' do
550
+ expect(session.ended?).to be(false)
551
+ end
552
+ end
553
+ end
554
+ end
555
+
556
+ context 'when a server error is encountered during a getmore' do
557
+
558
+ context 'when the error is a resumable error' do
559
+
560
+ shared_examples_for 'a change stream that encounters an error from a getmore' do
561
+
562
+ before do
563
+ change_stream
564
+ authorized_collection.insert_one(a: 1)
565
+ enum.next
566
+ authorized_collection.insert_one(a: 2)
567
+ expect(cursor).to receive(:get_more).once.and_raise(error)
568
+ expect(cursor).to receive(:kill_cursors).and_call_original
569
+ expect(Mongo::Operation::Commands::Aggregate).to receive(:new).and_call_original
570
+ end
571
+
572
+ let(:enum) do
573
+ change_stream.to_enum
574
+ end
575
+
576
+ let(:document) do
577
+ enum.next
578
+ end
579
+
580
+ it 'runs the command again while using the same read preference and caching the resume token' do
581
+ expect(document[:fullDocument][:a]).to eq(2)
582
+ expect(change_stream_document[:resumeAfter]).to eq(document[:_id])
583
+ end
584
+
585
+ context 'when provided a session' do
586
+
587
+ let(:options) do
588
+ { session: session}
589
+ end
590
+
591
+ let(:session) do
592
+ authorized_client.start_session
593
+ end
594
+
595
+ before do
596
+ enum.next
597
+ end
598
+
599
+ it 'does not close the session' do
600
+ expect(session.ended?).to be(false)
601
+ end
602
+ end
603
+ end
604
+
605
+ context 'when the error is a SocketError' do
606
+
607
+ let(:error) do
608
+ Mongo::Error::SocketError
609
+ end
610
+
611
+ it_behaves_like 'a change stream that encounters an error from a getmore'
612
+ end
613
+
614
+ context 'when the error is a SocketTimeoutError' do
615
+
616
+ let(:error) do
617
+ Mongo::Error::SocketTimeoutError
618
+ end
619
+
620
+ it_behaves_like 'a change stream that encounters an error from a getmore'
621
+ end
622
+
623
+ context "when the error is a not 'master error'" do
624
+
625
+ let(:error) do
626
+ Mongo::Error::OperationFailure.new('not master')
627
+ end
628
+
629
+ it_behaves_like 'a change stream that encounters an error from a getmore'
630
+ end
631
+
632
+ context "when the error is a not 'cursor not found error'" do
633
+
634
+ let(:error) do
635
+ Mongo::Error::OperationFailure.new('cursor not found (43)')
636
+ end
637
+
638
+ it_behaves_like 'a change stream that encounters an error from a getmore'
639
+ end
640
+ end
641
+
642
+ context 'when the error is another server error' do
643
+
644
+ before do
645
+ change_stream
646
+ authorized_collection.insert_one(a: 1)
647
+ enum.next
648
+ authorized_collection.insert_one(a: 2)
649
+ expect(cursor).to receive(:get_more).and_raise(Mongo::Error::OperationFailure)
650
+ expect(cursor).to receive(:kill_cursors).and_call_original
651
+ expect(Mongo::Operation::Commands::Aggregate).not_to receive(:new)
652
+ end
653
+
654
+ let(:enum) do
655
+ change_stream.to_enum
656
+ end
657
+
658
+ it 'does not run the command again and instead raises the error' do
659
+ expect {
660
+ enum.next
661
+ }.to raise_exception(Mongo::Error::OperationFailure)
662
+ end
663
+
664
+ context 'when provided a session' do
665
+
666
+ let(:options) do
667
+ { session: session}
668
+ end
669
+
670
+ let(:session) do
671
+ authorized_client.start_session
672
+ end
673
+
674
+ before do
675
+ begin; enum.next; rescue; end
676
+ end
677
+
678
+ it 'does not close the session' do
679
+ expect(session.ended?).to be(false)
680
+ end
681
+ end
682
+ end
683
+ end
684
+
685
+ context 'when a server error is encountered during the command following an error during getmore' do
686
+
687
+ context 'when the error is a resumable error' do
688
+
689
+ shared_examples_for 'a change stream that sent getmores, that then encounters an error when resuming' do
690
+
691
+ before do
692
+ change_stream
693
+ authorized_collection.insert_one(a: 1)
694
+ enum.next
695
+ authorized_collection.insert_one(a: 2)
696
+ expect(cursor).to receive(:get_more).and_raise(error)
697
+ expect(cursor).to receive(:kill_cursors).and_call_original
698
+ expect(change_stream).to receive(:send_initial_query).and_raise(error).once.ordered
699
+ end
700
+
701
+ let(:enum) do
702
+ change_stream.to_enum
703
+ end
704
+
705
+ let(:document) do
706
+ enum.next
707
+ end
708
+
709
+ it 'raises the error' do
710
+ expect {
711
+ document
712
+ }.to raise_exception(error)
713
+ end
714
+
715
+ context 'when provided a session' do
716
+
717
+ let(:options) do
718
+ { session: session}
719
+ end
720
+
721
+ let(:session) do
722
+ authorized_client.start_session
723
+ end
724
+
725
+ before do
726
+ begin; document; rescue; end
727
+ end
728
+
729
+ it 'does not close the session' do
730
+ expect(session.ended?).to be(false)
731
+ end
732
+ end
733
+ end
734
+
735
+ context 'when the error is a SocketError' do
736
+
737
+ let(:error) do
738
+ Mongo::Error::SocketError
739
+ end
740
+
741
+ it_behaves_like 'a change stream that sent getmores, that then encounters an error when resuming'
742
+ end
743
+
744
+ context 'when the error is a SocketTimeoutError' do
745
+
746
+ let(:error) do
747
+ Mongo::Error::SocketTimeoutError
748
+ end
749
+
750
+ it_behaves_like 'a change stream that sent getmores, that then encounters an error when resuming'
751
+ end
752
+
753
+ context "when the error is a 'not master error'" do
754
+
755
+ let(:error) do
756
+ Mongo::Error::OperationFailure.new('not master')
757
+ end
758
+
759
+ it_behaves_like 'a change stream that sent getmores, that then encounters an error when resuming'
760
+ end
761
+
762
+ context "when the error is a not 'cursor not found error'" do
763
+
764
+ let(:error) do
765
+ Mongo::Error::OperationFailure.new('cursor not found (43)')
766
+ end
767
+
768
+ it_behaves_like 'a change stream that sent getmores, that then encounters an error when resuming'
769
+ end
770
+ end
771
+
772
+ context 'when the error is another server error' do
773
+
774
+ before do
775
+ change_stream
776
+ authorized_collection.insert_one(a: 1)
777
+ enum.next
778
+ authorized_collection.insert_one(a: 2)
779
+ expect(cursor).to receive(:get_more).and_raise(Mongo::Error::OperationFailure.new('not master'))
780
+ expect(cursor).to receive(:kill_cursors).and_call_original
781
+ expect(change_stream).to receive(:send_initial_query).and_raise(Mongo::Error::OperationFailure).once.ordered
782
+ end
783
+
784
+ let(:enum) do
785
+ change_stream.to_enum
786
+ end
787
+
788
+ it 'does not run the command again and instead raises the error' do
789
+ expect {
790
+ enum.next
791
+ }.to raise_exception(Mongo::Error::OperationFailure)
792
+ end
793
+
794
+ context 'when provided a session' do
795
+
796
+ let(:options) do
797
+ { session: session}
798
+ end
799
+
800
+ let(:session) do
801
+ authorized_client.start_session
802
+ end
803
+
804
+ before do
805
+ begin; enum.next; rescue; end
806
+ end
807
+
808
+ it 'does not close the session' do
809
+ expect(session.ended?).to be(false)
810
+ end
811
+ end
812
+ end
813
+ end
814
+ end