tshield 0.11.5.0 → 0.11.6.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/README.md +21 -5
- data/lib/tshield/configuration.rb +9 -5
- data/lib/tshield/controllers/helpers/session_helpers.rb +11 -1
- data/lib/tshield/controllers/requests.rb +7 -3
- data/lib/tshield/controllers/sessions.rb +1 -1
- data/lib/tshield/request.rb +3 -12
- data/lib/tshield/request_matching.rb +8 -0
- data/lib/tshield/request_vcr.rb +19 -23
- data/lib/tshield/version.rb +1 -1
- data/spec/spec_helper.rb +3 -0
- data/spec/tshield/fixtures/matching/example.json +17 -0
- data/spec/tshield/request_matching_spec.rb +25 -0
- data/spec/tshield/request_vcr_spec.rb +2 -1
- data/tshield.gemspec +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c640cd371996f18c70873cdb29f9ebddd56feec72a2a28f97ab12e8c2dea7c79
|
4
|
+
data.tar.gz: db3eafdb27d4b6c6aa7b5f3ccd8480f045f8629beaf61ac431e6d038d8bf3dd2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '08a7fa0ab19a76dfbd9f1c262836c5c6e67b2c5ea85db39271162ccce204291e08a27962327f2e95c5921f696c3cfb6cdc541d69eaa8f441af994d63c7246053'
|
7
|
+
data.tar.gz: b8c45e92156a2f1fcbd2016338eaa9aa424edecff1b6248bb11c0cabc747f58ff82012d6fd048dc548825b6b46c208b8ed0bbb2b96a27036c6ce9bdf4accf413
|
data/README.md
CHANGED
@@ -2,6 +2,7 @@ TShield
|
|
2
2
|
=======
|
3
3
|
|
4
4
|
[](https://travis-ci.org/diegorubin/tshield)
|
5
|
+
[](https://coveralls.io/github/diegorubin/tshield?branch=master)
|
5
6
|
[](https://app.sourcelevel.io/github/diegorubin/tshield)
|
6
7
|
[](https://gitter.im/diegorubin/tshield?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
7
8
|
[](https://badge.fury.io/rb/tshield)
|
@@ -15,6 +16,17 @@ TShield is an open source proxy for mocks API responses.
|
|
15
16
|
* Lightweight
|
16
17
|
* MIT license
|
17
18
|
|
19
|
+
#### Table of Contents:
|
20
|
+
|
21
|
+
* [Basic Usage](#basic-usage)
|
22
|
+
* [Config options for Pattern Matching](#config-options-for-pattern-matching)
|
23
|
+
* [Config options for VCR](#config-options-for-vcr)
|
24
|
+
* [Manage Sessions](#manage-sessions)
|
25
|
+
* [Custom controllers](#custom-controllers)
|
26
|
+
* [Features](#features)
|
27
|
+
* [Examples](#examples)
|
28
|
+
* [Contributing](#contributing)
|
29
|
+
|
18
30
|
## Basic Usage
|
19
31
|
### Install
|
20
32
|
|
@@ -26,7 +38,7 @@ To run server execute this command
|
|
26
38
|
|
27
39
|
tshield
|
28
40
|
|
29
|
-
Default port is
|
41
|
+
Default port is `4567`
|
30
42
|
|
31
43
|
#### Command Line Options
|
32
44
|
|
@@ -65,7 +77,7 @@ at least the following attributes:
|
|
65
77
|
|
66
78
|
* __method__: a http method.
|
67
79
|
* __path__: url path.
|
68
|
-
* __response__: object with response data.
|
80
|
+
* __response__: object with response data. Into session can be used an array of objects to return different responses like vcr mode. See example: [multiples_response.json](https://github.com/diegorubin/tshield/blob/master/component_tests/matching/examples/multiple_response.json)
|
69
81
|
|
70
82
|
Response must be contain the following attributes:
|
71
83
|
|
@@ -201,6 +213,7 @@ domains:
|
|
201
213
|
* **name**: Name to identify the domain in the generated files
|
202
214
|
* **headers**: github-issue #17
|
203
215
|
* **not_save_headers**: List of headers that should be ignored in generated file
|
216
|
+
* **skip_query_params**: List of query params that should be ignored in generated file
|
204
217
|
* **cache_request**: <<some_description>>
|
205
218
|
* **filters**: Implementation of before or after filters used in domain requests
|
206
219
|
* **excluded_headers**: <<some_description>>
|
@@ -265,9 +278,12 @@ end
|
|
265
278
|
Description of some tshield features can be found in the features directory.
|
266
279
|
This features files are used as base for the component tests.
|
267
280
|
|
268
|
-
##
|
269
|
-
#### Basic
|
281
|
+
## Examples
|
282
|
+
#### Basic example for a client app requesting an API
|
270
283
|
[examples/client-api-nodejs](examples/client-api-nodejs)
|
271
284
|
|
285
|
+
#### Basic example for component/acceptance test
|
286
|
+
**[WIP]**
|
287
|
+
|
272
288
|
## Contributing
|
273
|
-
[Hacking or Contributing to
|
289
|
+
[Hacking or Contributing to TShield](CONTRIBUTING.md)
|
@@ -31,6 +31,7 @@ module TShield
|
|
31
31
|
attr_reader :request
|
32
32
|
attr_reader :domains
|
33
33
|
attr_reader :tcp_servers
|
34
|
+
attr_reader :session_path
|
34
35
|
|
35
36
|
def initialize(attributes)
|
36
37
|
attributes.each { |key, value| instance_variable_set("@#{key}", value) }
|
@@ -57,9 +58,8 @@ module TShield
|
|
57
58
|
|
58
59
|
def get_domain_for(path)
|
59
60
|
domains.each do |url, config|
|
60
|
-
|
61
|
-
|
62
|
-
end
|
61
|
+
result = self.class.get_url_for_domain_by_path(path, config)
|
62
|
+
return url if result
|
63
63
|
end
|
64
64
|
nil
|
65
65
|
end
|
@@ -99,8 +99,12 @@ module TShield
|
|
99
99
|
domains[domain]['not_save_headers'] || []
|
100
100
|
end
|
101
101
|
|
102
|
-
def
|
103
|
-
|
102
|
+
def read_session_path
|
103
|
+
session_path || '/sessions'
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.get_url_for_domain_by_path(path, config)
|
107
|
+
config['paths'].select { |pattern| path =~ Regexp.new(pattern) }[0]
|
104
108
|
end
|
105
109
|
|
106
110
|
def self.read_configuration_file(config_path)
|
@@ -7,7 +7,17 @@ module TShield
|
|
7
7
|
module SessionHelpers
|
8
8
|
def self.current_session_name(request)
|
9
9
|
session = TShield::Sessions.current(request.ip)
|
10
|
-
session
|
10
|
+
session[:name] if session
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.current_session_call(request, path, method)
|
14
|
+
session = TShield::Sessions.current(request.ip)
|
15
|
+
session ? session[:counter].current(path, method) : 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.update_session_call(request, path, method)
|
19
|
+
session = TShield::Sessions.current(request.ip)
|
20
|
+
session[:counter].add(path, method) if session
|
11
21
|
end
|
12
22
|
end
|
13
23
|
end
|
@@ -59,12 +59,15 @@ module TShield
|
|
59
59
|
method = request.request_method
|
60
60
|
request_content_type = request.content_type
|
61
61
|
session_name = TShield::Controllers::Helpers::SessionHelpers.current_session_name(request)
|
62
|
+
session_call = TShield::Controllers::Helpers::SessionHelpers
|
63
|
+
.current_session_call(request, path, method)
|
62
64
|
|
63
65
|
options = {
|
64
66
|
method: method,
|
65
67
|
headers: Helpers.build_headers(request),
|
66
68
|
raw_query: request.env['QUERY_STRING'],
|
67
69
|
session: session_name,
|
70
|
+
call: session_call,
|
68
71
|
ip: request.ip
|
69
72
|
}
|
70
73
|
|
@@ -75,12 +78,12 @@ module TShield
|
|
75
78
|
replace: '')
|
76
79
|
options[:body] = result
|
77
80
|
end
|
78
|
-
api_response = TShield::RequestMatching.new(path, options).match_request
|
81
|
+
api_response = TShield::RequestMatching.new(path, options.clone).match_request
|
79
82
|
|
80
83
|
unless api_response
|
81
84
|
add_headers(headers, path)
|
82
85
|
|
83
|
-
api_response ||= TShield::RequestVCR.new(path, options).response
|
86
|
+
api_response ||= TShield::RequestVCR.new(path, options.clone).response
|
84
87
|
api_response.headers.reject! do |key, _v|
|
85
88
|
configuration.get_excluded_headers(domain(path)).include?(key)
|
86
89
|
end
|
@@ -89,8 +92,9 @@ module TShield
|
|
89
92
|
logger.info(
|
90
93
|
"original=#{api_response.original} method=#{method} path=#{path} "\
|
91
94
|
"content-type=#{request_content_type} "\
|
92
|
-
"session=#{session_name}"
|
95
|
+
"session=#{session_name} call=#{session_call}"
|
93
96
|
)
|
97
|
+
TShield::Controllers::Helpers::SessionHelpers.update_session_call(request, path, method)
|
94
98
|
|
95
99
|
status api_response.status
|
96
100
|
headers api_response.headers
|
@@ -10,7 +10,7 @@ module TShield
|
|
10
10
|
# Actions to handle sessions
|
11
11
|
module Sessions
|
12
12
|
def self.registered(app)
|
13
|
-
session_path = TShield::Configuration.singleton.
|
13
|
+
session_path = TShield::Configuration.singleton.read_session_path
|
14
14
|
register_get(app, session_path)
|
15
15
|
register_post(app, session_path)
|
16
16
|
register_delete(app, session_path)
|
data/lib/tshield/request.rb
CHANGED
@@ -6,7 +6,6 @@ module TShield
|
|
6
6
|
# Base of request mock methods
|
7
7
|
class Request
|
8
8
|
attr_reader :configuration
|
9
|
-
attr_writer :content_idx
|
10
9
|
|
11
10
|
def initialize
|
12
11
|
@configuration = TShield::Configuration.singleton
|
@@ -14,15 +13,11 @@ module TShield
|
|
14
13
|
|
15
14
|
protected
|
16
15
|
|
17
|
-
def current_session
|
18
|
-
TShield::Sessions.current(@options[:ip])
|
19
|
-
end
|
20
|
-
|
21
16
|
def session_destiny(request_path)
|
22
|
-
session =
|
17
|
+
session = @options[:session]
|
23
18
|
return request_path unless session
|
24
19
|
|
25
|
-
request_path = File.join(request_path, session
|
20
|
+
request_path = File.join(request_path, session)
|
26
21
|
Dir.mkdir(request_path) unless File.exist?(request_path)
|
27
22
|
request_path
|
28
23
|
end
|
@@ -52,7 +47,7 @@ module TShield
|
|
52
47
|
method_path = File.join(path_path, method)
|
53
48
|
Dir.mkdir(method_path) unless File.exist?(method_path)
|
54
49
|
|
55
|
-
File.join(method_path, @
|
50
|
+
File.join(method_path, @options[:call].to_s)
|
56
51
|
end
|
57
52
|
|
58
53
|
def clear_path(path)
|
@@ -67,9 +62,5 @@ module TShield
|
|
67
62
|
|
68
63
|
[url_path, cleared_params].join('?')
|
69
64
|
end
|
70
|
-
|
71
|
-
def method
|
72
|
-
@options[:method].downcase
|
73
|
-
end
|
74
65
|
end
|
75
66
|
end
|
@@ -27,11 +27,19 @@ module TShield
|
|
27
27
|
@matched = find_stub(self.class.stubs)
|
28
28
|
return unless matched
|
29
29
|
|
30
|
+
@matched = current_response
|
31
|
+
|
30
32
|
TShield::Response.new(self.class.read_body(matched['body']),
|
31
33
|
matched['headers'],
|
32
34
|
matched['status'])
|
33
35
|
end
|
34
36
|
|
37
|
+
def current_response
|
38
|
+
return matched[@options[:call]] if matched.is_a? Array
|
39
|
+
|
40
|
+
matched
|
41
|
+
end
|
42
|
+
|
35
43
|
class << self
|
36
44
|
attr_reader :stubs
|
37
45
|
|
data/lib/tshield/request_vcr.rb
CHANGED
@@ -14,8 +14,6 @@ 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 :response
|
18
|
-
|
19
17
|
def initialize(path, options = {})
|
20
18
|
super()
|
21
19
|
@path = path
|
@@ -28,33 +26,35 @@ module TShield
|
|
28
26
|
end
|
29
27
|
|
30
28
|
def request
|
31
|
-
|
32
|
-
|
33
|
-
end
|
29
|
+
raw_query = @options[:raw_query]
|
30
|
+
@path = "#{@path}?#{raw_query}" unless !raw_query || raw_query.empty?
|
34
31
|
|
35
32
|
@url = "#{domain}#{@path}"
|
36
33
|
|
37
34
|
if exists
|
38
|
-
|
39
|
-
|
35
|
+
response.original = false
|
36
|
+
response
|
40
37
|
else
|
41
|
-
@method = method
|
42
38
|
configuration.get_before_filters(domain).each do |filter|
|
43
|
-
|
39
|
+
_method, @url, @options = filter.new.filter(method, @url, @options)
|
44
40
|
end
|
45
41
|
|
46
|
-
raw = HTTParty.send(
|
42
|
+
raw = HTTParty.send(method.to_s, @url, @options)
|
47
43
|
|
48
44
|
configuration.get_after_filters(domain).each do |filter|
|
49
45
|
raw = filter.new.filter(raw)
|
50
46
|
end
|
51
47
|
|
52
|
-
|
53
|
-
|
54
|
-
|
48
|
+
original_response = save(raw)
|
49
|
+
original_response.original = true
|
50
|
+
original_response
|
55
51
|
end
|
56
|
-
|
57
|
-
|
52
|
+
end
|
53
|
+
|
54
|
+
def response
|
55
|
+
@response ||= TShield::Response.new(saved_content['body'],
|
56
|
+
saved_content['headers'] || [],
|
57
|
+
saved_content['status'] || 200)
|
58
58
|
end
|
59
59
|
|
60
60
|
private
|
@@ -67,6 +67,10 @@ module TShield
|
|
67
67
|
@name ||= configuration.get_name(domain)
|
68
68
|
end
|
69
69
|
|
70
|
+
def method
|
71
|
+
@method ||= @options[:method].downcase
|
72
|
+
end
|
73
|
+
|
70
74
|
def save(raw_response)
|
71
75
|
headers = {}
|
72
76
|
raw_response.headers.each do |k, v|
|
@@ -93,8 +97,6 @@ module TShield
|
|
93
97
|
end
|
94
98
|
|
95
99
|
def file_exists
|
96
|
-
session = current_session
|
97
|
-
@content_idx = session ? session[:counter].current(@path, method) : 0
|
98
100
|
File.exist?(content_destiny)
|
99
101
|
end
|
100
102
|
|
@@ -102,12 +104,6 @@ module TShield
|
|
102
104
|
file_exists && configuration.cache_request?(domain)
|
103
105
|
end
|
104
106
|
|
105
|
-
def current_response
|
106
|
-
TShield::Response.new(saved_content['body'],
|
107
|
-
saved_content['headers'] || [],
|
108
|
-
saved_content['status'] || 200)
|
109
|
-
end
|
110
|
-
|
111
107
|
def key
|
112
108
|
@key ||= Digest::SHA1.hexdigest "#{@url}|#{method}"
|
113
109
|
end
|
data/lib/tshield/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -53,6 +53,23 @@
|
|
53
53
|
"body": "post content"
|
54
54
|
}
|
55
55
|
},
|
56
|
+
{
|
57
|
+
"method": "GET",
|
58
|
+
"path": "/matching/twice",
|
59
|
+
"response": [{
|
60
|
+
"status": 200,
|
61
|
+
"headers": {
|
62
|
+
"try": 1
|
63
|
+
},
|
64
|
+
"body": "first call"
|
65
|
+
}, {
|
66
|
+
"status": 201,
|
67
|
+
"headers": {
|
68
|
+
"try": 2
|
69
|
+
},
|
70
|
+
"body": "second call"
|
71
|
+
}]
|
72
|
+
},
|
56
73
|
{
|
57
74
|
"session": "a-session",
|
58
75
|
"stubs": [{
|
@@ -128,6 +128,31 @@ describe TShield::RequestMatching do
|
|
128
128
|
expect(@response.status).to eql(200)
|
129
129
|
end
|
130
130
|
end
|
131
|
+
context 'on match have multiples responses' do
|
132
|
+
before :each do
|
133
|
+
@responses = []
|
134
|
+
2.times.each do |time|
|
135
|
+
@responses << TShield::RequestMatching
|
136
|
+
.new('/matching/twice',
|
137
|
+
method: 'GET',
|
138
|
+
call: time).match_request
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'should return response object in first call' do
|
143
|
+
response = @responses[0]
|
144
|
+
expect(response.body).to eql('first call')
|
145
|
+
expect(response.headers).to eql('try' => 1)
|
146
|
+
expect(response.status).to eql(200)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'should return response object in second call' do
|
150
|
+
response = @responses[1]
|
151
|
+
expect(response.body).to eql('second call')
|
152
|
+
expect(response.headers).to eql('try' => 2)
|
153
|
+
expect(response.status).to eql(201)
|
154
|
+
end
|
155
|
+
end
|
131
156
|
context 'on not match' do
|
132
157
|
before :each do
|
133
158
|
@request_matching = TShield::RequestMatching.new('/')
|
data/tshield.gemspec
CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
|
|
29
29
|
s.add_dependency('httparty', '~> 0.14', '>= 0.14.0')
|
30
30
|
s.add_dependency('json', '~> 2.0', '>= 2.0')
|
31
31
|
s.add_dependency('sinatra', '~> 1.4', '>= 1.4.0')
|
32
|
+
s.add_development_dependency('coveralls')
|
32
33
|
s.add_development_dependency('cucumber', '~> 3.1', '>= 3.1.2')
|
33
34
|
s.add_development_dependency('guard', '~> 2.15', '>= 2.15.0')
|
34
35
|
s.add_development_dependency('guard-rspec', '~> 4.7', '>= 4.7.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.6.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: 2019-09-
|
12
|
+
date: 2019-09-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: byebug
|
@@ -111,6 +111,20 @@ dependencies:
|
|
111
111
|
- - "~>"
|
112
112
|
- !ruby/object:Gem::Version
|
113
113
|
version: '1.4'
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: coveralls
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
114
128
|
- !ruby/object:Gem::Dependency
|
115
129
|
name: cucumber
|
116
130
|
requirement: !ruby/object:Gem::Requirement
|