tshield 0.11.15.0 → 0.11.16.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
2
  SHA256:
3
- metadata.gz: e6ed543aa8bab1fff2c4d75d7479b7af0b3e2f06432330a697b6808048466228
4
- data.tar.gz: d14d318efb407d1a927cb2c5ac745081531c307f011d873965793c77ec0fd1b6
3
+ metadata.gz: 6430dfd31f893ec3e51fc02ad401ea29f9dbcfa09c9b34a30ef4e23b7b9a3216
4
+ data.tar.gz: 1f021ab7cc8688ee6ab077140d34dd239a76a578c0c738b639a974d36d91d48d
5
5
  SHA512:
6
- metadata.gz: 7a46edf78463d75e523fd842397bae66e749dc58cd31d5fab8500dab706028850963d8e84f366706e0aef7807f45283d74ad785379f38b9b144d40b801681155
7
- data.tar.gz: 2eafe43d7acd092e04ff7b7dc1f2fcdd6dc7de2789a7f10e441bda3b72e838e13df86b8193ec7f3cbbd30a7f43a5880ce4c1629faf313ee3db3363695858aee0
6
+ metadata.gz: b86bd18dc24834098e199e179d33a92b74b29a55fa495685e2939196c6f4be9558a324ee7b63590363bf3c0ec5deb512073c172ac828a2eea6f19f3ed842d77d
7
+ data.tar.gz: 597db12ad099b8024841fd6f6e9d5b9eb2954750d617382c95654a930273558b44c18fc4f8a3ea67b3d9d66a7820b1dc2ddfe462b2eac591de67683db3c213f3
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,36 @@ _DELETE_ to http://localhost:4567/sessions
249
250
  curl -X DELETE \
250
251
  http://localhost:4567/sessions
