spectre-core 1.8.4 → 1.12.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.
data/lib/spectre/http.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require_relative '../spectre'
2
+
1
3
  require 'net/http'
2
4
  require 'openssl'
3
5
  require 'json'
@@ -5,7 +7,6 @@ require 'securerandom'
5
7
  require 'logger'
6
8
  require 'ostruct'
7
9
 
8
-
9
10
  module Spectre
10
11
  module Http
11
12
  DEFAULT_HTTP_CONFIG = {
@@ -19,11 +20,16 @@ module Spectre
19
20
  'headers' => nil,
20
21
  'query' => nil,
21
22
  'content_type' => '',
22
- }
23
+ }.freeze
23
24
 
24
25
  @@modules = []
25
26
 
26
27
  class SpectreHttpRequest < Spectre::DslClass
28
+ class Headers
29
+ CONTENT_TYPE = 'Content-Type'
30
+ UNIQUE_HEADERS = [CONTENT_TYPE].freeze
31
+ end
32
+
27
33
  def initialize request
28
34
  @__req = request
29
35
  end
@@ -40,30 +46,34 @@ module Spectre
40
46
  @__req['path'] = url_path
41
47
  end
42
48
 
49
+ def timeout seconds
50
+ @__req['timeout'] = seconds
51
+ end
52
+
43
53
  def header name, value
44
- @__req['headers'] = [] if not @__req['headers']
54
+ @__req['headers'] ||= []
45
55
  @__req['headers'].append [name, value.to_s.strip]
46
56
  end
47
57
 
48
58
  def param name, value
49
- @__req['query'] = [] if not @__req['query']
59
+ @__req['query'] ||= []
50
60
  @__req['query'].append [name, value.to_s.strip]
51
61
  end
52
62
 
53
63
  def content_type media_type
54
- @__req['headers'] = [] if not @__req['headers']
55
- @__req['headers'].append ['Content-Type', media_type]
64
+ @__req['content_type'] = media_type
56
65
  end
57
66
 
58
67
  def json data
59
68
  data = data.to_h if data.is_a? OpenStruct
60
69
  body JSON.pretty_generate(data)
61
- content_type 'application/json'
70
+
71
+ # TODO: Only set content type, if not explicitly set
72
+ content_type('application/json')
62
73
  end
63
74
 
64
75
  def body body_content
65
- raise 'Body value must be a string' if not body_content.is_a? String
66
- @__req['body'] = body_content
76
+ @__req['body'] = body_content.to_s
67
77
  end
68
78
 
69
79
  def ensure_success!
@@ -78,9 +88,12 @@ module Spectre
78
88
  @__req['auth'] = method
79
89
  end
80
90
 
91
+ def no_auth!
92
+ @__req['auth'] = 'none'
93
+ end
94
+
81
95
  def certificate path
82
96
  @__req['cert'] = path
83
- use_ssl!
84
97
  end
85
98
 
86
99
  def use_ssl!
@@ -102,6 +115,8 @@ module Spectre
102
115
  end
103
116
 
104
117
  def [] key
118
+ return nil unless @headers.key?(key.downcase)
119
+
105
120
  @headers[key.downcase].first
106
121
  end
107
122
 
@@ -111,52 +126,30 @@ module Spectre
111
126
  end
112
127
 
113
128
  class SpectreHttpResponse
114
- def initialize res
115
- @res = res
116
- @data = nil
117
- @headers = SpectreHttpHeader.new @res[:headers]
118
- end
119
-
120
- def code
121
- @res[:code]
122
- end
129
+ attr_reader :code, :message, :headers, :body
123
130
 
124
- def message
125
- @res[:message]
126
- end
127
-
128
- def headers
129
- @headers
130
- end
131
-
132
- def body
133
- @res[:body]
131
+ def initialize net_res
132
+ @code = net_res.code.to_i
133
+ @message = net_res.message
134
+ @body = net_res.body
135
+ @headers = SpectreHttpHeader.new(net_res.to_hash)
136
+ @json_data = nil
134
137
  end
135
138
 
136
139
  def json
137
- return nil if not @res[:body]
138
-
139
- if @data == nil
140
+ if !@body.nil? and @json_data.nil?
140
141
  begin
141
- @data = JSON.parse(@res[:body], object_class: OpenStruct)
142
- rescue
143
- raise "Body content is not a valid JSON:\n#{@res[:body]}"
142
+ @json_data = JSON.parse(@body, object_class: OpenStruct)
143
+ rescue JSON::ParserError
144
+ raise "Body content is not a valid JSON:\n#{@body}"
144
145
  end
