mongo 2.4.3 → 2.5.0.beta

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