PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/spec/support/connection_string.rb

https://github.com/mongodb/mongo-ruby-driver
Ruby | 326 lines | 236 code | 59 blank | 31 comment | 24 complexity | fa298df21122dc1eead2f36434b77b55 MD5 | raw file
Possible License(s): Apache-2.0
  1. # Copyright (C) 2014-2019 MongoDB, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. RSpec::Matchers.define :have_hosts do |test|
  15. match do |cl|
  16. def find_server(client, host)
  17. client.cluster.instance_variable_get(:@servers).detect do |s|
  18. s.address.host == host.host
  19. end
  20. end
  21. def match_host?(server, host)
  22. server.address.host == host.host
  23. end
  24. def match_port?(server, host)
  25. server.address.port == host.port || !host.port
  26. end
  27. def match_address_family?(server, host)
  28. address_family(server) == host.address_family
  29. end
  30. def address_family(server)
  31. server.address.socket(2)
  32. server.address.instance_variable_get(:@resolver).class
  33. end
  34. test.hosts.all? do |host|
  35. server = find_server(cl, host)
  36. match_host?(server, host) &&
  37. match_port?(server, host) if server #&&
  38. #match_address_family?(server, host) if server
  39. end
  40. failure_message do |client|
  41. "With URI: #{test.uri_string}\n" +
  42. "Expected that test hosts: #{test.hosts} would match " +
  43. "client hosts: #{cl.cluster.instance_variable_get(:@servers)}"
  44. end
  45. end
  46. end
  47. RSpec::Matchers.define :match_auth do |test|
  48. def match_database?(client, auth)
  49. client.options[:database] == auth.database || !auth.database
  50. end
  51. def match_password?(client, auth)
  52. client.options[:password] == auth.password ||
  53. client.options[:password].nil? && auth.password == ''
  54. end
  55. match do |client|
  56. auth = test.auth
  57. return true unless auth
  58. client.options[:user] == auth.username &&
  59. match_password?(client, auth) &&
  60. match_database?(client, auth)
  61. end
  62. failure_message do |client|
  63. "With URI: #{test.uri_string}\n" +
  64. "Expected that test auth: #{test.auth} would match client auth: #{client.options}"
  65. end
  66. end
  67. RSpec::Matchers.define :match_options do |test|
  68. match do |client|
  69. options = test.options
  70. return true unless options
  71. options.match?(client.options)
  72. end
  73. failure_message do |client|
  74. "With URI: #{test.uri_string}\n" +
  75. "Expected that test options: #{test.options.options} would match client options: #{client.options}"
  76. end
  77. end
  78. module Mongo
  79. module ConnectionString
  80. class Spec
  81. attr_reader :description
  82. # Instantiate the new spec.
  83. #
  84. # @example Create the spec.
  85. # Spec.new(file)
  86. #
  87. # @param [ String ] file The name of the file.
  88. #
  89. # @since 2.0.0
  90. def initialize(file)
  91. file = File.new(file)
  92. @spec = YAML.load(ERB.new(file.read).result)
  93. file.close
  94. @description = File.basename(file)
  95. end
  96. def tests
  97. @tests ||= @spec['tests'].collect do |spec|
  98. Test.new(spec)
  99. end
  100. end
  101. end
  102. class Test
  103. include RSpec::Core::Pending
  104. attr_reader :description
  105. attr_reader :uri_string
  106. def initialize(spec)
  107. @spec = spec
  108. @description = @spec['description']
  109. @uri_string = @spec['uri']
  110. end
  111. def valid?
  112. @spec['valid']
  113. end
  114. def warn?
  115. @spec['warning']
  116. end
  117. def hosts
  118. @hosts ||= (@spec['hosts'] || []).collect do |host|
  119. Host.new(host)
  120. end
  121. end
  122. def options
  123. @options ||= Options.new(@spec['options']) if @spec['options']
  124. end
  125. def client
  126. @client ||= ClientRegistry.instance.new_local_client(@spec['uri'], monitoring_io: false)
  127. rescue Mongo::Error::LintError => e
  128. if e.message =~ /arbitraryButStillValid/
  129. skip 'Test uses a read concern that fails linter'
  130. end
  131. end
  132. def uri
  133. @uri ||= Mongo::URI.get(@spec['uri'])
  134. end
  135. def auth
  136. @auth ||= Auth.new(@spec['auth']) if @spec['auth']
  137. end
  138. def raise_error?
  139. @spec['error']
  140. end
  141. end
  142. class Host
  143. MAPPING = {
  144. 'ipv4' => Mongo::Address::IPv4,
  145. 'ipv6' => Mongo::Address::IPv6,
  146. 'unix' => Mongo::Address::Unix
  147. }
  148. attr_reader :host
  149. attr_reader :port
  150. def initialize(spec)
  151. @spec = spec
  152. @host = @spec['host']
  153. @port = @spec['port']
  154. end
  155. def address_family
  156. MAPPING[@spec['type']]
  157. end
  158. end
  159. class Auth
  160. attr_reader :username
  161. attr_reader :password
  162. attr_reader :database
  163. def initialize(spec)
  164. @spec = spec
  165. @username = @spec['username']
  166. @password = @spec['password']
  167. @database = @spec['db']
  168. end
  169. def to_s
  170. "username: #{username}, password: #{password}, database: #{database}"
  171. end
  172. end
  173. class Options
  174. MAPPINGS = {
  175. # Replica Set Options
  176. 'replicaset' => :replica_set,
  177. # Timeout Options
  178. 'connecttimeoutms' => :connect_timeout,
  179. 'sockettimeoutms' => :socket_timeout,
  180. 'serverselectiontimeoutms' => :server_selection_timeout,
  181. 'localthresholdms' => :local_threshold,
  182. 'heartbeatfrequencyms' => :heartbeat_frequency,
  183. 'maxidletimems' => :max_idle_time,
  184. # Write Options
  185. 'journal' => [:write_concern, 'j'],
  186. 'w' => [:write_concern, 'w'],
  187. 'wtimeoutms' => [:write_concern, 'wtimeout'],
  188. # Read Options
  189. 'readpreference' => ['read', 'mode'],
  190. 'readpreferencetags' => ['read', 'tag_sets'],
  191. 'maxstalenessseconds' => ['read', 'max_staleness'],
  192. # Pool Options
  193. 'minpoolsize' => :min_pool_size,
  194. 'maxpoolsize' => :max_pool_size,
  195. # Security Options
  196. 'tls' => :ssl,
  197. 'tlsallowinvalidcertificates' => :ssl_verify_certificate,
  198. 'tlsallowinvalidhostnames' => :ssl_verify_hostname,
  199. 'tlscafile' => :ssl_ca_cert,
  200. 'tlscertificatekeyfile' => :ssl_cert,
  201. 'tlscertificatekeyfilepassword' => :ssl_key_pass_phrase,
  202. 'tlsinsecure' => :ssl_verify,
  203. # Auth Options
  204. 'authsource' => :auth_source,
  205. 'authmechanism' => :auth_mech,
  206. 'authmechanismproperties' => :auth_mech_properties,
  207. # Client Options
  208. 'appname' => :app_name,
  209. 'readconcernlevel' => [:read_concern, 'level'],
  210. 'retrywrites' => :retry_writes,
  211. 'zlibcompressionlevel' => :zlib_compression_level,
  212. }
  213. attr_reader :options
  214. def initialize(options)
  215. @options = options
  216. end
  217. def match?(opts)
  218. @options.all? do |k, v|
  219. k = k.downcase
  220. expected =
  221. case k
  222. when 'authmechanism'
  223. Mongo::URI::AUTH_MECH_MAP[v].downcase.to_s
  224. when 'authsource'
  225. v == '$external' ? 'external' : v.downcase
  226. when 'authmechanismproperties'
  227. v.reduce({}) do |new_v, prop|
  228. prop_key = prop.first.downcase
  229. prop_val = prop.last == 'true' ? true : prop.last
  230. new_v[prop_key] = prop_val
  231. new_v
  232. end
  233. when 'compressors'
  234. v.dup.tap do |compressors|
  235. # The Ruby driver doesn't support snappy
  236. compressors.delete('snappy')
  237. end
  238. when 'readpreference'
  239. Mongo::URI::READ_MODE_MAP[v.downcase].to_s
  240. when 'tlsallowinvalidcertificates', 'tlsallowinvalidhostnames', 'tlsinsecure'
  241. !v
  242. else
  243. if k.end_with?('ms') && k != 'wtimeoutms'
  244. v / 1000.0
  245. elsif v.is_a?(String)
  246. v.downcase
  247. else
  248. v
  249. end
  250. end
  251. actual =
  252. case MAPPINGS[k]
  253. when nil
  254. opts[k]
  255. when Array
  256. opts[MAPPINGS[k].first][MAPPINGS[k].last]
  257. else
  258. opts[MAPPINGS[k]]
  259. end
  260. actual = actual.to_s if actual.is_a?(Symbol)
  261. actual.downcase! if actual.is_a?(String)
  262. expected == actual
  263. end
  264. end
  265. end
  266. end
  267. end