misty 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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