tshield 0.13.2.0 → 0.14.0.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
- SHA1:
3
- metadata.gz: 87b26d114e278df9af5848487fbe41a5783b69e9
4
- data.tar.gz: 36fd3d713a4c30d82631d7141c83154c90659281
2
+ SHA256:
3
+ metadata.gz: 83eb12f572bdfd5f6fade21d83ee7d4883253aa9e42cad7fe0e9feac380d7ae6
4
+ data.tar.gz: c1718a13671ee6899321dcda86c6d6dbaad23c1389cc24c0b6ea3b684024c725
5
5
  SHA512:
6
- metadata.gz: 5bd5f43cc13647026b199ad55ab149b842d6bbedae8dcfccb9a345dcc99abb72c132641ede6cd80bff36bd7e5e9123deb8dde882c4573b2d441a8c9c3e430df9
7
- data.tar.gz: 956d0f30dcf1c57ba98e3488deb0d8d80367c8c5ffa6c5b6c383eed282bf17f679463df5403e93ba17033ca1dcb8bfc69afe3deeff778a44b9431b47089c7f47
6
+ metadata.gz: 003b70dbe21e80655ebfe6eae9e0260137a3f6e3e478200c04eefbc238ae72d8d172b62c4d980a934fd56a08eb7d2ad59d93b4d762a3c019e88d3e2e6cc39d46
7
+ data.tar.gz: 6c4ada87361e89edff1b8fe7353389861c3748986631ad9693d01f148c71fb2dbe06547b318d94c3247dc584081c047fc915f5d1d014d6026172b8cf406b5753
data/README.md CHANGED
@@ -192,6 +192,7 @@ domains:
192
192
  headers:
193
193
  HTTP_AUTHORIZATION: Authorization
194
194
  HTTP_COOKIE: Cookie
195
+ send_header_content_type: true
195
196
  not_save_headers:
196
197
  - transfer-encoding
197
198
  cache_request: <<value>>
@@ -235,6 +236,7 @@ domains:
235
236
  * Define Base URI of service
236
237
  * **name**: Name to identify the domain in the generated files
237
238
  * **headers**: github-issue #17
239
+ * **send_header_content_type**: Boolean domain config to send header 'Content-Type' when requesting this domain
238
240
  * **not_save_headers**: List of headers that should be ignored in generated file
239
241
  * **skip_query_params**: List of query params that should be ignored in generated file
240
242
  * **cache_request**: <<some_description>>
@@ -29,10 +29,7 @@ module TShield
29
29
  # generated directory
30
30
  #
31
31
  attr_reader :request
32
- attr_reader :domains
33
- attr_reader :tcp_servers
34
- attr_reader :session_path
35
- attr_reader :windows_compatibility
32
+ attr_reader :domains, :tcp_servers, :session_path, :windows_compatibility
36
33
 
37
34
  def initialize(attributes)
38
35
  attributes.each { |key, value| instance_variable_set("@#{key}", value) }
@@ -65,6 +62,10 @@ module TShield
65
62
  nil
66
63
  end
67
64
 
65
+ def windows_compatibility?
66
+ windows_compatibility || false
67
+ end
68
+
68
69
  def get_headers(domain)
69
70
  (domains[domain] || {})['headers'] || {}
70
71
  end
@@ -100,12 +101,14 @@ module TShield
100
101
  domains[domain]['not_save_headers'] || []
101
102
  end
102
103
 
103
- def read_session_path
104
- session_path || '/sessions'
104
+ def send_header_content_type(domain)
105
+ return domains[domain]['send_header_content_type'] != false if domains[domain]
106
+
107
+ true
105
108
  end
106
109
 
107
- def get_questionmark_char
108
- windows_compatibility ? '%3f' : '?'
110
+ def read_session_path
111
+ session_path || '/sessions'
109
112
  end
110
113
 
111
114
  def grpc
@@ -135,6 +138,5 @@ module TShield
135
138
  def get_delay(domain, path)
136
139
  ((domains[domain] || {})['delay'] || {})[path] || 0
137
140
  end
138
-
139
141
  end
140
142
  end
@@ -57,6 +57,8 @@ module TShield
57
57
  ip: request.ip
58
58
  }
59
59
 
60
+ treat_headers_by_domain(options, path)
61
+
60
62
  if %w[POST PUT PATCH].include? method
