tshield 0.11.15.0 → 0.11.20.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
2
  SHA256:
3
- metadata.gz: e6ed543aa8bab1fff2c4d75d7479b7af0b3e2f06432330a697b6808048466228
4
- data.tar.gz: d14d318efb407d1a927cb2c5ac745081531c307f011d873965793c77ec0fd1b6
3
+ metadata.gz: 1880df41008713100967696d1bf1695b1e830851877f0ed5340f1f3f75cabd5e
4
+ data.tar.gz: 2a540ed72557ad9f9ba32073a5e98ed9b22812c1d0b96feaec00eeed35494386
5
5
  SHA512:
6
- metadata.gz: 7a46edf78463d75e523fd842397bae66e749dc58cd31d5fab8500dab706028850963d8e84f366706e0aef7807f45283d74ad785379f38b9b144d40b801681155
7
- data.tar.gz: 2eafe43d7acd092e04ff7b7dc1f2fcdd6dc7de2789a7f10e441bda3b72e838e13df86b8193ec7f3cbbd30a7f43a5880ce4c1629faf313ee3db3363695858aee0
6
+ metadata.gz: 7187004fa9e86545dcf81ebf70ea3059e04a0a8dcfe0143be3714ead2df3b9fc7d3e981641514f38192d69e57f826c85b14c46965f5d8171e3813e1bd9188574
7
+ data.tar.gz: d1e5e07ff88b722a944d78e22f1d3089214fb1ae9bed74c7bd42943700fc63bc096fc7916368513c83e660cb7936044dffd52e0f5c5b927d2618364a1411074e
data/Gemfile CHANGED
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  source 'https://rubygems.org'
4
- gem 'sinatra-cross_origin'
5
4
  gemspec
data/README.md CHANGED
@@ -13,9 +13,10 @@ 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
17
  * Lightweight
17
18
  * MIT license
18
-
19
+
19
20
  ## Table of Contents
20
21
 
