spec/runners/connection_string.rb in mongo-2.11.6 vs spec/runners/connection_string.rb in mongo-2.12.0.rc0

- old
+ new

@@ -1,9 +1,363 @@ +# Copyright (C) 2014-2019 MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +RSpec::Matchers.define :have_hosts do |test, hosts| + + match do |cl| + + def find_server(client, host) + client.cluster.servers_list.detect do |s| + if host.port + s.address.host == host.host && s.address.port == host.port + else + s.address.host == host.host + end + end + end + + def match_host?(server, host) + server.address.host == host.host + end + + def match_port?(server, host) + server.address.port == host.port || !host.port + end + + def match_address_family?(server, host) + address_family(server) == host.address_family + end + + def address_family(server) + server.address.socket(2) + server.address.instance_variable_get(:@resolver).class + end + + hosts.all? do |host| + server = find_server(cl, host) + server && + match_host?(server, host) && + match_port?(server, host) #&& + #match_address_family?(server, host) + end + end + + failure_message do |client| + "With URI: #{test.uri_string}\n" + + "Expected client hosts: #{client.cluster.instance_variable_get(:@servers)} " + + "to match #{hosts}" + end +end + +RSpec::Matchers.define :match_auth do |test| + + def match_database?(client, auth) + client.options[:database] == auth.database || !auth.database + end + + def match_password?(client, auth) + client.options[:password] == auth.password || + client.options[:password].nil? && auth.password == '' + end + + match do |client| + auth = test.auth + return true unless auth + client.options[:user] == auth.username && + match_password?(client, auth) && + match_database?(client, auth) + end + + failure_message do |client| + "With URI: #{test.uri_string}\n" + + "Expected that test auth: #{test.auth} would match client auth: #{client.options}" + end +end + +RSpec::Matchers.define :match_options do |test| + + match do |client| + options = test.options + return true unless options + options.match?(client.options) + end + + failure_message do |client| + "With URI: #{test.uri_string}\n" + + "Expected that test options: #{test.options.options} would match client options: #{client.options}" + end +end + +module Mongo + module ConnectionString + + class Spec + + attr_reader :description + + # Instantiate the new spec. + # + # @param [ String ] test_path The path to the file. + # + # @since 2.0.0 + def initialize(test_path) + @spec = YAML.load(File.read(test_path)) + @description = File.basename(test_path) + end + + def tests + @tests ||= @spec['tests'].collect do |spec| + Test.new(spec) + end + end + end + + class Test + include RSpec::Core::Pending + + attr_reader :description + attr_reader :uri_string + + def initialize(spec) + @spec = spec + @description = @spec['description'] + @uri_string = @spec['uri'] + end + + def valid? + @spec['valid'] + end + + def warn? + @spec['warning'] + end + + def hosts + @hosts ||= (@spec['hosts'] || []).collect do |host| + Host.new(host) + end + end + + def seeds + if @spec['seeds'] + @seeds ||= (@spec['seeds'] || []).collect do |host| + Host.new(host) + end + else + nil + end + end + + def options + @options ||= Options.new(@spec['options']) if @spec['options'] + end + + def client + @client ||= ClientRegistry.instance.new_local_client(@spec['uri'], monitoring_io: false) + rescue Mongo::Error::LintError => e + if e.message =~ /arbitraryButStillValid/ + skip 'Test uses a read concern that fails linter' + end + end + + def uri + @uri ||= Mongo::URI.get(@spec['uri']) + end + + def auth + @auth ||= Auth.new(@spec['auth']) if @spec['auth'] + end + + def raise_error? + @spec['error'] + end + + def read_concern_expectation + @spec['readConcern'] + end + + def write_concern_expectation + @spec['writeConcern'] + end + end + + class Host + + MAPPING = { + 'ipv4' => Mongo::Address::IPv4, + 'ipv6' => Mongo::Address::IPv6, + 'unix' => Mongo::Address::Unix + } + + attr_reader :host + attr_reader :port + + def initialize(spec) + if spec.is_a?(Hash) + # Connection string spec tests + @spec = spec + @host = @spec['host'] + @port = @spec['port'] + else + # DNS seed list spec tests + address = Mongo::Address.new(spec) + @host = address.host + @port = address.port + end + end + + def address_family + MAPPING[@spec['type']] + end + end + + class Auth + + attr_reader :username + attr_reader :password + attr_reader :database + + def initialize(spec) + @spec = spec + @username = @spec['username'] + @password = @spec['password'] + @database = @spec['db'] + end + + def to_s + "username: #{username}, password: #{password}, database: #{database}" + end + end + + class Options + + MAPPINGS = { + # Replica Set Options + 'replicaset' => :replica_set, + + # Timeout Options + 'connecttimeoutms' => :connect_timeout, + 'sockettimeoutms' => :socket_timeout, + 'serverselectiontimeoutms' => :server_selection_timeout, + 'localthresholdms' => :local_threshold, + 'heartbeatfrequencyms' => :heartbeat_frequency, + 'maxidletimems' => :max_idle_time, + + # Write Options + 'journal' => [:write_concern, 'j'], + 'w' => [:write_concern, 'w'], + 'wtimeoutms' => [:write_concern, 'wtimeout'], + + # Read Options + 'readpreference' => ['read', 'mode'], + 'readpreferencetags' => ['read', 'tag_sets'], + 'maxstalenessseconds' => ['read', 'max_staleness'], + + # Pool Options + 'minpoolsize' => :min_pool_size, + 'maxpoolsize' => :max_pool_size, + + # Security Options + 'tls' => :ssl, + 'tlsallowinvalidcertificates' => :ssl_verify_certificate, + 'tlsallowinvalidhostnames' => :ssl_verify_hostname, + 'tlscafile' => :ssl_ca_cert, + 'tlscertificatekeyfile' => :ssl_cert, + 'tlscertificatekeyfilepassword' => :ssl_key_pass_phrase, + 'tlsinsecure' => :ssl_verify, + + # Auth Options + 'authsource' => :auth_source, + 'authmechanism' => :auth_mech, + 'authmechanismproperties' => :auth_mech_properties, + + # Client Options + 'appname' => :app_name, + 'readconcernlevel' => [:read_concern, 'level'], + 'retrywrites' => :retry_writes, + 'zlibcompressionlevel' => :zlib_compression_level, + } + + attr_reader :options + + def initialize(options) + @options = options + end + + def match?(opts) + @options.all? do |k, v| + k = k.downcase + + expected = + case k + when 'authmechanism' + Mongo::URI::AUTH_MECH_MAP[v].downcase.to_s + when 'authmechanismproperties' + v.reduce({}) do |new_v, prop| + prop_key = prop.first.downcase + prop_val = prop.last == 'true' ? true : prop.last + new_v[prop_key] = prop_val + + new_v + end + when 'compressors' + v.dup.tap do |compressors| + # The Ruby driver doesn't support snappy + compressors.delete('snappy') + end + when 'readpreference' + Mongo::URI::READ_MODE_MAP[v.downcase].to_s + when 'tlsallowinvalidcertificates', 'tlsallowinvalidhostnames', 'tlsinsecure' + !v + else + if k.end_with?('ms') && k != 'wtimeoutms' + v / 1000.0 + elsif v.is_a?(String) + v.downcase + else + v + end + end + + actual = + case MAPPINGS[k] + when nil + opts[k] + when Array + opts[MAPPINGS[k].first][MAPPINGS[k].last] + else + opts[MAPPINGS[k]] + end + + if actual.is_a?(Symbol) + actual = actual.to_s + end + if actual.is_a?(String) + actual = actual.downcase + end + + expected == actual + end + end + end + end +end + def define_connection_string_spec_tests(test_paths, spec_cls = Mongo::ConnectionString::Spec, &block) - clean_slate_for_all + clean_slate_for_all_if_possible test_paths.each do |path| spec = spec_cls.new(path) @@ -18,13 +372,13 @@ end context 'when the uri is invalid', unless: test.valid? do it 'raises an error' do - expect{ + expect do test.uri - }.to raise_exception(Mongo::Error::InvalidURI) + end.to raise_exception(Mongo::Error::InvalidURI) end end context 'when the uri should warn', if: test.warn? do @@ -45,10 +399,10 @@ it 'creates a client with the correct hosts' do expect(test.client).to have_hosts(test, test.hosts) end - it 'creates a client with the correct authentication properties' do + it 'creates a client with the correct authentication options' do expect(test.client).to match_auth(test) end it 'creates a client with the correct options' do expect(test.client).to match_options(test)