61
63
  result = request.body.read.encode('UTF-8',
62
64
  invalid: :replace,
@@ -74,7 +76,7 @@ module TShield
74
76
  configuration.get_excluded_headers(domain(path)).include?(key)
75
77
  end
76
78
  end
77
-
79
+
78
80
  logger.info(
79
81
  "original=#{api_response.original} method=#{method} path=#{path} "\
80
82
  "content-type=#{request_content_type} "\
@@ -94,6 +96,11 @@ module TShield
94
96
  end
95
97
  end
96
98
 
99
+ def treat_headers_by_domain(options, path)
100
+ @send_header_content_type = configuration.send_header_content_type(domain(path))
101
+ options[:headers].delete('Content-Type') unless @send_header_content_type
102
+ end
103
+
97
104
  def configuration
98
105
  @configuration ||= TShield::Configuration.singleton
99
106
  end
@@ -107,7 +114,6 @@ module TShield
107
114
  logger.info("Response with delay of #{delay_in_seconds} seconds")
108
115
  sleep delay_in_seconds
109
116
  end
110
-
111
117
  end
112
118
  end
113
119
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TShield
4
+ # Increment counter for sessions requests
5
+ class GrpcCounter
6
+ def initialize
7
+ @requests = {}
8
+ end
9
+
10
+ def add(hexdigest)
11
+ count = @requests.fetch(hexdigest, 0)
12
+ count += 1
13
+ @requests[hexdigest] = count
14
+ end
15
+
16
+ def current(hexdigest)
17
+ @requests.fetch(hexdigest, 0)
18
+ end
19
+
20
+ def to_json(options = {})
21
+ @requests.to_json(options)
22
+ end
23
+ end
24
+ end
@@ -1,74 +1,131 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tshield/configuration'
3
4
  require 'tshield/sessions'
4
5
 
5
6
  module TShield
6
7
  module Grpc
8
+ # Grpc vcr module
7
9
  module VCR
10
+ # Path file to save Grpc request/response
11
+ class FilePath
12
+ attr_reader :path, :count
13
+
14
+ def initialize(path, count)
15
+ @path = path
16
+ @count = count
17
+ end
18
+ end
19
+
20
+ def initialize
21
+ @configuration = TShield::Configuration.singleton
22
+ end
23
+
8
24
  def handler_in_vcr_mode(method_name, request, parameters, options)
9
25
  parameters.peer =~ /ipv6:\[(.+?)\]|ipv4:(.+?):/
10
26
  peer = Regexp.last_match(1) || Regexp.last_match(2)
11
27
 
12
28
  TShield.logger.info("request from #{parameters.peer}")
13
29
  @session = TShield::Sessions.current(peer)
30
+ @digest = hexdigest(request)
31
+ counter = @session ? request_count.current(@digest) : 0
14
32
 
15
33
  TShield.logger.info("grpc using session #{@session || 'default'}")
16
34
  module_name = options['module']
17
35
 
18
- path = create_destiny(module_name, method_name, request)
19
- save_request(path, request)
20
- response = saved_response(path)
21
- if response
22
- TShield.logger.info("returning saved response for request #{request.to_json} saved into #{hexdigest(request)}")
23
- return response
36
+ path = create_destiny(module_name, method_name)
37
+ @file_path = FilePath.new(path, counter)
38
+ save_request(request)
39
+ response = {}
40
+ saved_error
41
+ begin
42
+ response = saved_response
43
+ if response
44
+ TShield.logger.info("returning saved response for request #{request.to_json} saved into #{@digest}")
45
+ request_count.add(@digest) if @session
46
+ return response
47
+ end
48
+
49
+ response = send_request(request, module_name, options, method_name)
50
+ save_response(response)
51
+ rescue GRPC::BadStatus => e
52
+ save_error({ code: e.code, details: e.details })
53
+ raise e
24
54
  end
55
+ request_count.add(@digest) if @session
56
+ response
57
+ end
25
58
 
59
+ def send_request(request, module_name, options, method_name)
26
60
  TShield.logger.info("calling server to get response for #{request.to_json}")
27
61
  client_class = Object.const_get("#{module_name}::Stub")
28
62
  client_instance = client_class.new(options['hostname'], :this_channel_is_insecure)
29
- response = client_instance.send(method_name, request)
30
- save_response(path, response)
31
- response
63
+ client_instance.send(method_name, request)
32
64
  end
33
65
 
34
- def saved_response(path)
35
- response_file = File.join(path, 'response')
66
+ def request_count
67
+ @session[:grpc_counter]
68
+ end
69
+
70
+ def encode_colon(value)
71
+ value.gsub(':', '%3a')
72
+ end
73
+
74
+ def saved_response
75
+ response_file = File.join(@file_path.path, "#{@file_path.count}.response")
36
76
  return false unless File.exist? response_file
37
77
 
38
78
  content = JSON.parse File.open(response_file).read
39
- response_class = File.open(File.join(path, 'response_class')).read.strip
79
+ response_class = File.open(File.join(@file_path.path, "#{@file_path.count}.response_class")).read.strip
40
80
  Kernel.const_get(response_class).new(content)
41
81
  end
42
82
 
43
- def save_request(path, request)
44
- file = File.open(File.join(path, 'original_request'), 'w')
83
+ def saved_error
84
+ error_file = File.join(@file_path.path, "#{@file_path.count}.error")
85
+ return false unless File.exist? error_file
86
+
87
+ request_count.add(@digest) if @session
88
+ content = JSON.parse File.open(error_file).read
89
+ grpc_error = GRPC::BadStatus.new(content['code'], content['details'])
90
+ raise grpc_error
91
+ end
92
+
93
+ def save_request(request)
94
+ file = File.open(File.join(@file_path.path, "#{@file_path.count}.original_request"), 'w')
45
95
  file.puts request.to_json
46
96
  file.close
47
97
  end
48
98
 
49
- def save_response(path, response)
50
- file = File.open(File.join(path, 'response'), 'w')
99
+ def save_error(error)
100
+ file = File.open(File.join(@file_path.path, "#{@file_path.count}.error"), 'w')
101
+ file.puts error.to_json
102
+ file.close
103
+ request_count.add(@digest) if @session
104
+ end
105
+
106
+ def save_response(response)
107
+ file = File.open(File.join(@file_path.path, "#{@file_path.count}.response"), 'w')
51
108
  file.puts response.to_json
52
109
  file.close
53
110
 
54
- response_class = File.open(File.join(path, 'response_class'), 'w')
111
+ response_class = File.open(File.join(@file_path.path, "#{@file_path.count}.response_class"), 'w')
55
112
  response_class.puts response.class.to_s
56
113
  response_class.close
57
114
  end
58
115
 
59
- def complete_path(module_name, method_name, request)
116
+ def complete_path(module_name, method_name)
60
117
  @session_name = (@session || {})[:name]
61
- path = ['requests', 'grpc', @session_name, module_name, method_name.to_s, hexdigest(request)].compact
62
- path
118
+ module_name = @configuration.windows_compatibility? ? encode_colon(module_name) : module_name
119
+ ['requests', @session_name, module_name, method_name.to_s, @digest].compact
63
120
  end
64
121
 
65
- def create_destiny(module_name, method_name, request)
122
+ def create_destiny(module_name, method_name)
66
123
  current_path = []
67
124
 
68
- path = complete_path(module_name, method_name, request)
125
+ path = complete_path(module_name, method_name)
69
126
  TShield.logger.info("using path #{path}")
70
- path.each do |path|
71
- current_path << path
127
+ path.each do |inner_path|
128
+ current_path << inner_path
72
129
  destiny = File.join current_path
73
130
  Dir.mkdir destiny unless File.exist? destiny
74
131
  end
data/lib/tshield/grpc.rb CHANGED
@@ -4,7 +4,6 @@ require 'grpc'
4
4
 
5
5
  require 'tshield/configuration'
6
6
  require 'tshield/grpc/vcr'
7
-
8
7
  module TShield
9
8
  module Grpc
10
9
  module RequestHandler
@@ -14,6 +13,7 @@ module TShield
14
13
  handler_in_vcr_mode(method_name, request, parameters, options)
15
14
  end
16
15
  end
16
+
17
17
  def self.run!
18
18
  @configuration = TShield::Configuration.singleton.grpc
19
19
 
@@ -41,6 +41,7 @@ module TShield
41
41
  handlers = []
42
42
  number_of_handlers = 0
43
43
  services.each do |file, options|
44
+
44
45
  require file
45
46
 
46
47
  base = Object.const_get("#{options['module']}::Service")
@@ -55,8 +56,7 @@ module TShield
55
56
  def self.build_handler(base, descriptions, number_of_handlers, options)
56
57
  handler = Class.new(base) do
57
58
  class << self
58
- attr_writer :options
59
- attr_reader :options
59
+ attr_accessor :options
60
60
  end
61
61
  descriptions.each do |service_name, description|
62
62
  puts description
@@ -5,6 +5,6 @@ require 'logger'
5
5
  # Logger instance for application
6
6
  module TShield
7
7
  def self.logger
8
- @logger ||= Logger.new(STDOUT)
8
+ @logger ||= Logger.new($stdout)
9
9
  end
10
10
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tshield/logger'
3
4
  require 'tshield/matching/filters'
4
5
  require 'tshield/request'
5
6
 
@@ -59,15 +60,38 @@ module TShield
59
60
  end
60
61
 
61
62
  def load_stub(file)
62
- content = JSON.parse File.open(file).read
63
+ content = read_stub_file(file)
63
64
  content.each do |stub|
64
- stub_session_name = init_stub_session(stub)
65
+ next unless valid_stub?(file, stub)
65
66
 
66
- if stub['stubs']
67
- load_items(stub['stubs'] || [], stub_session_name)
68
- else
69
- load_item(stub, stub_session_name)
70
- end
67
+ load_valid_stub(stub)
68
+ end
69
+ end
70
+
71
+ def read_stub_file(file)
72
+ JSON.parse File.open(file).read
73
+ rescue StandardError
74
+ TShield.logger.error "error in loading matching file #{file}"
75
+ []
76
+ end
77
+
78
+ def valid_stub?(file, stub)
79
+ is_valid = stub.is_a?(Hash) && mandatory_attributes?(stub)
80
+ TShield.logger.info "loading matching file #{file}" if is_valid
81
+ is_valid
82
+ end
83
+
84
+ def mandatory_attributes?(stub)
85
+ (stub['method'] && stub['path'] && stub['response']) || (stub['session'] && stub['stubs'])
86
+ end
87
+
88
+ def load_valid_stub(stub)
89
+ stub_session_name = init_stub_session(stub)
90
+
91
+ if stub['stubs']
92
+ load_items(stub['stubs'] || [], stub_session_name)
93
+ else
94
+ load_item(stub, stub_session_name)
71
95
  end
72
96
  end
73
97
 
@@ -79,7 +79,6 @@ module TShield
79
79
  end
80
80
 
81
81
  def apply_set_cookie_header_values(raw_response, headers = {})
82
-
83
82
  headers_clone = headers.clone
84
83
 
85
84
  field = raw_response.get_fields('Set-Cookie')
@@ -98,6 +97,7 @@ module TShield
98
97
 
99
98
  raw_response.headers.each do |k, v|
100
99
  next if k == 'set-cookie'
100
+
101
101
  headers[k] = v unless configuration.not_save_headers(domain).include? k
102
102
  end
103
103
 
@@ -155,14 +155,24 @@ module TShield
155
155
  content[:body] = body
156
156
  end
157
157
 
158
+ def encode_for_windows_dir(directory)
159
+ replace = [['<', '%3c'], ['>', '%3e'], [':', '%3a'], ['"', '%22'], ['?', '%3f'], [' ', '%20'], ['*', '%2a'],
160
+ ['/', '%2f']]
161
+ replace.each do |value|
162
+ directory = directory.gsub(value.first, value.last)
163
+ end
164
+ directory
165
+ end
166
+
158
167
  def safe_dir(url)
159
168
  if url.size > 225
160
169
  path = url.gsub(/(\?.*)/, '')
161
170
  params = Digest::SHA1.hexdigest Regexp.last_match(1)
162
- "#{path.gsub(%r{/}, '-').gsub(/^-/, '')}#{configuration.get_questionmark_char}#{params}"
171
+ directory = "#{path.gsub(%r{/}, '-').gsub(/^-/, '')}?#{params}"
163
172
  else
164
- url.gsub(%r{/}, '-').gsub(/^-/, '').gsub('?', configuration.get_questionmark_char)
173
+ directory = url.gsub(%r{/}, '-').gsub(/^-/, '')
165
174
  end
175
+ configuration.windows_compatibility? ? encode_for_windows_dir(directory) : directory
166
176
  end
167
177
  end
168
178
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'tshield/logger'
4
4
  require 'tshield/counter'
5
+ require 'tshield/grpc/grpc_counter'
5
6
  require 'tshield/errors'
6
7
 
7
8
  module TShield
@@ -14,6 +15,7 @@ module TShield
14
15
  sessions[normalize_ip(ip)] = {
15
16
  name: name,
16
17
  counter: TShield::Counter.new,
18
+ grpc_counter: TShield::GrpcCounter.new,
17
19
  secondary_sessions: []
18
20
  }
19
21
  end
@@ -4,8 +4,8 @@ module TShield
4
4
  # Control version of gem
5
5
  class Version
6
6
  MAJOR = 0
7
- MINOR = 13
8
- PATCH = 2
7
+ MINOR = 14
8
+ PATCH = 0
9
9
  PRE = 0
10
10
 
11
11
  class << self
@@ -67,19 +67,19 @@ describe TShield::Configuration do
67
67
 
68
68
  describe 'SO compatibility' do
69
69
  it 'should be compatible with windows when configuration is true' do
70
- allow(YAML).to receive(:safe_load).and_return({:windows_compatibility => true })
70
+ allow(YAML).to receive(:safe_load).and_return({ windows_compatibility: true })
71
71
  TShield::Configuration.clear
72
72
  @configuration = TShield::Configuration.singleton
73
73
 
74
- expect(@configuration.get_questionmark_char).to eq('%3f')
74
+ expect(@configuration.windows_compatibility?).to eq(true)
75
75
  end
76
76
 
77
77
  it 'should be compatible with Unix when configuration is false' do
78
- allow(YAML).to receive(:safe_load).and_return({:windows_compatibility => false })
78
+ allow(YAML).to receive(:safe_load).and_return({ windows_compatibility: false })
79
79
  TShield::Configuration.clear
80
80
  @configuration = TShield::Configuration.singleton
81
81
 
82
- expect(@configuration.get_questionmark_char).to eq('?')
82
+ expect(@configuration.windows_compatibility?).to eq(false)
83
83
  end
84
84
 
85
85
  it 'should be compatible with Unix when configuration is missing' do
@@ -87,9 +87,8 @@ describe TShield::Configuration do
87
87
  TShield::Configuration.clear
88
88
  @configuration = TShield::Configuration.singleton
89
89
 
90
- expect(@configuration.get_questionmark_char).to eq('?')
90
+ expect(@configuration.windows_compatibility?).to eq(false)
91
91
  end
92
-
93
92
  end
94
93
  end
95
94
  context 'on config not exist' do
@@ -108,14 +107,42 @@ describe TShield::Configuration do
108
107
 
109
108
  context 'on config exists without grpc entry' do
110
109
  before :each do
111
- options_instance = double
112
- allow(options_instance).to receive(:configuration_file)
113
- .and_return('spec/tshield/fixtures/config/tshield-without-grpc.yml')
114
- allow(TShield::Options).to receive(:instance).and_return(options_instance)
115
- @configuration = TShield::Configuration.singleton
110
+ @configuration = generate_configuration_from_file('spec/tshield/fixtures/config/tshield-without-grpc.yml')
111
+ TShield::Configuration.clear
116
112
  end
117
113
  it 'should set default value for port' do
118
114
  expect(@configuration.grpc).to eql('port' => 5678, 'proto_dir' => 'proto', 'services' => {})
119
115
  end
120
116
  end
117
+
118
+ context 'on config property request.domains.domain.send_header_content_type does not exists' do
119
+ before :each do
120
+ @configuration = generate_configuration_from_file('spec/tshield/fixtures/config/tshield-without-grpc.yml')
121
+ TShield::Configuration.clear
122
+ end
123
+ it 'should return send_header_content_type as true when property is not set' do
124
+ expect(@configuration.send_header_content_type('example.org')).to be true
125
+ end
126
+ end
127
+
128
+ context 'on config property request.domains.domain.send_header_content_type does exists' do
129
+ before :each do
130
+ TShield::Configuration.clear
131
+ end
132
+ it 'should return send_header_content_type as true when property is true' do
133
+ @configuration = generate_configuration_from_file('spec/tshield/fixtures/config/tshield-with-send-content-type-header.yml')
134
+ expect(@configuration.send_header_content_type('example.org')).to be true
135
+ end
136
+ it 'should return send_header_content_type as false when property is false' do
137
+ @configuration = generate_configuration_from_file('spec/tshield/fixtures/config/tshield-with-send-content-type-header_as_false.yml')
138
+ expect(@configuration.send_header_content_type('example.org')).to be false
139
+ end
140
+ end
141
+ end
142
+
143
+ def generate_configuration_from_file(file)
144
+ options_instance = double
145
+ allow(options_instance).to receive(:configuration_file).and_return(file)
146
+ allow(TShield::Options).to receive(:instance).and_return(options_instance)
147
+ TShield::Configuration.singleton
121
148
  end
@@ -58,3 +58,93 @@ describe TShield::Controllers::Requests do
58
58
  end
59
59
  end
60
60
  end
61
+
62
+ describe TShield::Controllers::Requests do
63
+ before :each do
64
+ @configuration = double
65
+ allow(TShield::Configuration)
66
+ .to receive(:singleton).and_return(@configuration)
67
+ allow(@configuration).to receive(:get_before_filters).and_return([])
68
+ allow(@configuration).to receive(:not_save_headers).and_return([])
69
+ allow(@configuration).to receive(:get_after_filters).and_return([])
70
+ allow(@configuration).to receive(:get_headers).and_return([])
71
+ allow(@configuration).to receive(:request).and_return('timeout' => 10)
72
+ allow(@configuration).to receive(:get_name).and_return('example.org')
73
+ allow(@configuration).to receive(:get_domain_for).and_return('example.org')
74
+ allow(@configuration).to receive(:get_delay).and_return(0)
75
+
76
+ allow(TShield::Options).to receive_message_chain(:instance, :break?)
77
+ @mock_logger = double
78
+ @controller = MockController.new(@mock_logger)
79
+ end
80
+ context 'when send_header_content_type is false for a single domain' do
81
+ it 'should remove application/json header when making a request' do
82
+ params = { 'captures' => ['/'] }
83
+ request = double
84
+ matcher = double
85
+ matched_response = double
86
+
87
+ allow(@configuration).to receive(:send_header_content_type).and_return(false)
88
+ allow(request).to receive(:request_method).and_return('GET')
89
+ allow(request).to receive(:content_type).and_return('application/json')
90
+ allow(request).to receive(:ip).and_return('0.0.0.0')
91
+ allow(request).to receive(:env).and_return('QUERY_STRING' => 'a=b')
92
+ allow(TShield::RequestMatching).to receive(:new).and_return(matcher)
93
+ allow(matcher).to receive(:match_request).and_return(nil)
94
+ allow(TShield::RequestVCR).to receive(:new).and_return(matcher)
95
+ allow(matcher).to receive(:vcr_response).and_return(matched_response)
96
+ allow(matched_response).to receive(:original).and_return(false)
97
+ allow(matched_response).to receive(:status).and_return(200)
98
+ allow(matched_response).to receive(:headers).and_return({})
99
+ allow(matched_response).to receive(:body).and_return('')
100
+ allow(@mock_logger).to receive(:info)
101
+
102
+ expect(TShield::RequestVCR).to receive(:new).with('/', {
103
+ call: 0,
104
+ headers: {},
105
+ ip: '0.0.0.0',
106
+ method: 'GET',
107
+ raw_query: 'a=b',
108
+ secondary_sessions: nil,
109
+ session: nil
110
+ })
111
+ @controller.treat(params, request, nil)
112
+ end
113
+ end
114
+
115
+ context 'when send_header_content_type is true for a single domain' do
116
+ it 'should NOT remove application/json header when making a request' do
117
+ params = { 'captures' => ['/'] }
118
+ request = double
119
+ matcher = double
120
+ matched_response = double
121
+
122
+ allow(@configuration).to receive(:send_header_content_type).and_return(true)
123
+ allow(request).to receive(:request_method).and_return('GET')
124
+ allow(request).to receive(:content_type).and_return('application/json')
125
+ allow(request).to receive(:ip).and_return('0.0.0.0')
126
+ allow(request).to receive(:env).and_return('QUERY_STRING' => 'a=b')
127
+ allow(TShield::RequestMatching).to receive(:new).and_return(matcher)
128
+ allow(matcher).to receive(:match_request).and_return(nil)
129
+ allow(TShield::RequestVCR).to receive(:new).and_return(matcher)
130
+ allow(matcher).to receive(:vcr_response).and_return(matched_response)
131
+ allow(matched_response).to receive(:original).and_return(false)
132
+ allow(matched_response).to receive(:status).and_return(200)
133
+ allow(matched_response).to receive(:headers).and_return({})
134
+ allow(matched_response).to receive(:body).and_return('')
135
+ allow(@mock_logger).to receive(:info)
136
+
137
+ expect(TShield::RequestVCR).to receive(:new).with('/', {
138
+ call: 0,
139
+ headers: { 'Content-Type' => 'application/json' },
140
+ ip: '0.0.0.0',
141
+ method: 'GET',
142
+ raw_query: 'a=b',
143
+ secondary_sessions: nil,
144
+ session: nil
145
+ })
146
+
147
+ @controller.treat(params, request, nil)
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,18 @@
1
+ ---
2
+ request:
3
+ timeout: 0
4
+ domains:
5
+ 'example.org':
6
+ name: 'example.org'
7
+ send_header_content_type: true
8
+ filters:
9
+ - 'ExampleFilter'
10
+ paths:
11
+ - '/api/one'
12
+ - '/api/two'
13
+ skip_query_params:
14
+ - 'a'
15
+ 'example.com':
16
+ name: 'example.com'
17
+ paths:
18
+ - '/api/three'
@@ -0,0 +1,18 @@
1
+ ---
2
+ request:
3
+ timeout: 0
4
+ domains:
5
+ 'example.org':
6
+ name: 'example.org'
7
+ send_header_content_type: false
8
+ filters:
9
+ - 'ExampleFilter'
10
+ paths:
11
+ - '/api/one'
12
+ - '/api/two'
13
+ skip_query_params:
14
+ - 'a'
15
+ 'example.com':
16
+ name: 'example.com'
17
+ paths:
18
+ - '/api/three'
@@ -36,6 +36,38 @@ describe TShield::RequestMatching do
36
36
  end
37
37
  end
38
38
 
39
+ context 'invalid matching file' do
40
+ before :each do
41
+ allow(Dir).to receive(:glob)
42
+ .and_return(['spec/tshield/fixtures/matching/invalid_matching_file.json'])
43
+ end
44
+
45
+ context 'on loading stubs' do
46
+ before :each do
47
+ @request_matching = TShield::RequestMatching.new('/')
48
+ end
49
+ it 'should stubs be empty' do
50
+ expect(@request_matching.class.stubs[DEFAULT_SESSION]).to be_nil
51
+ end
52
+ end
53
+ end
54
+
55
+ context 'empty matching file' do
56
+ before :each do
57
+ allow(Dir).to receive(:glob)
58
+ .and_return(['spec/tshield/fixtures/matching/empty.json'])
59
+ end
60
+
61
+ context 'on loading stubs' do
62
+ before :each do
63
+ @request_matching = TShield::RequestMatching.new('/')
64
+ end
65
+ it 'should stubs be empty' do
66
+ expect(@request_matching.class.stubs[DEFAULT_SESSION]).to be_nil
67
+ end
68
+ end
69
+ end
70
+
39
71
  context 'matching path' do
40
72
  before :each do
41
73
  allow(Dir).to receive(:glob)
@@ -81,7 +81,7 @@ describe TShield::RequestVCR do
81
81
  }
82
82
  )