145
146
  end
146
147
 
147
- @data
148
+ @json_data
148
149
  end
149
150
 
150
151
  def success?
151
- @res[:code] < 400
152
- end
153
-
154
- def to_s
155
- @res.to_s
156
- end
157
-
158
- def pretty
159
- @res.pretty
152
+ @code < 400
160
153
  end
161
154
  end
162
155
 
@@ -166,40 +159,44 @@ module Spectre
166
159
  @@response = nil
167
160
  @@request = nil
168
161
  @@modules = []
162
+ @@secure_keys = []
169
163
 
170
164
  def https name, &block
171
165
  http(name, secure: true, &block)
172
166
  end
173
167
 
174
- def http name, secure: nil, &block
168
+ def http name, secure: false, &block
175
169
  req = {}
176
170
 
177
171
  if @@http_cfg.key? name
178
- req.merge! @@http_cfg[name]
179
- raise "No `base_url' set for HTTP client '#{name}'. Check your HTTP config in your environment." if !req['base_url']
172
+ req.deep_merge! @@http_cfg[name].deep_clone
173
+ raise "No `base_url' set for HTTP client '#{name}'. Check your HTTP config in your environment." unless req['base_url']
180
174
  else
181
175
  req['base_url'] = name
182
176
  end
183
177
 
184
- req['user_ssl'] = secure if secure != nil
178
+ req['use_ssl'] = secure unless secure.nil?
185
179
 
186
- SpectreHttpRequest.new(req).instance_eval(&block) if block_given?
180
+ SpectreHttpRequest.new(req)._evaluate(&block) if block_given?
187
181
 
188
182
  invoke(req)
189
183
  end
190
184
 
191
185
  def request
192
186
  raise 'No request has been invoked yet' unless @@request
187
+
193
188
  @@request
194
189
  end
195
190
 
196
191
  def response
197
192
  raise 'There is no response. No request has been invoked yet.' unless @@response
193
+
198
194
  @@response
199
195
  end
200
196
 
201
197
  def register mod
202
198
  raise 'Module must not be nil' unless mod
199
+
203
200
  @@modules << mod
204
201
  end
205
202
 
@@ -209,7 +206,8 @@ module Spectre
209
206
  return str unless str or str.empty?
210
207
 
211
208
  begin
212
- json = JSON.parse str
209
+ json = JSON.parse(str)
210
+ json.obfuscate!(@@secure_keys) unless @@debug
213
211
 
214
212
  if pretty
215
213
  str = JSON.pretty_generate(json)
@@ -223,39 +221,55 @@ module Spectre
223
221
  str
224
222
  end
225
223
 
224
+ def secure? key
225
+ @@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
226
+ end
227
+
228
+ def header_to_s headers
229
+ s = ''
230
+ headers.each_header.each do |header, value|
231
+ value = '*****' if secure?(header) and not @@debug
232
+ s += "#{header.to_s.ljust(30, '.')}: #{value.to_s}\n"
233
+ end
234
+ s
235
+ end
236
+
226
237
  def invoke req
227
238
  @@request = nil
228
239
 
229
- if req['cert'] or req['use_ssl']
230
- scheme = 'https'
231
- else
232
- scheme = 'http'
233
- end
240
+ # Build URI
234
241
 
242
+ scheme = req['use_ssl'] ? 'https' : 'http'
235
243
  base_url = req['base_url']
236
244
 
237
- if not base_url.match /http(?:s)?:\/\//
245
+ unless base_url.match /http(?:s)?:\/\//
238
246
  base_url = scheme + '://' + base_url
239
247
  end
240
248
 
241
249
  if req['path']
242
- base_url = base_url + '/' if not base_url.end_with? '/'
250
+ base_url = base_url + '/' unless base_url.end_with? '/'
243
251
  base_url += req['path']
244
252
  end
245
253
 
246
254
  uri = URI(base_url)
247
255
 
248
- raise "'#{uri}' is not a valid uri" if not uri.host
256
+ raise "'#{uri}' is not a valid uri" unless uri.host
257
+
258
+ # Build query parameters
249
259
 
250
260
  uri.query = URI.encode_www_form(req['query']) unless not req['query'] or req['query'].empty?
251
261
 
262
+ # Create HTTP client
263
+
252
264
  net_http = Net::HTTP.new(uri.host, uri.port)
