tshield 0.11.15.0 → 0.11.20.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 +4 -4
- data/Gemfile +0 -1
- data/README.md +46 -6
- data/Rakefile +1 -1
- data/bin/tshield +2 -10
- data/config/tshield.yml +19 -0
- data/lib/tshield.rb +1 -1
- data/lib/tshield/configuration.rb +5 -0
- data/lib/tshield/controllers/helpers/session_helpers.rb +5 -0
- data/lib/tshield/controllers/requests.rb +3 -1
- data/lib/tshield/controllers/sessions.rb +3 -0
- data/lib/tshield/errors.rb +4 -0
- data/lib/tshield/extensions/string_extensions.rb +8 -0
- data/lib/tshield/grpc.rb +73 -0
- data/lib/tshield/grpc/vcr.rb +87 -0
- data/lib/tshield/matching/filters.rb +8 -0
- data/lib/tshield/request.rb +8 -8
- data/lib/tshield/request_vcr.rb +34 -21
- data/lib/tshield/server.rb +0 -1
- data/lib/tshield/sessions.rb +20 -2
- data/lib/tshield/version.rb +1 -1
- data/spec/spec_helper.rb +0 -5
- data/spec/tshield/configuration_spec.rb +19 -0
- data/spec/tshield/fixtures/config/tshield-without-grpc.yml +17 -0
- data/spec/tshield/fixtures/config/tshield.yml +2 -0
- data/spec/tshield/fixtures/matching/example.json +12 -0
- data/spec/tshield/fixtures/proto/test_services_pb.rb +13 -0
- data/spec/tshield/grpc_spec.rb +24 -0
- data/spec/tshield/request_matching_spec.rb +12 -0
- data/spec/tshield/sessions_spec.rb +18 -0
- data/tshield.gemspec +2 -0
- metadata +54 -3
- data/lib/tshield/simple_tcp_server.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1880df41008713100967696d1bf1695b1e830851877f0ed5340f1f3f75cabd5e
|
4
|
+
data.tar.gz: 2a540ed72557ad9f9ba32073a5e98ed9b22812c1d0b96feaec00eeed35494386
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7187004fa9e86545dcf81ebf70ea3059e04a0a8dcfe0143be3714ead2df3b9fc7d3e981641514f38192d69e57f826c85b14c46965f5d8171e3813e1bd9188574
|
7
|
+
data.tar.gz: d1e5e07ff88b722a944d78e22f1d3089214fb1ae9bed74c7bd42943700fc63bc096fc7916368513c83e660cb7936044dffd52e0f5c5b927d2618364a1411074e
|
data/Gemfile
CHANGED
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
data/bin/tshield
CHANGED
@@ -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
|
-
|
11
|
-
|
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!
|
data/config/tshield.yml
ADDED
@@ -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
|
data/lib/tshield.rb
CHANGED
@@ -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).
|
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
|
@@ -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
|
data/lib/tshield/grpc.rb
ADDED
@@ -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
|
|
data/lib/tshield/request.rb
CHANGED
@@ -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)
|
data/lib/tshield/request_vcr.rb
CHANGED
@@ -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
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
52
|
+
@vcr_response = original_response
|
47
53
|
end
|
48
54
|
|
49
55
|
configuration.get_after_filters(domain).each do |filter|
|
50
|
-
|
56
|
+
@vcr_response = filter.new.filter(@vcr_response)
|
51
57
|
end
|
52
|
-
resp
|
53
58
|
end
|
54
59
|
|
55
|
-
def response
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
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
|
105
|
-
|
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
|
data/lib/tshield/server.rb
CHANGED
data/lib/tshield/sessions.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
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
|
-
|
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
|
data/lib/tshield/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -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'
|
@@ -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,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
|
data/tshield.gemspec
CHANGED
@@ -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.
|
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-
|
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
|