File: connection_spec.rb

package info (click to toggle)
ruby-mongo 2.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,020 kB
  • sloc: ruby: 110,810; makefile: 5
file content (197 lines) | stat: -rw-r--r-- 6,134 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# frozen_string_literal: true
# rubocop:todo all

require 'spec_helper'

describe Mongo::Server::Monitor::Connection do
  clean_slate

  let(:address) do
    Mongo::Address.new(ClusterConfig.instance.primary_address_str, options)
  end

  declare_topology_double

  let(:monitor_app_metadata) do
    Mongo::Server::Monitor::AppMetadata.new(
      server_api: SpecConfig.instance.ruby_options[:server_api],
    )
  end

  let(:cluster) do
    double('cluster').tap do |cluster|
      allow(cluster).to receive(:topology).and_return(topology)
      allow(cluster).to receive(:app_metadata).and_return(Mongo::Server::Monitor::AppMetadata.new({}))
      allow(cluster).to receive(:options).and_return({})
      allow(cluster).to receive(:monitor_app_metadata).and_return(monitor_app_metadata)
      allow(cluster).to receive(:push_monitor_app_metadata).and_return(monitor_app_metadata)
      allow(cluster).to receive(:heartbeat_interval).and_return(1000)
      allow(cluster).to receive(:run_sdam_flow)
    end
  end

  let(:server) do
    Mongo::Server.new(address,
      cluster,
      Mongo::Monitoring.new,
      Mongo::Event::Listeners.new, {monitoring_io: false}.update(options))
  end

  let(:monitor) do
    metadata = Mongo::Server::Monitor::AppMetadata.new(options)
    register_background_thread_object(
      Mongo::Server::Monitor.new(server, server.event_listeners, server.monitoring,
        {
          app_metadata: metadata,
          push_monitor_app_metadata: metadata,
        }.update(options))
    ).tap do |monitor|
      monitor.scan!
    end
  end

  let(:connection) do
    # NB this connection is set up in the background thread,
    # when the :scan option to client is changed to default to false
    # we must wait here for the connection to be established.
    # Do not call connect! on this connection as then the main thread
    # will be racing the monitoring thread to connect.
    monitor.connection.tap do |connection|
      expect(connection).not_to be nil

      deadline = Mongo::Utils.monotonic_time + 5
      while Mongo::Utils.monotonic_time < deadline
        if connection.send(:socket)
          break
        end
        sleep 0.1
      end
      expect(connection.send(:socket)).not_to be nil
    end
  end

  context 'when a connect_timeout is in the options' do

    context 'when a socket_timeout is in the options' do

      let(:options) do
        SpecConfig.instance.test_options.merge(connect_timeout: 3, socket_timeout: 5)
      end

      it 'uses the connect_timeout for the address' do
        expect(connection.address.options[:connect_timeout]).to eq(3)
      end

      it 'uses the connect_timeout as the socket_timeout' do
        expect(connection.send(:socket).timeout).to eq(3)
      end
    end

    context 'when a socket_timeout is not in the options' do

      let(:options) do
        SpecConfig.instance.test_options.merge(connect_timeout: 3, socket_timeout: nil)
      end

      it 'uses the connect_timeout for the address' do
        expect(connection.address.options[:connect_timeout]).to eq(3)
      end

      it 'uses the connect_timeout as the socket_timeout' do
        expect(connection.send(:socket).timeout).to eq(3)
      end
    end
  end

  context 'when a connect_timeout is not in the options' do

    context 'when a socket_timeout is in the options' do

      let(:options) do
        SpecConfig.instance.test_options.merge(connect_timeout: nil, socket_timeout: 5)
      end

      it 'does not specify connect_timeout for the address' do
        expect(connection.address.options[:connect_timeout]).to be nil
      end

      it 'uses the connect_timeout as the socket_timeout' do
        expect(connection.send(:socket).timeout).to eq(10)
      end
    end

    context 'when a socket_timeout is not in the options' do

      let(:options) do
        SpecConfig.instance.test_options.merge(connect_timeout: nil, socket_timeout: nil)
      end

      it 'does not specify connect_timeout for the address' do
        expect(connection.address.options[:connect_timeout]).to be nil
      end

      it 'uses the connect_timeout as the socket_timeout' do
        expect(connection.send(:socket).timeout).to eq(10)
      end
    end
  end

  describe '#connect!' do

    let(:options) do
      SpecConfig.instance.test_options.merge(
        app_metadata: monitor_app_metadata,
      )
    end

    context 'when address resolution fails' do
      let(:connection) { described_class.new(server.address, options) }

      it 'propagates the exception' do
        connection

        expect(Socket).to receive(:getaddrinfo).and_raise(SocketError.new('Test exception'))
        lambda do
          connection.connect!
        end.should raise_error(Mongo::Error::SocketError, /SocketError: Test exception/)
      end
    end
  end

  describe '#check_document' do
    context 'with API version' do
      let(:meta) do
        Mongo::Server::AppMetadata.new({
          server_api: { version: '1' }
        })
      end

      [false, true].each do |hello_ok|
        it "returns hello document if server #{ if hello_ok then 'supports' else 'does not support' end } hello" do
          subject = described_class.new(double("address"), app_metadata: meta)
          expect(subject).to receive(:hello_ok?).and_return(hello_ok)
          document = subject.check_document
          expect(document['hello']).to eq(1)
        end
      end
    end

    context 'without API version' do
      let(:meta) { Mongo::Server::AppMetadata.new({}) }

      it 'returns legacy hello document' do
        subject = described_class.new(double("address"), app_metadata: meta)
        expect(subject).to receive(:hello_ok?).and_return(false)
        document = subject.check_document
        expect(document['isMaster']).to eq(1)
      end

      it 'returns hello document when server responded with helloOk' do
        subject = described_class.new(double("address"), app_metadata: meta)
        expect(subject).to receive(:hello_ok?).and_return(true)
        document = subject.check_document
        expect(document['hello']).to eq(1)
      end
    end
  end
end