tshield 0.11.17.0 → 0.13.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
- SHA256:
3
- metadata.gz: dda6e0a319ccae57965e4fdf2e51445327dbc9ee74a0b7545b10304d28405051
4
- data.tar.gz: 91a7ba920f3ccfecb2f1892fed014a196d2f53739a3e1dea592d5893b89aa6d6
2
+ SHA1:
3
+ metadata.gz: caa9310473bc1479ad6bc7db7ae67938b269bbd7
4
+ data.tar.gz: 34de8d7084f0eecb054f2b07b9950ef0dffbf32b
5
5
  SHA512:
6
- metadata.gz: d0fb66877992d29835ab23279d1364f1a468f3d547160d25ea1cd0f8451d5516eeeae414d5c75c0176aaf8faac3ad55b1230949bec2d3d0e38a9cb940cdb4c79
7
- data.tar.gz: ab7e413bf75bcf16edafa121c115e6457a794bdcdbb5c82bfe2b4678f976d97b01f245c4ca7bf7e88d62f329dd43d635d8d48a95c560d433f5c736e3b6f1dec4
6
+ metadata.gz: 41afa4ac6a4471ea700beaa32ddf4c664f7eeb4f7a61639853b883a5ee0b6dd8f4262f9e14198a186e89cece5bb158669587a00f80dc7bef6bb460a5f6f7c5a3
7
+ data.tar.gz: ef52b07f2bfc68d8ff0561c0676350493da7b7f16b26f1e61687f13a78929e4ec9659859a396dcb6bb51d5eab8c041cc15cbb2ca92fcecc8a5ed768101a2a7bd
data/README.md CHANGED
@@ -13,7 +13,7 @@ TShield is an open source proxy for mocks API responses.
13
13
  * REST
14
14
  * SOAP
15
15
  * Session manager to separate multiple scenarios (success, error, sucess variation, ...)
16
- gRPC [EXPERIMENTAL]
16
+ * gRPC [EXPERIMENTAL]
17
17
  * Lightweight
18
18
  * MIT license
19
19
 
@@ -68,6 +68,17 @@ domains:
68
68
  - /users
69
69
  ```
70
70
 
71
+ **Windows Compatibility:** If you need to use Tshield in Windows SO, change the config file and set the windows_compatibility to true.
72
+
73
+ Eg:
74
+ ```yaml
75
+ windows_compatibility: true
76
+ request:
77
+ # wait time for real service
78
+ timeout: 8
79
+ ...
80
+ ```
81
+
71
82
  ## Config options for Pattern Matching
72
83
 
73
84
  An example of file to create a stub:
@@ -93,6 +104,10 @@ Optional request attributes:
93
104
  this stub will be returned if all headers are in request.
94
105
  * **query**: works like headers but use query params.
95
106
 
107
+ Optional response attributes:
108
+
109
+ * **delay**: integer that represents time in seconds that the response will be delayed to return
110
+
96
111
  **Important**: If VCR config conflicts with Matching config Matching will be
97
112
  used. Matching config have priority.
98
113
 
@@ -155,6 +170,7 @@ To register stub into a session create an object with following attributes:
155
170
  "method": "GET",
156
171
  "path": "/matching/example",
157
172
  "response": {
173
+ "delay": 5,
158
174
  "body": "matching-example-response-in-session",
159
175
  "headers": {},
160
176
  "status": 200
@@ -195,6 +211,8 @@ domains:
195
211
  - transfer-encoding
196
212
  paths:
197
213
  - /secure
214
+ delay:
215
+ '/secure': 10
198
216
 
199
217
  'http://localhost:9092':
200
218
  name: 'my-other-service'
@@ -205,6 +223,8 @@ domains:
205
223
  - transfer-encoding
206
224
  paths:
207
225
  - /users
226
+ delay:
227
+ '/users': 5
208
228
  ```
209
229
  **request**
210
230
  * **timeout**: wait time for real service in seconds
@@ -220,6 +240,7 @@ domains:
220
240
  * **filters**: Implementation of before or after filters used in domain requests
221
241
  * **excluded_headers**: <<some_description>>
222
242
  * **paths**: Paths list of all services that will be called. Used to filter what domain will "receive the request"
243
+ * **delay**: List of times in seconds that the response will be delayed to return for an specific path defined above
223
244
 
224
245
  ## Manage Sessions
225
246
 
@@ -250,6 +271,16 @@ _DELETE_ to http://localhost:4567/sessions
250
271
  curl -X DELETE \
251
272
  http://localhost:4567/sessions
252
273
  ```
274
+ ### Append secondary TShield session
275
+ **Append session. Secondary sessions will used only for read content in VCR mode, writes will be do in the main session. Append only works if exists a current session setted.**
276
+
277
+ _POST_ to http://localhost:4567/sessions?name=<<same_name>>
278
+
279
+ ```
280
+ curl -X POST \
281
+ 'http://localhost:4567/sessions/append?name=my_valid'
282
+ ```
283
+
253
284
  ## [Experimental] Config options for gRPC
254
285
 
255
286
  ```yaml
@@ -265,7 +296,6 @@ grpc:
265
296
 
266
297
  ### Not Implemented Yet
267
298
 
268
- - Sessions
269
299
  - Matching
270
300
 
271
301
  ### Configuration
@@ -14,5 +14,6 @@ domains:
14
14
  skip_query_params:
15
15
  - b
16
16
  paths:
17
+ - /fake
17
18
  - /users
18
19
  - /resources
@@ -32,6 +32,7 @@ module TShield
32
32
  attr_reader :domains
33
33
  attr_reader :tcp_servers
34
34
  attr_reader :session_path
35
+ attr_reader :windows_compatibility
35
36
 
36
37
  def initialize(attributes)
37
38
  attributes.each { |key, value| instance_variable_set("@#{key}", value) }
