tshield 0.10.0.0 → 0.11.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tshield/request'
4
+
5
+ DEFAULT_SESSION = 'no-session'
6
+
7
+ module TShield
8
+ # Class to check request matching
9
+ class RequestMatching
10
+ attr_reader :matched
11
+
12
+ def initialize(path, options = {})
13
+ super()
14
+ @path = path
15
+ @options = options
16
+ @options[:session] ||= DEFAULT_SESSION
17
+ @options[:method] ||= 'GET'
18
+
19
+ klass = self.class
20
+ klass.load_stubs unless klass.stubs
21
+ end
22
+
23
+ def match_request
24
+ @matched = find_stub
25
+ return unless matched
26
+
27
+ TShield::Response.new(matched['body'],
28
+ matched['headers'],
29
+ matched['status'])
30
+ end
31
+
32
+ private
33
+
34
+ def find_stub
35
+ stubs = self.class.stubs
36
+ result = filter_stubs(stubs[@options[:session]])
37
+ return result if result
38
+
39
+ filter_stubs(stubs[DEFAULT_SESSION]) unless @options[:session] == DEFAULT_SESSION
40
+ end
41
+
42
+ def filter_by_method(stubs)
43
+ stubs.select { |stub| stub['method'] == @options[:method] }
44
+ end
45
+
46
+ def filter_by_headers(stubs)
47
+ stubs.select { |stub| self.class.include_headers(stub['headers'], @options[:headers]) }
48
+ end
49
+
50
+ def filter_by_query(stubs)
51
+ stubs.select { |stub| self.class.include_query(stub['query'], @options[:raw_query] || '') }
52
+ end
53
+
54
+ def filter_stubs(stubs)
55
+ result = filter_by_query(filter_by_headers(filter_by_method(stubs[@path] || [])))
56
+ result[0]['response'] unless result.empty?
57
+ end
58
+
59
+ class << self
60
+ attr_reader :stubs
61
+
62
+ def load_stubs
63
+ @stubs = {}
64
+ Dir.glob('matching/**/*.json').each do |entry|
65
+ load_stub(entry)
66
+ end
67
+ end
68
+
69
+ def load_stub(file)
70
+ content = JSON.parse File.open(file).read
71
+ content.each do |stub|
72
+ stub_session_name = init_stub_session(stub)
73
+
74
+ if stub['stubs']
75
+ load_items(stub['stubs'] || [], stub_session_name)
76
+ else
77
+ load_item(stub, stub_session_name)
78
+ end
79
+ end
80
+ end
81
+
82
+ def init_stub_session(stub)
83
+ stub_session_name = stub['session'] || DEFAULT_SESSION
84
+ stubs[stub_session_name] ||= {}
85
+ stub_session_name
86
+ end
87
+
88
+ def load_items(items, session_name)
89
+ items.each { |item| load_item(item, session_name) }
90
+ end
91
+
92
+ def load_item(item, session_name)
93
+ stubs[session_name][item['path']] ||= []
94
+ stubs[session_name][item['path']] << item
95
+ end
96
+
97
+ def include_headers(stub_headers, request_headers)
98
+ request_headers ||= {}
99
+ stub_headers ||= {}
100
+ result = stub_headers.reject { |key, value| request_headers[key.to_rack_name] == value }
101
+ result.empty? || stub_headers.empty?
102
+ end
103
+
104
+ def include_query(stub_query, raw_query)
105
+ request_query = build_query_hash(raw_query)
106
+ stub_query ||= {}
107
+ result = stub_query.reject { |key, value| request_query[key] == value.to_s }
108
+ result.empty? || stub_query.empty?
109
+ end
110
+
111
+ def build_query_hash(raw_query)
112
+ params = {}
113
+ raw_query.split('&').each do |query|
114
+ key, value = query.split('=')
115
+ params[key] = value
116
+ end
117
+
118
+ params
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+ require 'json'
5
+ require 'byebug'
6
+
7
+ require 'digest/sha1'
8
+
9
+ require 'tshield/configuration'
10
+ require 'tshield/options'
11
+ require 'tshield/request'
12
+ require 'tshield/response'
13
+
14
+ module TShield
15
+ # Module to write and read saved responses
16
+ class RequestVCR < TShield::Request
17
+ attr_reader :response
18
+
19
+ def initialize(path, options = {})
20
+ super()
21
+ @path = path
22
+ @options = options
23
+
24
+ request_configuration = configuration.request
25
+ @options[:timeout] = request_configuration['timeout']
26
+ @options[:verify] = request_configuration['verify_ssl']
27
+ request
28
+ end
29
+
30
+ def request
31
+ unless @options[:raw_query].nil? || @options[:raw_query].empty?
32
+ @path = "#{@path}?#{@options[:raw_query]}"
33
+ end
34
+
35
+ @url = "#{domain}#{@path}"
36
+
37
+ if exists
38
+ @response = current_response
39
+ @response.original = false
40
+ else
41
+ @method = method
42
+ configuration.get_before_filters(domain).each do |filter|
43
+ @method, @url, @options = filter.new.filter(@method, @url, @options)
44
+ end
45
+
46
+ raw = HTTParty.send(@method.to_s, @url, @options)
47
+
48
+ configuration.get_after_filters(domain).each do |filter|
49
+ raw = filter.new.filter(raw)
50
+ end
51
+
52
+ @response = save(raw)
53
+
54
+ @response.original = true
55
+ end
56
+ current_session[:counter].add(@path, method) if current_session
57
+ @response
58
+ end
59
+
60
+ private
61
+
62
+ def domain
63
+ @domain ||= configuration.get_domain_for(@path)
64
+ end
65
+
66
+ def name
67
+ @name ||= configuration.get_name(domain)
68
+ end
69
+
70
+ def save(raw_response)
71
+ headers = {}
72
+ raw_response.headers.each do |k, v|
73
+ headers[k] = v unless configuration.not_save_headers(domain).include? k
74
+ end
75
+
76
+ content = {
77
+ body: raw_response.body,
78
+ status: raw_response.code,
79
+ headers: headers
80
+ }
81
+
82
+ write(content)
83
+
84
+ TShield::Response.new(raw_response.body, headers, raw_response.code)
85
+ end
86
+
87
+ def saved_content
88
+ return @saved_content if @saved_content
89
+
90
+ @saved_content = JSON.parse(File.open(headers_destiny).read)
91
+ @saved_content['body'] = File.open(content_destiny).read unless @saved_content['body']
92
+ @saved_content
93
+ end
94
+
95
+ def file_exists
96
+ session = current_session
97
+ @content_idx = session ? session[:counter].current(@path, method) : 0
98
+ File.exist?(content_destiny)
99
+ end
100
+
101
+ def exists
102
+ file_exists && configuration.cache_request?(domain)
103
+ end
104
+
105
+ def current_response
106
+ TShield::Response.new(saved_content['body'],
107
+ saved_content['headers'] || [],
108
+ saved_content['status'] || 200)
109
+ end
110
+
111
+ def key
112
+ @key ||= Digest::SHA1.hexdigest "#{@url}|#{method}"
113
+ end
114
+
115
+ def write(content)
116
+ content_file = File.open(content_destiny, 'w')
117
+ content_file.write(content[:body])
118
+ content_file.close
119
+
120
+ body = content.delete :body
121
+
122
+ headers_file = File.open(headers_destiny, 'w')
123
+ headers_file.write(JSON.pretty_generate(content))
124
+ headers_file.close
125
+
126
+ content[:body] = body
127
+ end
128
+
129
+ def safe_dir(url)
130
+ if url.size > 225
131
+ path = url.gsub(/(\?.*)/, '')
132
+ params = Digest::SHA1.hexdigest Regexp.last_match(1)
133
+ "#{path.gsub(%r{/}, '-').gsub(/^-/, '')}?#{params}"
134
+ else
135
+ url.gsub(%r{/}, '-').gsub(/^-/, '')
136
+ end
137
+ end
138
+ end
139
+ end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TShield
4
+ # Response Object
4
5
  class Response
