tshield 0.11.5.0 → 0.11.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/diegorubin/tshield.svg)](https://travis-ci.org/diegorubin/tshield)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/github/diegorubin/tshield/badge.svg?branch=master)](https://coveralls.io/github/diegorubin/tshield?branch=master)
|
5
6
|
[![SourceLevel](https://app.sourcelevel.io/github/diegorubin/tshield.svg)](https://app.sourcelevel.io/github/diegorubin/tshield)
|
6
7
|
[![Join the chat at https://gitter.im/diegorubin/tshield](https://badges.gitter.im/diegorubin/tshield.svg)](https://gitter.im/diegorubin/tshield?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
7
8
|
[![Gem Version](https://badge.fury.io/rb/tshield.svg)](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
|