251
252
  ```
253
+ ## [Experimental] Config options for gRPC
254
+
255
+ ```yaml
256
+ grpc:
257
+ port: 5678
258
+ proto_dir: 'proto'
259
+ services:
260
+ 'helloworld_services_pb':
261
+ module: 'Helloworld::Greeter'
262
+ hostname: '0.0.0.0:50051'
263
+ ```
264
+
265
+
266
+ ### Not Implemented Yet
267
+
268
+ - Sessions
269
+ - Matching
270
+
271
+ ### Configuration
272
+
273
+ First, generate ruby files from proto files. Use `grpc_tools_ruby_protoc`
274
+ present in the gem `grpc-tools`. Example:
275
+
276
+ `grpc_tools_ruby_protoc -I proto --ruby_out=proto --grpc_out=proto proto/<INPUT>.proto`
277
+
278
+ Call example in component_tests using [grpcurl](https://github.com/fullstorydev/grpcurl):
279
+
280
+ `grpcurl -plaintext -import-path component_tests/proto -proto helloworld.proto -d '{"name": "teste"}' localhost:5678 helloworld.Greeter/SayHello`
281
+
282
+ ### Using in VCR mode
252
283
 
253
284
  ## Custom controllers
254
285
 
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,5 @@ require 'tshield/options'
5
5
  TShield::Options.init
6
6
 
7
7
  require 'tshield'
8
- tshield = Thread.new { TShield::Server.run! }
9
-
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
8
+ Thread.new { TShield::Server.run! }
9
+ TShield::Grpc.run!
@@ -0,0 +1,18 @@
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
+ - /users
18
+ - /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
@@ -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/sessions'
7
+ require 'tshield/grpc/vcr'
8
+
9
+ module TShield
10
+ module Grpc
11
+ module RequestHandler
12
+ include TShield::Grpc::VCR
13
+ def handler(method_name, request)
14
+ options = self.class.options
15
+ handler_in_vcr_mode(method_name, request, options)
16
+ end
17
+ end
18
+ def self.run!
19
+ @configuration = TShield::Configuration.singleton.grpc
20
+
21
+ lib_dir = File.join(Dir.pwd, @configuration['proto_dir'])
22
+ $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
23
+
24
+ TShield.logger.info("loading proto files from #{lib_dir}")
25
+
26
+ bind = "0.0.0.0:#{@configuration['port']}"
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, _unused_call|
65
+ handler(__method__, request)
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,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TShield
4
+ module Grpc
5
+ module VCR
6
+ def handler_in_vcr_mode(method_name, request, options)
7
+ module_name = options['module']
8
+
9
+ response = saved_response(module_name, method_name, request)
10
+ return response if response
11
+
12
+ client_class = Object.const_get("#{module_name}::Stub")
13
+ client_instance = client_class.new(options['hostname'], :this_channel_is_insecure)
14
+ response = client_instance.send(method_name, request)
15
+ save_request_and_response(request, response)
16
+ response
17
+ end
18
+
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')
22
+ return false unless File.exist? response_file
23
+
24
+ content = JSON.parse File.open(response_file).read
25
+ response_class = File.open(File.join(@complete_path, 'response_class')).read.strip
26
+ Kernel.const_get(response_class).new(content)
27
+ end
28
+
29
+ def save_request_and_response(request, response)
30
+ save_request(request)
31
+ save_response(response)
32
+ end
33
+
34
+ def save_request(request)
35
+ file = File.open(File.join(@complete_path, 'original_request'), 'w')
36
+ file.puts request.to_json
37
+ file.close
38
+ end
39
+
40
+ def save_response(response)
41
+ file = File.open(File.join(@complete_path, 'response'), 'w')
42
+ file.puts response.to_json
43
+ file.close
44
+
45
+ response_class = File.open(File.join(@complete_path, 'response_class'), 'w')
46
+ response_class.puts response.class.to_s
47
+ response_class.close
48
+ end
49
+
50
+ 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)]
54
+ end
55
+
56
+ def create_destiny(module_name, method_name, request)
57
+ current_path = []
58
+ complete_path(module_name, method_name, request).each do |path|
59
+ current_path << path
60
+ destiny = File.join current_path
61
+ Dir.mkdir destiny unless File.exist? destiny
62
+ end
63
+ end
64
+
65
+ def hexdigest(request)
66
+ Digest::SHA1.hexdigest request.to_json
67
+ end
68
+ end
69
+ end
70
+ end
@@ -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
@@ -5,7 +5,7 @@ module TShield
5
5
  class Version
6
6
  MAJOR = 0
7
7
  MINOR = 11
8
- PATCH = 15
8
+ PATCH = 16
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:
@@ -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
@@ -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.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Diego Rubin
@@ -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
@@ -386,6 +427,8 @@ files:
386
427
  - lib/tshield/controllers/sessions.rb
387
428
  - lib/tshield/counter.rb
388
429
  - lib/tshield/extensions/string_extensions.rb
430
+ - lib/tshield/grpc.rb
431
+ - lib/tshield/grpc/vcr.rb
389
432
  - lib/tshield/logger.rb
390
433
  - lib/tshield/matching/filters.rb
391
434
  - lib/tshield/options.rb
@@ -395,15 +438,17 @@ files:
395
438
  - lib/tshield/response.rb
396
439
  - lib/tshield/server.rb
397
440
  - lib/tshield/sessions.rb
398
- - lib/tshield/simple_tcp_server.rb
399
441
  - lib/tshield/version.rb
400
442
  - spec/spec_helper.rb
401
443
  - spec/tshield/after_filter_spec.rb
402
444
  - spec/tshield/configuration_spec.rb
403
445
  - spec/tshield/controllers/requests_spec.rb
446
+ - spec/tshield/fixtures/config/tshield-without-grpc.yml
404
447
  - spec/tshield/fixtures/config/tshield.yml
405
448
  - spec/tshield/fixtures/filters/example_filter.rb
406
449
  - spec/tshield/fixtures/matching/example.json
450
+ - spec/tshield/fixtures/proto/test_services_pb.rb
451
+ - spec/tshield/grpc_spec.rb
407
452
  - spec/tshield/options_spec.rb
408
453
  - spec/tshield/request_matching_spec.rb
409
454
  - spec/tshield/request_vcr_spec.rb
@@ -434,11 +479,14 @@ summary: Proxy for mocks API responses
434
479
  test_files:
435
480
  - spec/spec_helper.rb
436
481
  - spec/tshield/request_matching_spec.rb
482
+ - spec/tshield/grpc_spec.rb
437
483
  - spec/tshield/configuration_spec.rb
438
484
  - spec/tshield/request_vcr_spec.rb
439
485
  - spec/tshield/controllers/requests_spec.rb
440
486
  - spec/tshield/options_spec.rb
441
487
  - spec/tshield/after_filter_spec.rb
442
488
  - spec/tshield/fixtures/matching/example.json
489
+ - spec/tshield/fixtures/proto/test_services_pb.rb
443
490
  - spec/tshield/fixtures/filters/example_filter.rb
444
491
  - spec/tshield/fixtures/config/tshield.yml
492
+ - 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