require 'lite_spec_helper' describe Mongo::URI do describe '.get' do let(:uri) { described_class.get(string) } context 'when the scheme is mongodb://' do let(:string) do 'mongodb://localhost:27017' end it 'returns a Mongo::URI object' do expect(uri).to be_a(Mongo::URI) end end context 'when the scheme is mongodb+srv://' do require_external_connectivity let(:string) do 'mongodb+srv://test5.test.build.10gen.cc' end it 'returns a Mongo::URI::SRVProtocol object' do expect(uri).to be_a(Mongo::URI::SRVProtocol) end end context 'when the scheme is invalid' do let(:string) do 'mongo://localhost:27017' end it 'raises an exception' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end end let(:scheme) { 'mongodb://' } let(:uri) { described_class.new(string) } describe 'invalid uris' do context 'string is not uri' do let(:string) { 'tyler' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'empty string' do let(:string) { '' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongo://localhost:27017' do let(:string) { 'mongo://localhost:27017' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://' do let(:string) { 'mongodb://' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://localhost::27017' do let(:string) { 'mongodb://localhost::27017' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://localhost::27017/' do let(:string) { 'mongodb://localhost::27017/' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://::' do let(:string) { 'mongodb://::' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://localhost,localhost::' do let(:string) { 'mongodb://localhost,localhost::' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://localhost::27017,abc' do let(:string) { 'mongodb://localhost::27017,abc' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://localhost:-1' do let(:string) { 'mongodb://localhost:-1' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://localhost:0/' do let(:string) { 'mongodb://localhost:0/' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://localhost:65536' do let(:string) { 'mongodb://localhost:65536' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://localhost:foo' do let(:string) { 'mongodb://localhost:foo' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://[::1]:-1' do let(:string) { 'mongodb://[::1]:-1' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://[::1]:0/' do let(:string) { 'mongodb://[::1]:0/' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://[::1]:65536' do let(:string) { 'mongodb://[::1]:65536' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://[::1]:65536/' do let(:string) { 'mongodb://[::1]:65536/' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://[::1]:foo' do let(:string) { 'mongodb://[::1]:foo' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'no slash after hosts, and options' do let(:string) { 'mongodb://example.com?tls=true' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI, %r,MongoDB URI must have a slash \(/\) after the hosts if options are given,) end end context 'mongodb://example.com/?w' do let(:string) { 'mongodb://example.com/?w' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI, /Option w has no value/) end end context 'equal sign in option value' do let(:string) { 'mongodb://example.com/?w=a=b' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI, %r,Value for option w contains the key/value delimiter \(=\): a=b,) end end context 'slash in option value' do let(:string) { 'mongodb://example.com/?tlsCAFile=a/b' } it 'returns a Mongo::URI object' do expect(uri).to be_a(Mongo::URI) end it 'parses correctly' do expect(uri.servers).to eq(['example.com']) expect(uri.uri_options[:ssl_ca_cert]).to eq('a/b') end end context 'numeric value in a string option' do let(:string) { 'mongodb://example.com/?appName=1' } it 'returns a Mongo::URI object' do expect(uri).to be_a(Mongo::URI) end it 'sets option to the string value' do expect(uri.uri_options[:app_name]).to eq('1') end end context 'mongodb://alice:foo:bar@127.0.0.1' do let(:string) { 'mongodb://alice:foo:bar@127.0.0.1' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://alice@@127.0.0.1' do let(:string) { 'mongodb://alice@@127.0.0.1' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end context 'mongodb://alice@foo:bar@127.0.0.1' do let(:string) { 'mongodb://alice@foo:bar@127.0.0.1' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end end describe '#initialize' do context 'string is not uri' do let(:string) { 'tyler' } it 'raises an error' do expect { uri }.to raise_error(Mongo::Error::InvalidURI) end end end describe '#servers' do let(:string) { "#{scheme}#{servers}" } context 'single server' do let(:servers) { 'localhost' } it 'returns an array with the parsed server' do expect(uri.servers).to eq([servers]) end end context 'single server with port' do let(:servers) { 'localhost:27017' } it 'returns an array with the parsed server' do expect(uri.servers).to eq([servers]) end end context 'numerical ipv4 server' do let(:servers) { '127.0.0.1' } it 'returns an array with the parsed server' do expect(uri.servers).to eq([servers]) end end context 'numerical ipv6 server' do let(:servers) { '[::1]:27107' } it 'returns an array with the parsed server' do expect(uri.servers).to eq([servers]) end end context 'unix socket server' do let(:servers) { '%2Ftmp%2Fmongodb-27017.sock' } it 'returns an array with the parsed server' do expect(uri.servers).to eq([URI.unescape(servers)]) end end context 'multiple servers' do let(:servers) { 'localhost,127.0.0.1' } it 'returns an array with the parsed servers' do expect(uri.servers).to eq(servers.split(',')) end end context 'multiple servers with ports' do let(:servers) { '127.0.0.1:27107,localhost:27018' } it 'returns an array with the parsed servers' do expect(uri.servers).to eq(servers.split(',')) end end end describe '#client_options' do let(:db) { 'dummy_db' } let(:servers) { 'localhost' } let(:string) { "#{scheme}#{credentials}@#{servers}/#{db}" } let(:user) { 'tyler' } let(:password) { 's3kr4t' } let(:credentials) { "#{user}:#{password}" } let(:options) do uri.client_options end it 'includes the database in the options' do expect(options[:database]).to eq('dummy_db') end it 'includes the user in the options' do expect(options[:user]).to eq(user) end it 'includes the password in the options' do expect(options[:password]).to eq(password) end end describe '#credentials' do let(:servers) { 'localhost' } let(:string) { "#{scheme}#{credentials}@#{servers}" } let(:user) { 'tyler' } context 'username provided' do let(:credentials) { "#{user}:" } it 'returns the username' do expect(uri.credentials[:user]).to eq(user) end end context 'username and password provided' do let(:password) { 's3kr4t' } let(:credentials) { "#{user}:#{password}" } it 'returns the username' do expect(uri.credentials[:user]).to eq(user) end it 'returns the password' do expect(uri.credentials[:password]).to eq(password) end end end describe '#database' do let(:servers) { 'localhost' } let(:string) { "#{scheme}#{servers}/#{db}" } let(:db) { 'auth-db' } context 'database provided' do it 'returns the database name' do expect(uri.database).to eq(db) end end end describe '#uri_options' do let(:servers) { 'localhost' } let(:string) { "#{scheme}#{servers}/?#{options}" } context 'when no options were provided' do let(:string) { "#{scheme}#{servers}" } it 'returns an empty hash' do expect(uri.uri_options).to be_empty end end context 'write concern options provided' do context 'numerical w value' do let(:options) { 'w=1' } let(:concern) { Mongo::Options::Redacted.new(:w => 1)} it 'sets the write concern options' do expect(uri.uri_options[:write]).to eq(concern) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:write]).to eq(concern) end end context 'w=majority' do let(:options) { 'w=majority' } let(:concern) { Mongo::Options::Redacted.new(:w => :majority) } it 'sets the write concern options' do expect(uri.uri_options[:write]).to eq(concern) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:write]).to eq(concern) end end context 'journal' do let(:options) { 'journal=true' } let(:concern) { Mongo::Options::Redacted.new(:j => true) } it 'sets the write concern options' do expect(uri.uri_options[:write]).to eq(concern) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:write]).to eq(concern) end end context 'fsync' do let(:options) { 'fsync=true' } let(:concern) { Mongo::Options::Redacted.new(:fsync => true) } it 'sets the write concern options' do expect(uri.uri_options[:write]).to eq(concern) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:write]).to eq(concern) end end context 'wtimeoutMS' do let(:timeout) { 1234 } let(:options) { "w=2&wtimeoutMS=#{timeout}" } let(:concern) { Mongo::Options::Redacted.new(:w => 2, :wtimeout => timeout) } it 'sets the write concern options' do expect(uri.uri_options[:write]).to eq(concern) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:write]).to eq(concern) end end end context 'read preference option provided' do let(:options) { "readPreference=#{mode}" } context 'primary' do let(:mode) { 'primary' } let(:read) { Mongo::Options::Redacted.new(:mode => :primary) } it 'sets the read preference' do expect(uri.uri_options[:read]).to eq(read) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end end context 'primaryPreferred' do let(:mode) { 'primaryPreferred' } let(:read) { Mongo::Options::Redacted.new(:mode => :primary_preferred) } it 'sets the read preference' do expect(uri.uri_options[:read]).to eq(read) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end end context 'secondary' do let(:mode) { 'secondary' } let(:read) { Mongo::Options::Redacted.new(:mode => :secondary) } it 'sets the read preference' do expect(uri.uri_options[:read]).to eq(read) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end end context 'secondaryPreferred' do let(:mode) { 'secondaryPreferred' } let(:read) { Mongo::Options::Redacted.new(:mode => :secondary_preferred) } it 'sets the read preference' do expect(uri.uri_options[:read]).to eq(read) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end end context 'nearest' do let(:mode) { 'nearest' } let(:read) { Mongo::Options::Redacted.new(:mode => :nearest) } it 'sets the read preference' do expect(uri.uri_options[:read]).to eq(read) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end end end context 'read preference tags provided' do context 'single read preference tag set' do let(:options) do 'readPreferenceTags=dc:ny,rack:1' end let(:read) do Mongo::Options::Redacted.new(:tag_sets => [{ 'dc' => 'ny', 'rack' => '1' }]) end it 'sets the read preference tag set' do expect(uri.uri_options[:read]).to eq(read) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end end context 'multiple read preference tag sets' do let(:options) do 'readPreferenceTags=dc:ny&readPreferenceTags=dc:bos' end let(:read) do Mongo::Options::Redacted.new(:tag_sets => [{ 'dc' => 'ny' }, { 'dc' => 'bos' }]) end it 'sets the read preference tag sets' do expect(uri.uri_options[:read]).to eq(read) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end end end context 'read preference max staleness option provided' do let(:options) do 'readPreference=Secondary&maxStalenessSeconds=120' end let(:read) do Mongo::Options::Redacted.new(mode: :secondary, :max_staleness => 120) end it 'sets the read preference max staleness in seconds' do expect(uri.uri_options[:read]).to eq(read) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:read]).to eq(read) end context 'when the read preference and max staleness combination is invalid' do context 'when max staleness is combined with read preference mode primary' do let(:options) do 'readPreference=primary&maxStalenessSeconds=120' end it 'raises an exception when read preference is accessed on the client' do client = new_local_client_nmio(string) expect { client.server_selector }.to raise_exception(Mongo::Error::InvalidServerPreference) end end context 'when the max staleness value is too small' do let(:options) do 'readPreference=secondary&maxStalenessSeconds=89' end it 'does not raise an exception until the read preference is used' do client = new_local_client_nmio(string) expect(client.read_preference).to eq(BSON::Document.new(mode: :secondary, max_staleness: 89)) end end end end context 'replica set option provided' do let(:rs_name) { 'dummy_rs' } let(:options) { "replicaSet=#{rs_name}" } it 'sets the replica set option' do expect(uri.uri_options[:replica_set]).to eq(rs_name) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:replica_set]).to eq(rs_name) end end context 'auth mechanism provided' do let(:options) { "authMechanism=#{mechanism}" } context 'plain' do let(:mechanism) { 'PLAIN' } let(:expected) { :plain } it 'sets the auth mechanism to :plain' do expect(uri.uri_options[:auth_mech]).to eq(expected) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_mech]).to eq(expected) end it 'is case-insensitive' do client = new_local_client_nmio(string.downcase) expect(client.options[:auth_mech]).to eq(expected) end end context 'mongodb-cr' do let(:mechanism) { 'MONGODB-CR' } let(:expected) { :mongodb_cr } it 'sets the auth mechanism to :mongodb_cr' do expect(uri.uri_options[:auth_mech]).to eq(expected) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_mech]).to eq(expected) end it 'is case-insensitive' do client = new_local_client_nmio(string.downcase) expect(client.options[:auth_mech]).to eq(expected) end end context 'gssapi' do let(:mechanism) { 'GSSAPI' } let(:expected) { :gssapi } it 'sets the auth mechanism to :gssapi' do expect(uri.uri_options[:auth_mech]).to eq(expected) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_mech]).to eq(expected) end it 'is case-insensitive' do client = new_local_client_nmio(string.downcase) expect(client.options[:auth_mech]).to eq(expected) end end context 'scram-sha-1' do let(:mechanism) { 'SCRAM-SHA-1' } let(:expected) { :scram } it 'sets the auth mechanism to :scram' do expect(uri.uri_options[:auth_mech]).to eq(expected) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_mech]).to eq(expected) end it 'is case-insensitive' do client = new_local_client_nmio(string.downcase) expect(client.options[:auth_mech]).to eq(expected) end end context 'mongodb-x509' do let(:mechanism) { 'MONGODB-X509' } let(:expected) { :mongodb_x509 } it 'sets the auth mechanism to :mongodb_x509' do expect(uri.uri_options[:auth_mech]).to eq(expected) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_mech]).to eq(expected) end it 'is case-insensitive' do client = new_local_client_nmio(string.downcase) expect(client.options[:auth_mech]).to eq(expected) end context 'when a username is not provided' do it 'recognizes the mechanism with no username' do client = new_local_client_nmio(string.downcase) expect(client.options[:auth_mech]).to eq(expected) expect(client.options[:user]).to be_nil end end end end context 'auth source provided' do let(:options) { "authSource=#{source}" } context 'regular db' do let(:source) { 'foo' } it 'sets the auth source to the database' do expect(uri.uri_options[:auth_source]).to eq(source) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_source]).to eq(source) end end context '$external' do let(:source) { '$external' } let(:expected) { :external } it 'sets the auth source to :external' do expect(uri.uri_options[:auth_source]).to eq(expected) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_source]).to eq(expected) end end end context 'auth mechanism properties provided' do context 'service_name' do let(:options) do "authMechanismProperties=SERVICE_NAME:#{service_name}" end let(:service_name) { 'foo' } let(:expected) { Mongo::Options::Redacted.new({ service_name: service_name }) } it 'sets the auth mechanism properties' do expect(uri.uri_options[:auth_mech_properties]).to eq(expected) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_mech_properties]).to eq(expected) end end context 'canonicalize_host_name' do let(:options) do "authMechanismProperties=CANONICALIZE_HOST_NAME:#{canonicalize_host_name}" end let(:canonicalize_host_name) { 'true' } let(:expected) { Mongo::Options::Redacted.new({ canonicalize_host_name: true }) } it 'sets the auth mechanism properties' do expect(uri.uri_options[:auth_mech_properties]).to eq(expected) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_mech_properties]).to eq(expected) end end context 'service_realm' do let(:options) do "authMechanismProperties=SERVICE_REALM:#{service_realm}" end let(:service_realm) { 'dumdum' } let(:expected) { Mongo::Options::Redacted.new({ service_realm: service_realm }) } it 'sets the auth mechanism properties' do expect(uri.uri_options[:auth_mech_properties]).to eq(expected) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_mech_properties]).to eq(expected) end end context 'multiple properties' do let(:options) do "authMechanismProperties=SERVICE_REALM:#{service_realm}," + "CANONICALIZE_HOST_NAME:#{canonicalize_host_name}," + "SERVICE_NAME:#{service_name}" end let(:service_name) { 'foo' } let(:canonicalize_host_name) { 'true' } let(:service_realm) { 'dumdum' } let(:expected) do Mongo::Options::Redacted.new({ service_name: service_name, canonicalize_host_name: true, service_realm: service_realm }) end it 'sets the auth mechanism properties' do expect(uri.uri_options[:auth_mech_properties]).to eq(expected) end it 'sets the options on a client created with the uri' do client = new_local_client_nmio(string) expect(client.options[:auth_mech_properties]).to eq(expected) end end end context 'connectTimeoutMS' do let(:options) { "connectTimeoutMS=4567" } it 'sets the the connect timeout' do expect(uri.uri_options[:connect_timeout]).to eq(4.567) end end context 'socketTimeoutMS' do let(:options) { "socketTimeoutMS=8910" } it 'sets the socket timeout' do expect(uri.uri_options[:socket_timeout]).to eq(8.910) end end context 'when providing serverSelectionTimeoutMS' do let(:options) { "serverSelectionTimeoutMS=3561" } it 'sets the the connect timeout' do expect(uri.uri_options[:server_selection_timeout]).to eq(3.561) end end context 'when providing localThresholdMS' do let(:options) { "localThresholdMS=3561" } it 'sets the the connect timeout' do expect(uri.uri_options[:local_threshold]).to eq(3.561) end end context 'when providing maxPoolSize' do let(:max_pool_size) { 10 } let(:options) { "maxPoolSize=#{max_pool_size}" } it 'sets the max pool size option' do expect(uri.uri_options[:max_pool_size]).to eq(max_pool_size) end end context 'when providing minPoolSize' do let(:min_pool_size) { 5 } let(:options) { "minPoolSize=#{min_pool_size}" } it 'sets the min pool size option' do expect(uri.uri_options[:min_pool_size]).to eq(min_pool_size) end end context 'when providing waitQueueTimeoutMS' do let(:wait_queue_timeout) { 500 } let(:options) { "waitQueueTimeoutMS=#{wait_queue_timeout}" } it 'sets the wait queue timeout option' do expect(uri.uri_options[:wait_queue_timeout]).to eq(0.5) end end context 'ssl' do let(:options) { "ssl=#{ssl}" } context 'true' do let(:ssl) { true } it 'sets the ssl option to true' do expect(uri.uri_options[:ssl]).to be true end end context 'false' do let(:ssl) { false } it 'sets the ssl option to false' do expect(uri.uri_options[:ssl]).to be false end end end context 'grouped and non-grouped options provided' do let(:options) { 'w=1&ssl=true' } it 'do not overshadow top level options' do expect(uri.uri_options).not_to be_empty end end context 'when an invalid option is provided' do let(:options) { 'invalidOption=10' } let(:uri_options) do uri.uri_options end it 'does not raise an exception' do expect(uri_options).to be_empty end context 'when an invalid option is combined with valid options' do let(:options) { 'invalidOption=10&waitQueueTimeoutMS=500&ssl=true' } it 'does not raise an exception' do expect(uri_options).not_to be_empty end it 'sets the valid options' do expect(uri_options[:wait_queue_timeout]).to eq(0.5) expect(uri_options[:ssl]).to be true end end end context 'when an app name option is provided' do let(:options) { "appname=uri_test" } it 'sets the app name on the client' do client = new_local_client_nmio(string) expect(client.options[:app_name]).to eq('uri_test') end end context 'when a supported compressors option is provided' do let(:options) { "compressors=zlib" } it 'sets the compressors as an array on the client' do client = new_local_client_nmio(string) expect(client.options[:compressors]).to eq(['zlib']) end end context 'when a non-supported compressors option is provided' do let(:options) { "compressors=snoopy" } let(:client) do client = new_local_client_nmio(string) end it 'sets no compressors on the client and warns' do expect(Mongo::Logger.logger).to receive(:warn) expect(client.options[:compressors]).to be_nil end end context 'when a zlibCompressionLevel option is provided' do let(:options) { "zlibCompressionLevel=6" } it 'sets the zlib compression level on the client' do client = new_local_client_nmio(string) expect(client.options[:zlib_compression_level]).to eq(6) end end end end