21
22
  * [Basic Usage](#basic-usage)
@@ -26,7 +27,7 @@ TShield is an open source proxy for mocks API responses.
26
27
  * [Features](#features)
27
28
  * [Examples](#examples)
28
29
  * [Contributing](#contributing)
29
-
30
+
30
31
  ## Basic Usage
31
32
  ### Install
32
33
 
@@ -37,7 +38,7 @@ TShield is an open source proxy for mocks API responses.
37
38
  To run server execute this command
38
39
 
39
40
  tshield
40
-
41
+
41
42
  Default port is `4567`
42
43
 
43
44
  #### Command Line Options
@@ -102,7 +103,7 @@ To register stub into a session create an object with following attributes:
102
103
  * **session**: name of session.
103
104
  * **stubs**: an array with objects described above.
104
105
 
105
- ### Example of matching configuration
106
+ ### Example of HTTP matching configuration
106
107
 
107
108
  ```json
108
109
  [
@@ -163,7 +164,7 @@ To register stub into a session create an object with following attributes:
163
164
  ]
164
165
  ```
165
166
 
166
- ## Config options for VCR
167
+ ## Config options for HTTP VCR
167
168
  ```yaml
168
169
  request:
169
170
  timeout: 8
@@ -227,7 +228,7 @@ You can use TShield sessions to separate multiple scenarios for your mocks
227
228
  By default TShield save request/response into
228
229
 
229
230
  requests/<<domain_name>>/<<resource_with_param>>/<<http_verb>>/<<index_based.content and json>>
230
-
231
+
231
232
  If you start a session a folder with de **session_name** will be placed between **"requests/"** and **"<<domain_name>>"**
232
233
 
233
234
  ### Start TShield session
@@ -249,6 +250,45 @@ _DELETE_ to http://localhost:4567/sessions
249
250
  curl -X DELETE \
250
251
  http://localhost:4567/sessions
251
252
  ```
253
+ ### Append secondary TShield session
254
+ **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.**
255
+
256
+ _POST_ to http://localhost:4567/sessions?name=<<same_name>>
257
+
258
+ ```
259
+ curl -X POST \
260
+ 'http://localhost:4567/sessions/append?name=my_valid'
261
+ ```
262
+
263
+ ## [Experimental] Config options for gRPC
264
+
265
+ ```yaml
266
+ grpc:
267
+ port: 5678
268
+ proto_dir: 'proto'
269
+ services:
270
+ 'helloworld_services_pb':
271
+ module: 'Helloworld::Greeter'
272
+ hostname: '0.0.0.0:50051'
273
+ ```
274
+
275
+
276
+ ### Not Implemented Yet
277
+
278
+ - Matching
279
+
280
+ ### Configuration
281
+
282
+ First, generate ruby files from proto files. Use `grpc_tools_ruby_protoc`
283
+ present in the gem `grpc-tools`. Example:
284
+
285
+ `grpc_tools_ruby_protoc -I proto --ruby_out=proto --grpc_out=proto proto/<INPUT>.proto`
286
+
287
+ Call example in component_tests using [grpcurl](https://github.com/fullstorydev/grpcurl):
288
+
289
+ `grpcurl -plaintext -import-path component_tests/proto -proto helloworld.proto -d '{"name": "teste"}' localhost:5678 helloworld.Greeter/SayHello`
290
+
291
+ ### Using in VCR mode
252
292
 
253
293
  ## Custom controllers
254
294
 
data/Rakefile CHANGED
@@ -32,5 +32,5 @@ end
32
32
 
33
33
  task :server do
34
34
  $LOAD_PATH.unshift File.dirname('./lib/tshield.rb')
35
- exec 'bin/tshield'
35
+ Thread.new { exec 'bin/tshield' }
36
36
  end
@@ -5,14 +5,6 @@ require 'tshield/options'
5
5
  TShield::Options.init
6
6
 
7
7
  require 'tshield'
8
- tshield = Thread.new { TShield::Server.run! }
9
8
 
10
- configuration = TShield::Configuration.load_configuration
11
- (configuration.tcp_servers || []).each do |tcp_server|
12
- puts "initializing #{tcp_server['name']}"
13
- require "./servers/#{tcp_server['file']}"
14
- klass = Object.const_get(tcp_server['name'])
15
- Thread.new { klass.new.listen(tcp_server['port']) }
16
- end
17
-
18
- tshield.join
9
+ Thread.new { TShield::Grpc.run! }
10
+ TShield::Server.run!
@@ -0,0 +1,19 @@
1
+ ---
2
+ grpc:
3
+ port: 5678
4
+ proto_dir: 'proto'
5
+ services:
6
+ 'helloworld_services_pb':
7
+ module: 'Helloworld::Greeter'
8
+ hostname: '0.0.0.0:50051'
9
+ request:
10
+ timeout: 10
11
+ domains:
12
+ 'http://localhost:9090':
13
+ name: 'components'
14
+ skip_query_params:
15
+ - b
16
+ paths:
17
+ - /fake
18
+ - /users
19
+ - /resources
@@ -2,8 +2,8 @@
2
2
 
3
3
  require 'tshield/extensions/string_extensions'
4
4
  require 'tshield/options'
5
- require 'tshield/simple_tcp_server'
6
5
  require 'tshield/server'
6
+ require 'tshield/grpc'
7
7
 
8
8
  # TShield: API mocks for development and testing
9
9
  module TShield
@@ -103,6 +103,11 @@ module TShield
103
103
  session_path || '/sessions'
104
104
  end
105
105
 
106
+ def grpc
107
+ defaults = { 'port' => 5678, 'proto_dir' => 'proto', 'services' => {} }
108
+ defaults.merge(@grpc || {})
109
+ end
110
+
106
111
  def self.get_url_for_domain_by_path(path, config)
107
112
  config['paths'].select { |pattern| path =~ Regexp.new(pattern) }[0]
108
113
  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,7 +69,7 @@ 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
@@ -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
@@ -5,6 +5,14 @@ module StringExtensions
5
5
  def to_rack_name
6
6
  "HTTP_#{upcase.tr('-', '_')}"
7
7
  end
8
+
9
+ def underscore
10
+ gsub(/::/, '/')
11
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
12
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
13
+ .tr('-', '_')
14
+ .downcase
15
+ end
8
16
  end
9
17
 
10
18
  String.include StringExtensions
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: false
2
+
3
+ require 'grpc'
4
+
5
+ require 'tshield/configuration'
6
+ require 'tshield/grpc/vcr'
7
+
8
+ module TShield
9
+ module Grpc
10
+ module RequestHandler
11
+ include TShield::Grpc::VCR
12
+ def handler(method_name, request, parameters)
13
+ options = self.class.options
14
+ handler_in_vcr_mode(method_name, request, parameters, options)
15
+ end
16
+ end
17
+ def self.run!
18
+ @configuration = TShield::Configuration.singleton.grpc
19
+
20
+ lib_dir = File.join(Dir.pwd, @configuration['proto_dir'])
21
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
22
+
23
+ TShield.logger.info("loading proto files from #{lib_dir}")
24
+
25
+ bind = "0.0.0.0:#{@configuration['port']}"
26
+ TShield.logger.info("Starting gRPC server in #{bind}")
27
+
28
+ server = GRPC::RpcServer.new
29
+ server.add_http2_port(bind, :this_port_is_insecure)
30
+
31
+ services = load_services(@configuration['services'])
32
+ services.each do |class_service|
33
+ class_service.include RequestHandler
34
+ server.handle(class_service)
35
+ end
36
+
37
+ server.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT']) unless services.empty?
38
+ end
39
+
40
+ def self.load_services(services)
41
+ handlers = []
42
+ number_of_handlers = 0
43
+ services.each do |file, options|
44
+ require file
45
+
46
+ base = Object.const_get("#{options['module']}::Service")
47
+ number_of_handlers += 1
48
+
49
+ implementation = build_handler(base, base.rpc_descs, number_of_handlers, options)
50
+ handlers << implementation
51
+ end
52
+ handlers
53
+ end
54
+
55
+ def self.build_handler(base, descriptions, number_of_handlers, options)
56
+ handler = Class.new(base) do
57
+ class << self
58
+ attr_writer :options
59
+ attr_reader :options
60
+ end
61
+ descriptions.each do |service_name, description|
62
+ puts description
63
+ method_name = service_name.to_s.underscore.to_sym
64
+ define_method(method_name) do |request, parameters|
65
+ handler(__method__, request, parameters)
66
+ end
67
+ end
68
+ end
69
+ handler.options = options
70
+ TShield::Grpc.const_set "GrpcService#{number_of_handlers}", handler
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tshield/sessions'
4
+
5
+ module TShield
6
+ module Grpc
7
+ module VCR
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'}")
16
+ module_name = options['module']
17
+
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
24
+
25
+ TShield.logger.info("calling server to get response for #{request.to_json}")
26
+ client_class = Object.const_get("#{module_name}::Stub")
27
+ client_instance = client_class.new(options['hostname'], :this_channel_is_insecure)
28
+ response = client_instance.send(method_name, request)
29
+ save_request_and_response(path, request, response)
30
+ response
31
+ end
32
+
33
+ def saved_response(path)
34
+ response_file = File.join(path, 'response')
35
+ return false unless File.exist? response_file
36
+
37
+ content = JSON.parse File.open(response_file).read
38
+ response_class = File.open(File.join(path, 'response_class')).read.strip
39
+ Kernel.const_get(response_class).new(content)
40
+ end
41
+
42
+ def save_request_and_response(path, request, response)
43
+ save_request(path, request)
44
+ save_response(path, response)
45
+ end
46
+
47
+ def save_request(path, request)
48
+ file = File.open(File.join(path, 'original_request'), 'w')
49
+ file.puts request.to_json
50
+ file.close
51
+ end
52
+
53
+ def save_response(path, response)
54
+ file = File.open(File.join(path, 'response'), 'w')
55
+ file.puts response.to_json
56
+ file.close
57
+
58
+ response_class = File.open(File.join(path, 'response_class'), 'w')
59
+ response_class.puts response.class.to_s
60
+ response_class.close
61
+ end
62
+
63
+ def complete_path(module_name, method_name, request)
64
+ @session_name = (@session || {})[:name]
65
+ path = ['requests', 'grpc', @session_name, module_name, method_name.to_s, hexdigest(request)].compact
66
+ path
67
+ end
68
+
69
+ def create_destiny(module_name, method_name, request)
70
+ current_path = []
71
+
72
+ path = complete_path(module_name, method_name, request)
73
+ TShield.logger.info("using path #{path}")
74
+ path.each do |path|
75
+ current_path << path
76
+ destiny = File.join current_path
77
+ Dir.mkdir destiny unless File.exist? destiny
78
+ end
79
+ path
80
+ end
81
+
82
+ def hexdigest(request)
83
+ Digest::SHA1.hexdigest request.to_json
84
+ end
85
+ end
86
+ end
87
+ end
@@ -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)
@@ -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
@@ -57,7 +57,6 @@ module TShield
57
57
 
58
58
  def self.run!
59
59
  register_resources
60
- require 'byebug'
61
60
  super
62
61
  end
63
62
  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
@@ -5,7 +5,7 @@ module TShield
5
5
  class Version
6
6
  MAJOR = 0
7
7
  MINOR = 11
8
- PATCH = 15
8
+ PATCH = 20
9
9
  PRE = 0
10
10
 
11
11
  class << self
@@ -15,9 +15,4 @@ require 'webmock/rspec'
15
15
  require 'tshield/extensions/string_extensions'
16
16
 
17
17
  RSpec.configure do |config|
18
- config.before(:each) do
19
- allow(File).to receive(:join).and_return(
20
- 'spec/tshield/fixtures/config/tshield.yml'
21
- )
22
- end
23
18
  end
@@ -35,6 +35,12 @@ describe TShield::Configuration do
35
35
  )
36
36
  end
37
37
 
38
+ context 'on grpc configuration' do
39
+ it 'recover server port' do
40
+ expect(@configuration.grpc['port']).to(eq(5678))
41
+ end
42
+ end
43
+
38
44
  context 'on load filters' do
39
45
  it 'recover filters for a domain' do
40
46
  expect(@configuration.get_filters('example.org')).to eq([ExampleFilter])
@@ -72,4 +78,17 @@ describe TShield::Configuration do
72
78
  expect { TShield::Configuration.singleton }.to raise_error RuntimeError
73
79
  end
74
80
  end
81
+
82
+ context 'on config exists without grpc entry' do
83
+ before :each do
84
+ options_instance = double
85
+ allow(options_instance).to receive(:configuration_file)
86
+ .and_return('spec/tshield/fixtures/config/tshield-without-grpc.yml')
87
+ allow(TShield::Options).to receive(:instance).and_return(options_instance)
88
+ @configuration = TShield::Configuration.singleton
89
+ end
90
+ it 'should set default value for port' do
91
+ expect(@configuration.grpc).to eql('port' => 5678, 'proto_dir' => 'proto', 'services' => {})
92
+ end
93
+ end
75
94
  end
@@ -0,0 +1,17 @@
1
+ ---
2
+ request:
3
+ timeout: 0
4
+ domains:
5
+ 'example.org':
6
+ name: 'example.org'
7
+ filters:
8
+ - 'ExampleFilter'
9
+ paths:
10
+ - '/api/one'
11
+ - '/api/two'
12
+ skip_query_params:
13
+ - 'a'
14
+ 'example.com':
15
+ name: 'example.com'
16
+ paths:
17
+ - '/api/three'
@@ -1,4 +1,6 @@
1
1
  ---
2
+ grpc:
3
+ port: 5678
2
4
  request:
3
5
  timeout: 0
4
6
  domains:
@@ -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
  ]
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TestServices
4
+ class Service
5
+ def self.rpc_descs
6
+ { 'ServiceMethod' => {} }
7
+ end
8
+ end
9
+
10
+ class Stub
11
+ def initialize(attributes, options = {}); end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'tshield/grpc'
6
+
7
+ describe TShield::Grpc do
8
+ context 'on load services' do
9
+ before :each do
10
+ lib_dir = File.join(__dir__, 'fixtures/proto')
11
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ @services = {
14
+ 'test_services_pb' => { 'module' => 'TestServices', 'hostname' => '0.0.0.0:5678' }
15
+ }
16
+ end
17
+
18
+ it 'should implement a service from options' do
19
+ implementation = TShield::Grpc.load_services(@services).first
20
+ instance = implementation.new
21
+ expect(instance.respond_to?(:service_method)).to be_truthy
22
+ end
23
+ end
24
+ end
@@ -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
@@ -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
@@ -25,6 +25,8 @@ Gem::Specification.new do |s|
25
25
  s.required_ruby_version = '>= 2.3'
26
26
 
27
27
  s.add_dependency('byebug', '~> 11.0', '>= 11.0.1')
28
+ s.add_dependency('grpc', '~> 1.28', '>= 1.28.0')
29
+ s.add_dependency('grpc-tools', '~> 1.28', '>= 1.28.0')
28
30
  s.add_dependency('httparty', '~> 0.14', '>= 0.14.0')
29
31
  s.add_dependency('json', '~> 2.0', '>= 2.0')
30
32
  s.add_dependency('puma', '~> 4.3', '>= 4.3.3')
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.15.0
4
+ version: 0.11.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Diego Rubin
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-07-08 00:00:00.000000000 Z
12
+ date: 2020-07-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: byebug
@@ -31,6 +31,46 @@ dependencies:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
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
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '1.28'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 1.28.0
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.28'
54
+ - !ruby/object:Gem::Dependency
55
+ name: grpc-tools
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.28.0
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '1.28'
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: 1.28.0
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.28'
34
74
  - !ruby/object:Gem::Dependency
35
75
  name: httparty
36
76
  requirement: !ruby/object:Gem::Requirement
@@ -376,6 +416,7 @@ files:
376
416
  - README.md
377
417
  - Rakefile
378
418
  - bin/tshield
419
+ - config/tshield.yml
379
420
  - lib/tshield.rb
380
421
  - lib/tshield/after_filter.rb
381
422
  - lib/tshield/before_filter.rb
@@ -385,7 +426,10 @@ files:
385
426
  - lib/tshield/controllers/requests.rb
386
427
  - lib/tshield/controllers/sessions.rb
387
428
  - lib/tshield/counter.rb
429
+ - lib/tshield/errors.rb
388
430
  - lib/tshield/extensions/string_extensions.rb
431
+ - lib/tshield/grpc.rb
432
+ - lib/tshield/grpc/vcr.rb
389
433
  - lib/tshield/logger.rb
390
434
  - lib/tshield/matching/filters.rb
391
435
  - lib/tshield/options.rb
@@ -395,18 +439,21 @@ files:
395
439
  - lib/tshield/response.rb
396
440
  - lib/tshield/server.rb
397
441
  - lib/tshield/sessions.rb
398
- - lib/tshield/simple_tcp_server.rb
399
442
  - lib/tshield/version.rb
400
443
  - spec/spec_helper.rb
401
444
  - spec/tshield/after_filter_spec.rb
402
445
  - spec/tshield/configuration_spec.rb
403
446
  - spec/tshield/controllers/requests_spec.rb
447
+ - spec/tshield/fixtures/config/tshield-without-grpc.yml
404
448
  - spec/tshield/fixtures/config/tshield.yml
405
449
  - spec/tshield/fixtures/filters/example_filter.rb
406
450
  - spec/tshield/fixtures/matching/example.json
451
+ - spec/tshield/fixtures/proto/test_services_pb.rb
452
+ - spec/tshield/grpc_spec.rb
407
453
  - spec/tshield/options_spec.rb
408
454
  - spec/tshield/request_matching_spec.rb
409
455
  - spec/tshield/request_vcr_spec.rb
456
+ - spec/tshield/sessions_spec.rb
410
457
  - tshield.gemspec
411
458
  homepage: https://github.com/diegorubin/tshield
412
459
  licenses:
@@ -434,11 +481,15 @@ summary: Proxy for mocks API responses
434
481
  test_files:
435
482
  - spec/spec_helper.rb
436
483
  - spec/tshield/request_matching_spec.rb
484
+ - spec/tshield/grpc_spec.rb
437
485
  - spec/tshield/configuration_spec.rb
486
+ - spec/tshield/sessions_spec.rb
438
487
  - spec/tshield/request_vcr_spec.rb
439
488
  - spec/tshield/controllers/requests_spec.rb
440
489
  - spec/tshield/options_spec.rb
441
490
  - spec/tshield/after_filter_spec.rb
442
491
  - spec/tshield/fixtures/matching/example.json
492
+ - spec/tshield/fixtures/proto/test_services_pb.rb
443
493
  - spec/tshield/fixtures/filters/example_filter.rb
444
494
  - spec/tshield/fixtures/config/tshield.yml
495
+ - spec/tshield/fixtures/config/tshield-without-grpc.yml
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'socket'
4
-
5
- module TShield
6
- class SimpleTCPServer
7
- def initialize
8
- @running = true
9
- end
10
-
11
- def on_connect(_client)
12
- raise 'should implement method on_connect'
13
- end
14
-
15
- def close
16
- @running = false
17
- end
18
-
19
- def listen(port)
20
- puts "listening #{port}"
21
- @server = TCPServer.new(port)
22
- while @running
23
- client = @server.accept
24
- on_connect(client)
25
- end
26
- end
27
- end
28
- end