265
+ net_http.read_timeout = req['timeout'] || 180
253
266
 
254
267
  if uri.scheme == 'https'
255
268
  net_http.use_ssl = true
256
269
 
257
270
  if req.key? 'cert'
258
271
  raise "Certificate '#{req['cert']}' does not exist" unless File.exists? req['cert']
272
+
259
273
  net_http.verify_mode = OpenSSL::SSL::VERIFY_PEER
260
274
  net_http.ca_file = req['cert']
261
275
  else
@@ -263,6 +277,8 @@ module Spectre
263
277
  end
264
278
  end
265
279
 
280
+ # Create HTTP Request
281
+
266
282
  net_req = Net::HTTPGenericRequest.new(req['method'], true, true, uri)
267
283
  net_req.body = req['body']
268
284
  net_req.content_type = req['content_type'] if req['content_type'] and not req['content_type'].empty?
@@ -275,28 +291,37 @@ module Spectre
275
291
 
276
292
  req_id = SecureRandom.uuid()[0..5]
277
293
 
294
+ # Run HTTP modules
295
+
296
+ @@modules.each do |mod|
297
+ mod.on_req(net_http, net_req, req) if mod.respond_to? :on_req
298
+ end
299
+
278
300
  # Log request
279
301
 
280
- req_log = "[>] #{req_id} #{req['method']} #{uri}"
281
- req['headers'].each do |header|
282
- req_log += "\n#{header[0].to_s.ljust(30, '.')}: #{header[1].to_s}"
283
- end if req['headers']
284
- req_log += "\n" + try_format_json(req['body'], pretty: true) if req['body'] != nil and not req['body'].empty?
302
+ req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
303
+ req_log += header_to_s(net_req)
304
+ req_log += try_format_json(req['body'], pretty: true) if req['body'] != nil and not req['body'].empty?
285
305
 
286
- @@logger.info req_log
306
+ @@logger.info(req_log)
287
307
 
288
308
  # Request
289
309
 
290
310
  start_time = Time.now
291
311
 
292
- @@modules.each do |mod|
293
- mod.on_req(net_http, net_req, req) if mod.respond_to? :on_req
312
+ begin
313
+ net_res = net_http.request(net_req)
314
+ rescue SocketError => e
315
+ raise "The request '#{req['method']} #{uri}' failed. Please check if the given URL '#{uri}' is valid and available or a corresponding HTTP config in the environment file exists. See log for more details. Original.\nOriginal error was: #{e.message}"
294
316
  end
295
317
 
296
- net_res = net_http.request(net_req)
297
-
298
318
  end_time = Time.now
299
319
 
320
+ req['started_at'] = start_time
321
+ req['finished_at'] = end_time
322
+
323
+ # Run HTTP modules
324
+
300
325
  @@modules.each do |mod|
301
326
  mod.on_res(net_http, net_res, req) if mod.respond_to? :on_res
302
327
  end
@@ -304,30 +329,26 @@ module Spectre
304
329
  # Log response
305
330
 
306
331
  res_log = "[<] #{req_id} #{net_res.code} #{net_res.message} (#{end_time - start_time}s)\n"
307
- net_res.each_header do |header, value|
308
- res_log += "#{header.to_s.ljust(30, '.')}: #{value}\n"
309
- end
310
- res_log += try_format_json(net_res.body, pretty: true) if net_res.body != nil and !net_res.body.empty?
332
+ res_log += header_to_s(net_res)
333
+ res_log += try_format_json(net_res.body, pretty: true) unless net_res.body.nil? or net_res.body.empty?
311
334
 
312
335
  @@logger.info(res_log)
313
336
 
314
- if req['ensure_success']
315
- code = Integer(net_res.code)
316
- fail "Response code of #{req_id} did not indicate success: #{net_res.code} #{net_res.message}" if code >= 400
317
- end
337
+ fail "Response code of #{req_id} did not indicate success: #{net_res.code} #{net_res.message}" if req['ensure_success'] and net_res.code.to_i >= 400
338
+
339
+ # Set global request and response variables
318
340
 
319
341
  @@request = OpenStruct.new(req)
320
- @@response = SpectreHttpResponse.new({
321
- code: net_res.code.to_i,
322
- message: net_res.message,
323
- headers: net_res.to_hash,
324
- body: net_res.body
325
- })
342
+ @@request.freeze
343
+
344
+ @@response = SpectreHttpResponse.new(net_res)
326
345
  end
