spectre-core 1.8.1 → 1.10.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 +4 -4
- data/exe/spectre +38 -14
- data/lib/spectre.rb +2 -2
- data/lib/spectre/assertion.rb +12 -18
- data/lib/spectre/helpers.rb +35 -7
- data/lib/spectre/http.rb +32 -17
- data/lib/spectre/reporter/console.rb +1 -0
- metadata +19 -65
- data/lib/spectre/database/postgres.rb +0 -78
- data/lib/spectre/ftp.rb +0 -195
- data/lib/spectre/mysql.rb +0 -97
- data/lib/spectre/ssh.rb +0 -149
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76697e95cde1f8359db1f305ba1d15869bd53903d49a0575fa1ee6f2cda7bf98
|
4
|
+
data.tar.gz: 72562ba547d8f8ff68e880d8f196bd31c7da8f1fd5639231bb56aa838830879a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1ce860bd80ee58af11d506ad9e297f9a432a6f71f62b7702574c5dd52af0f6a6de7fa8bd2e35c167e387e063f86f97a77f51e0541399a866133c0a3524b22a9
|
7
|
+
data.tar.gz: eebfbac67335a7663f407a26094336ee9815a02ebe74789b82254a45ca6539ccc036f289a3eed37cfc581b8e178986f70cbdf5d3c1af49a657ebce4c9ce7246b
|
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', '--
|
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
|
-
|
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]
|
@@ -295,7 +297,7 @@ if action == 'run'
|
|
295
297
|
})
|
296
298
|
|
297
299
|
log_dir = File.dirname cfg['log_file']
|
298
|
-
|
300
|
+
FileUtils.makedirs log_dir if !Dir.exists? log_dir
|
299
301
|
|
300
302
|
# Load Modules
|
301
303
|
cfg['modules']
|
@@ -303,12 +305,19 @@ if action == 'run'
|
|
303
305
|
.select { |mod| !cfg['exclude'].include? mod }
|
304
306
|
.each do |mod|
|
305
307
|
begin
|
306
|
-
|
307
|
-
|
308
|
-
|
308
|
+
spectre_lib_mod = File.join('../lib', mod)
|
309
|
+
|
310
|
+
if File.exists? mod
|
309
311
|
require_relative mod
|
312
|
+
|
313
|
+
elsif File.exists? spectre_lib_mod
|
314
|
+
require_relative spectre_lib_mod
|
315
|
+
|
316
|
+
else
|
317
|
+
require mod
|
310
318
|
end
|
311
|
-
|
319
|
+
|
320
|
+
rescue LoadError => e
|
312
321
|
puts "Unable to load module #{mod}. Check if the module exists or remove it from your spectre config:\n#{e.message}"
|
313
322
|
exit 1
|
314
323
|
end
|
@@ -337,7 +346,11 @@ if action == 'run'
|
|
337
346
|
reporter.report(run_infos)
|
338
347
|
end
|
339
348
|
|
340
|
-
|
349
|
+
errors = run_infos.select { |x| x.error != nil or x.failure != nil }
|
350
|
+
|
351
|
+
exit 0 if cfg['ignore_failure'] or errors.count == 0
|
352
|
+
|
353
|
+
exit 1
|
341
354
|
end
|
342
355
|
|
343
356
|
|
@@ -464,6 +477,16 @@ reports/
|
|
464
477
|
**/environments/*.env.secret.yml
|
465
478
|
]
|
466
479
|
|
480
|
+
DEFAULT_GEMFILE = %[source 'https://rubygems.org'
|
481
|
+
|
482
|
+
gem 'spectre-core', '>= #{Spectre::VERSION}'
|
483
|
+
# gem 'spectre-mysql', '>= 1.0.0'
|
484
|
+
# gem 'spectre-ssh', '>= 1.0.0'
|
485
|
+
# gem 'spectre-ftp', '>= 1.0.0'
|
486
|
+
# gem 'spectre-curl', '>= 1.0.0'
|
487
|
+
# gem 'spectre-git', '>= 0.1.0'
|
488
|
+
]
|
489
|
+
|
467
490
|
if action == 'init'
|
468
491
|
DEFAULT_FILES = [
|
469
492
|
['./environments/default.env.yml', DEFAULT_ENV_CFG],
|
@@ -471,6 +494,7 @@ if action == 'init'
|
|
471
494
|
['./specs/sample.spec.rb', SAMPLE_SPEC],
|
472
495
|
['./spectre.yml', DEFAULT_SPECTRE_CFG],
|
473
496
|
['./.gitignore', DEFAULT_GITIGNORE],
|
497
|
+
['./Gemfile', DEFAULT_GEMFILE],
|
474
498
|
]
|
475
499
|
|
476
500
|
%w(environments logs specs).each do |dir_name|
|
data/lib/spectre.rb
CHANGED
data/lib/spectre/assertion.rb
CHANGED
@@ -6,7 +6,7 @@ module Spectre
|
|
6
6
|
module Assertion
|
7
7
|
class ::Object
|
8
8
|
def should_be(val)
|
9
|
-
|
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
|
@@ -14,11 +14,11 @@ module Spectre
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def should_not_be(val)
|
17
|
-
|
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
|
-
|
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,7 +87,7 @@ 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
|
@@ -110,7 +110,7 @@ module Spectre
|
|
110
110
|
end
|
111
111
|
|
112
112
|
def should_not_be(val)
|
113
|
-
|
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
|
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
|
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
|
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
|
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/helpers.rb
CHANGED
@@ -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
|
@@ -17,12 +18,22 @@ class ::String
|
|
17
18
|
file_content = File.read(self)
|
18
19
|
|
19
20
|
if with
|
20
|
-
with
|
21
|
-
|
22
|
-
|
21
|
+
file_content.with(with)
|
22
|
+
else
|
23
|
+
file_content
|
23
24
|
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def with mapping
|
28
|
+
return self unless mapping and mapping.is_a? Hash
|
24
29
|
|
25
|
-
|
30
|
+
new_string = self
|
31
|
+
|
32
|
+
mapping.each do |key, value|
|
33
|
+
new_string = new_string.gsub '#{' + key.to_s + '}', value.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
new_string
|
26
37
|
end
|
27
38
|
|
28
39
|
def exists?
|
@@ -35,13 +46,24 @@ class ::String
|
|
35
46
|
File.delete self
|
36
47
|
end
|
37
48
|
|
38
|
-
def trim count=50
|
49
|
+
def trim count = 50
|
39
50
|
if (self.length + 3) > count
|
40
|
-
return self[0..count] + '...'
|
51
|
+
return self[0..count-4] + '...'
|
41
52
|
end
|
42
53
|
|
43
54
|
self
|
44
55
|
end
|
56
|
+
|
57
|
+
def pick path
|
58
|
+
raise ArgumentError.new("`path' must not be nil or empty") if path.nil? or path.empty?
|
59
|
+
|
60
|
+
begin
|
61
|
+
JsonPath.on(self, path)
|
62
|
+
|
63
|
+
rescue MultiJson::ParseError
|
64
|
+
# do nothing and return nil
|
65
|
+
end
|
66
|
+
end
|
45
67
|
end
|
46
68
|
|
47
69
|
|
@@ -49,6 +71,12 @@ class ::OpenStruct
|
|
49
71
|
def to_json *args, **kwargs
|
50
72
|
self.to_h.inject({}) { |memo, (k,v)| memo[k] = v.is_a?(OpenStruct) ? v.to_h : v; memo }.to_json(*args, **kwargs)
|
51
73
|
end
|
74
|
+
|
75
|
+
def pick path
|
76
|
+
raise ArgumentError.new("`path' must not be nil or empty") if path.nil? or path.empty?
|
77
|
+
|
78
|
+
JsonPath.on(self, path)
|
79
|
+
end
|
52
80
|
end
|
53
81
|
|
54
82
|
|
@@ -60,5 +88,5 @@ end
|
|
60
88
|
|
61
89
|
|
62
90
|
def uuid length = 5
|
63
|
-
SecureRandom.uuid().gsub('-', '')[0..length]
|
91
|
+
SecureRandom.uuid().gsub('-', '')[0..length-1]
|
64
92
|
end
|
data/lib/spectre/http.rb
CHANGED
@@ -62,8 +62,7 @@ module Spectre
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def body body_content
|
65
|
-
|
66
|
-
@__req['body'] = body_content
|
65
|
+
@__req['body'] = body_content.to_s
|
67
66
|
end
|
68
67
|
|
69
68
|
def ensure_success!
|
@@ -102,6 +101,7 @@ module Spectre
|
|
102
101
|
end
|
103
102
|
|
104
103
|
def [] key
|
104
|
+
return nil if not @headers.has_key?(key.downcase)
|
105
105
|
@headers[key.downcase].first
|
106
106
|
end
|
107
107
|
|
@@ -166,6 +166,7 @@ module Spectre
|
|
166
166
|
@@response = nil
|
167
167
|
@@request = nil
|
168
168
|
@@modules = []
|
169
|
+
@@secure_keys = []
|
169
170
|
|
170
171
|
def https name, &block
|
171
172
|
http(name, secure: true, &block)
|
@@ -209,7 +210,8 @@ module Spectre
|
|
209
210
|
return str unless str or str.empty?
|
210
211
|
|
211
212
|
begin
|
212
|
-
json = JSON.parse
|
213
|
+
json = JSON.parse(str)
|
214
|
+
json.obfuscate!(@@secure_keys) if not @@debug
|
213
215
|
|
214
216
|
if pretty
|
215
217
|
str = JSON.pretty_generate(json)
|
@@ -223,6 +225,19 @@ module Spectre
|
|
223
225
|
str
|
224
226
|
end
|
225
227
|
|
228
|
+
def is_secure? key
|
229
|
+
@@secure_keys.any? { |x| key.to_s.downcase.include? x.downcase }
|
230
|
+
end
|
231
|
+
|
232
|
+
def header_to_s headers
|
233
|
+
s = ''
|
234
|
+
headers.each_header.each do |header, value|
|
235
|
+
value = '*****' if is_secure?(header) and not @@debug
|
236
|
+
s += "#{header.to_s.ljust(30, '.')}: #{value.to_s}\n"
|
237
|
+
end
|
238
|
+
s
|
239
|
+
end
|
240
|
+
|
226
241
|
def invoke req
|
227
242
|
@@request = nil
|
228
243
|
|
@@ -275,28 +290,28 @@ module Spectre
|
|
275
290
|
|
276
291
|
req_id = SecureRandom.uuid()[0..5]
|
277
292
|
|
293
|
+
# Run HTTP modules
|
294
|
+
|
295
|
+
@@modules.each do |mod|
|
296
|
+
mod.on_req(net_http, net_req, req) if mod.respond_to? :on_req
|
297
|
+
end
|
298
|
+
|
278
299
|
# Log request
|
279
300
|
|
280
|
-
req_log = "[>] #{req_id} #{req['method']} #{uri}"
|
281
|
-
|
282
|
-
|
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?
|
301
|
+
req_log = "[>] #{req_id} #{req['method']} #{uri}\n"
|
302
|
+
req_log += header_to_s(net_req)
|
303
|
+
req_log += try_format_json(req['body'], pretty: true) if req['body'] != nil and not req['body'].empty?
|
285
304
|
|
286
305
|
@@logger.info req_log
|
287
306
|
|
288
307
|
# Request
|
289
308
|
|
290
309
|
start_time = Time.now
|
291
|
-
|
292
|
-
@@modules.each do |mod|
|
293
|
-
mod.on_req(net_http, net_req, req) if mod.respond_to? :on_req
|
294
|
-
end
|
295
|
-
|
296
310
|
net_res = net_http.request(net_req)
|
297
|
-
|
298
311
|
end_time = Time.now
|
299
312
|
|
313
|
+
# Run HTTP modules
|
314
|
+
|
300
315
|
@@modules.each do |mod|
|
301
316
|
mod.on_res(net_http, net_res, req) if mod.respond_to? :on_res
|
302
317
|
end
|
@@ -304,9 +319,7 @@ module Spectre
|
|
304
319
|
# Log response
|
305
320
|
|
306
321
|
res_log = "[<] #{req_id} #{net_res.code} #{net_res.message} (#{end_time - start_time}s)\n"
|
307
|
-
|
308
|
-
res_log += "#{header.to_s.ljust(30, '.')}: #{value}\n"
|
309
|
-
end
|
322
|
+
res_log += header_to_s(net_res)
|
310
323
|
res_log += try_format_json(net_res.body, pretty: true) if net_res.body != nil and !net_res.body.empty?
|
311
324
|
|
312
325
|
@@logger.info(res_log)
|
@@ -328,6 +341,8 @@ module Spectre
|
|
328
341
|
|
329
342
|
Spectre.register do |config|
|
330
343
|
@@logger = ::Logger.new config['log_file'], progname: 'spectre/http'
|
344
|
+
@@secure_keys = config['secure_keys'] || []
|
345
|
+
@@debug = config['debug']
|
331
346
|
|
332
347
|
if config.key? 'http'
|
333
348
|
@@http_cfg = {}
|
metadata
CHANGED
@@ -1,85 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spectre-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christian Neubauer
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ectoplasm
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.1.0
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: openssl
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 2.2.0
|
19
|
+
version: 1.2.0
|
34
20
|
type: :runtime
|
35
21
|
prerelease: false
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
37
23
|
requirements:
|
38
|
-
- - "
|
24
|
+
- - ">="
|
39
25
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
26
|
+
version: 1.2.0
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
28
|
+
name: jsonpath
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
44
30
|
requirements:
|
45
|
-
- - "
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 6.1.0
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
31
|
+
- - ">="
|
53
32
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: net-sftp
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 3.0.0
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 3.0.0
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: mysql2
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: 0.5.3
|
33
|
+
version: 1.1.0
|
76
34
|
type: :runtime
|
77
35
|
prerelease: false
|
78
36
|
version_requirements: !ruby/object:Gem::Requirement
|
79
37
|
requirements:
|
80
|
-
- - "
|
38
|
+
- - ">="
|
81
39
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
40
|
+
version: 1.1.0
|
83
41
|
description: A DSL and command line tool to describe and run automated tests
|
84
42
|
email:
|
85
43
|
- me@christianneubauer.de
|
@@ -93,10 +51,8 @@ files:
|
|
93
51
|
- lib/spectre/assertion.rb
|
94
52
|
- lib/spectre/bag.rb
|
95
53
|
- lib/spectre/curl.rb
|
96
|
-
- lib/spectre/database/postgres.rb
|
97
54
|
- lib/spectre/diagnostic.rb
|
98
55
|
- lib/spectre/environment.rb
|
99
|
-
- lib/spectre/ftp.rb
|
100
56
|
- lib/spectre/helpers.rb
|
101
57
|
- lib/spectre/http.rb
|
102
58
|
- lib/spectre/http/basic_auth.rb
|
@@ -105,20 +61,18 @@ files:
|
|
105
61
|
- lib/spectre/logger/console.rb
|
106
62
|
- lib/spectre/logger/file.rb
|
107
63
|
- lib/spectre/mixin.rb
|
108
|
-
- lib/spectre/mysql.rb
|
109
64
|
- lib/spectre/reporter/console.rb
|
110
65
|
- lib/spectre/reporter/junit.rb
|
111
66
|
- lib/spectre/resources.rb
|
112
|
-
|
113
|
-
homepage: https://bitbucket.org/cneubaur/spectre-core
|
67
|
+
homepage: https://github.com/cneubauer/spectre-core
|
114
68
|
licenses:
|
115
69
|
- MIT
|
116
70
|
metadata:
|
117
71
|
allowed_push_host: https://rubygems.org/
|
118
|
-
homepage_uri: https://
|
119
|
-
source_code_uri: https://
|
120
|
-
changelog_uri: https://
|
121
|
-
post_install_message:
|
72
|
+
homepage_uri: https://github.com/cneubauer/spectre-core
|
73
|
+
source_code_uri: https://github.com/cneubauer/spectre-core
|
74
|
+
changelog_uri: https://github.com/cneubauer/spectre-core/blob/master/CHANGELOG.md
|
75
|
+
post_install_message:
|
122
76
|
rdoc_options: []
|
123
77
|
require_paths:
|
124
78
|
- lib
|
@@ -133,8 +87,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
87
|
- !ruby/object:Gem::Version
|
134
88
|
version: '0'
|
135
89
|
requirements: []
|
136
|
-
rubygems_version: 3.
|
137
|
-
signing_key:
|
90
|
+
rubygems_version: 3.0.8
|
91
|
+
signing_key:
|
138
92
|
specification_version: 4
|
139
93
|
summary: Describe and run automated tests
|
140
94
|
test_files: []
|
@@ -1,78 +0,0 @@
|
|
1
|
-
require 'pg'
|
2
|
-
|
3
|
-
|
4
|
-
class PG::Result
|
5
|
-
alias :count :ntuples
|
6
|
-
end
|
7
|
-
|
8
|
-
|
9
|
-
module Spectre
|
10
|
-
module Database
|
11
|
-
module Postgres
|
12
|
-
@@modules = []
|
13
|
-
|
14
|
-
class SQLStatement
|
15
|
-
attr_accessor :query, :params
|
16
|
-
|
17
|
-
def initialize
|
18
|
-
@query = nil
|
19
|
-
@params = []
|
20
|
-
end
|
21
|
-
|
22
|
-
def statement query
|
23
|
-
@query = query
|
24
|
-
end
|
25
|
-
|
26
|
-
def param val
|
27
|
-
@params << val
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
|
32
|
-
class << self
|
33
|
-
def postgres name, &block
|
34
|
-
raise "postgres '#{name}' not configured" unless @@db_cfg.key? name
|
35
|
-
|
36
|
-
statement = SQLStatement.new
|
37
|
-
statement.instance_eval &block
|
38
|
-
|
39
|
-
cfg = @@db_cfg[name]
|
40
|
-
|
41
|
-
begin
|
42
|
-
con = PG.connect({
|
43
|
-
host: cfg['host'],
|
44
|
-
port: cfg['port'],
|
45
|
-
dbname: cfg['database'],
|
46
|
-
user: cfg['username'],
|
47
|
-
password: cfg['username'],
|
48
|
-
})
|
49
|
-
|
50
|
-
if statement.params
|
51
|
-
@@result = con.exec_params(statement.query, statement.params)
|
52
|
-
else
|
53
|
-
@@result = con.exec(statement.query)
|
54
|
-
end
|
55
|
-
|
56
|
-
ensure
|
57
|
-
con.close if con
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
|
62
|
-
def result
|
63
|
-
@@result
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
Spectre.register do |config|
|
68
|
-
@@db_cfg = {}
|
69
|
-
|
70
|
-
config['postgres'].each do |name, cfg|
|
71
|
-
@@db_cfg[name] = cfg
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
Spectre.delegate :postgres, :result, to: self
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
data/lib/spectre/ftp.rb
DELETED
@@ -1,195 +0,0 @@
|
|
1
|
-
require 'net/ftp'
|
2
|
-
require 'net/sftp'
|
3
|
-
require 'logger'
|
4
|
-
require 'json'
|
5
|
-
|
6
|
-
|
7
|
-
module Spectre
|
8
|
-
module FTP
|
9
|
-
@@cfg = {}
|
10
|
-
|
11
|
-
class FTPConnection < DslClass
|
12
|
-
def initialize host, username, password, opts, logger
|
13
|
-
@__logger = logger
|
14
|
-
@__session = nil
|
15
|
-
|
16
|
-
@__host = host
|
17
|
-
@__username = username
|
18
|
-
@__password = password
|
19
|
-
@__opts = opts
|
20
|
-
end
|
21
|
-
|
22
|
-
def connect!
|
23
|
-
return unless @__session == nil or @__session.closed?
|
24
|
-
@__logger.info "Connecting to '#{@__host}' with user '#{@__username}'"
|
25
|
-
@__session = Net::FTP.new(@__host, @__opts)
|
26
|
-
@__session.login @__username, @__password
|
27
|
-
end
|
28
|
-
|
29
|
-
def close
|
30
|
-
return unless @__session and not @__session.closed?
|
31
|
-
@__session.close
|
32
|
-
end
|
33
|
-
|
34
|
-
def can_connect?
|
35
|
-
begin
|
36
|
-
connect!
|
37
|
-
return true
|
38
|
-
rescue
|
39
|
-
return false
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def download remotefile, to: File.basename(remotefile)
|
44
|
-
connect!
|
45
|
-
@__logger.info "Downloading '#{@__username}@#{@__host}:#{File.join @__session.pwd, remotefile}' to '#{File.expand_path to}'"
|
46
|
-
@__session.getbinaryfile(remotefile, to)
|
47
|
-
end
|
48
|
-
|
49
|
-
def upload localfile, to: File.basename(localfile)
|
50
|
-
connect!
|
51
|
-
@__logger.info "Uploading '#{File.expand_path localfile}' to '#{@__username}@#{@__host}:#{File.join @__session.pwd, to}'"
|
52
|
-
@__session.putbinaryfile(localfile, to)
|
53
|
-
end
|
54
|
-
|
55
|
-
def list
|
56
|
-
connect!
|
57
|
-
file_list = @__session.list
|
58
|
-
@__logger.info "Listing file in #{@__session.pwd}\n#{file_list}"
|
59
|
-
file_list
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
|
64
|
-
class SFTPConnection < DslClass
|
65
|
-
def initialize host, username, opts, logger
|
66
|
-
opts[:non_interactive] = true
|
67
|
-
|
68
|
-
@__logger = logger
|
69
|
-
@__session = nil
|
70
|
-
@__host = host
|
71
|
-
@__username = username
|
72
|
-
@__opts = opts
|
73
|
-
end
|
74
|
-
|
75
|
-
def connect!
|
76
|
-
return unless @__session == nil or @__session.closed?
|
77
|
-
@__logger.info "Connecting to '#{@__host}' with user '#{@__username}'"
|
78
|
-
@__session = Net::SFTP.start(@__host, @__username, @__opts)
|
79
|
-
@__session.connect!
|
80
|
-
end
|
81
|
-
|
82
|
-
def close
|
83
|
-
return unless @__session and not @__session.closed?
|
84
|
-
# @__session.close!
|
85
|
-
end
|
86
|
-
|
87
|
-
def can_connect?
|
88
|
-
begin
|
89
|
-
connect!
|
90
|
-
return true
|
91
|
-
rescue
|
92
|
-
return false
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def download remotefile, to: File.basename(remotefile)
|
97
|
-
@__logger.info "Downloading '#{@__username}@#{@__host}:#{remotefile}' to '#{File.expand_path to}'"
|
98
|
-
connect!
|
99
|
-
@__session.download!(remotefile, to)
|
100
|
-
end
|
101
|
-
|
102
|
-
def upload localfile, to: File.basename(localfile)
|
103
|
-
@__logger.info "Uploading '#{File.expand_path localfile}' to '#{@__username}@#{@__host}:#{to}'"
|
104
|
-
connect!
|
105
|
-
@__session.upload!(localfile, to)
|
106
|
-
end
|
107
|
-
|
108
|
-
def stat path
|
109
|
-
connect!
|
110
|
-
file_info = @__session.stat! path
|
111
|
-
@__logger.info "Stat '#{path}'\n#{JSON.pretty_generate file_info.attributes}"
|
112
|
-
file_info.attributes
|
113
|
-
end
|
114
|
-
|
115
|
-
def exists path
|
116
|
-
begin
|
117
|
-
file_info = @__session.stat! path
|
118
|
-
|
119
|
-
rescue Net::SFTP::StatusException => e
|
120
|
-
return false if e.description == 'no such file'
|
121
|
-
raise e
|
122
|
-
end
|
123
|
-
|
124
|
-
return true
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
|
129
|
-
class << self
|
130
|
-
def ftp name, config={}, &block
|
131
|
-
raise "FTP connection '#{name}' not configured" unless @@cfg.key?(name) or config.count > 0
|
132
|
-
cfg = @@cfg[name] || {}
|
133
|
-
|
134
|
-
host = config[:host] || cfg['host'] || name
|
135
|
-
username = config[:username] || cfg['username']
|
136
|
-
password = config[:password] || cfg['password']
|
137
|
-
|
138
|
-
opts = {}
|
139
|
-
opts[:ssl] = config[:ssl]
|
140
|
-
opts[:port] = config[:port] || cfg['port'] || 21
|
141
|
-
|
142
|
-
@@logger.info "Connecting to #{host} with user #{username}"
|
143
|
-
|
144
|
-
ftp_conn = FTPConnection.new(host, username, password, opts, @@logger)
|
145
|
-
|
146
|
-
begin
|
147
|
-
ftp_conn.instance_eval &block
|
148
|
-
ensure
|
149
|
-
ftp_conn.close
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def sftp name, config={}, &block
|
154
|
-
raise "FTP connection '#{name}' not configured" unless @@cfg.key?(name) or config.count > 0
|
155
|
-
|
156
|
-
cfg = @@cfg[name] || {}
|
157
|
-
|
158
|
-
host = config[:host] || cfg['host'] || name
|
159
|
-
username = config[:username] || cfg['username']
|
160
|
-
password = config[:password] || cfg['password']
|
161
|
-
|
162
|
-
opts = {}
|
163
|
-
opts[:password] = password
|
164
|
-
opts[:port] = config[:port] || cfg['port'] || 22
|
165
|
-
opts[:keys] = [cfg['key']] if cfg.key? 'key'
|
166
|
-
opts[:passphrase] = cfg['passphrase'] if cfg.key? 'passphrase'
|
167
|
-
|
168
|
-
opts[:auth_methods] = []
|
169
|
-
opts[:auth_methods].push 'publickey' if opts[:keys]
|
170
|
-
opts[:auth_methods].push 'password' if opts[:password]
|
171
|
-
|
172
|
-
sftp_con = SFTPConnection.new(host, username, opts, @@logger)
|
173
|
-
|
174
|
-
begin
|
175
|
-
sftp_con.instance_eval &block
|
176
|
-
ensure
|
177
|
-
sftp_con.close
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
Spectre.register do |config|
|
183
|
-
@@logger = ::Logger.new config['log_file'], progname: 'spectre/ftp'
|
184
|
-
|
185
|
-
if config.key? 'ftp'
|
186
|
-
|
187
|
-
config['ftp'].each do |name, cfg|
|
188
|
-
@@cfg[name] = cfg
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
Spectre.delegate :ftp, :sftp, to: self
|
194
|
-
end
|
195
|
-
end
|
data/lib/spectre/mysql.rb
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
require 'mysql2'
|
2
|
-
|
3
|
-
module Spectre
|
4
|
-
module MySQL
|
5
|
-
|
6
|
-
class MySqlQuery < DslClass
|
7
|
-
def initialize query
|
8
|
-
@__query = query
|
9
|
-
end
|
10
|
-
|
11
|
-
def host hostname
|
12
|
-
@__query['host'] = hostname
|
13
|
-
end
|
14
|
-
|
15
|
-
def username user
|
16
|
-
@__query['username'] = user
|
17
|
-
end
|
18
|
-
|
19
|
-
def password pass
|
20
|
-
@__query['password'] = pass
|
21
|
-
end
|
22
|
-
|
23
|
-
def database name
|
24
|
-
@__query['database'] = name
|
25
|
-
end
|
26
|
-
|
27
|
-
def query statement
|
28
|
-
@__query['query'] = [] if not @__query.key? 'query'
|
29
|
-
@__query['query'].append(statement)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class << self
|
34
|
-
@@mysql_cfg = {}
|
35
|
-
@@result = nil
|
36
|
-
@@last_conn = nil
|
37
|
-
|
38
|
-
def mysql name = nil, &block
|
39
|
-
query = {}
|
40
|
-
|
41
|
-
if name != nil and @@mysql_cfg.key? name
|
42
|
-
query.merge! @@mysql_cfg[name]
|
43
|
-
raise "No `host' set for MySQL client '#{name}'. Check your MySQL config in your environment." if !query['host']
|
44
|
-
elsif name != nil
|
45
|
-
query['host'] = name
|
46
|
-
elsif @@last_conn == nil
|
47
|
-
raise 'No name given and there was no previous MySQL connection to use'
|
48
|
-
end
|
49
|
-
|
50
|
-
MySqlQuery.new(query).instance_eval(&block) if block_given?
|
51
|
-
|
52
|
-
if name != nil
|
53
|
-
@@last_conn = {
|
54
|
-
host: query['host'],
|
55
|
-
username: query['username'],
|
56
|
-
password: query['password'],
|
57
|
-
database: query['database']
|
58
|
-
}
|
59
|
-
end
|
60
|
-
|
61
|
-
@@logger.info "Connecting to database #{query['username']}@#{query['host']}:#{query['database']}"
|
62
|
-
|
63
|
-
client = ::Mysql2::Client.new(**@@last_conn)
|
64
|
-
|
65
|
-
res = []
|
66
|
-
|
67
|
-
query['query'].each do |statement|
|
68
|
-
@@logger.info 'Executing statement "' + statement + '"'
|
69
|
-
res = client.query(statement, cast_booleans: true)
|
70
|
-
end if query['query']
|
71
|
-
|
72
|
-
@@result = res.map { |row| OpenStruct.new row } if res
|
73
|
-
|
74
|
-
client.close
|
75
|
-
end
|
76
|
-
|
77
|
-
def result
|
78
|
-
raise 'No MySQL query has been executed yet' unless @@result
|
79
|
-
@@result
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
Spectre.register do |config|
|
84
|
-
@@logger = ::Logger.new config['log_file'], progname: 'spectre/mysql'
|
85
|
-
|
86
|
-
if config.key? 'mysql'
|
87
|
-
@@mysql_cfg = {}
|
88
|
-
|
89
|
-
config['mysql'].each do |name, cfg|
|
90
|
-
@@mysql_cfg[name] = cfg
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
Spectre.delegate :mysql, :result, to: self
|
96
|
-
end
|
97
|
-
end
|
data/lib/spectre/ssh.rb
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
require 'net/ssh'
|
2
|
-
require 'logger'
|
3
|
-
|
4
|
-
|
5
|
-
module Spectre
|
6
|
-
module SSH
|
7
|
-
@@cfg = {}
|
8
|
-
|
9
|
-
class SSHConnection < DslClass
|
10
|
-
def initialize host, username, opts, logger
|
11
|
-
opts[:non_interactive] = true
|
12
|
-
|
13
|
-
@__logger = logger
|
14
|
-
@__host = host
|
15
|
-
@__username = username
|
16
|
-
@__opts = opts
|
17
|
-
@__session = nil
|
18
|
-
@__exit_code = nil
|
19
|
-
@__output = ''
|
20
|
-
end
|
21
|
-
|
22
|
-
def file_exists path
|
23
|
-
exec "ls #{path}"
|
24
|
-
exit_code == 0
|
25
|
-
end
|
26
|
-
|
27
|
-
def owner_of path
|
28
|
-
exec "stat -c %U #{path}"
|
29
|
-
output.chomp
|
30
|
-
end
|
31
|
-
|
32
|
-
def connect!
|
33
|
-
return unless @__session == nil or @__session.closed?
|
34
|
-
@__session = Net::SSH.start(@__host, @__username, @__opts)
|
35
|
-
end
|
36
|
-
|
37
|
-
def close
|
38
|
-
return unless @__session and not @__session.closed?
|
39
|
-
@__session.close
|
40
|
-
end
|
41
|
-
|
42
|
-
def can_connect?
|
43
|
-
@__output = nil
|
44
|
-
|
45
|
-
begin
|
46
|
-
connect!
|
47
|
-
@__session.open_channel.close
|
48
|
-
@__output = "successfully connected to #{@__host} with user #{@__username}"
|
49
|
-
@__exit_code = 0
|
50
|
-
return true
|
51
|
-
rescue Exception => e
|
52
|
-
@__logger.error e.message
|
53
|
-
@__output = "unable to connect to #{@__host} with user #{@__username}"
|
54
|
-
@__exit_code = 1
|
55
|
-
end
|
56
|
-
|
57
|
-
return false
|
58
|
-
end
|
59
|
-
|
60
|
-
def exec command
|
61
|
-
connect!
|
62
|
-
|
63
|
-
log_str = "#{@__session.options[:user]}@#{@__session.host} -p #{@__session.options[:port]} #{command}"
|
64
|
-
|
65
|
-
@channel = @__session.open_channel do |channel|
|
66
|
-
channel.exec(command) do |ch, success|
|
67
|
-
abort "could not execute #{command} on #{@__session.host}" unless success
|
68
|
-
|
69
|
-
@__output = ''
|
70
|
-
|
71
|
-
channel.on_data do |ch, data|
|
72
|
-
@__output += data
|
73
|
-
end
|
74
|
-
|
75
|
-
channel.on_extended_data do |ch,type,data|
|
76
|
-
@__output += data
|
77
|
-
end
|
78
|
-
|
79
|
-
channel.on_request('exit-status') do |ch, data|
|
80
|
-
@__exit_code = data.read_long
|
81
|
-
end
|
82
|
-
|
83
|
-
# channel.on_request('exit-signal') do |ch, data|
|
84
|
-
# exit_code = data.read_long
|
85
|
-
# end
|
86
|
-
end
|
87
|
-
|
88
|
-
end
|
89
|
-
|
90
|
-
@channel.wait
|
91
|
-
@__session.loop
|
92
|
-
|
93
|
-
log_str += "\n" + @__output
|
94
|
-
@__logger.info log_str
|
95
|
-
end
|
96
|
-
|
97
|
-
def output
|
98
|
-
@__output
|
99
|
-
end
|
100
|
-
|
101
|
-
def exit_code
|
102
|
-
@__exit_code
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
|
107
|
-
class << self
|
108
|
-
def ssh name, config = {}, &block
|
109
|
-
raise "SSH connection '#{name}' not configured" unless @@cfg.key?(name) or config.count > 0
|
110
|
-
|
111
|
-
cfg = @@cfg[name] || {}
|
112
|
-
|
113
|
-
host = cfg['host'] || name
|
114
|
-
username = config[:username] || cfg['username']
|
115
|
-
password = config[:password] || cfg['password']
|
116
|
-
|
117
|
-
opts = {}
|
118
|
-
opts[:password] = password
|
119
|
-
opts[:port] = config[:port] || cfg['port'] || 22
|
120
|
-
opts[:keys] = [cfg['key']] if cfg.key? 'key'
|
121
|
-
opts[:passphrase] = cfg['passphrase'] if cfg.key? 'passphrase'
|
122
|
-
|
123
|
-
opts[:auth_methods] = []
|
124
|
-
opts[:auth_methods].push 'publickey' if opts[:keys]
|
125
|
-
opts[:auth_methods].push 'password' if opts[:password]
|
126
|
-
|
127
|
-
ssh_con = SSHConnection.new(host, username, opts, @@logger)
|
128
|
-
|
129
|
-
begin
|
130
|
-
ssh_con.instance_eval &block
|
131
|
-
ensure
|
132
|
-
ssh_con.close
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
Spectre.register do |config|
|
138
|
-
@@logger = ::Logger.new config['log_file'], progname: 'spectre/ssh'
|
139
|
-
|
140
|
-
if config.key? 'ssh'
|
141
|
-
config['ssh'].each do |name, cfg|
|
142
|
-
@@cfg[name] = cfg
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
Spectre.delegate :ssh, to: self
|
148
|
-
end
|
149
|
-
end
|