tshield 0.11.17.0 → 0.13.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
- 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