webspicy 0.19.0 → 0.20.4
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/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
|