5
- attr_accessor :body, :headers, :status, :original
6
+ attr_accessor :original
7
+ attr_reader :body, :headers, :status
6
8
 
7
9
  def initialize(body, headers, status)
8
10
  @body = body
@@ -3,6 +3,7 @@
3
3
  require 'sinatra'
4
4
  require 'haml'
5
5
 
6
+ require 'tshield/options'
6
7
  require 'tshield/controllers/requests'
7
8
  require 'tshield/controllers/sessions'
8
9
 
@@ -15,6 +16,7 @@ module TShield
15
16
  set :public_dir, File.join(File.dirname(__FILE__), 'assets')
16
17
  set :views, File.join(File.dirname(__FILE__), 'views')
17
18
  set :bind, '0.0.0.0'
19
+ set :port, TShield::Options.instance.port
18
20
 
19
21
  def self.register_resources
20
22
  load_controllers
@@ -26,8 +28,6 @@ module TShield
26
28
  return unless File.exist?('controllers')
27
29
 
28
30
  Dir.entries('controllers').each do |entry|
29
- require 'byebug'
30
- debugger
31
31
  next if entry =~ /^\.\.?$/
32
32
 
33
33
  entry.gsub!('.rb', '')
@@ -4,7 +4,7 @@ module TShield
4
4
  # Control version of gem