@@ -103,6 +104,10 @@ module TShield
103
104
  session_path || '/sessions'
104
105
  end
105
106
 
107
+ def get_questionmark_char
108
+ windows_compatibility ? '%3f' : '?'
109
+ end
110
+
106
111
  def grpc
107
112
  defaults = { 'port' => 5678, 'proto_dir' => 'proto', 'services' => {} }
108
113
  defaults.merge(@grpc || {})
@@ -126,5 +131,10 @@ module TShield
126
131
  )
127
132
  raise 'Startup aborted'
128
133
  end
134
+
135
+ def get_delay(domain, path)
136
+ ((domains[domain] || {})['delay'] || {})[path] || 0
137
+ end
138
+
129
139
  end
130
140
  end
@@ -10,6 +10,11 @@ module TShield
10
10
  session[:name] if session
11
11
  end
12
12
 
13
+ def self.secondary_sessions(request)
14
+ session = TShield::Sessions.current(request.ip)
15
+ session[:secondary_sessions] if session
16
+ end
17
+
13
18
  def self.current_session_call(request, callid, method)
14
19
  session = TShield::Sessions.current(request.ip)
15
20
  session ? session[:counter].current(callid, method) : 0
@@ -43,6 +43,7 @@ module TShield
43
43
  request_content_type = request.content_type
44
44
 
45
45
  session_name = TShield::Controllers::Helpers::SessionHelpers.current_session_name(request)
46
+ secondary_sessions = TShield::Controllers::Helpers::SessionHelpers.secondary_sessions(request)
46
47
  session_call = TShield::Controllers::Helpers::SessionHelpers
47
48
  .current_session_call(request, callid, method)
48
49
 
@@ -51,6 +52,7 @@ module TShield
51
52
  headers: Helpers.build_headers(request),
52
53
  raw_query: request.env['QUERY_STRING'],
53
54
  session: session_name,
55
+ secondary_sessions: secondary_sessions,
54
56
  call: session_call,
55
57
  ip: request.ip
56
58
  }
@@ -67,12 +69,12 @@ module TShield
67
69
  unless api_response
68
70
  add_headers(options, path)
69
71
 
70
- api_response ||= TShield::RequestVCR.new(path, options.clone).response
72
+ api_response ||= TShield::RequestVCR.new(path, options.clone).vcr_response
71
73
  api_response.headers.reject! do |key, _v|
72
74
  configuration.get_excluded_headers(domain(path)).include?(key)
73
75
  end
74
76
  end
75
-
77
+
76
78
  logger.info(
77
79
  "original=#{api_response.original} method=#{method} path=#{path} "\
78
80
  "content-type=#{request_content_type} "\
@@ -80,6 +82,7 @@ module TShield
80
82
  )
81
83
  TShield::Controllers::Helpers::SessionHelpers.update_session_call(request, callid, method)
82
84
 
85
+ delay(path)
83
86
  status api_response.status
84
87
  headers api_response.headers
85
88
  body api_response.body
@@ -98,6 +101,13 @@ module TShield
98
101
  def domain(path)
99
102
  @domain ||= configuration.get_domain_for(path)
100
103
  end
104
+
105
+ def delay(path)
106
+ delay_in_seconds = configuration.get_delay(domain(path), path) || 0
107
+ logger.info("Response with delay of #{delay_in_seconds} seconds")
108
+ sleep delay_in_seconds
109
+ end
110
+
101
111
  end
102
112
  end
103
113
  end
@@ -23,6 +23,9 @@ module TShield
23
23
  end
24
24
 
25
25
  def self.register_post(app, session_path)
26
+ app.post "#{session_path}/append" do
27
+ TShield::Sessions.append(request.ip, params[:name]).to_json
28
+ end
26
29
  app.post session_path do
27
30
  TShield::Sessions.start(request.ip, params[:name]).to_json
28
31
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AppendSessionWithoutMainSessionError < RuntimeError
4
+ end
@@ -3,16 +3,15 @@
3
3
  require 'grpc'
4
4
 
5
5
  require 'tshield/configuration'
6
- require 'tshield/sessions'
7
6
  require 'tshield/grpc/vcr'
8
7
 
9
8
  module TShield
10
9
  module Grpc
11
10
  module RequestHandler
12
11
  include TShield::Grpc::VCR
13
- def handler(method_name, request)
12
+ def handler(method_name, request, parameters)
14
13
  options = self.class.options
15
- handler_in_vcr_mode(method_name, request, options)
14
+ handler_in_vcr_mode(method_name, request, parameters, options)
16
15
  end
17
16
  end
18
17
  def self.run!
@@ -62,8 +61,8 @@ module TShield
62
61
  descriptions.each do |service_name, description|
63
62
  puts description
64
63
  method_name = service_name.to_s.underscore.to_sym
65
- define_method(method_name) do |request, _unused_call|
66
- handler(__method__, request)
64
+ define_method(method_name) do |request, parameters|
65
+ handler(__method__, request, parameters)
67
66
  end
68
67
  end
69
68
  end
@@ -1,65 +1,82 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tshield/sessions'
4
+
3
5
  module TShield
4
6
  module Grpc
5
7
  module VCR
6
- def handler_in_vcr_mode(method_name, request, options)
8
+ def handler_in_vcr_mode(method_name, request, parameters, options)
9
+ parameters.peer =~ /ipv6:\[(.+?)\]|ipv4:(.+?):/
10
+ peer = Regexp.last_match(1) || Regexp.last_match(2)
11
+
12
+ TShield.logger.info("request from #{parameters.peer}")
13
+ @session = TShield::Sessions.current(peer)
14
+
15
+ TShield.logger.info("grpc using session #{@session || 'default'}")
7
16
  module_name = options['module']
8
17
 
