spectre-core 1.8.3 → 1.11.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4917b9c51ecccb083b1032af33b98c2efd2a0905890d8c33d2a739f2f85e7391
4
- data.tar.gz: a8800ce11d35f41dd78c4f63e3fb9f0192bd64c9ab02e436a99ac2641e13ba62
3
+ metadata.gz: 8333942c6050c7adf1bb9c238538b5364b5648514264612aefc161f715c48e58
4
+ data.tar.gz: 34ca46ef693108549a69c754bc3b22d5be33e642d9a6dfde040b664af47cfe58
5
5
  SHA512:
6
- metadata.gz: d60156f1003d5cfd86d6ecb570f785979a24a192f4ca3800916eebb5ceaecdff78125ab893c61aef41a67992fedca580a6d2ff95026504358569a1abede4bdba
7
- data.tar.gz: df1057c2157e65a5bcc59c84594a1ef2cc5dfc9a9db0c16a96738f952adc68d747c25801573d16cdaa0a57fe005102436ed26b6ec3b4dbce75fdbf23a0fea516
6
+ metadata.gz: a840c8d9afd7010e0873cf273400a8a5d82b28ad05941d1c4562c03a8aed60fabc7ed704dc2538c5960a940440204df0228d0ce77b6ee11ec68f820c3deb6954
7
+ data.tar.gz: f6913fa578a938341fbd15455c1e122a4c425bacb68d1353ce7c5f0c8ed96b206eba2625d28d1d8df91fec4161343a28b15db5535c3785000c88cbee914c0f2b
data/exe/spectre CHANGED
@@ -48,6 +48,7 @@ DEFAULT_CONFIG = {
48
48
  },
49
49
  'debug' => false,
50
50
  'out_path' => './reports',
51
+ 'secure_keys' => ['password', 'secret', 'token', 'secure', 'authorization'],
51
52
  'spec_patterns' => ['./specs/**/*.spec.rb'],
52
53
  'mixin_patterns' => ['../common/mixins/**/*.mixin.rb', './mixins/**/*.mixin.rb'],
53
54
  'env_patterns' => ['./environments/**/*.env.yml'],
@@ -68,10 +69,6 @@ DEFAULT_CONFIG = {
68
69
  'spectre/http/basic_auth',
69
70
  'spectre/http/keystone',
70
71
  'spectre/resources',
71
- 'spectre/ssh',
72
- 'spectre/ftp',
73
- 'spectre/mysql',
74
- # 'spectre/postgres',
75
72
  ],
76
73
  'include' => [
77
74
 
@@ -126,11 +123,15 @@ Specific options:}
126
123
  cmd_options['colored'] = false
127
124
  end
128
125
 
126
+ opts.on('--ignore-failure', 'Always exit with code 0') do
127
+ cmd_options['ignore_failure'] = true
128
+ end
129
+
129
130
  opts.on('-o PATH', '--out PATH', 'Output directory path') do |path|
130
- cmd_options['out_path'] = path
131
+ cmd_options['out_path'] = File.absolute_path(path)
131
132
  end
132
133
 
133
- opts.on('-r NAME', '--reporter NAME', Array, "A list of reporters to use") do |reporters|
134
+ opts.on('-r NAME', '--reporters NAME', Array, "A list of reporters to use") do |reporters|
134
135
  cmd_options['reporters'] = reporters
135
136
  end
136
137
 
@@ -205,8 +206,9 @@ envs = {}
205
206
  read_env_files = {}
206
207
  cfg['env_patterns'].each do |pattern|
207
208
  Dir.glob(pattern).each do|f|
208
- spec_env = YAML.load_file(f)
209
- name = spec_env.delete('name') || 'default'
209
+ spec_env = YAML.load_file(f) || {}
210
+
211
+ name = spec_env['name'] || 'default'
210
212
 
211
213
  if envs.key? name
212
214
  existing_env_file = read_env_files[name]