327
346
  end
328
347
 
329
348
  Spectre.register do |config|
330
- @@logger = ::Logger.new config['log_file'], progname: 'spectre/http'
349
+ @@logger = ::Logger.new(config['log_file'], progname: 'spectre/http')
350
+ @@secure_keys = config['secure_keys'] || []
351
+ @@debug = config['debug']
331
352
 
332
353
  if config.key? 'http'
333
354
  @@http_cfg = {}
@@ -340,4 +361,4 @@ module Spectre
340
361
 
341
362
  Spectre.delegate :http, :https, :request, :response, to: self
342
363
  end
343
- end
364
+ end
@@ -1,142 +1,143 @@
1
- require 'ectoplasm'
2
-
3
- module Spectre
4
- module Logger
5
- class Console
6
- def initialize config
7
- raise 'No log format section in config for console logger' unless config.key? 'log_format' and config['log_format'].key? 'console'
8
-
9
- @config = config['log_format']['console']
10
- @indent = @config['indent'] || 2
11
- @width = @config['width'] || 80
12
- @fmt_end_context = @config['end_context']
13
- @fmt_sep = @config['separator']
14
- @fmt_start_group = @config['start_group']
15
- @fmt_end_group = @config['end_group']
16
-
17
- @process = nil
18
- @level = 0
19
- end
20
-
21
- def start_subject subject
22
- puts subject.desc.blue
23
- end
24
-
25
- def start_context context
26
- return unless context.__desc
27
- puts (' ' * indent) + context.__desc.magenta
28
- @level += 1
29
- end
30
-
31
- def end_context context
32
- return unless context.__desc
33
- @level -= 1
34
- puts (' ' * indent) + @fmt_end_context.gsub('<desc>', context.__desc).magenta if @fmt_end_context
35
- end
36
-
37
- def start_spec spec, data=nil
38
- text = spec.desc
39
- text += " with #{data}" if data
40
- puts (' ' * indent) + text.cyan
41
-
42
- @level += 1
43
- end
44
-
45
- def end_spec spec, data
46
- @level -= 1
47
- end
48
-
49
- def log_separator desc
50
- if desc
51
- desc = @fmt_sep.gsub('<indent>', ' ' * indent).gsub('<desc>', desc) if @fmt_sep
52
- puts desc.blue
53
- else
54
- puts
55
- end
56
- end
57
-
58
- def start_group desc
59
- desc = @fmt_start_group.gsub('<desc>', desc) if @fmt_start_group
60
- puts (' ' * indent) + desc.blue
61
- @level += 1
62
- end
63
-
64
- def end_group desc
65
- if desc and @fmt_start_group
66
- desc = @fmt_start_group.gsub('<desc>', desc) if @fmt_start_group
67
- puts (' ' * indent) + desc.blue
68
- end
69
-
70
- @level -= 1
71
- end
72
-
73
- def log_process desc
74
- print_line(desc)
75
- @process = desc
76
- @level += 1
77
- end
78
-
79
- def log_status desc, status, annotation=nil
80
- status = status.green if status == Status::OK
81
- status = status.blue if status == Status::INFO
82
- status = status.grey if status == Status::DEBUG
83
- status = status.red if status == Status::FAILED
84
- status = status.red if status == Status::ERROR
85
- status = status.grey if status == Status::SKIPPED
86
-
87
- txt = status
88
- txt += ' ' + annotation if annotation
89
-
90
- @level -= 1
91
-
92
- if @process
93
- puts txt
94
- else
95
- print_line('', status)
96
- end
97
-
98
- @process = nil
99
- end
100
-
101
- def log_info message
102
- print_line(message, Status::INFO.blue)
103
- end
104
-
105
- def log_debug message
106
- print_line(message, Status::DEBUG.grey)
107
- end
108
-
109
- def log_error spec, exception
110
- txt = (Status::ERROR + ' - ' + exception.class.name).red
111
- print_line('', txt)
112
- end
113
-
114
- def log_skipped spec
115
- print_line('', Status::SKIPPED.grey)
116
- end
117
-
118
- private
119
-
120
- def indent
121
- (@level+1) * @indent
122
- end
123
-
124
- def print_line text='', status=nil
125
- puts if @process
126
-
127
- ind = indent
128
- line = (' ' * indent) + text
129
- remaining = @width - text.length - ind
130
- line += '.' * (@width - text.length - ind) if remaining > 0
131
-
132
- print line
133
-
134
- if status
135
- puts status
136
- @process = nil
137
- end
138
- end
139
-
140
- end
141
- end
142
- end
1
+ require 'ectoplasm'
2
+
3
+ module Spectre
4
+ module Logger
5
+ class Console
6
+ def initialize config
7
+ raise 'No log format section in config for console logger' unless config.key? 'log_format' and config['log_format'].key? 'console'
8
+
9
+ @config = config['log_format']['console']
10
+ @indent = @config['indent'] || 2
11
+ @width = @config['width'] || 80
12
+ @fmt_end_context = @config['end_context']
13
+ @fmt_sep = @config['separator']
14
+ @fmt_start_group = @config['start_group']
15
+ @fmt_end_group = @config['end_group']
16
+
17
+ @process = nil
18
+ @level = 0
19
+ end
20
+
21
+ def start_subject subject
22
+ puts subject.desc.blue
23
+ end
24
+
25
+ def start_context context
26
+ return unless context.__desc
27
+
28
+ puts (' ' * indent) + context.__desc.magenta
29
+ @level += 1
30
+ end
31
+
32
+ def end_context context
33
+ return unless context.__desc
34
+
35
+ @level -= 1
36
+ puts (' ' * indent) + @fmt_end_context.gsub('<desc>', context.__desc).magenta if @fmt_end_context
37
+ end
38
+
39
+ def start_spec spec, data=nil
40
+ text = spec.desc
41
+ text += " with #{data}" if data
42
+ puts (' ' * indent) + text.cyan
43
+
44
+ @level += 1
45
+ end
46
+
47
+ def end_spec _spec, _data
48
+ @level -= 1
49
+ end
50
+
51
+ def log_separator desc
52
+ if desc
53
+ desc = @fmt_sep.gsub('<indent>', ' ' * indent).gsub('<desc>', desc) if @fmt_sep
54
+ puts desc.blue
55
+ else
56
+ puts
57
+ end
58
+ end
59
+
60
+ def start_group desc
61
+ desc = @fmt_start_group.gsub('<desc>', desc) if @fmt_start_group
62
+ puts (' ' * indent) + desc.blue
63
+ @level += 1
64
+ end
65
+
66
+ def end_group desc
67
+ if desc and @fmt_start_group
68
+ desc = @fmt_start_group.gsub('<desc>', desc) if @fmt_start_group
69
+ puts (' ' * indent) + desc.blue
70
+ end
71
+
72
+ @level -= 1
73
+ end
74
+
75
+ def log_process desc
76
+ print_line(desc)
77
+ @process = desc
78
+ @level += 1
79
+ end
80
+
81
+ def log_status _desc, status, annotation=nil
82
+ status = status.green if status == Status::OK
83
+ status = status.blue if status == Status::INFO
84
+ status = status.grey if status == Status::DEBUG
85
+ status = status.red if status == Status::FAILED
86
+ status = status.red if status == Status::ERROR
87
+ status = status.grey if status == Status::SKIPPED
88
+
89
+ txt = status
90
+ txt += ' ' + annotation if annotation
91
+
92
+ @level -= 1
93
+
94
+ if @process
95
+ puts txt
96
+ else
97
+ print_line('', status)
98
+ end
99
+
100
+ @process = nil
101
+ end
102
+
103
+ def log_info message
104
+ print_line(message, Status::INFO.blue)
105
+ end
106
+
107
+ def log_debug message
108
+ print_line(message, Status::DEBUG.grey)
109
+ end
110
+
111
+ def log_error _spec, exception
112
+ txt = (Status::ERROR + ' - ' + exception.class.name).red
113
+ print_line('', txt)
114
+ end
115
+
116
+ def log_skipped _spec
117
+ print_line('', Status::SKIPPED.grey)
118
+ end
119
+
120
+ private
121
+
122
+ def indent
123
+ (@level+1) * @indent
124
+ end
125
+
126
+ def print_line text='', status=nil
127
+ puts if @process
128
+
129
+ ind = indent
130
+ line = (' ' * indent) + text
131
+ remaining = @width - text.length - ind
132
+ line += '.' * (@width - text.length - ind) if remaining > 0
133
+
134
+ print line
135
+
136
+ if status
137
+ puts status
138
+ @process = nil
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -93,4 +93,4 @@ module Spectre
93
93
  end
94
94
  end
95
95
  end
96
- end
96
+ end