5
5
  class Version
6
6
  MAJOR = 0
7
- MINOR = 10
7
+ MINOR = 11
8
8
  PATCH = 0
9
9
  PRE = 0
10
10
 
data/spec/spec_helper.rb CHANGED
@@ -9,6 +9,8 @@ SimpleCov.start
9
9
  require 'httparty'
10
10
  require 'webmock/rspec'
11
11
 
12
+ require 'tshield/extensions/string_extensions'
13
+
12
14
  RSpec.configure do |config|
13
15
  config.before(:each) do
14
16
  allow(File).to receive(:join).and_return(
@@ -29,6 +29,12 @@ describe TShield::Configuration do
29
29
  )
30
30
  end
31
31
 
32
+ it 'recover skip query params' do
33
+ expect(@configuration.domains['example.org']['skip_query_params']).to(
34
+ include('a')
35
+ )
36
+ end
37
+
32
38
  context 'on load filters' do
33
39
  it 'recover filters for a domain' do
34
40
  expect(@configuration.get_filters('example.org')).to eq([ExampleFilter])
@@ -9,6 +9,8 @@ domains:
9
9
  paths:
10
10
  - '/api/one'
11
11
  - '/api/two'
12
+ skip_query_params:
13
+ - 'a'
12
14
  'example.com':
13
15
  name: 'example.com'
14
16
  paths:
@@ -0,0 +1,55 @@
1
+ [{
2
+ "method": "GET",
3
+ "path": "/matching/example",
4
+ "query": {
5
+ "query1": "value"
6
+ },
7
+ "response": {
8
+ "status": 200,
9
+ "headers": {},
10
+ "body": "query content"
11
+ }
12
+ },
13
+ {
14
+ "method": "GET",
15
+ "path": "/matching/example",
16
+ "response": {
17
+ "status": 200,
18
+ "headers": {},
19
+ "body": "body content"
20
+ }
21
+ },
22
+ {
23
+ "method": "POST",
24
+ "path": "/matching/example",
25
+ "headers": {
26
+ "header1": "value"
27
+ },
28
+ "response": {
29
+ "status": 201,
30
+ "headers": {},
31
+ "body": "headers content"
32
+ }
33
+ },
34
+ {
35
+ "method": "POST",
36
+ "path": "/matching/example",
37
+ "response": {
38
+ "status": 201,
39
+ "headers": {},
40
+ "body": "post content"
41
+ }
42
+ },
43
+ {
44
+ "session": "a-session",
45
+ "stubs": [{
46
+ "method": "GET",
47
+ "path": "/matching/example",
48
+ "response": {
49
+ "status": 200,
50
+ "headers": {},
51
+ "body": "body content in session"
52
+ }
53
+ }]
54
+ }
55
+ ]
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: false
2
+
3
+ require 'optparse'
4
+ require 'tshield/options'
5
+ require 'spec_helper'
6
+
7
+ describe TShield::Options do
8
+ context 'with parsing' do
9
+ before :each do
10
+ options_parser = double
11
+ @opts = double
12
+
13
+ allow(OptionParser).to receive(:new)
14
+ .and_return(options_parser)
15
+ .and_yield(@opts)
16
+
17
+ allow(options_parser).to receive(:parse!)
18
+
19
+ allow(@opts).to receive(:banner=)
20
+ allow(@opts).to receive(:on)
21
+ allow(@opts).to receive(:on_tail)
22
+ end
23
+
24
+ it 'should recover default port' do
25
+ TShield::Options.init
26
+ expect(TShield::Options.instance.port).to eql(4567)
27
+ end
28
+
29
+ it 'should recover custom port' do
30
+ allow(@opts).to receive(:on)
31
+ .with('-p', '--port [PORT]', 'Sinatra port')
32
+ .and_yield('4568')
33
+
34
+ TShield::Options.init
35
+ expect(TShield::Options.instance.port).to eql(4568)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'tshield/configuration'
6
+ require 'tshield/request_matching'
7
+ require 'tshield/response'
8
+
9
+ describe TShield::RequestMatching do
10
+ before :each do
11
+ @configuration = double
12
+ allow(TShield::Configuration)
13
+ .to receive(:singleton).and_return(@configuration)
14
+ end
15
+
16
+ context 'matching path' do
17
+ before :each do
18
+ allow(Dir).to receive(:glob)
19
+ .and_return(['spec/tshield/fixtures/matching/example.json'])
20
+ end
21
+
22
+ context 'on loading stubs' do
23
+ before :each do
24
+ @request_matching = TShield::RequestMatching.new('/')
25
+ end
26
+ context 'for path /matching/example' do
27
+ it 'should map path' do
28
+ expect(@request_matching.class.stubs[DEFAULT_SESSION]).to include('/matching/example')
29
+ end
30
+ context 'on settings' do
31
+ before :each do
32
+ @entry = @request_matching.class.stubs[DEFAULT_SESSION]['/matching/example'][0]
33
+ end
34
+ it 'should answer for the method GET' do
35
+ expect(@entry['method']).to include('GET')
36
+ end
37
+ it 'should have response body' do
38
+ expect(@entry['response']['body']).to include('query content')
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ context 'on matching request' do
45
+ context 'on match' do
46
+ before :each do
47
+ @request_matching = TShield::RequestMatching.new('/matching/example', method: 'GET')
48
+ end
49
+ it 'should return response object' do
50
+ @response = @request_matching.match_request
51
+ expect(@response.body).to eql('body content')
52
+ expect(@response.headers).to eql({})
53
+ expect(@response.status).to eql(200)
54
+ end
55
+ end
56
+ context 'on match by path and method' do
57
+ before :each do
58
+ @request_matching = TShield::RequestMatching
59
+ .new('/matching/example', method: 'POST')
60
+ end
61
+ it 'should return response object' do
62
+ @response = @request_matching.match_request
63
+ expect(@response.body).to eql('post content')
64
+ expect(@response.headers).to eql({})
65
+ expect(@response.status).to eql(201)
66
+ end
67
+ end
68
+ context 'on match by path and method and headers' do
69
+ before :each do
70
+ @request_matching = TShield::RequestMatching
71
+ .new('/matching/example',
72
+ method: 'POST',
73
+ headers: { 'HTTP_HEADER1' => 'value' })
74
+ end
75
+ it 'should return response object' do
76
+ @response = @request_matching.match_request
77
+ expect(@response.body).to eql('headers content')
78
+ expect(@response.headers).to eql({})
79
+ expect(@response.status).to eql(201)
80
+ end
81
+ end
82
+ context 'on match by path and method and query' do
83
+ before :each do
84
+ @request_matching = TShield::RequestMatching
85
+ .new('/matching/example',
86
+ method: 'GET',
87
+ raw_query: 'query1=value')
88
+ end
89
+ it 'should return response object' do
90
+ @response = @request_matching.match_request
91
+ expect(@response.body).to eql('query content')
92
+ expect(@response.headers).to eql({})
93
+ expect(@response.status).to eql(200)
94
+ end
95
+ end
96
+ context 'on not match' do
97
+ before :each do
98
+ @request_matching = TShield::RequestMatching.new('/')
99
+ end
100
+ it 'should return nil' do
101
+ expect(@request_matching.match_request).to be_nil
102
+ end
103
+ end
104
+ end
105
+ context 'on session' do
106
+ context 'load session infos' do
107
+ before :each do
108
+ @request_matching = TShield::RequestMatching.new('/')
109
+ end
110
+
111
+ it 'should map path' do
112
+ expect(@request_matching.class.stubs['a-session']).to include('/matching/example')
113
+ end
114
+ end
115
+ context 'on match' do
116
+ it 'should return response object from session settings' do
117
+ @request_matching = TShield::RequestMatching.new('/matching/example',
118
+ method: 'GET',
119
+ session: 'a-session')
120
+ @response = @request_matching.match_request
121
+ expect(@response.body).to eql('body content in session')
122
+ expect(@response.headers).to eql({})
123
+ expect(@response.status).to eql(200)
124
+ end
125
+ it 'should return response object from default settings' do
126
+ @request_matching = TShield::RequestMatching.new('/matching/example',
127
+ method: 'POST',
128
+ session: 'a-session')
129
+ @response = @request_matching.match_request
130
+ expect(@response.body).to eql('post content')
131
+ expect(@response.headers).to eql({})
132
+ expect(@response.status).to eql(201)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end