@@ -236,6 +238,10 @@ cfg.merge! env if env
236
238
 
237
239
  String.colored! if cfg['colored']
238
240
 
241
+ # Load environment exlicitly before loading specs to make it available in spec definition
242
+ require_relative '../lib/spectre/environment' unless cfg['exclude'].include? 'spectre/environment'
243
+ Spectre.configure(cfg)
244
+
239
245
 
240
246
  ###########################################
241
247
  # Load Specs
@@ -258,7 +264,7 @@ if action == 'list'
258
264
  colors = [:blue, :magenta, :yellow, :green]
259
265
  specs = Spectre.specs(cfg['specs'], cfg['tags'])
260
266
 
261
- exit 1 if specs.length == 0
267
+ exit 1 unless specs.any?
262
268
 
263
269
  counter = 0
264
270
 
@@ -298,22 +304,38 @@ if action == 'run'
298
304
  FileUtils.makedirs log_dir if !Dir.exists? log_dir
299
305
 
300
306
  # Load Modules
307
+
301
308
  cfg['modules']
302
309
  .concat(cfg['include'])
303
310
  .select { |mod| !cfg['exclude'].include? mod }
304
311
  .each do |mod|
305
312
  begin
306
- if !File.exists? mod
307
- require_relative File.join('../lib', mod)
308
- else
313
+ spectre_lib_mod = File.join('../lib', mod)
314
+
315
+ if File.exists? mod
309
316
  require_relative mod
317
+
318
+ elsif File.exists? spectre_lib_mod
319
+ require_relative spectre_lib_mod
320
+
321
+ else
322
+ require mod
310
323
  end
311
- rescue => e
324
+
325
+ rescue LoadError => e
312
326
  puts "Unable to load module #{mod}. Check if the module exists or remove it from your spectre config:\n#{e.message}"
313
327
  exit 1
314
328
  end
315
329
  end
316
330
 
331
+ # Load mixins
332
+
333
+ cfg['mixin_patterns'].each do |pattern|
334
+ Dir.glob(pattern).each do|f|
335
+ require_relative File.join(Dir.pwd, f)
336
+ end
337
+ end
338
+
317
339
  Spectre.configure(cfg)
318
340
 
319
341
  Spectre::Logger.debug! if cfg['debug']
@@ -325,7 +347,7 @@ if action == 'run'
325
347
 
326
348
  specs = Spectre.specs(cfg['specs'], cfg['tags'])
327
349
 
328
- if specs.length == 0
350
+ if not specs.any?
329
351
  puts "No specs found in #{Dir.pwd}"
330
352
  exit 1
331
353
  end
@@ -337,7 +359,11 @@ if action == 'run'
337
359
  reporter.report(run_infos)
338
360
  end
339
361
 
340
- exit 0
362
+ errors = run_infos.select { |x| x.error != nil or x.failure != nil }
363
+
364
+ exit 0 if cfg['ignore_failure'] or not errors.any?
365
+
366
+ exit 1
341
367
  end
342
368
 
343
369
 
@@ -347,7 +373,7 @@ end
347
373
 
348
374
 
349
375
  if action == 'envs'
350
- exit 1 if envs.length == 0
376
+ exit 1 unless envs.any?
351
377
  puts envs.pretty
352
378
  exit 0
353
379
  end