9
- response = saved_response(module_name, method_name, request)
10
- return response if response
18
+ path = create_destiny(module_name, method_name, request)
19
+ response = saved_response(path)
20
+ if response
21
+ TShield.logger.info("returning saved response for request #{request.to_json} saved into #{hexdigest(request)}")
22
+ return response
23
+ end
11
24
 
25
+ TShield.logger.info("calling server to get response for #{request.to_json}")
12
26
  client_class = Object.const_get("#{module_name}::Stub")
13
27
  client_instance = client_class.new(options['hostname'], :this_channel_is_insecure)
14
28
  response = client_instance.send(method_name, request)
15
- save_request_and_response(request, response)
29
+ save_request_and_response(path, request, response)
16
30
  response
17
31
  end
18
32
 
19
- def saved_response(module_name, method_name, request)
20
- create_destiny(module_name, method_name, request)
21
- response_file = File.join(@complete_path, 'response')
33
+ def saved_response(path)
34
+ response_file = File.join(path, 'response')
22
35
  return false unless File.exist? response_file
23
36
 
24
37
  content = JSON.parse File.open(response_file).read
25
- response_class = File.open(File.join(@complete_path, 'response_class')).read.strip
38
+ response_class = File.open(File.join(path, 'response_class')).read.strip
26
39
  Kernel.const_get(response_class).new(content)
27
40
  end
28
41
 
29
- def save_request_and_response(request, response)
30
- save_request(request)
31
- save_response(response)
42
+ def save_request_and_response(path, request, response)
43
+ save_request(path, request)
44
+ save_response(path, response)
32
45
  end
33
46
 
34
- def save_request(request)
35
- file = File.open(File.join(@complete_path, 'original_request'), 'w')
47
+ def save_request(path, request)
48
+ file = File.open(File.join(path, 'original_request'), 'w')
36
49
  file.puts request.to_json
37
50
  file.close
38
51
  end
39
52
 
40
- def save_response(response)
41
- file = File.open(File.join(@complete_path, 'response'), 'w')
53
+ def save_response(path, response)
54
+ file = File.open(File.join(path, 'response'), 'w')
42
55
  file.puts response.to_json
43
56
  file.close
44
57
 
45
- response_class = File.open(File.join(@complete_path, 'response_class'), 'w')
58
+ response_class = File.open(File.join(path, 'response_class'), 'w')
46
59
  response_class.puts response.class.to_s
47
60
  response_class.close
48
61
  end
49
62
 
50
63
  def complete_path(module_name, method_name, request)
51
- return @complete_path if @complete_path
52
-
53
- @complete_path = ['requests', 'grpc', module_name, method_name.to_s, hexdigest(request)]
64
+ @session_name = (@session || {})[:name]
65
+ path = ['requests', 'grpc', @session_name, module_name, method_name.to_s, hexdigest(request)].compact
66
+ path
54
67
  end
55
68
 
56
69
  def create_destiny(module_name, method_name, request)
57
70
  current_path = []
58
- complete_path(module_name, method_name, request).each do |path|
71
+
72
+ path = complete_path(module_name, method_name, request)
73
+ TShield.logger.info("using path #{path}")
74
+ path.each do |path|
59
75
  current_path << path
60
76
  destiny = File.join current_path
61
77
  Dir.mkdir destiny unless File.exist? destiny
62
78
  end
79
+ path
63
80
  end
64
81
 
65
82
  def hexdigest(request)
@@ -8,6 +8,14 @@ module TShield
8
8
  result = filter_stubs(stubs[@options[:session]] || {})
9
9
  return result if result
10
10
 
11
+ find_in_secondary_sessions(stubs, @options[:secondary_sessions] || [])
12
+ end
13
+
14
+ def find_in_secondary_sessions(stubs, sessions)
15
+ sessions.each do |session|
16
+ result = filter_stubs(stubs[session] || {})
17
+ return result if result
18
+ end
11
19
  filter_stubs(stubs[DEFAULT_SESSION] || {}) unless @options[:session] == DEFAULT_SESSION
12
20
  end
13
21
 
@@ -13,8 +13,8 @@ module TShield
13
13
 
14
14
  protected
15
15
 
16
- def session_destiny(request_path)
17
- session = @options[:session]
16
+ def session_destiny(request_path, current_session = nil)
17
+ session = current_session || @options[:session]
18
18
  return request_path unless session
19
19
 
20
20
  request_path = File.join(request_path, session)
@@ -22,19 +22,19 @@ module TShield
22
22
  request_path
23
23
  end
24
24
 
25
- def content_destiny
26
- "#{destiny}.content"
25
+ def content_destiny(current_session = nil)
26
+ "#{destiny(current_session)}.content"
27
27
  end
28
28
 
29
- def headers_destiny
30
- "#{destiny}.json"
29
+ def headers_destiny(current_session = nil)
30
+ "#{destiny(current_session)}.json"
31
31
  end
32
32
 
33
- def destiny
33
+ def destiny(current_session = nil)
34
34
  request_path = File.join('requests')
35
35
  Dir.mkdir(request_path) unless File.exist?(request_path)
36
36
 
37
- request_path = session_destiny(request_path)
37
+ request_path = session_destiny(request_path, current_session)
38
38
 
39
39
  name_path = File.join(request_path, name)
40
40
  Dir.mkdir(name_path) unless File.exist?(name_path)
@@ -29,6 +29,7 @@ module TShield
29
29
 
30
30
  @matched = current_response
31
31
 
32
+ sleep matched['delay'] || 0
32
33
  TShield::Response.new(self.class.read_body(matched['body']),
33
34
  matched['headers'],
34
35
  matched['status'])
@@ -2,11 +2,11 @@
2
2
 
3
3
  require 'httparty'
4
4
  require 'json'
5
- require 'byebug'
6
5
 
7
6
  require 'digest/sha1'
8
7
 
9
8
  require 'tshield/configuration'
9
+ require 'tshield/logger'
10
10
  require 'tshield/options'
11
11
  require 'tshield/request'
