mongoid 7.1.10 → 7.1.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -83,9 +83,11 @@ describe Mongoid::Clients::Options, retry: 3 do
83
83
 
84
84
  let!(:connections_and_cluster_during) do
85
85
  connections = nil
86
- cluster = Minim.with(options) do |klass|
86
+ cluster = nil
87
+ Minim.with(options) do |klass|
87
88
  klass.where(name: 'emily').to_a
88
89
  connections = Minim.mongo_client.database.command(serverStatus: 1).first['connections']['current']
90
+ cluster = Minim.collection.cluster
89
91
  end
90
92
  [ connections, cluster ]
91
93
  end
@@ -124,7 +126,10 @@ describe Mongoid::Clients::Options, retry: 3 do
124
126
  end
125
127
 
126
128
  it 'disconnects the new cluster when the block exits' do
127
- expect(connections_before).to eq(connections_after)
129
+ expect(cluster_after).not_to be(cluster_during)
130
+
131
+ cluster_during.connected?.should be false
132
+ cluster_before.connected?.should be true
128
133
  end
129
134
  end
130
135
 
@@ -138,13 +143,14 @@ describe Mongoid::Clients::Options, retry: 3 do
138
143
 
139
144
  it 'does not create a new cluster' do
140
145
  expect(connections_during).to eq(connections_before)
146
+
147
+ cluster_during.should be cluster_before
141
148
  end
142
149
 
143
150
  it 'does not disconnect the original cluster' do
144
- skip 'https://jira.mongodb.org/browse/MONGOID-5130'
145
-
146
- expect(connections_after).to eq(connections_before)
147
151
  expect(cluster_before).to be(cluster_after)
152
+
153
+ cluster_before.connected?.should be true
148
154
  end
149
155
  end
150
156
 
@@ -5,9 +5,19 @@ require "spec_helper"
5
5
 
6
6
  describe Mongoid::Config::Environment do
7
7
 
8
- after(:all) do
9
- Rails = RailsTemp
10
- Object.send(:remove_const, :RailsTemp)
8
+ around do |example|
9
+ if defined?(Rails)
10
+ SavedRails = Rails
11
+ example.run
12
+ Object.send(:remove_const, :Rails) if defined?(Rails)
13
+ Rails = SavedRails
14
+ Object.send(:remove_const, :SavedRails)
15
+ else
16
+ example.run
17
+ if defined?(Rails)
18
+ Object.send(:remove_const, :Rails)
19
+ end
20
+ end
11
21
  end
12
22
 
13
23
  describe "#env_name" do
@@ -24,11 +34,6 @@ describe Mongoid::Config::Environment do
24
34
  end
25
35
  end
26
36
 
27
- after do
28
- RailsTemp = Rails
29
- Object.send(:remove_const, :Rails)
30
- end
31
-
32
37
  it "returns the rails environment" do
33
38
  expect(described_class.env_name).to eq("production")
34
39
  end
@@ -86,4 +91,77 @@ describe Mongoid::Config::Environment do
86
91
  end
87
92
  end
88
93
  end
94
+
95
+ describe "#load_yaml" do
96
+ let(:path) { 'mongoid.yml' }
97
+ let(:environment) {}
98
+ before { allow(Rails).to receive('env').and_return('test') }
99
+
100
+ subject { described_class.load_yaml(path, environment) }
101
+
102
+ context 'when file not found' do
103
+ let(:path) { 'not/a/valid/path'}
104
+
105
+ it { expect { subject }.to raise_error(Errno::ENOENT) }
106
+ end
107
+
108
+ context 'when file found' do
109
+ before do
110
+ allow(File).to receive(:new).with('mongoid.yml').and_return(StringIO.new(file_contents))
111
+ end
112
+
113
+ let(:file_contents) do
114
+ <<~FILE
115
+ test:
116
+ clients: ['test']
117
+ development:
118
+ clients: ['dev']
119
+ FILE
120
+ end
121
+
122
+ context 'when file cannot be parsed as YAML' do
123
+ let(:file_contents) { "*\nbad:%123abc" }
124
+
125
+ it { expect { subject }.to raise_error(Psych::SyntaxError) }
126
+ end
127
+
128
+ context 'when file contains ERB errors' do
129
+ let(:file_contents) { '<%= foo %>' }
130
+
131
+ it { expect { subject }.to raise_error(NameError) }
132
+ end
133
+
134
+ context 'when file is empty' do
135
+ let(:file_contents) { '' }
136
+
137
+ it { expect { subject }.to raise_error(Mongoid::Errors::EmptyConfigFile) }
138
+ end
139
+
140
+ context 'when file does not contain a YAML Hash object' do
141
+ let(:file_contents) { '["this", "is", "an", "array"]' }
142
+
143
+ it { expect { subject }.to raise_error(Mongoid::Errors::InvalidConfigFile) }
144
+ end
145
+
146
+ context 'when environment not specified' do
147
+ it 'uses the rails environment' do
148
+ is_expected.to eq("clients"=>["test"])
149
+ end
150
+ end
151
+
152
+ context 'when environment is specified' do
153
+ let(:environment) { 'development' }
154
+
155
+ it 'uses the specified environment' do
156
+ is_expected.to eq("clients"=>["dev"])
157
+ end
158
+ end
159
+
160
+ context 'when environment is missing' do
161
+ let(:environment) { 'staging' }
162
+
163
+ it { is_expected.to be_nil }
164
+ end
165
+ end
166
+ end
89
167
  end
