tshield 0.13.2.0 → 0.14.0.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
- 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