12
12
  require 'tshield/response'
@@ -14,6 +14,8 @@ require 'tshield/response'
14
14
  module TShield
15
15
  # Module to write and read saved responses
16
16
  class RequestVCR < TShield::Request
17
+ attr_reader :vcr_response
18
+
17
19
  def initialize(path, options = {})
18
20
  super()
19
21
  @path = path
@@ -35,27 +37,31 @@ module TShield
35
37
  _method, @url, @options = filter.new.filter(method, @url, @options)
36
38
  end
37
39
 
38
- if exists
39
- response.original = false
40
- resp = response
40
+ in_session = find_in_sessions
41
+ if in_session
42
+ # TODO: create concept of global session in vcr
43
+ in_session = nil if in_session == 'global'
44
+ @vcr_response = response(in_session)
45
+ @vcr_response.original = false
41
46
  else
47
+ TShield.logger.info("calling original service for request with options #{@options}")
42
48
  raw = HTTParty.send(method.to_s, @url, @options)
43
49
 
44
50
  original_response = save(raw)
45
51
  original_response.original = true
46
- resp = original_response
52
+ @vcr_response = original_response
47
53
  end
48
54
 
49
55
  configuration.get_after_filters(domain).each do |filter|
50
- resp = filter.new.filter(resp)
56
+ @vcr_response = filter.new.filter(@vcr_response)
51
57
  end
52
- resp
53
58
  end
54
59
 
55
- def response
56
- @response ||= TShield::Response.new(saved_content['body'],
57
- saved_content['headers'] || [],
58
- saved_content['status'] || 200)
60
+ def response(session)
61
+ response_content = saved_content(session)
62
+ TShield::Response.new(response_content['body'],
63
+ response_content['headers'] || [],
64
+ response_content['status'] || 200)
59
65
  end
60
66
 
61
67
  private
@@ -89,20 +95,27 @@ module TShield
89
95
  TShield::Response.new(raw_response.body, headers, raw_response.code)
90
96
  end
91
97
 
92
- def saved_content
93
- return @saved_content if @saved_content
94
-
95
- @saved_content = JSON.parse(File.open(headers_destiny).read)
96
- @saved_content['body'] = File.open(content_destiny).read unless @saved_content['body']
97
- @saved_content
98
+ def saved_content(session)
99
+ content = JSON.parse(File.open(headers_destiny(session)).read)
100
+ content['body'] = File.open(content_destiny(session)).read unless content['body']
101
+ content
98
102
  end
99
103
 
100
- def file_exists
101
- File.exist?(content_destiny)
104
+ def file_exists(session)
105
+ File.exist?(content_destiny(session))
102
106
  end
103
107
 
104
- def exists
105
- file_exists && configuration.cache_request?(domain)
108
+ def find_in_sessions
109
+ in_session = nil
110
+
111
+ ([@options[:session]] + (@options[:secondary_sessions] || [])).each do |session|
112
+ if file_exists(session) && configuration.cache_request?(domain)
113
+ in_session = (session || 'global')
114
+ break
115
+ end
116
+ TShield.logger.info("saved response not found in #{session}")
117
+ end
118
+ in_session
106
119
  end
107
120
 
108
121
  def key
@@ -127,9 +140,9 @@ module TShield
127
140
  if url.size > 225
128
141
  path = url.gsub(/(\?.*)/, '')
129
142
  params = Digest::SHA1.hexdigest Regexp.last_match(1)
130
- "#{path.gsub(%r{/}, '-').gsub(/^-/, '')}?#{params}"
143
+ "#{path.gsub(%r{/}, '-').gsub(/^-/, '')}#{configuration.get_questionmark_char}#{params}"
131
144
  else
132
- url.gsub(%r{/}, '-').gsub(/^-/, '')
145
+ url.gsub(%r{/}, '-').gsub(/^-/, '').gsub('?', configuration.get_questionmark_char)
133
146
  end
134
147
  end
135
148
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'byebug'
3
+ require 'tshield/logger'
4
4
  require 'tshield/counter'
5
+ require 'tshield/errors'
5
6
 
6
7
  module TShield
7
8
  # Manage sessions
@@ -9,17 +10,34 @@ module TShield
9
10
  # Start and stop session for ip
10
11
  module Sessions
11
12
  def self.start(ip, name)
12
- sessions[normalize_ip(ip)] = { name: name, counter: TShield::Counter.new }
13
+ TShield.logger.info("starting session #{name} for ip #{normalize_ip(ip)}")
14
+ sessions[normalize_ip(ip)] = {
15
+ name: name,
16
+ counter: TShield::Counter.new,
17
+ secondary_sessions: []
18
+ }
13
19
  end
14
20
 
15
21
  def self.stop(ip)
22
+ TShield.logger.info("stoping session for ip #{normalize_ip(ip)}")
16
23
  sessions[normalize_ip(ip)] = nil
17
24
  end
18
25
 
19
26
  def self.current(ip)
27
+ TShield.logger.info("fetching session for ip #{normalize_ip(ip)}")
20
28
  sessions[normalize_ip(ip)]
21
29
  end
22
30
 
31
+ def self.append(ip, name)
32
+ TShield.logger.info("appeding session #{name} for ip #{normalize_ip(ip)}")
33
+
34
+ current_session = sessions[normalize_ip(ip)]
35
+ raise AppendSessionWithoutMainSessionError, "not found main session for #{ip}" unless current_session
36
+
37
+ current_session[:secondary_sessions] << name
38
+ current_session
39
+ end
40
+
23
41
  def self.sessions
24
42
  @sessions ||= {}
25
43
  end
@@ -4,8 +4,8 @@ module TShield
4
4
  # Control version of gem
5
5
  class Version
6
6
  MAJOR = 0
7
- MINOR = 11
8
- PATCH = 17
7
+ MINOR = 13
8
+ PATCH = 0
9
9
  PRE = 0