@@ -801,27 +801,28 @@ describe Mongoid::Contextual::Atomic do
801
801
  context.unset(:name)
802
802
  end
803
803
 
804
- it "unsets the first existing field" do
805
- expect(depeche_mode.reload.name).to be_nil
806
- end
807
-
808
- it "unsets the last existing field" do
809
- expect(new_order.reload.name).to be_nil
804
+ it "unsets the fields from all documents" do
805
+ depeche_mode.reload
806
+ new_order.reload
807
+ expect(depeche_mode.name).to be_nil
808
+ expect(depeche_mode.years).to_not be_nil
809
+ expect(new_order.name).to be_nil
810
+ expect(new_order.years).to_not be_nil
810
811
  end
811
812
  end
812
813
 
813
814
  context "when the field is aliased" do
814
-
815
815
  before do
816
816
  context.unset(:years)
817
817
  end
818
818
 
819
- it "unsets the first existing field" do
820
- expect(depeche_mode.reload.years).to be_nil
821
- end
822
-
823
- it "unsets the last existing field" do
824
- expect(new_order.reload.years).to be_nil
819
+ it "unsets the fields from all documents" do
820
+ depeche_mode.reload
821
+ new_order.reload
822
+ expect(depeche_mode.name).to_not be_nil
823
+ expect(depeche_mode.years).to be_nil
824
+ expect(new_order.name).to_not be_nil
825
+ expect(new_order.years).to be_nil
825
826
  end
826
827
  end
827
828
  end
@@ -829,7 +830,8 @@ describe Mongoid::Contextual::Atomic do
829
830
  context "when unsetting multiple fields" do
830
831
 
831
832
  let!(:new_order) do
832
- Band.create(name: "New Order", genres: [ "electro", "dub" ], years: 10)
833
+ Band.create(name: "New Order", genres: %w[electro dub], years: 10,
834
+ likes: 200, rating: 4.3, origin: 'Space')
833
835
  end
834
836
 
835
837
  let(:criteria) do
@@ -846,12 +848,13 @@ describe Mongoid::Contextual::Atomic do
846
848
  context.unset(:name, :genres)
847
849
  end
848
850
 
849
- it "unsets name field" do
850
- expect(new_order.reload.name).to be_nil
851
- end
852
-
853
- it "unsets genres field" do
854
- expect(new_order.reload.genres).to be_nil
851
+ it "unsets the specified fields" do
852
+ new_order.reload
853
+ expect(new_order.name).to be_nil
854
+ expect(new_order.genres).to be_nil
855
+ expect(new_order.years).to_not be_nil
856
+ expect(new_order.likes).to_not be_nil
857
+ expect(new_order.rating).to_not be_nil
855
858
  end
856
859
  end
857
860
 
@@ -861,12 +864,46 @@ describe Mongoid::Contextual::Atomic do
861
864
  context.unset(:name, :years)
862
865
  end
863
866
 