83
83
 
84
- allow(@configuration).to receive(:get_questionmark_char).and_return('?')
84
+ allow(@configuration).to receive(:windows_compatibility?).and_return(false)
85
85
 
86
86
  allow(HTTParty).to receive(:send).and_return(RawResponse.new)
87
87
  file_double = double
@@ -106,41 +106,40 @@ describe TShield::RequestVCR do
106
106
  end
107
107
 
108
108
  it 'should create response directory in windows standard' do
109
-
110
109
  allow(@configuration).to receive(:domains).and_return(
111
- 'example.org' => {
112
- 'skip_query_params' => []
113
- }
110
+ 'example.org' => {
111
+ 'skip_query_params' => []
112
+ }
114
113
  )
115
114
 
116
- allow(@configuration).to receive(:get_questionmark_char).and_return('%3f')
115
+ allow(@configuration).to receive(:windows_compatibility?).and_return(true)
117
116
 
118
117
  allow(HTTParty).to receive(:send).and_return(RawResponse.new)
119
118
  file_double = double
120
119
 
121
120
  allow(File).to receive(:join)
122
- .with('./requests/example.org', '%3fparam=value')
123
- .and_return('./requests/example.org/%3fparam=value')
121
+ .with('./requests/example.org', '%3fparam=value')
122
+ .and_return('./requests/example.org/%3fparam=value')
124
123
  allow(File).to receive(:join)