10
10
 
11
11
  class << self
@@ -64,6 +64,33 @@ describe TShield::Configuration do
64
64
  expect(@configuration.get_domain_for('/api/four')).to be_nil
65
65
  end
66
66
  end
67
+
68
+ describe 'SO compatibility' do
69
+ it 'should be compatible with windows when configuration is true' do
70
+ allow(YAML).to receive(:safe_load).and_return({:windows_compatibility => true })
71
+ TShield::Configuration.clear
72
+ @configuration = TShield::Configuration.singleton
73
+
74
+ expect(@configuration.get_questionmark_char).to eq('%3f')
75
+ end
76
+
77
+ it 'should be compatible with Unix when configuration is false' do
78
+ allow(YAML).to receive(:safe_load).and_return({:windows_compatibility => false })
79
+ TShield::Configuration.clear
80
+ @configuration = TShield::Configuration.singleton
81
+
82
+ expect(@configuration.get_questionmark_char).to eq('?')
83
+ end
84
+
85
+ it 'should be compatible with Unix when configuration is missing' do
86
+ allow(YAML).to receive(:safe_load).and_return({})
87
+ TShield::Configuration.clear
88
+ @configuration = TShield::Configuration.singleton
89
+
90
+ expect(@configuration.get_questionmark_char).to eq('?')
91
+ end
92
+
93
+ end
67
94
  end
68
95
  context 'on config not exist' do
69
96
  before :each do
@@ -99,5 +99,17 @@
99
99
  "body": "body content in session"
100
100
  }
101
101
  }]
102
+ },
103
+ {
104
+ "session": "second-session",
105
+ "stubs": [{
106
+ "method": "GET",
107
+ "path": "/matching/second-example",
108
+ "response": {
109
+ "status": 200,
110
+ "headers": {},
111
+ "body": "body content in second-session"
112
+ }
113
+ }]
102
114
  }
103
115
  ]
@@ -235,6 +235,18 @@ describe TShield::RequestMatching do
235
235
  expect(@response.headers).to eql({})
236
236
  expect(@response.status).to eql(201)
237
237
  end
238
+ context 'with secondary session' do
239
+ it 'should return response object from session settings' do
240
+ @request_matching = TShield::RequestMatching.new('/matching/second-example',
241
+ method: 'GET',
242
+ session: 'a-session',
243
+ secondary_sessions: ['second-session'])
244
+ @response = @request_matching.match_request
245
+ expect(@response.body).to eql('body content in second-session')
246
+ expect(@response.headers).to eql({})
247
+ expect(@response.status).to eql(200)
248
+ end
249
+ end
238
250
  end
239
251
  end
240
252
  end
@@ -62,6 +62,8 @@ describe TShield::RequestVCR do
62
62
  }
63
63
  )
64
64
 
65
+ allow(@configuration).to receive(:get_questionmark_char).and_return('?')
66
+
65
67
  allow(HTTParty).to receive(:send).and_return(RawResponse.new)
66
68
  file_double = double
67
69
 
@@ -83,6 +85,53 @@ describe TShield::RequestVCR do
83
85
  method: 'GET',
84
86
  call: 0
85
87
  end
88
+
89
+ it 'should create response directory in windows standard' do
90
+
91
+ allow(@configuration).to receive(:domains).and_return(
92
+ 'example.org' => {
93
+ 'skip_query_params' => []
94
+ }
95
+ )
96
+
97
+ allow(@configuration).to receive(:get_questionmark_char).and_return('%3f')
98
+
99
+ allow(HTTParty).to receive(:send).and_return(RawResponse.new)
100
+ file_double = double
101
+
102
+ allow(File).to receive(:join)
103
+ .with('./requests/example.org', '%3fparam=value')
104
+ .and_return('./requests/example.org/%3fparam=value')
105
+ allow(File).to receive(:join)
106
+ .with('./requests/example.org/%3fparam=value', 'get')
107
+ .and_return('./requests/example.org/%3fparam=value/get')
108
+ allow(File).to receive(:join)
109
+ .with('./requests/example.org/%3fparam=value/get', '0')
110
+ .and_return('./requests/example.org/%3fparam=value/get/0')
111
+
112
+ allow(file_double).to receive(:read).and_return('{}')
113
+
114
+ expect(File).to receive('open')
115
+ .with('./requests/example.org/%3fparam=value/get/0.content', 'w')
116
+ .and_return(file_double)
117
+
118
+ expect(File).to receive('open')
119
+ .with('./requests/example.org/%3fparam=value/get/0.json', 'w')
120
+ .and_return(file_double)
121
+
122
+ expect(file_double).to receive(:write).ordered.with('this is the body')
123
+ expect(file_double).to receive(:write)
124
+ .with("{\n \"status\": 200,\n \"headers\": {\n }\n}")
125
+ expect(file_double).to receive(:close)
126
+ expect(file_double).to receive(:close)
127
+
128
+ TShield::RequestVCR.new '/',
129
+ raw_query: 'param=value',
130
+ method: 'GET',
131
+ call: 0
132
+ end
133
+
134
+
86
135
  end
87
136
  end
88
137
 
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tshield/sessions'
4
+ require 'spec_helper'
5
+
6
+ describe TShield::Sessions do
7
+ context 'on append session' do
8
+ it 'should raise error if not has a main session' do
9
+ expect { TShield::Sessions.append 'ip', 'secondary-session' }
10
+ .to raise_error(AppendSessionWithoutMainSessionError)
11
+ end
12
+ it 'should append if has main session' do
13
+ TShield::Sessions.start 'ip', 'main-session'
14
+ result = TShield::Sessions.append 'ip', 'secondary-session'
15
+ expect(result[:secondary_sessions]).to include('secondary-session')
16
+ end
17
+ end
18
+ end
@@ -22,19 +22,18 @@ Gem::Specification.new do |s|
22
22
 