864
- it "unsets the unaliased field" do
865
- expect(new_order.reload.name).to be_nil
867
+ it "unsets the specified fields" do
868
+ new_order.reload
869
+ expect(new_order.name).to be_nil
870
+ expect(new_order.genres).to_not be_nil
871
+ expect(new_order.years).to be_nil
872
+ expect(new_order.likes).to_not be_nil
873
+ expect(new_order.rating).to_not be_nil
874
+ end
875
+ end
876
+
877
+ context "when using Hash arguments" do
878
+
879
+ before do
880
+ context.unset({ years: true, likes: "" }, { rating: false, origin: nil })
881
+ end
882
+
883
+ it "unsets the specified fields" do
884
+ new_order.reload
885
+ expect(new_order.name).to_not be_nil
886
+ expect(new_order.genres).to_not be_nil
887
+ expect(new_order.years).to be_nil
888
+ expect(new_order.likes).to be_nil
889
+ expect(new_order.rating).to be_nil
890
+ expect(new_order.origin).to be_nil
891
+ end
892
+ end
893
+
894
+ context "when mixing argument types" do
895
+
896
+ before do
897
+ context.unset(:name, [:years], { likes: "" }, { rating: false })
866
898
  end
867
899
 
868
- it "unsets the aliased field" do
869
- expect(new_order.reload.years).to be_nil
900
+ it "unsets the specified fields" do
901
+ new_order.reload
902
+ expect(new_order.name).to be_nil
903
+ expect(new_order.genres).to_not be_nil
904
+ expect(new_order.years).to be_nil
905
+ expect(new_order.likes).to be_nil
906
+ expect(new_order.rating).to be_nil
870
907
  end
871
908
  end
872
909
  end
@@ -895,7 +932,9 @@ describe Mongoid::Contextual::Atomic do
895
932
  end
896
933
 
897
934
  it "unsets the unaliased field" do
898
- expect(depeche_mode.reload.name).to be_nil
935
+ depeche_mode.reload
936
+ expect(depeche_mode.name).to be_nil
937
+ expect(depeche_mode.years).to_not be_nil
899
938
  end
900
939
  end
901
940
  end
@@ -59,7 +59,7 @@ describe Mongoid::Contextual::GeoNear do
59
59
  end
60
60
 
61
61
  it "is nil except for 4.0 sharded when it is 0" do
62
- expect(geo_near.average_distance).to be expected_value
62
+ expect(geo_near.average_distance).to eq expected_value
63
63
  end
64
64
  end
65
65
  end
@@ -458,7 +458,7 @@ describe Mongoid::Document do
458
458
  end
459
459
  end
460
460
 
461
- context ':compact option' do
461
+ context 'deprecated :compact option' do
462
462
  # Since rails 6 differs in how it treats id fields,
463
463
  # run this test on one version of rails. Currently rails 6 is in beta,
464
464
  # when it is released this version should be changed to 6.
@@ -470,6 +470,26 @@ describe Mongoid::Document do
470
470
  expect(church.as_json.keys.sort).to eq(%w(_id location name))
471
471
  end
472
472
 
473
+ context 'deprecation' do
474
+ let(:church) do
475
+ Church.create!(name: 'St. Basil')
476
+ end
477
+
478
+ let(:message) do
479
+ '#as_json :compact option is deprecated. Please call #compact on the returned Hash object instead.'
480
+ end
481
+
482
+ it 'logs a deprecation warning when :compact is given' do
483
+ expect_any_instance_of(Logger).to receive(:warn).with(message)
484
+ church.as_json(compact: true)
485
+ end
486
+
487
+ it 'does not log a deprecation warning when :compact is not given' do
488
+ expect_any_instance_of(Logger).to_not receive(:warn).with(message)
489
+ church.as_json
490
+ end
491
+ end
492
+
473
493
  context 'there is a nil valued attribute' do
474
494
  let(:church) do
475
495
  Church.create!(name: 'St. Basil')
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ require "spec_helper"
5
+
6
+ describe Mongoid::Errors::InvalidConfigFile do
7
+
8
+ describe "#message" do
9
+
10
+ let(:error) do
11
+ described_class.new('/my/path')
12
+ end
13
+
14
+ it "contains the problem in the message" do
15
+ expect(error.message).to include(
16
+ "Invalid configuration file: /my/path."
17
+ )
18
+ end
19
+
20
+ it "contains the summary in the message" do
21
+ expect(error.message).to include(
22
+ "Your mongoid.yml configuration file does not contain the"
23
+ )
24
+ end
25
+
26
+ it "contains the resolution in the message" do
27
+ expect(error.message).to include(
28
+ "Ensure your configuration file contains the correct contents."
29
+ )
30
+ end
31
+ end
32
+ end
@@ -458,6 +458,7 @@ describe Mongoid::Persistable::Updatable do
458
458
  describe "##{method}" do