@@ -464,6 +490,16 @@ reports/
464
490
  **/environments/*.env.secret.yml
465
491
  ]
466
492
 
493
+ DEFAULT_GEMFILE = %[source 'https://rubygems.org'
494
+
495
+ gem 'spectre-core', '>= #{Spectre::VERSION}'
496
+ # gem 'spectre-mysql', '>= 1.0.0'
497
+ # gem 'spectre-ssh', '>= 1.0.0'
498
+ # gem 'spectre-ftp', '>= 1.0.0'
499
+ # gem 'spectre-curl', '>= 1.0.0'
500
+ # gem 'spectre-git', '>= 0.1.0'
501
+ ]
502
+
467
503
  if action == 'init'
468
504
  DEFAULT_FILES = [
469
505
  ['./environments/default.env.yml', DEFAULT_ENV_CFG],
@@ -471,6 +507,7 @@ if action == 'init'
471
507
  ['./specs/sample.spec.rb', SAMPLE_SPEC],
472
508
  ['./spectre.yml', DEFAULT_SPECTRE_CFG],
473
509
  ['./.gitignore', DEFAULT_GITIGNORE],
510
+ ['./Gemfile', DEFAULT_GEMFILE],
474
511
  ]
475
512
 
476
513
  %w(environments logs specs).each do |dir_name|
@@ -6,19 +6,19 @@ module Spectre
6
6
  module Assertion
7
7
  class ::Object
8
8
  def should_be(val)
9
- raise AssertionFailure.new("The value '#{self.to_s.trim}' should be '#{val.to_s.trim}'", val, self) unless self.to_s == val.to_s
9
+ raise AssertionFailure.new("The value '#{self.to_s.trim}' should be '#{val.to_s.trim}'", val, self) unless self.to_s == val.to_s
10
10
  end
11
11
 
12
12
  def should_be_empty
13
- raise AssertionFailure.new("The value '#{self.to_s.trim}' should be empty", nil, self) unless self == nil
13
+ raise AssertionFailure.new("The value '#{self.to_s.trim}' should be empty", nil, self) unless self.nil?
14
14
  end
15
15
 
16
16
  def should_not_be(val)
17
- raise AssertionFailure.new("The value '#{self.to_s.trim}' should not be '#{val.to_s.trim}'", val, self) unless self.to_s != val.to_s
17
+ raise AssertionFailure.new("The value '#{self.to_s.trim}' should not be '#{val.to_s.trim}'", val, self) unless self.to_s != val.to_s
18
18
  end
19
19
 
20
20
  def should_not_exist
21
- raise AssertionFailure.new("The value '#{self.to_s.trim}' should not exist, but it does", val, self) unless self.to_s != nil
21
+ raise AssertionFailure.new("The value '#{self.to_s.trim}' should not exist, but it does", val, self) unless self.to_s != nil
22
22
  end
23
23
 
24
24
  def should_not_be_empty
@@ -76,7 +76,7 @@ module Spectre
76
76
  val = OpenStruct.new(val)
77
77
  end
78
78
 
79
- raise AssertionFailure.new("The list [#{list.join(', ').trim}] should contain '#{val.trim}'", val, list) unless list.include? val
79
+ raise AssertionFailure.new("The list [#{list.join(', ').trim}] should contain '#{val.to_s.trim}'", val, list) unless list.include? val
80
80
  end
81
81
 
82
82
  def should_not_contain(val)
@@ -87,15 +87,15 @@ module Spectre
87
87
  val = OpenStruct.new(val)
88
88
  end
89
89
 
90
- raise AssertionFailure.new("The list [#{list.join(', ').trim}] should not contain '#{val.trim}'", val, list) if list.include? val
90
+ raise AssertionFailure.new("The list [#{list.join(', ').trim}] should not contain '#{val.to_s.trim}'", val, list) if list.include? val
91
91
  end
92
92
 
93
93
  def should_be_empty
94
- raise AssertionFailure.new('empty list', self) unless self.length == 0
94
+ raise AssertionFailure.new('empty list', self) unless self.empty?
95
95
  end
96
96
 
97
97
  def should_not_be_empty
98
- raise AssertionFailure.new('no empty list', self) unless self.length > 0
98
+ raise AssertionFailure.new('no empty list', self) if self.empty?
99
99
  end
100
100
  end
101
101
 
@@ -110,7 +110,7 @@ module Spectre
110
110
  end
111
111
 
112
112
  def should_not_be(val)
113
- raise AssertionFailure.new("The text '#{self.trim}' should not be '#{val.to_s.trim}'", val, self) unless self != val
113
+ raise AssertionFailure.new("The text '#{self.trim}' should not be '#{val.to_s.trim}'", val, self) unless self != val
114
114
  end
115
115
 
116
116
  def should_not_be_empty
@@ -118,8 +118,10 @@ module Spectre
118
118
  end
119
119
 
120
120
  def should_contain(value)
121
+ raise AssertionFailure.new("`value' must not be nil") if value.nil?
122
+
121
123
  predicate = proc { |x| self.include? x.to_s }
122
- evaluation = SingleEvaluation.new value
124
+ evaluation = SingleEvaluation.new(value)
123
125
  success = evaluation.call(predicate)
124
126
 
125
127
  return if success
@@ -165,7 +167,7 @@ module Spectre
165
167
 
166
168
  class SingleEvaluation < Evaluation
167
169
  def initialize value
168
- super value, nil
170
+ super(value, nil)
169
171
  end
170
172
 
171
173
  def call predicate
@@ -180,7 +182,7 @@ module Spectre
180
182
 
181
183
  class OrEvaluation < Evaluation
182
184
  def initialize value, other
183
- super value, other
185
+ super(value, other)
184
186
  end
185
187
 
186
188
  def call predicate
@@ -195,7 +197,7 @@ module Spectre
195
197
 
196
198
  class AndEvaluation < Evaluation
197
199
  def initialize value, other
198
- super value, other
200
+ super(value, other)
199
201
  end
200
202
 
201
203
  def call predicate
@@ -222,14 +224,6 @@ module Spectre
222
224
  class << self
223
225
  @@success = nil
224
226
 
225
- def eval_assertion predicate, val
226
- if val.is_a? Proc
227
- val.call(predicate)
228
- else
229
- predicate.call(val)
230
- end
231
- end
232
-
233
227
  def expect desc
234
228
  begin
235
229
  Logger.log_process("expect #{desc}")
data/lib/spectre/curl.rb CHANGED
@@ -192,7 +192,8 @@ module Spectre::Curl
192
192
  return str unless str or str.empty?
193
193
 
194
194
  begin
195
- json = JSON.parse str
195
+ json = JSON.parse(str)
196
+ json.obfuscate!(@@secure_keys) if not @@debug
196
197
 
197
198
  if pretty
198
199
  str = JSON.pretty_generate(json)
@@ -206,6 +207,25 @@ module Spectre::Curl
206
207
  str
207
208
  end
208
209
 
210
+ def is_secure? key
211
+ @@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
212
+ end
213
+
214
+ def header_to_s headers
215
+ s = ''
216
+
217
+ return s unless headers
218
+
219
+ headers.each do |header|
220
+ key = header[0].to_s
221
+ value = header[1].to_s
222
+ value = '*****' if is_secure?(key) and not @@debug
223
+ s += "#{key.ljust(30, '.')}: #{value}\n"
224
+ end
225
+
226
+ s
227
+ end
228
+
209
229
  def invoke req
210
230
  cmd = [@@curl_path]
211
231
 
@@ -230,11 +250,11 @@ module Spectre::Curl
230
250
  uri += '?'
231
251
  uri += req['query']
232
252
  .map { |x| x.join '='}
233
- .join '&'
253
+ .join('&')
234
254
  end
235
255
 
236
- cmd.append '"' + uri + '"'
237
- cmd.append '-X', req['method'] unless req['method'] == 'GET' or (req['body'] and req['method'] == 'POST')
256
+ cmd.append('"' + uri + '"')
257
+ cmd.append('-X', req['method']) unless req['method'] == 'GET' or (req['body'] and req['method'] == 'POST')
238
258
 
239
259
  # Call all registered modules
240
260
  @@modules.each do |mod|
@@ -243,43 +263,41 @@ module Spectre::Curl
243
263
 
244
264
  # Add headers to curl command
245
265
  req['headers'].each do |header|
246
- cmd.append '-H', '"' + header.join(':') + '"'
266
+ cmd.append('-H', '"' + header.join(':') + '"')
247
267
  end if req['headers']
248
268
 
249
269
  # Add request body
250
270
  if req['body'] != nil and not req['body'].empty?
251
271
  req_body = try_format_json(req['body']).gsub(/"/, '\\"')
252
- cmd.append '-d', '"' + req_body + '"'
272
+ cmd.append('-d', '"' + req_body + '"')
253
273
  elsif ['POST', 'PUT', 'PATCH'].include? req['method'].upcase
254
- cmd.append '-d', '"\n"'
274
+ cmd.append('-d', '"\n"')
255
275
  end
256
276
 
257
277
  # Add certificate path if one if given
258
278
  if req['cert']
259
279
  raise "Certificate '#{req['cert']}' does not exist" unless File.exists? req['cert']
260
- cmd.append '--cacert', req['cert']
280
+ cmd.append('--cacert', req['cert'])
261
281
  elsif req['use_ssl'] or uri.start_with? 'https'
262
- cmd.append '-k'
282
+ cmd.append('-k')
263
283
  end
264
284
 
265
- cmd.append '-i'
266
- cmd.append '-v'
285
+ cmd.append('-i')
286
+ cmd.append('-v')
267
287
 
268
- @@request = OpenStruct.new req
288
+ @@request = OpenStruct.new(req)
269
289
 
270
- sys_cmd = cmd.join ' '
290
+ sys_cmd = cmd.join(' ')
271
291
 
272
- @@logger.debug sys_cmd
292
+ @@logger.debug(sys_cmd)
273
293
 
274
294
  req_id = SecureRandom.uuid()[0..5]
275
295
 
276
296
  req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
277
- req['headers'].each do |header|
278
- req_log += "#{header[0].to_s.ljust(30, '.')}: #{header[1].to_s}\n"
279
- end if req['headers']
280
- req_log += req['body'] if req['body'] != nil and not req['body'].empty?
297
+ req_log += header_to_s(req['headers'])
298
+ req_log += try_format_json(req['body'], pretty: true)
281
299
 
282
- @@logger.info req_log
300
+ @@logger.info(req_log)
283
301
 
284
302
  start_time = Time.now
285
303
 
@@ -297,7 +315,7 @@ module Spectre::Curl
297
315
 
298
316
  raise "Unable to request #{uri}. Please check if this service is reachable." unless output
299
317
 
300
- @@logger.debug "[<] #{req_id} stdout:\n#{output}"
318
+ @@logger.debug("[<] #{req_id} stdout:\n#{output}")
301
319
 
302
320
  header, body = output.split /\r?\n\r?\n/
303
321
 
@@ -342,7 +360,7 @@ module Spectre::Curl
342
360
 
343
361
  @@logger.info res_log
344
362
 
345
- @@response = SpectreHttpResponse.new res
363
+ @@response = SpectreHttpResponse.new(res)
346
364
 
347
365
  raise "Response did not indicate success: #{@@response.code} #{@@response.message}" if req['ensure_success'] and not @@response.success?
348
366
 
@@ -351,7 +369,12 @@ module Spectre::Curl
351
369
  end
352
370
 
353
371
  Spectre.register do |config|
354
- @@logger = ::Logger.new config['log_file'], progname: 'spectre/curl'
372
+ @@debug = config['debug']
373
+
374
+ @@logger = ::Logger.new(config['log_file'], progname: 'spectre/curl')
375
+ @@logger.level = @@debug ? Logger::DEBUG : Logger::INFO
376
+
377
+ @@secure_keys = config['secure_keys'] || []
355
378
 
356
379
  @@curl_path = config['curl_path'] || 'curl'
357
380
 
@@ -2,6 +2,7 @@ require 'securerandom'
2
2
  require 'json'
3
3
  require 'date'
4
4
  require 'ostruct'
5
+ require 'jsonpath'
5
6
 
6
7
  class ::String
7
8
  def as_json
@@ -12,17 +13,57 @@ class ::String
12
13
  DateTime.parse(self)
13
14
  end
14
15
 
16
+ def as_timestamp
17
+ DateTime.parse(self).to_time.to_i
18
+ end
19
+
20
+ def with mapping
21
+ return self unless mapping and mapping.is_a? Hash
22
+
23
+ new_string = self
24
+
25
+ mapping.each do |key, value|
26
+ new_string = new_string.gsub('#{' + key.to_s + '}', value.to_s)
27
+ end
28
+
29
+ new_string
30
+ end
31
+
32
+ def trim size = 50
33
+ if (self.length + 3) > size
34
+ return self[0..size-4] + '...'
35
+ end
36
+
37
+ self
38
+ end
39
+
40
+ def pick path
41
+ raise ArgumentError.new("`path' must not be nil or empty") if path.nil? or path.empty?
42
+
43
+ begin
44
+ JsonPath.on(self, path)
45
+
46
+ rescue MultiJson::ParseError
47
+ # do nothing and return nil
48
+ end
49
+ end
50
+
51
+ # File helpers
52
+
15
53
  def content with: nil
16
54
  fail "'#{self}' is not a file path, or the file does not exist." if !File.exists? self
17
55
  file_content = File.read(self)
18
56
 
19
57
  if with
20
- with.each do |key, value|
21
- file_content = file_content.gsub '#{' + key.to_s + '}', value.to_s
22
- end
58
+ file_content.with(with)
59
+ else
60
+ file_content
23
61
  end
62
+ end
24
63
 
25
- file_content
64
+ def file_size
65
+ fail "'#{self}' is not a file path, or the file does not exist." if !File.exists? self
66
+ File.size(self)
26
67
  end
27
68
 
28
69
  def exists?
@@ -31,17 +72,8 @@ class ::String
31
72
 
32
73
  def remove!
33
74
  fail "'#{self}' is not a file path, or the file does not exist." if !File.exists? self
34
-
35
75
  File.delete self
36
76
  end
37
-
38
- def trim count=50
39
- if (self.length + 3) > count
40
- return self[0..count] + '...'
41
- end
42
-
43
- self
44
- end
45
77
  end
46
78
 
47
79
 
@@ -49,6 +81,22 @@ class ::OpenStruct
49
81
  def to_json *args, **kwargs
50
82
  self.to_h.inject({}) { |memo, (k,v)| memo[k] = v.is_a?(OpenStruct) ? v.to_h : v; memo }.to_json(*args, **kwargs)
51
83
  end
84
+
85
+ def pick path
86
+ raise ArgumentError.new("`path' must not be nil or empty") if path.nil? or path.empty?
87
+
88
+ JsonPath.on(self, path)
89
+ end
90
+
91
+ def default_to! defaults
92
+ defaults.each_key do |key|
93
+ if not self[key] != nil
94
+ self[key] = defaults[key]
95
+ end
96
+ end
97
+ end
98
+
99
+ alias :defaults_to! :default_to!
52
100
  end
53
101
 
54
102
 
@@ -56,9 +104,31 @@ class ::Hash
56
104
  def symbolize_keys
57
105
  self.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo }
58
106
  end
107
+
108
+ def default_to! defaults
109
+ defaults.each_key do |key|
110
+ if not self[key] != nil
111
+ self[key] = defaults[key]
112
+ end
113
+ end
114
+ end
115
+
116
+ alias :defaults_to! :default_to!
117
+ end
118
+
119
+
120
+ class ::Array
121
+ def last
122
+ self[-1]
123
+ end
59
124
  end
60
125
 
61
126
 
62
127
  def uuid length = 5
63
- SecureRandom.uuid().gsub('-', '')[0..length]
128
+ SecureRandom.uuid().gsub('-', '')[0..length-1]
129
+ end
130
+
131
+
132
+ def now
133
+ Time.now
64
134
  end