125
- .with('./requests/example.org/%3fparam=value', 'get')
126
- .and_return('./requests/example.org/%3fparam=value/get')
124
+ .with('./requests/example.org/%3fparam=value', 'get')
125
+ .and_return('./requests/example.org/%3fparam=value/get')
127
126
  allow(File).to receive(:join)
128
- .with('./requests/example.org/%3fparam=value/get', '0')
129
- .and_return('./requests/example.org/%3fparam=value/get/0')
127
+ .with('./requests/example.org/%3fparam=value/get', '0')
128
+ .and_return('./requests/example.org/%3fparam=value/get/0')
130
129
 
131
130
  allow(file_double).to receive(:read).and_return('{}')
132
131
 
133
132
  expect(File).to receive('open')
134
- .with('./requests/example.org/%3fparam=value/get/0.content', 'w')
135
- .and_return(file_double)
133
+ .with('./requests/example.org/%3fparam=value/get/0.content', 'w')
134
+ .and_return(file_double)
136
135
 
137
136
  expect(File).to receive('open')
138
- .with('./requests/example.org/%3fparam=value/get/0.json', 'w')
139
- .and_return(file_double)
137
+ .with('./requests/example.org/%3fparam=value/get/0.json', 'w')
138
+ .and_return(file_double)
140
139
 
