webspicy 0.19.0 → 0.20.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/webspicy +1 -2
- data/lib/webspicy.rb +0 -2
- data/lib/webspicy/configuration.rb +15 -0
- data/lib/webspicy/configuration/scope.rb +1 -0
- data/lib/webspicy/specification.rb +4 -3
- data/lib/webspicy/specification/condition.rb +29 -4
- data/lib/webspicy/specification/err.rb +18 -0
- data/lib/webspicy/specification/oldies.rb +4 -0
- data/lib/webspicy/specification/oldies/bridge.rb +32 -0
- data/lib/webspicy/specification/{errcondition.rb → oldies/errcondition.rb} +5 -0
- data/lib/webspicy/specification/{postcondition.rb → oldies/postcondition.rb} +5 -0
- data/lib/webspicy/specification/{precondition.rb → oldies/precondition.rb} +5 -2
- data/lib/webspicy/specification/post.rb +20 -0
- data/lib/webspicy/specification/post/missing_condition_impl.rb +15 -0
- data/lib/webspicy/specification/post/unexpected_condition_impl.rb +15 -0
- data/lib/webspicy/specification/pre.rb +19 -0
- data/lib/webspicy/specification/{precondition → pre}/global_request_headers.rb +4 -4
- data/lib/webspicy/specification/{precondition → pre}/robust_to_invalid_input.rb +4 -4
- data/lib/webspicy/specification/service.rb +29 -5
- data/lib/webspicy/specification/test_case.rb +3 -0
- data/lib/webspicy/support.rb +12 -2
- data/lib/webspicy/support/colorize.rb +6 -0
- data/lib/webspicy/tester.rb +89 -27
- data/lib/webspicy/tester/assertions.rb +2 -2
- data/lib/webspicy/tester/client.rb +0 -26
- data/lib/webspicy/tester/fakeses.rb +41 -0
- data/lib/webspicy/tester/fakeses/email.rb +38 -0
- data/lib/webspicy/tester/fakesmtp.rb +39 -0
- data/lib/webspicy/tester/fakesmtp/email.rb +27 -0
- data/lib/webspicy/tester/reporter.rb +5 -0
- data/lib/webspicy/tester/reporter/documentation.rb +31 -8
- data/lib/webspicy/tester/reporter/error_count.rb +11 -7
- data/lib/webspicy/tester/reporter/exceptions.rb +2 -0
- data/lib/webspicy/tester/reporter/file_progress.rb +5 -2
- data/lib/webspicy/tester/reporter/file_summary.rb +3 -2
- data/lib/webspicy/tester/reporter/progress.rb +6 -4
- data/lib/webspicy/tester/reporter/summary.rb +9 -7
- data/lib/webspicy/tester/result.rb +16 -13
- data/lib/webspicy/tester/result/errcondition_met.rb +1 -3
- data/lib/webspicy/tester/result/error_schema_met.rb +1 -0
- data/lib/webspicy/tester/result/invocation_succeeded.rb +13 -0
- data/lib/webspicy/tester/result/output_schema_met.rb +1 -0
- data/lib/webspicy/tester/result/postcondition_met.rb +1 -3
- data/lib/webspicy/version.rb +2 -2
- data/lib/webspicy/web/invocation.rb +7 -3
- data/spec/blackbox/commandline.yml +24 -0
- data/spec/blackbox/fixtures/passing/config.rb +9 -0
- data/spec/blackbox/fixtures/passing/formaldef/get.yml +30 -0
- data/spec/unit/specification/{precondition → pre}/test_global_request_headers.rb +9 -4
- data/spec/unit/specification/test_condition.rb +18 -0
- data/spec/unit/tester/fakeses/test_email.rb +40 -0
- data/tasks/test.rake +2 -1
- metadata +34 -18
data/lib/webspicy/support.rb
CHANGED
@@ -3,18 +3,28 @@ module Webspicy
|
|
3
3
|
|
4
4
|
SORL_OPTS = { max: 5, wait: 0.05, raise: false }
|
5
5
|
|
6
|
-
def sooner_or_later(opts = nil)
|
6
|
+
def sooner_or_later(opts = nil, &bl)
|
7
7
|
opts = SORL_OPTS.merge(opts || {})
|
8
8
|
left, wait_ms = opts[:max], opts[:wait]
|
9
|
-
|
9
|
+
r = _sooner_or_later(&bl)
|
10
|
+
until (r && !r.is_a?(Tester::Failure)) || left == 0
|
10
11
|
sleep(wait_ms)
|
11
12
|
wait_ms, left = wait_ms*2, left - 1
|
13
|
+
r = _sooner_or_later(&bl)
|
12
14
|
end
|
15
|
+
raise r if r.is_a?(Tester::Failure)
|
13
16
|
raise TimeoutError, "Timeout on sooner-or-later" if r.nil? && opts[:raise]
|
14
17
|
r
|
15
18
|
end
|
16
19
|
module_function :sooner_or_later
|
17
20
|
|
21
|
+
def _sooner_or_later(&bl)
|
22
|
+
bl.call
|
23
|
+
rescue Tester::Failure => ex
|
24
|
+
ex
|
25
|
+
end
|
26
|
+
module_function :_sooner_or_later
|
27
|
+
|
18
28
|
end # module Support
|
19
29
|
end # module Webspicy
|
20
30
|
require_relative 'support/data_object'
|
@@ -3,11 +3,17 @@ module Webspicy
|
|
3
3
|
module Colorize
|
4
4
|
|
5
5
|
def colorize(str, kind, config = nil)
|
6
|
+
return str if config && !config.colorize
|
6
7
|
color = (config || self.config).colors[kind]
|
7
8
|
ColorizedString[str].colorize(color)
|
8
9
|
end
|
9
10
|
module_function :colorize
|
10
11
|
|
12
|
+
def colorize_section(str, cfg = nil)
|
13
|
+
colorize(str, :section, cfg)
|
14
|
+
end
|
15
|
+
module_function :colorize_section
|
16
|
+
|
11
17
|
def colorize_highlight(str, cfg = nil)
|
12
18
|
colorize(str, :highlight, cfg)
|
13
19
|
end
|
data/lib/webspicy/tester.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Webspicy
|
2
2
|
class Tester
|
3
|
+
extend Forwardable
|
3
4
|
|
4
5
|
class FailFast < Exception; end
|
5
6
|
|
@@ -13,27 +14,23 @@ module Webspicy
|
|
13
14
|
@service = nil
|
14
15
|
@test_case = nil
|
15
16
|
@invocation = nil
|
16
|
-
@
|
17
|
+
@invocation_error = nil
|
18
|
+
@reporter = @config.reporter
|
17
19
|
end
|
18
20
|
attr_reader :config, :scope, :hooks, :client
|
19
21
|
attr_reader :specification, :spec_file
|
20
22
|
attr_reader :service, :test_case
|
21
|
-
attr_reader :invocation, :result
|
23
|
+
attr_reader :invocation, :invocation_error, :result
|
22
24
|
attr_reader :reporter
|
23
25
|
|
26
|
+
def_delegators :@config, *[
|
27
|
+
:world
|
28
|
+
]
|
29
|
+
|
24
30
|
def failfast?
|
25
31
|
config.failfast
|
26
32
|
end
|
27
33
|
|
28
|
-
def default_reporter
|
29
|
-
@reporter = Reporter::Composite.new
|
30
|
-
#@reporter << Reporter::Progress.new
|
31
|
-
@reporter << Reporter::Documentation.new
|
32
|
-
@reporter << Reporter::Exceptions.new
|
33
|
-
@reporter << Reporter::Summary.new
|
34
|
-
@reporter << Reporter::ErrorCount.new
|
35
|
-
end
|
36
|
-
|
37
34
|
def call
|
38
35
|
reporter.init(self)
|
39
36
|
begin
|
@@ -44,26 +41,43 @@ module Webspicy
|
|
44
41
|
reporter.find(Reporter::ErrorCount).report
|
45
42
|
end
|
46
43
|
|
47
|
-
|
44
|
+
def call!
|
45
|
+
res = call
|
46
|
+
abort("KO") unless res == 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_and_call(method, url, mutation)
|
50
|
+
unless tc = scope.find_test_case(method, url)
|
51
|
+
raise Error, "No such service `#{method} #{url}`"
|
52
|
+
end
|
53
|
+
mutated = tc.mutate(mutation)
|
54
|
+
fork_tester(test_case: mutated) do |t|
|
55
|
+
instrumented = t.instrument_test_case
|
56
|
+
t.client.call(instrumented)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def bind_condition(c)
|
61
|
+
c = Specification::Oldies::Bridge.new(c) unless c.respond_to?(:bind)
|
62
|
+
c.bind(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
48
66
|
|
49
67
|
def run_config
|
50
68
|
config.each_scope do |scope|
|
51
69
|
@scope = scope
|
52
70
|
@hooks = Support::Hooks.for(scope.config)
|
53
71
|
@client = scope.get_client
|
54
|
-
reporter.before_all
|
55
|
-
@hooks.fire_before_all(@scope, @client)
|
56
|
-
reporter.before_all_done
|
57
|
-
reporter.before_scope
|
58
72
|
run_scope
|
59
|
-
reporter.scope_done
|
60
|
-
reporter.after_all
|
61
|
-
@hooks.fire_after_all(@scope, @client)
|
62
|
-
reporter.after_all_done
|
63
73
|
end
|
64
74
|
end
|
65
75
|
|
66
76
|
def run_scope
|
77
|
+
reporter.before_all
|
78
|
+
hooks.fire_before_all(self)
|
79
|
+
reporter.before_all_done
|
80
|
+
reporter.before_scope
|
67
81
|
scope.each_specification_file do |spec_file|
|
68
82
|
@specification = load_specification(spec_file)
|
69
83
|
if @specification
|
@@ -75,6 +89,10 @@ module Webspicy
|
|
75
89
|
raise FailFast
|
76
90
|
end
|
77
91
|
end
|
92
|
+
reporter.scope_done
|
93
|
+
reporter.after_all
|
94
|
+
hooks.fire_after_all(self)
|
95
|
+
reporter.after_all_done
|
78
96
|
end
|
79
97
|
|
80
98
|
def load_specification(spec_file)
|
@@ -107,35 +125,79 @@ module Webspicy
|
|
107
125
|
end
|
108
126
|
|
109
127
|
def run_test_case
|
110
|
-
hooks.fire_around(
|
128
|
+
hooks.fire_around(self) do
|
111
129
|
reporter.before_each
|
112
|
-
hooks.fire_before_each(
|
130
|
+
hooks.fire_before_each(self)
|
113
131
|
reporter.before_each_done
|
114
132
|
|
115
133
|
reporter.before_instrument
|
116
|
-
|
134
|
+
instrument_test_case
|
117
135
|
reporter.instrument_done
|
118
136
|
|
119
|
-
|
120
|
-
@invocation = client.call(test_case)
|
121
|
-
reporter.invocation_done
|
137
|
+
call_test_case_target
|
122
138
|
|
123
139
|
reporter.before_assertions
|
124
140
|
check_invocation
|
125
141
|
reporter.assertions_done
|
126
142
|
|
127
143
|
reporter.after_each
|
128
|
-
hooks.fire_after_each(
|
144
|
+
hooks.fire_after_each(self)
|
129
145
|
reporter.after_each_done
|
130
146
|
|
131
147
|
raise FailFast if !result.success? and failfast?
|
132
148
|
end
|
133
149
|
end
|
134
150
|
|
151
|
+
def call_test_case_target
|
152
|
+
reporter.before_invocation
|
153
|
+
@invocation = client.call(test_case)
|
154
|
+
reporter.invocation_done
|
155
|
+
rescue *PASSTHROUGH_EXCEPTIONS
|
156
|
+
raise
|
157
|
+
rescue => ex
|
158
|
+
@invocation_error = ex
|
159
|
+
reporter.invocation_done
|
160
|
+
end
|
161
|
+
|
162
|
+
def instrument_test_case
|
163
|
+
service = test_case.service
|
164
|
+
service.preconditions.each do |pre|
|
165
|
+
instrument_one(pre)
|
166
|
+
end
|
167
|
+
service.postconditions.each do |post|
|
168
|
+
instrument_one(post)
|
169
|
+
end if test_case.example?
|
170
|
+
service.errconditions.each do |err|
|
171
|
+
instrument_one(err)
|
172
|
+
end if test_case.counterexample?
|
173
|
+
config.listeners(:instrument).each do |i|
|
174
|
+
i.call(self)
|
175
|
+
end
|
176
|
+
test_case
|
177
|
+
end
|
178
|
+
|
179
|
+
def instrument_one(condition)
|
180
|
+
bind_condition(condition).instrument
|
181
|
+
rescue ArgumentError
|
182
|
+
raise "#{condition.class} implements old PRE/POST contract"
|
183
|
+
end
|
184
|
+
|
135
185
|
def check_invocation
|
136
186
|
@result = Result.from(self)
|
137
187
|
end
|
138
188
|
|
189
|
+
def fork_tester(binding = {})
|
190
|
+
yield dup.tap{|t|
|
191
|
+
binding.each_pair do |k,v|
|
192
|
+
t.send(:"#{k}=", v)
|
193
|
+
end
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
attr_writer :test_case
|
200
|
+
|
139
201
|
end # class Tester
|
140
202
|
end # module Webspicy
|
141
203
|
require_relative 'tester/reporter'
|
@@ -40,7 +40,7 @@ module Webspicy
|
|
40
40
|
actual_size(target, path) == expected
|
41
41
|
end
|
42
42
|
|
43
|
-
def actual_size(target, path)
|
43
|
+
def actual_size(target, path)
|
44
44
|
target = extract_path(target, path)
|
45
45
|
respond_to!(target, :size).size
|
46
46
|
end
|
@@ -68,7 +68,7 @@ module Webspicy
|
|
68
68
|
an_array(target).find { |t| t[:id] == id }
|
69
69
|
end
|
70
70
|
|
71
|
-
def idFD(element, expected)
|
71
|
+
def idFD(element, expected)
|
72
72
|
expected.keys.all? do |k|
|
73
73
|
value_equal(expected[k], element[k])
|
74
74
|
end
|
@@ -12,32 +12,6 @@ module Webspicy
|
|
12
12
|
:config
|
13
13
|
]
|
14
14
|
|
15
|
-
def find_and_call(method, url, mutation)
|
16
|
-
unless tc = scope.find_test_case(method, url)
|
17
|
-
raise Error, "No such service `#{method} #{url}`"
|
18
|
-
end
|
19
|
-
mutated = tc.mutate(mutation)
|
20
|
-
instrumented = instrument(mutated)
|
21
|
-
call(instrumented)
|
22
|
-
end
|
23
|
-
|
24
|
-
def instrument(test_case)
|
25
|
-
service = test_case.service
|
26
|
-
service.preconditions.each do |pre|
|
27
|
-
pre.instrument(test_case, self) if pre.respond_to?(:instrument)
|
28
|
-
end
|
29
|
-
service.postconditions.each do |post|
|
30
|
-
post.instrument(test_case, self) if post.respond_to?(:instrument)
|
31
|
-
end if test_case.example?
|
32
|
-
service.errconditions.each do |post|
|
33
|
-
post.instrument(test_case, self) if post.respond_to?(:instrument)
|
34
|
-
end if test_case.counterexample?
|
35
|
-
config.listeners(:instrument).each do |i|
|
36
|
-
i.call(test_case, self)
|
37
|
-
end
|
38
|
-
test_case
|
39
|
-
end
|
40
|
-
|
41
15
|
end # class Client
|
42
16
|
end # class Tester
|
43
17
|
end # module Webspicy
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Webspicy
|
4
|
+
class Tester
|
5
|
+
class Fakeses
|
6
|
+
include Webspicy::Support::World::Item
|
7
|
+
|
8
|
+
DEFAULT_OPTIONS = {
|
9
|
+
endpoint: "http://fake-ses/_/api"
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
14
|
+
end
|
15
|
+
attr_reader :options
|
16
|
+
|
17
|
+
def endpoint
|
18
|
+
options[:endpoint]
|
19
|
+
end
|
20
|
+
|
21
|
+
def clear!
|
22
|
+
res = HTTP.post("#{endpoint}/reset")
|
23
|
+
end
|
24
|
+
|
25
|
+
def emails
|
26
|
+
res = HTTP.get("#{endpoint}/emails")
|
27
|
+
JSON.parse(res.body).map{|data| Email.new(data) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def emails_count
|
31
|
+
emails.length
|
32
|
+
end
|
33
|
+
|
34
|
+
def last_email
|
35
|
+
emails.last
|
36
|
+
end
|
37
|
+
|
38
|
+
end # class Fakeses
|
39
|
+
end # class Tester
|
40
|
+
end # module Websipcy
|
41
|
+
require_relative 'fakeses/email'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Tester
|
3
|
+
class Fakeses
|
4
|
+
class Email
|
5
|
+
|
6
|
+
def initialize(data)
|
7
|
+
@data = data
|
8
|
+
end
|
9
|
+
attr_reader :data
|
10
|
+
|
11
|
+
def from
|
12
|
+
data["body"]["Source"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def to
|
16
|
+
data["body"]
|
17
|
+
.each_pair
|
18
|
+
.select{|(k,v)|
|
19
|
+
k =~ /Destinations.member/
|
20
|
+
}
|
21
|
+
.map{|(k,v)| v }
|
22
|
+
end
|
23
|
+
|
24
|
+
def subject
|
25
|
+
rx = /^Subject:\s*(.*)$/
|
26
|
+
raw_data
|
27
|
+
.each_line
|
28
|
+
.find{|l| l =~ rx }[rx, 1]
|
29
|
+
end
|
30
|
+
|
31
|
+
def raw_data
|
32
|
+
@raw_data ||= Base64.decode64(data["body"]["RawMessage.Data"])
|
33
|
+
end
|
34
|
+
|
35
|
+
end # class Email
|
36
|
+
end # class Fakeses
|
37
|
+
end # class Tester
|
38
|
+
end # module Webspicy
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Tester
|
3
|
+
class Fakesmtp
|
4
|
+
include Webspicy::Support::World::Item
|
5
|
+
|
6
|
+
DEFAULT_OPTIONS = {
|
7
|
+
endpoint: "http://fakesmtp"
|
8
|
+
}
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
12
|
+
end
|
13
|
+
attr_reader :options
|
14
|
+
|
15
|
+
def endpoint
|
16
|
+
options[:endpoint]
|
17
|
+
end
|
18
|
+
|
19
|
+
def clear!
|
20
|
+
res = HTTP.delete("#{endpoint}/emails")
|
21
|
+
end
|
22
|
+
|
23
|
+
def emails
|
24
|
+
res = HTTP.get("#{endpoint}/emails")
|
25
|
+
JSON.parse(res.body).map{|data| Email.new(data) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def emails_count
|
29
|
+
emails.length
|
30
|
+
end
|
31
|
+
|
32
|
+
def last_email
|
33
|
+
emails.last
|
34
|
+
end
|
35
|
+
|
36
|
+
end # class Fakesmtp
|
37
|
+
end # class Tester
|
38
|
+
end # module Webspicy
|
39
|
+
require_relative 'fakesmtp/email'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Tester
|
3
|
+
class Fakesmtp
|
4
|
+
class Email
|
5
|
+
|
6
|
+
def initialize(data)
|
7
|
+
@data = data
|
8
|
+
end
|
9
|
+
attr_reader :data
|
10
|
+
|
11
|
+
def from
|
12
|
+
@from ||= data["headerLines"]
|
13
|
+
.select{|h| h["key"] == "from" }
|
14
|
+
.map{|h| h["line"][/From:\s*(.*)$/, 1] }
|
15
|
+
.first
|
16
|
+
end
|
17
|
+
|
18
|
+
def to
|
19
|
+
@to ||= data["headerLines"]
|
20
|
+
.select{|h| h["key"] == "to" }
|
21
|
+
.map{|h| h["line"][/To:\s*(.*)$/, 1] }
|
22
|
+
end
|
23
|
+
|
24
|
+
end # class Email
|
25
|
+
end # class Fakesmtp
|
26
|
+
end # class Tester
|
27
|
+
end # module Webspicy
|