459
459
 
460
460
  context "when saving with a hash field with invalid keys" do
461
+ max_server_version '4.9'
461
462
 
462
463
  let(:person) do
463
464
  Person.create
@@ -494,6 +495,7 @@ describe Mongoid::Persistable::Updatable do
494
495
  end
495
496
 
496
497
  context "when the document has been destroyed" do
498
+ max_server_version '4.9'
497
499
 
498
500
  let(:person) do
499
501
  Person.create
@@ -257,22 +257,39 @@ module Mrss
257
257
  end
258
258
  end
259
259
 
260
- def require_multi_shard
260
+ def require_multi_mongos
261
261
  before(:all) do
262
262
  if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length == 1
263
- skip 'Test requires a minimum of two shards if run in sharded topology'
263
+ skip 'Test requires a minimum of two mongoses if run in sharded topology'
264
+ end
265
+
266
+ if ClusterConfig.instance.topology == :load_balanced && SpecConfig.instance.single_mongos?
267
+ skip 'Test requires a minimum of two mongoses if run in load-balanced topology'
264
268
  end
265
269
  end
266
270
  end
267
271
 
268
- def require_no_multi_shard
272
+ # In sharded topology operations are distributed to the mongoses.
273
+ # When we set fail points, the fail point may be set on one mongos and
274
+ # operation may be executed on another mongos, causing failures.
275
+ # Tests that are not setting targeted fail points should utilize this
276
+ # method to restrict themselves to single mongos.
277
+ #
278
+ # In load-balanced topology, the same problem can happen when there is
279
+ # more than one mongos behind the load balancer.
280
+ def require_no_multi_mongos
269
281
  before(:all) do
270
282
  if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length > 1
271
- skip 'Test requires a single shard if run in sharded topology'
283
+ skip 'Test requires a single mongos if run in sharded topology'
284
+ end
285
+ if ClusterConfig.instance.topology == :load_balanced && !SpecConfig.instance.single_mongos?
286
+ skip 'Test requires a single mongos, as indicated by SINGLE_MONGOS=1 environment variable, if run in load-balanced topology'
272
287
  end
273
288
  end
274
289
  end
275
290
 
291
+ alias :require_no_multi_shard :require_no_multi_mongos
292
+
276
293
  def require_wired_tiger
277
294
  before(:all) do