141
140
  expect(file_double).to receive(:write).ordered.with('this is the body')
142
141
  expect(file_double).to receive(:write)
143
- .with("{\n \"status\": 200,\n \"headers\": {\n }\n}")
142
+ .with("{\n \"status\": 200,\n \"headers\": {\n }\n}")
144
143
  expect(file_double).to receive(:close)
145
144
  expect(file_double).to receive(:close)
146
145
 
@@ -149,8 +148,6 @@ describe TShield::RequestVCR do
149
148
  method: 'GET',
150
149
  call: 0
151
150
  end
152
-
153
-
154
151
  end
155
152
  end
156
153
 
@@ -163,7 +160,7 @@ describe TShield::RequestVCR do
163
160
  'this is the body'
164
161
  end
165
162
 
166
- def get_fields(field = "")
163
+ def get_fields(_field = '')
167
164
  []
168
165
  end
169
166
 
@@ -174,22 +171,19 @@ describe TShield::RequestVCR do
174
171
 
175
172
  class RawResponseCookiesMultipleValues
176
173
  def headers
177
- {'Set-Cookie' => ['FirstCookie=An Value', 'SecondCookie=An Value']}
174
+ { 'Set-Cookie' => ['FirstCookie=An Value', 'SecondCookie=An Value'] }
178
175
  end