23
23
  s.test_files = Dir['spec/**/*']
24
24
 
25
- s.required_ruby_version = '>= 2.3'
25
+ s.required_ruby_version = '>= 2.4'
26
26
 
27
- s.add_dependency('byebug', '~> 11.0', '>= 11.0.1')
28
27
  s.add_dependency('grpc', '~> 1.28', '>= 1.28.0')
29
28
  s.add_dependency('grpc-tools', '~> 1.28', '>= 1.28.0')
30
29
  s.add_dependency('httparty', '~> 0.14', '>= 0.14.0')
31
30
  s.add_dependency('json', '~> 2.0', '>= 2.0')
32
31
  s.add_dependency('puma', '~> 4.3', '>= 4.3.3')
33
- s.add_dependency('sinatra', '~> 1.4', '>= 1.4.0')
32
+ s.add_dependency('sinatra', '~> 2.1', '>= 2.1.0')
34
33
  s.add_dependency('sinatra-cross_origin', '~> 0.4.0', '>= 0.4')
35
- s.add_development_dependency('coveralls')
34
+ s.add_development_dependency('coveralls', '~> 0.8', '>= 0.8.23')
36
35
  s.add_development_dependency('cucumber', '~> 3.1', '>= 3.1.2')
37
- s.add_development_dependency('guard', '~> 2.15', '>= 2.15.0')
36
+ s.add_development_dependency('guard', '~> 2.16', '>= 2.16.2')
38
37
  s.add_development_dependency('guard-rspec', '~> 4.7', '>= 4.7.3')
39
38
  s.add_development_dependency('rake', '>= 10.0', '~> 13.0')
40
39
  s.add_development_dependency('rdoc', '~> 6.0', '>= 6.0')
@@ -42,6 +41,6 @@ Gem::Specification.new do |s|
42
41
  s.add_development_dependency('rspec', '~> 3.5', '>= 3.5.0')
43
42
  s.add_development_dependency('rubocop', '~> 0.73.0', '>= 0.73.0')
44
43
  s.add_development_dependency('rubocop-rails', '~> 2.2.0', '>= 2.2.1')
45
- s.add_development_dependency('simplecov', '~> 0.12', '>= 0.12.0')
44
+ s.add_development_dependency('simplecov', '~> 0.16', '>= 0.16.1')
46
45
  s.add_development_dependency('webmock', '~> 2.1', '>= 2.1.0')
47
46
  end
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.11.17.0
4
+ version: 0.13.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Diego Rubin
@@ -9,106 +9,86 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-07-09 00:00:00.000000000 Z
12
+ date: 2020-11-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: byebug
15
+ name: grpc
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: '11.0'
20
+ version: '1.28'
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 11.0.1
23
+ version: 1.28.0
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: '11.0'
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 11.0.1
34
- - !ruby/object:Gem::Dependency
35
- name: grpc
36
- requirement: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: 1.28.0
41
28
  - - "~>"
42
29
  - !ruby/object:Gem::Version
43
30
  version: '1.28'
44
- type: :runtime
45
- prerelease: false
46
- version_requirements: !ruby/object:Gem::Requirement
47
- requirements:
48
31
  - - ">="
49
32
  - !ruby/object:Gem::Version
50
33
  version: 1.28.0
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '1.28'
54
34
  - !ruby/object:Gem::Dependency
55
35
  name: grpc-tools
56
36
  requirement: !ruby/object:Gem::Requirement
57
37
  requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: 1.28.0
61
38
  - - "~>"
62
39
  - !ruby/object:Gem::Version
63
40
  version: '1.28'
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.28.0
64
44
  type: :runtime
65
45
  prerelease: false
66
46
  version_requirements: !ruby/object:Gem::Requirement
67
47
  requirements:
68
- - - ">="
69
- - !ruby/object:Gem::Version
70
- version: 1.28.0
71
48
  - - "~>"
72
49
  - !ruby/object:Gem::Version
73
50
  version: '1.28'
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.28.0
74
54
  - !ruby/object:Gem::Dependency
75
55
  name: httparty
76
56
  requirement: !ruby/object:Gem::Requirement
77
57
  requirements:
78
- - - ">="
79
- - !ruby/object:Gem::Version
80
- version: 0.14.0
81
58
  - - "~>"
82
59
  - !ruby/object:Gem::Version
83
60
  version: '0.14'
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 0.14.0
84
64
  type: :runtime
85
65
  prerelease: false
86
66
  version_requirements: !ruby/object:Gem::Requirement
87
67
  requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- version: 0.14.0
91
68
  - - "~>"
92
69
  - !ruby/object:Gem::Version
93
70
  version: '0.14'
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 0.14.0
94
74
  - !ruby/object:Gem::Dependency
95
75
  name: json
96
76
  requirement: !ruby/object:Gem::Requirement
97
77
  requirements:
98
- - - ">="
78
+ - - "~>"
99
79
  - !ruby/object:Gem::Version
100
80
  version: '2.0'
101
- - - "~>"
81
+ - - ">="
102
82
  - !ruby/object:Gem::Version
103
83
  version: '2.0'
104
84
  type: :runtime
105
85
  prerelease: false
106
86
  version_requirements: !ruby/object:Gem::Requirement
107
87
  requirements:
108
- - - ">="
88
+ - - "~>"
109
89
  - !ruby/object:Gem::Version
110
90
  version: '2.0'
111
- - - "~>"
91
+ - - ">="
112
92
  - !ruby/object:Gem::Version
113
93
  version: '2.0'
114
94
  - !ruby/object:Gem::Dependency
@@ -135,56 +115,62 @@ dependencies:
135
115
  name: sinatra
136
116
  requirement: !ruby/object:Gem::Requirement
137
117
  requirements:
138
- - - ">="
139
- - !ruby/object:Gem::Version
140
- version: 1.4.0
141
118
  - - "~>"