278
295
  # Storage detection fails for serverless instances. However, it is safe to
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mrss
4
+ # Test event subscriber.
5
+ class EventSubscriber
6
+
7
+ # The mappings of event names to types.
8
+ MAPPINGS = {
9
+ 'topology_opening_event' => Mongo::Monitoring::Event::TopologyOpening,
10
+ 'topology_description_changed_event' => Mongo::Monitoring::Event::TopologyChanged,
11
+ 'topology_closed_event' => Mongo::Monitoring::Event::TopologyClosed,
12
+ 'server_opening_event' => Mongo::Monitoring::Event::ServerOpening,
13
+ 'server_description_changed_event' => Mongo::Monitoring::Event::ServerDescriptionChanged,
14
+ 'server_closed_event' => Mongo::Monitoring::Event::ServerClosed
15
+ }.freeze
16
+
17
+ attr_reader :all_events
18
+
19
+ attr_reader :started_events
20
+
21
+ attr_reader :succeeded_events
22
+
23
+ attr_reader :failed_events
24
+
25
+ attr_reader :published_events
26
+
27
+ # @param [ String ] name Optional name for the event subscriber.
28
+ def initialize(name: nil)
29
+ @mutex = Mutex.new
30
+ clear_events!
31
+ @name = name
32
+ end
33
+
34
+ def to_s
35
+ %Q`#<EventSubscriber:#{@name ? "\"#{@name}\"" : '%x' % object_id} \
36
+ started=#{started_events.length} \
37
+ succeeded=#{succeeded_events.length} \
38
+ failed=#{failed_events.length} \
39
+ published=#{published_events.length}>`
40
+ end
41
+
42
+ alias :inspect :to_s
43
+
44
+ # Event retrieval
45
+
46
+ def select_started_events(cls)
47
+ started_events.select do |event|
48
+ event.is_a?(cls)
49
+ end
50
+ end
51
+
52
+ def select_succeeded_events(cls)
53
+ succeeded_events.select do |event|
54
+ event.is_a?(cls)
55
+ end
56
+ end
57
+
58
+ def select_completed_events(*classes)
59
+ (succeeded_events + failed_events).select do |event|
60
+ classes.any? { |c| c === event }
61
+ end
62
+ end
63
+
64
+ def select_published_events(cls)
65
+ published_events.select do |event|
66
+ event.is_a?(cls)
67
+ end
68
+ end
69
+
70
+ # Filters command started events for the specified command name.
71
+ def command_started_events(command_name)
72
+ started_events.select do |event|
73
+ event.command[command_name]
74
+ end
75
+ end
76
+
77
+ def non_auth_command_started_events
78
+ started_events.reject do |event|
79
+ %w(authenticate getnonce saslSstart saslContinue).any? do |cmd|
80
+ event.command[cmd]
81
+ end
82
+ end
83
+ end
84
+
85
+ # Locates command stated events for the specified command name,
86
+ # asserts that there is exactly one such event, and returns it.
87
+ def single_command_started_event(command_name, include_auth: false)
88
+ events = if include_auth
89
+ started_events
90
+ else
91
+ non_auth_command_started_events
92
+ end
93
+ events.select! do |event|
94
+ event.command[command_name]
95
+ end
96
+ if events.length != 1
97
+ raise "Expected a single #{command_name} event but we have #{events.length}"
98
+ end
99
+ events.first
100
+ end
101
+
102
+
103
+ # Get the first succeeded event published for the name, and then delete it.
104
+ #
105
+ # @param [ String ] name The event name.
106
+ #
107
+ # @return [ Event ] The matching event.
108
+ def first_event(name)
109
+ cls = MAPPINGS[name]
110
+ if cls.nil?
111
+ raise ArgumentError, "Bogus event name #{name}"
112
+ end
113
+ matching = succeeded_events.find do |event|
114
+ cls === event
115
+ end
116
+ succeeded_events.delete(matching)
117
+ matching
118
+ end
119
+
120
+ # Event recording
121
+
122
+ # Cache the started event.
123
+ #
124
+ # @param [ Event ] event The event.
125
+ def started(event)
126
+ @mutex.synchronize do
127
+ started_events << event
128
+ all_events << event
129
+ end
130
+ end
131
+
132
+ # Cache the succeeded event.
133
+ #
134
+ # @param [ Event ] event The event.
135
+ def succeeded(event)
136
+ @mutex.synchronize do
137
+ succeeded_events << event
138
+ all_events << event
139
+ end
140
+ end
141
+
142
+ # Cache the failed event.
143
+ #
144
+ # @param [ Event ] event The event.
145
+ def failed(event)
146
+ @mutex.synchronize do
147
+ failed_events << event
148
+ all_events << event
149
+ end
150
+ end
151
+
152
+ def published(event)
153
+ @mutex.synchronize do
154
+ published_events << event
155
+ all_events << event
156
+ end
157
+ end
158
+
159
+ # Clear all cached events.
160
+ def clear_events!
161
+ @all_events = []
162
+ @started_events = []
163
+ @succeeded_events = []
164
+ @failed_events = []
165
+ @published_events = []
166
+ self
167
+ end
168
+ end
169
+ # Only handles succeeded events correctly.
170
+ class PhasedEventSubscriber < EventSubscriber
171
+ def initialize
172
+ super
173
+ @phase_events = {}
174
+ end
175
+
176
+ def phase_finished(phase_index)
177
+ @phase_events[phase_index] = succeeded_events
178
+ @succeeded_events = []
179
+ end
180
+
181
+ def phase_events(phase_index)
182
+ @phase_events[phase_index]
183
+ end
184
+
185
+ def event_count
186
+ @phase_events.inject(0) do |sum, event|
187
+ sum + event.length
188
+ end
189
+ end
190
+ end
191
+
192
+ class VerboseEventSubscriber < EventSubscriber
193
+ %w(started succeeded failed published).each do |meth|
194
+ define_method(meth) do |event|
195
+ puts event.summary
196
+ super(event)
197
+ end
198
+ end
199
+ end
200
+ end