179
176
 
180
177
  def body
181
178
  'this is the body'
182
179
  end
183
180
 
184
- def get_fields(field = "")
185
- self.headers[filed] unless self.headers.key?(field)
181
+ def get_fields(field = '')
182
+ headers[filed] unless headers.key?(field)
186
183
  end
187
184
 
188
185
  def code
189
186
  200
190
187
  end
191
188
  end
192
-
193
189
  end
194
-
195
-
data/tshield.gemspec CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |s|
28
28
  s.add_dependency('grpc-tools', '~> 1.28', '>= 1.28.0')
29
29
  s.add_dependency('httparty', '~> 0.14', '>= 0.14.0')
30
30
  s.add_dependency('json', '~> 2.0', '>= 2.0')
31
- s.add_dependency('puma', '~> 4.3', '>= 4.3.3')
31
+ s.add_dependency('puma', '>= 4.3.3', '< 6.0')
32
32
  s.add_dependency('sinatra', '~> 2.1', '>= 2.1.0')
33
33
  s.add_dependency('sinatra-cross_origin', '~> 0.4.0', '>= 0.4')
34
34
  s.add_development_dependency('coveralls', '~> 0.8', '>= 0.8.23')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tshield
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.2.0
4
+ version: 0.14.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Diego Rubin
@@ -9,148 +9,148 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-01-06 00:00:00.000000000 Z
12
+ date: 2021-10-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: grpc
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "~>"
19
- - !ruby/object:Gem::Version
20
- version: '1.28'
21
18
  - - ">="
22
19
  - !ruby/object:Gem::Version
23
20
  version: 1.28.0
21
+ - - "~>"
22
+ - !ruby/object:Gem::Version
23
+ version: '1.28'
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
- - - "~>"
29
- - !ruby/object:Gem::Version
30
- version: '1.28'
31
28
  - - ">="
32
29
  - !ruby/object:Gem::Version
33
30
  version: 1.28.0
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.28'
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: grpc-tools
36
36
  requirement: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '1.28'
41
38
  - - ">="
42
39
  - !ruby/object:Gem::Version
43
40
  version: 1.28.0
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '1.28'
44
44
  type: :runtime
45
45
  prerelease: false
46
46
  version_requirements: !ruby/object:Gem::Requirement
47
47
  requirements:
