misty 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: '08884c370b4d79670e9d2b9c8adbbed79f1663a5'
4
- data.tar.gz: 121229f87354ccfd1c492cc168b9e5ccaed57363
3
+ metadata.gz: 5dc7dd3f594f1b57498eb00f5ec84950d93f1a7f
4
+ data.tar.gz: 55db4993251e461c1a0ff34c71b88b4cbc84d475
5
5
  SHA512:
6
- metadata.gz: d537db25e7d1bcbf6d3e6d55bd480367261d5a6011351c8d0f32dddba91c12af99e2141db3a58051ed1c9a4040c3cdbf5a7f2d9a2ab138e8f72f6ff7f3e8825f
7
- data.tar.gz: c32da00cd2ba16c7d5a559ff3905a6e3fdca8a4e97d868a6594f6fc0ae1858b0f3eb84f03a6882bec4c8036206ca14c721bf8ef2d925ba1d415d1e329dfbd638
6
+ metadata.gz: 25e78f721473b692864310a5adb8a0b59a46a92e704414532647495372711487397f75cedfa0020943624d185dad98985bb25a9be47aeb55e197ebbba1f758ba
7
+ data.tar.gz: a387e6ba5f95e5c284a1bf9f1b6259d8703ae2e6d019ed2643bf95a5746d9594d70ee6047964e0b279a12ac1c4a2e8465fa67680f0cef182595044a7764566ad
data/README.md CHANGED
@@ -67,7 +67,7 @@ puts network.body
67
67
  ```
68
68
 
69
69
  ## Services
70
- Once the Misty::Cloud object is created, the Openstack services can be used.
70
+ Once a Misty::Cloud object is created, the Openstack services can be used.
71
71
 
72
72
  The Cloud object is authenticated by the identity server (bootstrap) and is provided with a service catalog.
73
73
  When an OpenStack API service is required, the catalog entry's endpoint is used and the service is dynamically called.
@@ -111,6 +111,15 @@ search: searchlight, versions: ["v1"]
111
111
  shared_file_systems: manila, microversion: v2
112
112
  ```
113
113
 
114
+ ### Headers
115
+ HTTP headers can be defined at 3 different levels:
116
+ * Global headers are applied across all services, see `:headers` in "Global parameters" section.
117
+ * Service level headers are applied on every request of an involved service, see "Services options" section.
118
+ * Request level header are passed on per request basis.
119
+
120
+ The Headers are cumulative, therefore a request level header will be added on top of the global and Service levels
121
+ headers.
122
+
114
123
  ### Prefixes
115
124
  A shorter name can be used to call a service only if it's unique among all services.
116
125
  For instance `net` or `network` can be used instead of `networking`.
