tshield 0.10.0.0 → 0.11.0.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.
@@ -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