48
- - - "~>"
49
- - !ruby/object:Gem::Version
50
- version: '1.28'
51
48
  - - ">="
52
49
  - !ruby/object:Gem::Version
53
50
  version: 1.28.0
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.28'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: httparty
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '0.14'
61
58
  - - ">="
62
59
  - !ruby/object:Gem::Version
63
60
  version: 0.14.0
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '0.14'
64
64
  type: :runtime
65
65
  prerelease: false
66
66
  version_requirements: !ruby/object:Gem::Requirement
67
67
  requirements:
68
- - - "~>"
69
- - !ruby/object:Gem::Version
70
- version: '0.14'
71
68
  - - ">="
72
69
  - !ruby/object:Gem::Version
73
70
  version: 0.14.0
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '0.14'
74
74
  - !ruby/object:Gem::Dependency
75
75
  name: json
76
76
  requirement: !ruby/object:Gem::Requirement
77
77
  requirements:
78
- - - "~>"
78
+ - - ">="
79
79
  - !ruby/object:Gem::Version
80
80
  version: '2.0'
81
- - - ">="
81
+ - - "~>"
82
82
  - !ruby/object:Gem::Version
83
83
  version: '2.0'
84
84
  type: :runtime
85
85
  prerelease: false
86
86
  version_requirements: !ruby/object:Gem::Requirement
87
87
  requirements:
88
- - - "~>"
88
+ - - ">="
89
89
  - !ruby/object:Gem::Version
90
90
  version: '2.0'
91
- - - ">="
91
+ - - "~>"
92
92
  - !ruby/object:Gem::Version
93
93
  version: '2.0'
94
94
  - !ruby/object:Gem::Dependency
95
95
  name: puma
96
96
  requirement: !ruby/object:Gem::Requirement
97
97
  requirements:
98
- - - "~>"
99
- - !ruby/object:Gem::Version
100
- version: '4.3'
101
98
  - - ">="
102
99
  - !ruby/object:Gem::Version
103
100
  version: 4.3.3
101
+ - - "<"
102
+ - !ruby/object:Gem::Version
103
+ version: '6.0'
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '4.3'
111
108
  - - ">="
112
109
  - !ruby/object:Gem::Version
113
110
  version: 4.3.3
111
+ - - "<"
112
+ - !ruby/object:Gem::Version
113
+ version: '6.0'
114
114
  - !ruby/object:Gem::Dependency
115
115
  name: sinatra
116
116
  requirement: !ruby/object:Gem::Requirement
117
117
  requirements:
118
- - - "~>"
119
- - !ruby/object:Gem::Version
120
- version: '2.1'
121
118
  - - ">="
122
119
  - !ruby/object:Gem::Version
123
120
  version: 2.1.0
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '2.1'
124
124
  type: :runtime
125
125
  prerelease: false
126
126
  version_requirements: !ruby/object:Gem::Requirement
127
127
  requirements:
128
- - - "~>"
129
- - !ruby/object:Gem::Version
130
- version: '2.1'
131
128
  - - ">="
132
129
  - !ruby/object:Gem::Version
133
130
  version: 2.1.0
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: '2.1'
134
134
  - !ruby/object:Gem::Dependency
135
135
  name: sinatra-cross_origin
136
136
  requirement: !ruby/object:Gem::Requirement
137
137
  requirements:
138
- - - "~>"
139
- - !ruby/object:Gem::Version
140
- version: 0.4.0
141
138
  - - ">="
142
139
  - !ruby/object:Gem::Version
143
140
  version: '0.4'
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: 0.4.0
144
144
  type: :runtime
145
145
  prerelease: false
146
146
  version_requirements: !ruby/object:Gem::Requirement
147
147
  requirements:
148
- - - "~>"
149
- - !ruby/object:Gem::Version
150
- version: 0.4.0
151
148
  - - ">="
152
149
  - !ruby/object:Gem::Version
153
150
  version: '0.4'
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: 0.4.0
154
154
  - !ruby/object:Gem::Dependency
155
155
  name: coveralls
156
156
  requirement: !ruby/object:Gem::Requirement
@@ -255,80 +255,80 @@ dependencies:
255
255
  name: rdoc
256
256
  requirement: !ruby/object:Gem::Requirement
257
257
  requirements:
258
- - - "~>"
258
+ - - ">="
259
259
  - !ruby/object:Gem::Version
260
260
  version: '6.0'
261
- - - ">="
261
+ - - "~>"
262
262
  - !ruby/object:Gem::Version
263
263
  version: '6.0'
264
264
  type: :development
265
265
  prerelease: false
266
266
  version_requirements: !ruby/object:Gem::Requirement
267
267
  requirements:
268
- - - "~>"
268
+ - - ">="
269
269
  - !ruby/object:Gem::Version
270
270
  version: '6.0'
271
- - - ">="
271
+ - - "~>"
272
272
  - !ruby/object:Gem::Version
273
273
  version: '6.0'
274
274
  - !ruby/object:Gem::Dependency
275
275
  name: reek
276
276
  requirement: !ruby/object:Gem::Requirement
277
277
  requirements:
278
- - - "~>"
278
+ - - ">="
279
279
  - !ruby/object:Gem::Version
280
280
  version: 5.4.0
281
- - - ">="
281
+ - - "~>"
282
282
  - !ruby/object:Gem::Version
283
283
  version: 5.4.0
284
284
  type: :development
285
285
  prerelease: false
286
286
  version_requirements: !ruby/object:Gem::Requirement
287
287
  requirements:
288
- - - "~>"
288
+ - - ">="
289
289
  - !ruby/object:Gem::Version
290
290
  version: 5.4.0
291
- - - ">="
291
+ - - "~>"
292
292
  - !ruby/object:Gem::Version
293
293
  version: 5.4.0
294
294
  - !ruby/object:Gem::Dependency
295
295
  name: rspec
296
296
  requirement: !ruby/object:Gem::Requirement