@@ -249,6 +258,15 @@ openstack = Misty::Cloud.new(:auth => auth, :content_type => :ruby, :log_file =>
249
258
  #### Global parameters
250
259
  The following options are applied to each service unless specifically provided for a service.
251
260
 
261
+ * :content_type
262
+ Format of the body of the successful HTTP responses to be JSON or Ruby structures.
263
+ Type: Symbol
264
+ Allowed values: `:json`, `:ruby`
265
+ Default: `:ruby`
266
+ * :headers
267
+ HTTP Headers to be applied to all services
268
+ Type: Hash
269
+ Default: {}
252
270
  * :region_id
253
271
  Type: String
254
272
  Default: "regionOne"
@@ -260,11 +278,6 @@ The following options are applied to each service unless specifically provided f
260
278
  When using SSL mode (defined by URI scheme => "https://")
261
279
  Type: Boolean
262
280
  Default: `true`
263
- * :content_type
264
- Format of the body of the successful HTTP responses to be JSON or Ruby structures.
265
- Type: Symbol
266
- Allowed values: `:json`, `:ruby`
267
- Default: `:ruby`
268
281
 
269
282
  ### Services Options
270
283
  Each service can have specific parameters.
@@ -311,6 +324,23 @@ Example:
311
324
  openstack = Misty::Cloud.new(:auth => auth, :log_level => 0, :identity => {:region_id => 'regionTwo'}, :compute => {:version => '2.27', :interface => 'admin'})
312
325
  ```
313
326
 
327
+ ### Services Headers
328
+ HTTP headers can be optionally added to any request.
329
+ A Header object must be created and passed as the last parameter of a request.
330
+
331
+ For example for an already initialized cloud:
332
+ ```ruby
333
+ header = Misty::HTTP::Header.new(
334
+ 'x-container-meta-web-listings' => false,
335
+ 'x-container-meta-quota-count' => "",
336
+ 'x-container-meta-quota-bytes' => nil,
337
+ 'x-versions-location' => "",
338
+ 'x-container-meta-web-index' => ""
339
+ )
340
+
341
+ openstack.object_storage.create_update_or_delete_container_metadata(container_name, header)
342
+ ```
343
+
314
344
  ## Direct REST HTTP Methods
315
345
  To send requests directly use the 'get', 'delete', 'post' and 'put' methods directly:
316
346
  ```ruby
@@ -1,5 +1,6 @@
1
1
  require 'misty/auth/auth_v2'
2
2
  require 'misty/auth/auth_v3'
3
+ require 'misty/http/header'
3
4
 
4
5
  module Misty
5
6
  class Cloud
@@ -13,7 +14,7 @@ module Misty
13
14
  val.gsub(/\./,'_')
14
15
  end
15
16
 
16
- def initialize(params)# = {:auth => {}})
17
+ def initialize(params)
17
18
  @params = params
18
19
  @config = self.class.set_configuration(params)
19
20
  @auth = Misty::Auth.factory(params[:auth], @config)
@@ -27,7 +28,8 @@ module Misty
27
28
  config.log.level = params[:log_level] ? params[:log_level] : Misty::LOG_LEVEL
28
29
  config.region_id = params[:region_id] ? params[:region_id] : Misty::REGION_ID
29
30
  config.ssl_verify_mode = params.key?(:ssl_verify_mode) ? params[:ssl_verify_mode] : Misty::SSL_VERIFY_MODE
30
- config.headers = params[:headers]
31
+ config.headers = Misty::HTTP::Header.new('Accept' => 'application/json; q=1.0')
32
+ config.headers.add(params[:headers]) if params[:headers] && !params[:headers].empty?
31
33
  config
32
34
  end
33
35
 
@@ -2,13 +2,14 @@ require 'misty/http/net_http'
2
2
  require 'misty/http/method_builder'
3
3
  require 'misty/http/request'
4
4
  require 'misty/http/direct'
5
+ require 'misty/http/header'
5
6
 
6
7
  module Misty
7
8
  module HTTP
8
9
  class Client
9
10
  class Options
10
- attr_accessor :base_path, :base_url, :interface, :region_id,
11
- :service_names, :ssl_verify_mode, :version, :headers
11
+ attr_accessor :base_path, :base_url, :headers, :interface, :region_id,
12
+ :service_names, :ssl_verify_mode, :version
12
13
  end
13
14
 
14
15
  class InvalidDataError < StandardError; end
@@ -20,7 +21,7 @@ module Misty
20
21
 
21
22
  INTERFACES = %w{admin public internal}
22
23
 
23
- attr_reader :microversion
24
+ attr_reader :headers, :microversion
24
25
 
25
26
  def requests
26
27
  list = []
@@ -50,7 +51,7 @@ module Misty
50
51
  # :ssl_verify_mode => true
51
52
  # (micro)version: Can be numbered (3.1) or by state (CURRENT, LATEST or SUPPORTED)
52
53
  # :version => "CURRENT"
53
- def initialize(auth, config, options)
54
+ def initialize(auth, config, options = {})
54
55
  @auth = auth
55
56
  @config = config
56
57
  @options = setup(options)
@@ -59,6 +60,10 @@ module Misty
59
60
  @base_path = @base_path.chomp('/')
60
61
  @version = nil
61
62
  @microversion = false
63
+ @headers = Misty::HTTP::Header.new(@config.headers.get.clone)
64
+ @headers.add('X-Auth-Token' => @auth.get_token.to_s)
65
+ @headers.add(microversion_header) if microversion
66
+ @headers.add(@options.headers) unless @options.headers.empty?
62
67
  end
63
68
 
64
69
  # Sub classes to override
@@ -72,16 +77,6 @@ module Misty
72
77
  ''
73
78
  end
74
79
 
75
- def headers
76
- header = {}
77
- header.merge!({'Accept' => 'application/json; q=1.0'})
78
- header.merge!('X-Auth-Token' => @auth.get_token.to_s)
79
- header.merge!(@config.headers) if @config.headers
80
- header.merge!(@options.headers) if @options.headers
81
- header.merge!(microversion_header) if microversion
82
- header
83
- end
84
-
85
80
  private
86
81
 
87
82
  def baseclass
@@ -92,11 +87,11 @@ module Misty
92
87
  options = Options.new()
93
88
  options.base_path = params[:base_path] ? params[:base_path] : nil
94
89
  options.base_url = params[:base_url] ? params[:base_url] : nil
90
+ options.headers = params[:headers] ? params[:headers] : {}
95
91
  options.interface = params[:interface] ? params[:interface] : @config.interface
96
92
  options.region_id = params[:region_id] ? params[:region_id] : @config.region_id
97
93
  options.service_names = params[:service_name] ? self.class.service_names << params[:service_name] : self.class.service_names
98
- options.ssl_verify_mode = params[:ssl_verify_mode] ? params[:ssl_verify_mode] : @config.ssl_verify_mode
99
- options.headers = params[:headers] ? params[:headers] : {}
94
+ options.ssl_verify_mode = params[:ssl_verify_mode].nil? ? @config.ssl_verify_mode : params[:ssl_verify_mode]
100
95
  options.version = params[:version] ? params[:version] : 'CURRENT'
101
96
 
102
97
  unless INTERFACES.include?(options.interface)
@@ -7,19 +7,19 @@ module Misty
7
7
  end
8
8
 
9
9
  def delete(path, base_path = nil)
10
- http_delete(base_set(base_path) + path, headers)
10
+ http_delete(base_set(base_path) + path, @headers.get)
11
11
  end
12
12
 
13
13
  def get(path, base_path = nil)
14
- http_get(base_set(base_path) + path, headers)
14
+ http_get(base_set(base_path) + path, @headers.get)
15
15
  end
16
16
 
17
17
  def post(path, data, base_path = nil)
18
- http_post(base_set(base_path) + path, headers, data)
18
+ http_post(base_set(base_path) + path, @headers.get, data)
19
19
  end
20
20
 
21
21
  def put(path, data, base_path = nil)
22
- http_put(base_set(base_path) + path, headers, data)
22
+ http_put(base_set(base_path) + path, @headers.get, data)
23
23
  end
24
24
  end
25
25
  end
@@ -0,0 +1,29 @@
1
+ module Misty
2
+ module HTTP
3
+ class Header
4
+ class ArgumentError < RuntimeError; end
5
+ class TypeError < RuntimeError; end
6
+
7
+ def self.valid?(param)
8
+ raise Misty::HTTP::Header::TypeError unless param.class == Hash
9
+ param.each do |key, val|
10
+ raise Misty::HTTP::Header::ArgumentError unless key.class == String
11
+ raise Misty::HTTP::Header::ArgumentError unless val.class == String
12
+ end
13
+ true
14
+ end
15
+
16
+ def initialize(param = {})
17
+ @headers = param if self.class.valid?(param)
18
+ end
19
+
20
+ def add(param = {})
21
+ @headers.merge!(param) if self.class.valid?(param)
22
+ end
23
+
24
+ def get
25
+ @headers
26
+ end
27
+ end
28
+ end
29
+ end
@@ -12,6 +12,9 @@ module Misty
12
12
  private
13
13
 
14
14
  def method_call(method, *args)
15
+ header = @headers.get.clone
16
+ header.merge!(args.pop.get) if args[-1].class == Misty::HTTP::Header
17
+
15
18
  base, path = prefixed_path(method[:path])
16
19
  size_path_parameters = count_path_params(path)
17
20
  path_params = args[0, size_path_parameters]
@@ -21,23 +24,23 @@ module Misty
21
24
 
22
25
  case method[:request]
23
26
  when :COPY
24
- http_copy(final_path, headers)
27
+ http_copy(final_path, header)
25
28
  when :DELETE
26
- http_delete(final_path, headers)
29
+ http_delete(final_path, header)
27
30
  when :GET
28
31
  final_path << query_param(args_left[0]) if args_left && args_left.size == 1
29
- http_get(final_path, headers)
32
+ http_get(final_path, header)
30
33
  when :HEAD
31
- http_head(final_path, headers, data)
34
+ http_head(final_path, header)
32
35
  when :POST
33
36
  data = args_left[0] if args_left && args_left.size == 1
34
- http_post(final_path, headers, data)
37
+ http_post(final_path, header, data)
35
38
  when :PUT
36
39
  data = args_left[0] if args_left && args_left.size == 1
37
- http_put(final_path, headers, data)
40
+ http_put(final_path, header, data)
38
41
  when :PATCH
39
42
  data = args_left[0] if args_left && args_left.size == 1
40
- http_patch(final_path, headers, data)
43
+ http_patch(final_path, header, data)
41
44
  else
42
45
  raise SyntaxError, "Invalid HTTP request: #{method[:request]}"
43
46
  end
@@ -1,3 +1,3 @@
1
1
  module Misty
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -46,6 +46,18 @@ describe Misty::Cloud do
46
46
  cloud.image.show_image_member_details('id1', 'id2').response.must_be_kind_of Net::HTTPOK
47
47
  end
48
48
 
49
+ it 'successful with an extra header parameter' do
50
+ stub_request(:post, 'http://localhost:5000/v3/auth/tokens').
51
+ to_return(:status => 200, :body => JSON.dump(auth_response_v3('network', 'neutron')), :headers => {'x-subject-token'=>'token_data'})
52
+
53
+ stub_request(:get, "http://localhost/v2.0/networks?name=value").
54
+ with(:headers => {'Test-Header'=>'test header value'}).
55
+ to_return(:status => 200, :body => "", :headers => {})
56
+
57
+ header = Misty::HTTP::Header.new('Test-Header' => 'test header value')
58
+ cloud.network.list_networks('name=value', header).response.must_be_kind_of Net::HTTPOK
59
+ end
60
+
49
61
  it 'fails when not enough arguments' do
50
62
  proc do
51
63
  # '/v2/images/{image_id}/members/{member_id}'
@@ -56,9 +56,12 @@ describe Misty::Cloud do
56
56
  Misty::Auth.stub :factory, nil do
57
57
  config = Misty::Cloud.set_configuration({})
58
58
  config.must_be_kind_of Misty::Cloud::Config
59
+
59
60
  config.content_type.must_equal Misty::CONTENT_TYPE
60
- config.log.must_be_kind_of Logger
61
+ config.headers.must_be_kind_of Misty::HTTP::Header
62
+ config.headers.get.must_equal({"Accept"=>"application/json; q=1.0"})
61
63
  config.interface.must_equal Misty::INTERFACE
64
+ config.log.must_be_kind_of Logger
62
65
  config.region_id.must_equal Misty::REGION_ID
63
66
  config.ssl_verify_mode.must_equal Misty::SSL_VERIFY_MODE
64
67
  end
@@ -21,12 +21,6 @@ describe Misty::HTTP::Client do
21
21
  end
22
22
  end
23
23
 
24
- describe '#headers' do
25
- it 'returns hash' do
26
- service.headers.must_be_kind_of Hash
27
- end
28
- end
29
-
30
24
  describe '#baseclass' do
31
25
  it 'returns base class name' do
32
26
  service.send(:baseclass).must_equal 'V2_0'
@@ -34,11 +28,12 @@ describe Misty::HTTP::Client do
34
28
  end
35
29
 
36
30
  describe '#setup' do
37
- it 'sets default options' do
31
+ it 'use default options' do
38
32
  options = service.send(:setup, {})
39
33
  options.must_be_kind_of Misty::HTTP::Client::Options
40
34
  options.base_path.must_be_nil
41
35
  options.base_url.must_be_nil
36
+ options.headers.must_equal ({})
42
37
  options.interface.must_equal 'public'
43
38
  options.region_id.must_equal 'regionOne'
44
39
  options.service_names.must_include 'network'
@@ -46,6 +41,24 @@ describe Misty::HTTP::Client do
46
41
  options.version.must_equal 'CURRENT'
47
42
  end
48
43
 
44
+ it 'use custom options' do
45
+ options = service.send(:setup, {
46
+ :base_path => '/test_path',
47
+ :base_url => 'test_url',
48
+ :headers => {'Key 1' => 'Value 1'},
49
+ :region_id => 'regionTwo',
50
+ :interface => 'internal',
51
+ :ssl_verify_mode => false,
52
+ :version => 'LATEST'})
53
+ options.base_path.must_equal '/test_path'
54
+ options.base_url.must_equal 'test_url'
55
+ options.headers.must_equal ({'Key 1' => 'Value 1'})
56
+ options.interface.must_equal 'internal'
57
+ options.region_id.must_equal 'regionTwo'
58
+ options.ssl_verify_mode.must_equal false
59
+ options.version.must_equal 'LATEST'
60
+ end
61
+
49
62
  it 'fails with invalid interface' do
50
63
  proc do
51
64
  service.send(:setup, {:interface => 'something'})
@@ -58,4 +71,15 @@ describe Misty::HTTP::Client do
58
71
  end.must_raise Misty::HTTP::Client::InvalidDataError
59
72
  end
60
73
  end
74
+
75
+ describe 'headers' do
76
+ it 'returns default global along with token' do
77
+ service.headers.get.must_equal ({"Accept"=>"application/json; q=1.0", "X-Auth-Token"=>"token_id"})
78
+ end
79
+
80
+ it 'inject headers parameter' do
81
+ service(content_type = :ruby, params = {:headers => {'Oh' => 'my!'}}).
82
+ headers.get.must_equal ({"Accept"=>"application/json; q=1.0", "X-Auth-Token"=>"token_id", "Oh"=>"my!"})
83
+ end
84
+ end
61
85
  end
@@ -0,0 +1,39 @@
1
+ require 'test_helper'
2
+ require 'misty/http/client'
3
+
4
+ describe Misty::HTTP::Header do
5
+ describe 'create' do
6
+ it 'successful with Hash parameter' do
7
+ header = Misty::HTTP::Header.new('key 1' => 'value 1', 'key 2' => 'value 2')
8
+
9
+ header.get.must_equal ({'key 1' => 'value 1', 'key 2' => 'value 2'})
10
+ end
11
+
12
+ it 'fails when parameter is not a Hash' do
13
+ proc do
14
+ header = Misty::HTTP::Header.new('A string')
15
+ end.must_raise Misty::HTTP::Header::TypeError
16
+ end
17
+
18
+ it 'fails when a key is not a String' do
19
+ proc do
20
+ header = Misty::HTTP::Header.new('key 1' => 'value 1', 2 => 'value 2')
21
+ end.must_raise Misty::HTTP::Header::ArgumentError
22
+ end
23
+
24
+ it 'fails when a value is not a String' do
25
+ proc do
26
+ header = Misty::HTTP::Header.new('key 1' => 'value 1', 'key 2' => 2)
27
+ end.must_raise Misty::HTTP::Header::ArgumentError
28
+ end
29
+ end
30
+
31
+ describe 'add' do
32
+ it 'merge headers' do
33
+ header = Misty::HTTP::Header.new('key 1' => 'value 1', 'key 2' => 'value 2')
34
+ header.add('key 3' => 'value 3', 'key 4' => 'value 4')
35
+
36
+ header.get.must_equal ({'key 1' => 'value 1', 'key 2' => 'value 2', 'key 3' => 'value 3', 'key 4' => 'value 4'})
37
+ end
38
+ end
39
+ end
@@ -39,13 +39,13 @@ describe Misty::HTTP::MethodBuilder do
39
39
  describe '#get_method' do
40
40
  class Service
41
41
  def self.api
42
- {
43
- '/' => { GET: [:list_api_versions] },
44
- '/v2.0/' => { GET: [:show_api_v2_details] },
45
- '/v2.0/extensions' => { GET: [:list_extensions] },
46
- '/v2.0/extensions/{alias}' => { GET: [:show_extension_details] },
47
- '/v2.0/networks/{network_id}' => { GET: [:show_network_details], PUT: [:update_network], DELETE: [:delete_network] }
48
- }
42
+ {
43
+ '/' => { GET: [:list_api_versions] },
44
+ '/v2.0/' => { GET: [:show_api_v2_details] },
45
+ '/v2.0/extensions' => { GET: [:list_extensions] },
46
+ '/v2.0/extensions/{alias}' => { GET: [:show_extension_details] },
47
+ '/v2.0/networks/{network_id}' => { GET: [:show_network_details], PUT: [:update_network], DELETE: [:delete_network] }
48
+ }
49
49
  end
50
50
  end
51
51
 
@@ -71,8 +71,8 @@ describe Misty::HTTP::Request do
71
71
 
72
72
  it '#http_head' do
73
73
  stub_request(:head, 'http://localhost/resource').
74
- with(:headers => request_header).
75
- to_return(:status => '200')
74
+ with(:headers => request_header).
75
+ to_return(:status => '200')
76
76
 
77
77
  response = service.http_head('/resource', {})
78
78
  response.must_be_kind_of Net::HTTPSuccess
@@ -81,8 +81,8 @@ describe Misty::HTTP::Request do
81
81
 
82
82
  it '#http_options' do
83
83
  stub_request(:options, 'http://localhost/resource').
84
- with(:headers => request_header).
85
- to_return(:status => '200')
84
+ with(:headers => request_header).
85
+ to_return(:status => '200')
86
86
 
87
87
  response = service.http_options('/resource', {})
88
88
  response.must_be_kind_of Net::HTTPSuccess
@@ -37,6 +37,7 @@ describe Misty::Microversion do
37
37
  setup.interface = Misty::INTERFACE
38
38
  setup.region_id = Misty::REGION_ID
39
39
  setup.ssl_verify_mode = Misty::SSL_VERIFY_MODE
40
+ setup.headers = Misty::HTTP::Header.new('Accept' => 'application/json; q=1.0')
40
41
 
41
42
  stub_request(:get, 'http://localhost/').
42
43
  with(:headers => {'Accept'=>'application/json'}).
@@ -4,7 +4,7 @@ def request_header
4
4
  {'Accept' => 'application/json; q=1.0'}
5
5
  end
6
6
 
7
- def service(content_type = :ruby)
7
+ def service(content_type = :ruby, params = {})
8
8
  auth = Minitest::Mock.new
9
9
 
10
10
  def auth.get_url(*args)
@@ -21,10 +21,11 @@ def service(content_type = :ruby)
21
21
  setup.interface = Misty::INTERFACE
22
22
  setup.region_id = Misty::REGION_ID
23
23
  setup.ssl_verify_mode = Misty::SSL_VERIFY_MODE
24
+ setup.headers = Misty::HTTP::Header.new('Accept' => 'application/json; q=1.0')
24
25
 
25
26
  stub_request(:get, 'http://localhost/').
26
27
  with(:headers => request_header).
27
28
  to_return(:status => 200, :body => '', :headers => {})
28
29
 
29
- Misty::Openstack::Neutron::V2_0.new(auth, setup, {})
30
+ Misty::Openstack::Neutron::V2_0.new(auth, setup, params)
30
31
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: misty
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gilles Dubreuil
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-10-10 00:00:00.000000000 Z
11
+ date: 2017-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -135,6 +135,7 @@ files:
135
135
  - lib/misty/cloud.rb
136
136
  - lib/misty/http/client.rb
137
137
  - lib/misty/http/direct.rb
138
+ - lib/misty/http/header.rb
138
139
  - lib/misty/http/method_builder.rb
139
140
  - lib/misty/http/net_http.rb
140
141
  - lib/misty/http/request.rb
@@ -214,6 +215,7 @@ files:
214
215
  - test/unit/cloud_test.rb
215
216
  - test/unit/http/client_test.rb
216
217
  - test/unit/http/direct_test.rb
218
+ - test/unit/http/header_test.rb
217
219
  - test/unit/http/method_builder_test.rb
218
220
  - test/unit/http/request_test.rb
219
221
  - test/unit/microversion_test.rb