142
119
  - !ruby/object:Gem::Version
143
- version: '1.4'
120
+ version: '2.1'
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 2.1.0
144
124
  type: :runtime
145
125
  prerelease: false
146
126
  version_requirements: !ruby/object:Gem::Requirement
147
127
  requirements:
148
- - - ">="
149
- - !ruby/object:Gem::Version
150
- version: 1.4.0
151
128
  - - "~>"
152
129
  - !ruby/object:Gem::Version
153
- version: '1.4'
130
+ version: '2.1'
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: 2.1.0
154
134
  - !ruby/object:Gem::Dependency
155
135
  name: sinatra-cross_origin
156
136
  requirement: !ruby/object:Gem::Requirement
157
137
  requirements:
158
- - - ">="
159
- - !ruby/object:Gem::Version
160
- version: '0.4'
161
138
  - - "~>"
162
139
  - !ruby/object:Gem::Version
163
140
  version: 0.4.0
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0.4'
164
144
  type: :runtime
165
145
  prerelease: false
166
146
  version_requirements: !ruby/object:Gem::Requirement
167
147
  requirements:
168
- - - ">="
169
- - !ruby/object:Gem::Version
170
- version: '0.4'
171
148
  - - "~>"
172
149
  - !ruby/object:Gem::Version
173
150
  version: 0.4.0
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0.4'
174
154
  - !ruby/object:Gem::Dependency
175
155
  name: coveralls
176
156
  requirement: !ruby/object:Gem::Requirement
177
157
  requirements:
158
+ - - "~>"
159
+ - !ruby/object:Gem::Version
160
+ version: '0.8'
178
161
  - - ">="
179
162
  - !ruby/object:Gem::Version
180
- version: '0'
163
+ version: 0.8.23
181
164
  type: :development
182
165
  prerelease: false
183
166
  version_requirements: !ruby/object:Gem::Requirement
184
167
  requirements:
168
+ - - "~>"
169
+ - !ruby/object:Gem::Version
170
+ version: '0.8'
185
171
  - - ">="
186
172
  - !ruby/object:Gem::Version
187
- version: '0'
173
+ version: 0.8.23
188
174
  - !ruby/object:Gem::Dependency
189
175
  name: cucumber
190
176
  requirement: !ruby/object:Gem::Requirement
@@ -209,22 +195,22 @@ dependencies:
209
195
  name: guard
210
196
  requirement: !ruby/object:Gem::Requirement
211
197
  requirements:
212
- - - ">="
213
- - !ruby/object:Gem::Version
214
- version: 2.15.0
215
198
  - - "~>"
216
199
  - !ruby/object:Gem::Version
217
- version: '2.15'
200
+ version: '2.16'
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: 2.16.2
218
204
  type: :development
219
205
  prerelease: false
220
206
  version_requirements: !ruby/object:Gem::Requirement
221
207
  requirements:
222
- - - ">="
223
- - !ruby/object:Gem::Version
224
- version: 2.15.0
225
208
  - - "~>"
226
209
  - !ruby/object:Gem::Version
227
- version: '2.15'
210
+ version: '2.16'
211
+ - - ">="
212
+ - !ruby/object:Gem::Version
213
+ version: 2.16.2
228
214
  - !ruby/object:Gem::Dependency
229
215
  name: guard-rspec
230
216
  requirement: !ruby/object:Gem::Requirement
@@ -269,80 +255,80 @@ dependencies:
269
255
  name: rdoc
270
256
  requirement: !ruby/object:Gem::Requirement
271
257
  requirements:
272
- - - ">="
258
+ - - "~>"
273
259
  - !ruby/object:Gem::Version
274
260
  version: '6.0'
275
- - - "~>"
261
+ - - ">="
276
262
  - !ruby/object:Gem::Version
277
263
  version: '6.0'
278
264
  type: :development
279
265
  prerelease: false
280
266
  version_requirements: !ruby/object:Gem::Requirement
281
267
  requirements:
282
- - - ">="
268
+ - - "~>"
283
269
  - !ruby/object:Gem::Version
284
270
  version: '6.0'
285
- - - "~>"
271
+ - - ">="
286
272
  - !ruby/object:Gem::Version
287
273
  version: '6.0'
288
274
  - !ruby/object:Gem::Dependency
289
275
  name: reek
290
276
  requirement: !ruby/object:Gem::Requirement
291
277
  requirements:
292
- - - ">="
278
+ - - "~>"
293
279
  - !ruby/object:Gem::Version
294
280
  version: 5.4.0
295
- - - "~>"
281
+ - - ">="
296
282
  - !ruby/object:Gem::Version
297
283
  version: 5.4.0
298
284
  type: :development
299
285
  prerelease: false
300
286
  version_requirements: !ruby/object:Gem::Requirement
301
287
  requirements:
302
- - - ">="
288
+ - - "~>"
303
289
  - !ruby/object:Gem::Version
304
290
  version: 5.4.0
305
- - - "~>"
291
+ - - ">="
306
292
  - !ruby/object:Gem::Version
307
293
  version: 5.4.0
308
294
  - !ruby/object:Gem::Dependency
309
295
  name: rspec
310
296
  requirement: !ruby/object:Gem::Requirement
311
297
  requirements:
312
- - - ">="
313
- - !ruby/object:Gem::Version
314
- version: 3.5.0
315
298
  - - "~>"
316
299
  - !ruby/object:Gem::Version
317
300
  version: '3.5'
301
+ - - ">="
302
+ - !ruby/object:Gem::Version
303
+ version: 3.5.0
318
304
  type: :development
319
305
  prerelease: false
320
306
  version_requirements: !ruby/object:Gem::Requirement
321
307
  requirements:
322
- - - ">="
323
- - !ruby/object:Gem::Version
324
- version: 3.5.0
325
308
  - - "~>"
326
309
  - !ruby/object:Gem::Version