297
297
  requirements:
298
- - - "~>"
299
- - !ruby/object:Gem::Version
300
- version: '3.5'
301
298
  - - ">="
302
299
  - !ruby/object:Gem::Version
303
300
  version: 3.5.0
301
+ - - "~>"
302
+ - !ruby/object:Gem::Version
303
+ version: '3.5'
304
304
  type: :development
305
305
  prerelease: false
306
306
  version_requirements: !ruby/object:Gem::Requirement
307
307
  requirements:
308
- - - "~>"
309
- - !ruby/object:Gem::Version
310
- version: '3.5'
311
308
  - - ">="
312
309
  - !ruby/object:Gem::Version
313
310
  version: 3.5.0
311
+ - - "~>"
312
+ - !ruby/object:Gem::Version
313
+ version: '3.5'
314
314
  - !ruby/object:Gem::Dependency
315
315
  name: rubocop
316
316
  requirement: !ruby/object:Gem::Requirement
317
317
  requirements:
318
- - - "~>"
318
+ - - ">="
319
319
  - !ruby/object:Gem::Version
320
320
  version: 0.73.0
321
- - - ">="
321
+ - - "~>"
322
322
  - !ruby/object:Gem::Version
323
323
  version: 0.73.0
324
324
  type: :development
325
325
  prerelease: false
326
326
  version_requirements: !ruby/object:Gem::Requirement
327
327
  requirements:
328
- - - "~>"
328
+ - - ">="
329
329
  - !ruby/object:Gem::Version
330
330
  version: 0.73.0
331
- - - ">="
331
+ - - "~>"
332
332
  - !ruby/object:Gem::Version
333
333
  version: 0.73.0
334
334
  - !ruby/object:Gem::Dependency
@@ -375,22 +375,22 @@ dependencies:
375
375
  name: webmock
376
376
  requirement: !ruby/object:Gem::Requirement
377
377
  requirements:
378
- - - "~>"
379
- - !ruby/object:Gem::Version
380
- version: '2.1'
381
378
  - - ">="
382
379
  - !ruby/object:Gem::Version
383
380
  version: 2.1.0
381
+ - - "~>"
382
+ - !ruby/object:Gem::Version
383
+ version: '2.1'
384
384
  type: :development
385
385
  prerelease: false
386
386
  version_requirements: !ruby/object:Gem::Requirement
387
387
  requirements:
388
- - - "~>"
389
- - !ruby/object:Gem::Version
390
- version: '2.1'
391
388
  - - ">="
392
389
  - !ruby/object:Gem::Version
393
390
  version: 2.1.0
391
+ - - "~>"
392
+ - !ruby/object:Gem::Version
393
+ version: '2.1'
394
394
  description: Proxy for mocks API responses
395
395
  email: rubin.diego@gmail.com
396
396
  executables:
@@ -415,6 +415,7 @@ files:
415
415
  - lib/tshield/errors.rb
416
416
  - lib/tshield/extensions/string_extensions.rb
417
417
  - lib/tshield/grpc.rb
418
+ - lib/tshield/grpc/grpc_counter.rb
418
419
  - lib/tshield/grpc/vcr.rb
419
420
  - lib/tshield/logger.rb
420
421
  - lib/tshield/matching/filters.rb
@@ -430,6 +431,8 @@ files:
430
431
  - spec/tshield/after_filter_spec.rb
431
432
  - spec/tshield/configuration_spec.rb
432
433
  - spec/tshield/controllers/requests_spec.rb
434
+ - spec/tshield/fixtures/config/tshield-with-send-content-type-header.yml
435
+ - spec/tshield/fixtures/config/tshield-with-send-content-type-header_as_false.yml
433
436
  - spec/tshield/fixtures/config/tshield-without-grpc.yml
434
437
  - spec/tshield/fixtures/config/tshield.yml
435
438
  - spec/tshield/fixtures/filters/example_filter.rb
@@ -460,23 +463,24 @@ required_rubygems_version: !ruby/object:Gem::Requirement
460
463
  - !ruby/object:Gem::Version
461
464
  version: '0'
462
465
  requirements: []
463
- rubyforge_project:
464
- rubygems_version: 2.6.14.4
466
+ rubygems_version: 3.0.3.1
465
467
  signing_key:
466
468
  specification_version: 4
467
469
  summary: Proxy for mocks API responses
468
470
  test_files:
469
471
  - spec/spec_helper.rb
470
- - spec/tshield/fixtures/matching/example.json
472
+ - spec/tshield/request_matching_spec.rb
473
+ - spec/tshield/sessions_spec.rb
474
+ - spec/tshield/request_vcr_spec.rb
475
+ - spec/tshield/after_filter_spec.rb
476
+ - spec/tshield/grpc_spec.rb
477
+ - spec/tshield/options_spec.rb
471
478
  - spec/tshield/fixtures/config/tshield-without-grpc.yml
479
+ - spec/tshield/fixtures/config/tshield-with-send-content-type-header_as_false.yml
480
+ - spec/tshield/fixtures/config/tshield-with-send-content-type-header.yml
472
481
  - spec/tshield/fixtures/config/tshield.yml
473
- - spec/tshield/fixtures/filters/example_filter.rb
474
482
  - spec/tshield/fixtures/proto/test_services_pb.rb
475
- - spec/tshield/options_spec.rb
476
- - spec/tshield/configuration_spec.rb
477
- - spec/tshield/sessions_spec.rb
478
- - spec/tshield/request_matching_spec.rb
483
+ - spec/tshield/fixtures/matching/example.json
484
+ - spec/tshield/fixtures/filters/example_filter.rb
479
485
  - spec/tshield/controllers/requests_spec.rb
480
- - spec/tshield/grpc_spec.rb
481
- - spec/tshield/request_vcr_spec.rb
482
- - spec/tshield/after_filter_spec.rb
486
+ - spec/tshield/configuration_spec.rb