327
310
  version: '3.5'
311
+ - - ">="
312
+ - !ruby/object:Gem::Version
313
+ version: 3.5.0
328
314
  - !ruby/object:Gem::Dependency
329
315
  name: rubocop
330
316
  requirement: !ruby/object:Gem::Requirement
331
317
  requirements:
332
- - - ">="
318
+ - - "~>"
333
319
  - !ruby/object:Gem::Version
334
320
  version: 0.73.0
335
- - - "~>"
321
+ - - ">="
336
322
  - !ruby/object:Gem::Version
337
323
  version: 0.73.0
338
324
  type: :development
339
325
  prerelease: false
340
326
  version_requirements: !ruby/object:Gem::Requirement
341
327
  requirements:
342
- - - ">="
328
+ - - "~>"
343
329
  - !ruby/object:Gem::Version
344
330
  version: 0.73.0
345
- - - "~>"
331
+ - - ">="
346
332
  - !ruby/object:Gem::Version
347
333
  version: 0.73.0
348
334
  - !ruby/object:Gem::Dependency
@@ -369,42 +355,42 @@ dependencies:
369
355
  name: simplecov
370
356
  requirement: !ruby/object:Gem::Requirement
371
357
  requirements:
372
- - - ">="
373
- - !ruby/object:Gem::Version
374
- version: 0.12.0
375
358
  - - "~>"
376
359
  - !ruby/object:Gem::Version
377
- version: '0.12'
360
+ version: '0.16'
361
+ - - ">="
362
+ - !ruby/object:Gem::Version
363
+ version: 0.16.1
378
364
  type: :development
379
365
  prerelease: false
380
366
  version_requirements: !ruby/object:Gem::Requirement
381
367
  requirements:
382
- - - ">="
383
- - !ruby/object:Gem::Version
384
- version: 0.12.0
385
368
  - - "~>"
386
369
  - !ruby/object:Gem::Version
387
- version: '0.12'
370
+ version: '0.16'
371
+ - - ">="
372
+ - !ruby/object:Gem::Version
373
+ version: 0.16.1
388
374
  - !ruby/object:Gem::Dependency
389
375
  name: webmock
390
376
  requirement: !ruby/object:Gem::Requirement
391
377
  requirements:
392
- - - ">="
393
- - !ruby/object:Gem::Version
394
- version: 2.1.0
395
378
  - - "~>"
396
379
  - !ruby/object:Gem::Version
397
380
  version: '2.1'
381
+ - - ">="
382
+ - !ruby/object:Gem::Version
383
+ version: 2.1.0
398
384
  type: :development
399
385
  prerelease: false
400
386
  version_requirements: !ruby/object:Gem::Requirement
401
387
  requirements:
402
- - - ">="
403
- - !ruby/object:Gem::Version
404
- version: 2.1.0
405
388
  - - "~>"
406
389
  - !ruby/object:Gem::Version
407
390
  version: '2.1'
391
+ - - ">="
392
+ - !ruby/object:Gem::Version
393
+ version: 2.1.0
408
394
  description: Proxy for mocks API responses
409
395
  email: rubin.diego@gmail.com
410
396
  executables:
@@ -426,6 +412,7 @@ files:
426
412
  - lib/tshield/controllers/requests.rb
427
413
  - lib/tshield/controllers/sessions.rb
428
414
  - lib/tshield/counter.rb
415
+ - lib/tshield/errors.rb
429
416
  - lib/tshield/extensions/string_extensions.rb
430
417
  - lib/tshield/grpc.rb
431
418
  - lib/tshield/grpc/vcr.rb
@@ -452,6 +439,7 @@ files:
452
439
  - spec/tshield/options_spec.rb
453
440
  - spec/tshield/request_matching_spec.rb
454
441
  - spec/tshield/request_vcr_spec.rb
442
+ - spec/tshield/sessions_spec.rb
455
443
  - tshield.gemspec
456
444
  homepage: https://github.com/diegorubin/tshield
457
445
  licenses:
@@ -465,28 +453,30 @@ required_ruby_version: !ruby/object:Gem::Requirement
465
453
  requirements:
466
454
  - - ">="
467
455
  - !ruby/object:Gem::Version
468
- version: '2.3'
456
+ version: '2.4'
469
457
  required_rubygems_version: !ruby/object:Gem::Requirement
470
458
  requirements:
471
459
  - - ">="
472
460
  - !ruby/object:Gem::Version
473
461
  version: '0'
474
462
  requirements: []
475
- rubygems_version: 3.0.8
463
+ rubyforge_project:
464
+ rubygems_version: 2.6.14.4
476
465
  signing_key:
477
466
  specification_version: 4
478
467
  summary: Proxy for mocks API responses
479
468
  test_files:
480
469
  - spec/spec_helper.rb
481
- - spec/tshield/request_matching_spec.rb
482
- - spec/tshield/grpc_spec.rb
483
- - spec/tshield/configuration_spec.rb
484
470
  - spec/tshield/request_vcr_spec.rb
471
+ - spec/tshield/sessions_spec.rb
485
472
  - spec/tshield/controllers/requests_spec.rb
473
+ - spec/tshield/request_matching_spec.rb
474
+ - spec/tshield/grpc_spec.rb
486
475
  - spec/tshield/options_spec.rb
487
- - spec/tshield/after_filter_spec.rb
488
- - spec/tshield/fixtures/matching/example.json
489
476
  - spec/tshield/fixtures/proto/test_services_pb.rb
490
477
  - spec/tshield/fixtures/filters/example_filter.rb
478
+ - spec/tshield/fixtures/matching/example.json
491
479
  - spec/tshield/fixtures/config/tshield.yml
492
480
  - spec/tshield/fixtures/config/tshield-without-grpc.yml
481
+ - spec/tshield/configuration_spec.rb
482
+ - spec/tshield/after_filter_spec.rb