webspicy 0.19.0 → 0.20.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/lib/webspicy.rb +0 -2
- 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 +29 -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 +26 -5
- data/lib/webspicy/support.rb +12 -2
- data/lib/webspicy/tester.rb +72 -11
- 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/documentation.rb +7 -0
- data/lib/webspicy/tester/reporter/exceptions.rb +2 -0
- data/lib/webspicy/tester/reporter/file_progress.rb +3 -0
- data/lib/webspicy/tester/reporter/file_summary.rb +1 -0
- data/lib/webspicy/tester/reporter/progress.rb +2 -0
- data/lib/webspicy/tester/reporter/summary.rb +1 -0
- data/lib/webspicy/tester/result.rb +16 -13
- data/lib/webspicy/tester/result/errcondition_met.rb +1 -1
- data/lib/webspicy/tester/result/invocation_succeeded.rb +13 -0
- data/lib/webspicy/tester/result/postcondition_met.rb +1 -1
- data/lib/webspicy/version.rb +1 -1
- 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
- metadata +21 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0bfd23b321b3e1216316e09c2b9e2d761a9e3179c62830d4b6a99b13042f4cc
|
4
|
+
data.tar.gz: 0661ba60f10ac2433bb5952d3c17141d880363e1d0c763836877a08b9673a8d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d276e4fb01cfd084e94917d6987b9e545c8c28edeaa082b55330ac3978f28d3c358e39f9a09ceda6f0aff59e55f9ff0fd989c2f9c247885d78a6d89a7a82f37d
|
7
|
+
data.tar.gz: bdfa965f896d9167432a8dbb4404720b9f278b590d031f92d8ad44c501be9a03e922a5bb58ae667644de803743210668cde3726ae0b901e6750f07ecb2718011
|
data/lib/webspicy.rb
CHANGED
@@ -33,8 +33,6 @@ module Webspicy
|
|
33
33
|
HttpClient = Web::HttpClient
|
34
34
|
RackTestClient = Web::RackTestClient
|
35
35
|
Resource = Specification
|
36
|
-
Precondition = Specification::Precondition
|
37
|
-
Postcondition = Specification::Postcondition
|
38
36
|
FileUpload = Specification::FileUpload
|
39
37
|
Scope = Configuration::Scope
|
40
38
|
Checker = Tester::FileChecker
|
@@ -94,8 +94,9 @@ module Webspicy
|
|
94
94
|
end # module Webspicy
|
95
95
|
require_relative 'specification/service'
|
96
96
|
require_relative 'specification/condition'
|
97
|
-
require_relative 'specification/
|
98
|
-
require_relative 'specification/
|
99
|
-
require_relative 'specification/
|
97
|
+
require_relative 'specification/pre'
|
98
|
+
require_relative 'specification/post'
|
99
|
+
require_relative 'specification/err'
|
100
|
+
require_relative 'specification/oldies'
|
100
101
|
require_relative 'specification/test_case'
|
101
102
|
require_relative 'specification/file_upload'
|
@@ -1,11 +1,40 @@
|
|
1
1
|
module Webspicy
|
2
2
|
class Specification
|
3
3
|
module Condition
|
4
|
+
extend Forwardable
|
4
5
|
|
5
6
|
MATCH_ALL = "__all__"
|
6
7
|
|
7
8
|
attr_accessor :matching_description
|
8
9
|
|
10
|
+
# Given a service and a condition, returns a Pre instance of there is a
|
11
|
+
# match, nil otherwise.
|
12
|
+
def self.match(service, condition)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Bind the condition instance to a current tester.
|
16
|
+
def bind(tester)
|
17
|
+
@tester = tester
|
18
|
+
self
|
19
|
+
end
|
20
|
+
attr_reader :tester
|
21
|
+
|
22
|
+
def_delegators :@tester, *[
|
23
|
+
:config, :scope, :client,
|
24
|
+
:specification, :spec_file,
|
25
|
+
:service, :test_case,
|
26
|
+
:invocation, :result,
|
27
|
+
:reporter
|
28
|
+
]
|
29
|
+
|
30
|
+
def sooner_or_later(*args, &bl)
|
31
|
+
Webspicy::Support.sooner_or_later(*args, &bl)
|
32
|
+
end
|
33
|
+
|
34
|
+
def fail!(msg)
|
35
|
+
raise Tester::Failure, msg
|
36
|
+
end
|
37
|
+
|
9
38
|
def to_s
|
10
39
|
if matching_description == MATCH_ALL
|
11
40
|
self.class.name
|
@@ -14,10 +43,6 @@ module Webspicy
|
|
14
43
|
end
|
15
44
|
end
|
16
45
|
|
17
|
-
def sooner_or_later(*args, &bl)
|
18
|
-
Webspicy::Support.sooner_or_later(*args, &bl)
|
19
|
-
end
|
20
|
-
|
21
46
|
end # module Condition
|
22
47
|
end # class Specification
|
23
48
|
end # module Webspicy
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Err
|
4
|
+
include Condition
|
5
|
+
|
6
|
+
# Instrument the current test_case so as to prepare for errcondition
|
7
|
+
# check later
|
8
|
+
def instrument
|
9
|
+
end
|
10
|
+
|
11
|
+
# Check that the errcondition is met on last invocation & result
|
12
|
+
# of an counterexample
|
13
|
+
def check!
|
14
|
+
end
|
15
|
+
|
16
|
+
end # module Err
|
17
|
+
end # module Specification
|
18
|
+
end # module Webspicy
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Oldies
|
4
|
+
class Bridge
|
5
|
+
include Condition
|
6
|
+
|
7
|
+
def initialize(target)
|
8
|
+
@target = target
|
9
|
+
end
|
10
|
+
attr_reader :target
|
11
|
+
|
12
|
+
def instrument
|
13
|
+
target.instrument(test_case, client)
|
14
|
+
end
|
15
|
+
|
16
|
+
def check!
|
17
|
+
target.check(invocation)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"#{target} (backward compatibility bridge)"
|
22
|
+
end
|
23
|
+
|
24
|
+
end # class Bridge
|
25
|
+
end # module Errcondition
|
26
|
+
end # module Specification
|
27
|
+
Precondition = Specification::Precondition
|
28
|
+
Postcondition = Specification::Postcondition
|
29
|
+
end # module Webspicy
|
@@ -1,8 +1,13 @@
|
|
1
1
|
module Webspicy
|
2
2
|
class Specification
|
3
|
+
# Deprecated, use Pre instead
|
3
4
|
module Precondition
|
4
5
|
include Condition
|
5
6
|
|
7
|
+
def bind(tester)
|
8
|
+
Oldies::Bridge.new(self).bind(tester)
|
9
|
+
end
|
10
|
+
|
6
11
|
def self.match(service, pre)
|
7
12
|
end
|
8
13
|
|
@@ -16,5 +21,3 @@ module Webspicy
|
|
16
21
|
end # module Precondition
|
17
22
|
end # class Specification
|
18
23
|
end # module Webspicy
|
19
|
-
require_relative 'precondition/global_request_headers'
|
20
|
-
require_relative 'precondition/robust_to_invalid_input'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Post
|
4
|
+
include Condition
|
5
|
+
|
6
|
+
# Instrument the current test_case so as to prepare for postcondition
|
7
|
+
# check later
|
8
|
+
def instrument
|
9
|
+
end
|
10
|
+
|
11
|
+
# Check that the postcondition is met on last invocation & result
|
12
|
+
# of an example
|
13
|
+
def check!
|
14
|
+
end
|
15
|
+
|
16
|
+
end # module Post
|
17
|
+
end # module Specification
|
18
|
+
end # module Webspicy
|
19
|
+
require_relative "post/missing_condition_impl"
|
20
|
+
require_relative "post/unexpected_condition_impl"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Postcondition
|
4
|
+
class MissingConditionImpl
|
5
|
+
include Post
|
6
|
+
|
7
|
+
def check
|
8
|
+
msg = matching_description.gsub(/\(x\)/, "<!>")
|
9
|
+
raise "#{msg} (not instrumented)"
|
10
|
+
end
|
11
|
+
|
12
|
+
end # class MissingConditionImpl
|
13
|
+
end # module Postcondition
|
14
|
+
end # class Specification
|
15
|
+
end # module Webspicy
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Postcondition
|
4
|
+
class UnexpectedConditionImpl
|
5
|
+
include Post
|
6
|
+
|
7
|
+
def check
|
8
|
+
msg = matching_description.gsub(/\( \)/, "<x>")
|
9
|
+
raise "#{msg} (is instrumented)"
|
10
|
+
end
|
11
|
+
|
12
|
+
end # class UnexpectedConditionImpl
|
13
|
+
end # module Postcondition
|
14
|
+
end # class Specification
|
15
|
+
end # module Webspicy
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Webspicy
|
2
|
+
class Specification
|
3
|
+
module Pre
|
4
|
+
include Condition
|
5
|
+
|
6
|
+
# Instrument the current test_case so as to meet the precondition
|
7
|
+
def instrument
|
8
|
+
end
|
9
|
+
|
10
|
+
# Provide counterexamples of this precondition for a given service.
|
11
|
+
def counterexamples(service)
|
12
|
+
[]
|
13
|
+
end
|
14
|
+
|
15
|
+
end # module Pre
|
16
|
+
end # class Specification
|
17
|
+
end # module Webspicy
|
18
|
+
require_relative 'pre/global_request_headers'
|
19
|
+
require_relative 'pre/robust_to_invalid_input'
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Webspicy
|
2
2
|
class Specification
|
3
|
-
module
|
3
|
+
module Pre
|
4
4
|
class GlobalRequestHeaders
|
5
|
-
include
|
5
|
+
include Pre
|
6
6
|
|
7
7
|
DEFAULT_OPTIONS = {}
|
8
8
|
|
@@ -22,7 +22,7 @@ module Webspicy
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
def instrument
|
25
|
+
def instrument
|
26
26
|
extra = headers.reject{|k|
|
27
27
|
test_case.headers.has_key?(k)
|
28
28
|
}
|
@@ -30,6 +30,6 @@ module Webspicy
|
|
30
30
|
end
|
31
31
|
|
32
32
|
end # class GlobalRequestHeaders
|
33
|
-
end # module
|
33
|
+
end # module Pre
|
34
34
|
end # class Specification
|
35
35
|
end # module Webspicy
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Webspicy
|
2
2
|
class Specification
|
3
|
-
module
|
3
|
+
module Pre
|
4
4
|
class RobustToInvalidInput
|
5
|
-
include
|
5
|
+
include Pre
|
6
6
|
|
7
7
|
def self.match(service, pre)
|
8
8
|
self.new
|
@@ -49,7 +49,7 @@ module Webspicy
|
|
49
49
|
status: Support::StatusRange.str("4xx")
|
50
50
|
},
|
51
51
|
:assert => []
|
52
|
-
})]
|
52
|
+
})]
|
53
53
|
else
|
54
54
|
[]
|
55
55
|
end
|
@@ -63,6 +63,6 @@ module Webspicy
|
|
63
63
|
end
|
64
64
|
|
65
65
|
end # class RobustToInvalidInput
|
66
|
-
end # module
|
66
|
+
end # module Pre
|
67
67
|
end # class Specification
|
68
68
|
end # module Webspicy
|
@@ -112,16 +112,37 @@ module Webspicy
|
|
112
112
|
# Because we want pre & post to be able to match in all cases
|
113
113
|
# we need at least one condition
|
114
114
|
descriptions = [Condition::MATCH_ALL] if descriptions.empty?
|
115
|
-
|
115
|
+
mapping = {}
|
116
|
+
instances = conditions.map{|c|
|
116
117
|
instance = nil
|
117
118
|
descr = descriptions.find do |d|
|
118
119
|
instance = c.match(self, d)
|
119
120
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
121
|
+
instance.tap{|i|
|
122
|
+
mapping[descr] ||= i if i
|
123
|
+
i.matching_description = descr if i.respond_to?(:matching_description=)
|
124
|
+
}
|
124
125
|
}.compact
|
126
|
+
mapped = descriptions
|
127
|
+
.select{|d| mapping[d] }
|
128
|
+
.map{|d| mapping[d] }
|
129
|
+
unmapped = descriptions
|
130
|
+
.reject{|d| mapping[d] }
|
131
|
+
.select{|d| d.strip =~ /^(\(\w+\))?\(x\)/ }
|
132
|
+
.map{|d|
|
133
|
+
Postcondition::MissingConditionImpl.new.tap{|mc|
|
134
|
+
mc.matching_description = d
|
135
|
+
}
|
136
|
+
}
|
137
|
+
unexpected = descriptions
|
138
|
+
.select{|d| mapping[d] }
|
139
|
+
.select{|d| d.strip =~ /^(\(\w+\))?\( \)/ }
|
140
|
+
.map{|d|
|
141
|
+
Postcondition::UnexpectedConditionImpl.new.tap{|mc|
|
142
|
+
mc.matching_description = d
|
143
|
+
}
|
144
|
+
}
|
145
|
+
mapped + unmapped + unexpected
|
125
146
|
end
|
126
147
|
|
127
148
|
def bind_examples
|
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'
|
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,14 +14,19 @@ module Webspicy
|
|
13
14
|
@service = nil
|
14
15
|
@test_case = nil
|
15
16
|
@invocation = nil
|
17
|
+
@invocation_error = nil
|
16
18
|
@reporter = default_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
|
@@ -44,7 +50,18 @@ module Webspicy
|
|
44
50
|
reporter.find(Reporter::ErrorCount).report
|
45
51
|
end
|
46
52
|
|
47
|
-
|
53
|
+
def find_and_call(method, url, mutation)
|
54
|
+
unless tc = scope.find_test_case(method, url)
|
55
|
+
raise Error, "No such service `#{method} #{url}`"
|
56
|
+
end
|
57
|
+
mutated = tc.mutate(mutation)
|
58
|
+
fork_tester(test_case: mutated) do |t|
|
59
|
+
instrumented = t.instrument_test_case
|
60
|
+
t.client.call(instrumented)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
48
65
|
|
49
66
|
def run_config
|
50
67
|
config.each_scope do |scope|
|
@@ -52,13 +69,13 @@ module Webspicy
|
|
52
69
|
@hooks = Support::Hooks.for(scope.config)
|
53
70
|
@client = scope.get_client
|
54
71
|
reporter.before_all
|
55
|
-
@hooks.fire_before_all(
|
72
|
+
@hooks.fire_before_all(self)
|
56
73
|
reporter.before_all_done
|
57
74
|
reporter.before_scope
|
58
75
|
run_scope
|
59
76
|
reporter.scope_done
|
60
77
|
reporter.after_all
|
61
|
-
@hooks.fire_after_all(
|
78
|
+
@hooks.fire_after_all(self)
|
62
79
|
reporter.after_all_done
|
63
80
|
end
|
64
81
|
end
|
@@ -107,35 +124,79 @@ module Webspicy
|
|
107
124
|
end
|
108
125
|
|
109
126
|
def run_test_case
|
110
|
-
hooks.fire_around(
|
127
|
+
hooks.fire_around(self) do
|
111
128
|
reporter.before_each
|
112
|
-
hooks.fire_before_each(
|
129
|
+
hooks.fire_before_each(self)
|
113
130
|
reporter.before_each_done
|
114
131
|
|
115
132
|
reporter.before_instrument
|
116
|
-
|
133
|
+
instrument_test_case
|
117
134
|
reporter.instrument_done
|
118
135
|
|
119
|
-
|
120
|
-
@invocation = client.call(test_case)
|
121
|
-
reporter.invocation_done
|
136
|
+
call_test_case_target
|
122
137
|
|
123
138
|
reporter.before_assertions
|
124
139
|
check_invocation
|
125
140
|
reporter.assertions_done
|
126
141
|
|
127
142
|
reporter.after_each
|
128
|
-
hooks.fire_after_each(
|
143
|
+
hooks.fire_after_each(self)
|
129
144
|
reporter.after_each_done
|
130
145
|
|
131
146
|
raise FailFast if !result.success? and failfast?
|
132
147
|
end
|
133
148
|
end
|
134
149
|
|
150
|
+
def call_test_case_target
|
151
|
+
reporter.before_invocation
|
152
|
+
@invocation = client.call(test_case)
|
153
|
+
reporter.invocation_done
|
154
|
+
rescue *PASSTHROUGH_EXCEPTIONS
|
155
|
+
raise
|
156
|
+
rescue => ex
|
157
|
+
@invocation_error = ex
|
158
|
+
reporter.invocation_done
|
159
|
+
end
|
160
|
+
|
161
|
+
def instrument_test_case
|
162
|
+
service = test_case.service
|
163
|
+
service.preconditions.each do |pre|
|
164
|
+
instrument_one(pre)
|
165
|
+
end
|
166
|
+
service.postconditions.each do |post|
|
167
|
+
instrument_one(post)
|
168
|
+
end if test_case.example?
|
169
|
+
service.errconditions.each do |err|
|
170
|
+
instrument_one(err)
|
171
|
+
end if test_case.counterexample?
|
172
|
+
config.listeners(:instrument).each do |i|
|
173
|
+
i.call(self)
|
174
|
+
end
|
175
|
+
test_case
|
176
|
+
end
|
177
|
+
|
178
|
+
def instrument_one(condition)
|
179
|
+
condition.bind(self).instrument
|
180
|
+
rescue ArgumentError
|
181
|
+
raise "#{condition.class} implements old PRE/POST contract"
|
182
|
+
end
|
183
|
+
|
135
184
|
def check_invocation
|
136
185
|
@result = Result.from(self)
|
137
186
|
end
|
138
187
|
|
188
|
+
def fork_tester(binding = {})
|
189
|
+
yield dup.tap{|t|
|
190
|
+
binding.each_pair do |k,v|
|
191
|
+
t.send(:"#{k}=", v)
|
192
|
+
end
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
attr_writer :test_case
|
199
|
+
|
139
200
|
end # class Tester
|
140
201
|
end # module Webspicy
|
141
202
|
require_relative 'tester/reporter'
|
@@ -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
|
@@ -35,30 +35,37 @@ module Webspicy
|
|
35
35
|
def spec_file_error(e)
|
36
36
|
io.puts spec_file_error_line(spec_file, e)
|
37
37
|
io.puts
|
38
|
+
io.flush
|
38
39
|
end
|
39
40
|
|
40
41
|
def before_test_case
|
41
42
|
io.puts service_line(service, test_case)
|
43
|
+
io.flush
|
42
44
|
end
|
43
45
|
|
44
46
|
def check_success(check)
|
45
47
|
io.puts check_success_line(check)
|
48
|
+
io.flush
|
46
49
|
end
|
47
50
|
|
48
51
|
def check_failure(check, ex)
|
49
52
|
io.puts check_failure_line(check, ex)
|
53
|
+
io.flush
|
50
54
|
end
|
51
55
|
|
52
56
|
def check_error(check, ex)
|
53
57
|
io.puts check_error_line(check, ex)
|
58
|
+
io.flush
|
54
59
|
end
|
55
60
|
|
56
61
|
def test_case_done
|
57
62
|
io.puts
|
63
|
+
io.flush
|
58
64
|
end
|
59
65
|
|
60
66
|
def service_done
|
61
67
|
io.puts
|
68
|
+
io.flush
|
62
69
|
end
|
63
70
|
|
64
71
|
end # class Documentation
|
@@ -5,15 +5,18 @@ module Webspicy
|
|
5
5
|
|
6
6
|
def spec_file_error(e)
|
7
7
|
io.print colorize_error("X")
|
8
|
+
io.flush
|
8
9
|
end
|
9
10
|
|
10
11
|
def spec_file_done
|
11
12
|
io.print colorize_success(".")
|
13
|
+
io.flush
|
12
14
|
end
|
13
15
|
|
14
16
|
def report
|
15
17
|
io.puts
|
16
18
|
io.puts
|
19
|
+
io.flush
|
17
20
|
end
|
18
21
|
|
19
22
|
end # class FileProgress
|
@@ -5,26 +5,28 @@ module Webspicy
|
|
5
5
|
|
6
6
|
def initialize(tester)
|
7
7
|
@tester = tester
|
8
|
+
@scope = tester.scope
|
9
|
+
@client = tester.client
|
10
|
+
@specification = tester.specification
|
11
|
+
@service = tester.service
|
12
|
+
@test_case = tester.test_case
|
8
13
|
@invocation = tester.invocation
|
9
14
|
@assertions = []
|
10
15
|
@failures = []
|
11
16
|
@errors = []
|
12
|
-
|
17
|
+
if @invocation
|
18
|
+
check!
|
19
|
+
else
|
20
|
+
@errors << [InvocationSuceeded.new(self), tester.invocation_error]
|
21
|
+
end
|
13
22
|
end
|
14
|
-
attr_reader :tester, :
|
23
|
+
attr_reader :tester, :scope, :client
|
24
|
+
attr_reader :specification, :service, :test_case, :invocation
|
15
25
|
attr_reader :assertions, :failures, :errors
|
16
26
|
|
17
27
|
def_delegators :@tester, *[
|
18
|
-
:reporter
|
19
|
-
]
|
20
|
-
|
21
|
-
def_delegators :@invocation, *[
|
22
28
|
:config,
|
23
|
-
:
|
24
|
-
:client,
|
25
|
-
:specification,
|
26
|
-
:service,
|
27
|
-
:test_case
|
29
|
+
:reporter
|
28
30
|
]
|
29
31
|
|
30
32
|
def self.from(tester)
|
@@ -94,13 +96,13 @@ module Webspicy
|
|
94
96
|
|
95
97
|
def check_postconditions!
|
96
98
|
service.postconditions.each do |c|
|
97
|
-
check_one! Result::PostconditionMet.new(self, c)
|
99
|
+
check_one! Result::PostconditionMet.new(self, c.bind(tester))
|
98
100
|
end
|
99
101
|
end
|
100
102
|
|
101
103
|
def check_errconditions!
|
102
104
|
service.errconditions.each do |c|
|
103
|
-
check_one! Result::ErrconditionMet.new(self, c)
|
105
|
+
check_one! Result::ErrconditionMet.new(self, c.bind(tester))
|
104
106
|
end
|
105
107
|
end
|
106
108
|
|
@@ -130,6 +132,7 @@ module Webspicy
|
|
130
132
|
end # class Tester
|
131
133
|
end # module Webspicy
|
132
134
|
require_relative "result/check"
|
135
|
+
require_relative "result/invocation_succeeded"
|
133
136
|
require_relative "result/response_status_met"
|
134
137
|
require_relative "result/response_header_met"
|
135
138
|
require_relative "result/output_schema_met"
|
data/lib/webspicy/version.rb
CHANGED
@@ -2,16 +2,21 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Webspicy
|
4
4
|
class Specification
|
5
|
-
module
|
5
|
+
module Pre
|
6
6
|
describe GlobalRequestHeaders do
|
7
7
|
let(:gbr){
|
8
8
|
GlobalRequestHeaders.new('Accept' => 'application/json')
|
9
9
|
}
|
10
10
|
|
11
|
+
def instrument(tc)
|
12
|
+
t = OpenStruct.new(test_case: tc)
|
13
|
+
gbr.bind(t).instrument
|
14
|
+
end
|
15
|
+
|
11
16
|
describe "instrument" do
|
12
17
|
it 'injects the headers' do
|
13
18
|
tc = TestCase.new({})
|
14
|
-
|
19
|
+
instrument(tc)
|
15
20
|
expect(tc.headers['Accept']).to eql("application/json")
|
16
21
|
end
|
17
22
|
|
@@ -21,7 +26,7 @@ module Webspicy
|
|
21
26
|
'Content-Type' => 'text/plain'
|
22
27
|
}
|
23
28
|
})
|
24
|
-
|
29
|
+
instrument(tc)
|
25
30
|
expect(tc.headers['Content-Type']).to eql("text/plain")
|
26
31
|
expect(tc.headers['Accept']).to eql("application/json")
|
27
32
|
end
|
@@ -32,7 +37,7 @@ module Webspicy
|
|
32
37
|
'Accept' => 'text/plain'
|
33
38
|
}
|
34
39
|
})
|
35
|
-
|
40
|
+
instrument(tc)
|
36
41
|
expect(tc.headers['Accept']).to eql("text/plain")
|
37
42
|
end
|
38
43
|
end
|
@@ -15,6 +15,24 @@ module Webspicy
|
|
15
15
|
expect(x).to eql(nil)
|
16
16
|
end
|
17
17
|
|
18
|
+
it 'supports the block raising a Failure' do
|
19
|
+
expect {
|
20
|
+
sooner_or_later(max: 2){
|
21
|
+
raise Tester::Failure
|
22
|
+
}
|
23
|
+
}.to raise_error(Tester::Failure)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'catches Failure occuring before successes' do
|
27
|
+
seen = 0
|
28
|
+
x = sooner_or_later(max: 2){
|
29
|
+
seen += 1
|
30
|
+
raise Tester::Failure if seen == 1
|
31
|
+
12
|
32
|
+
}
|
33
|
+
expect(x).to eql(12)
|
34
|
+
end
|
35
|
+
|
18
36
|
it 'can raise for us' do
|
19
37
|
expect{
|
20
38
|
sooner_or_later(max: 1, raise: true){ nil }
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'webspicy/tester/fakeses'
|
3
|
+
module Webspicy
|
4
|
+
class Tester
|
5
|
+
class Fakeses
|
6
|
+
describe Email do
|
7
|
+
|
8
|
+
DATA = Base64.encode64 <<~J
|
9
|
+
From: Webspicy <noreply@webspicy.io>
|
10
|
+
To: someone@world.com
|
11
|
+
Subject: Hey world, hello!
|
12
|
+
J
|
13
|
+
|
14
|
+
DATA = JSON.parse <<~J
|
15
|
+
{
|
16
|
+
"id": "1782605f-da34-9c02-6a38-d7e101029cbf",
|
17
|
+
"body": {
|
18
|
+
"Source": "noreply@webspicy.io",
|
19
|
+
"Destinations.member.1": "someone@world.com",
|
20
|
+
"RawMessage.Data": "#{DATA.gsub /\n/, ''}",
|
21
|
+
"Action": "SendRawEmail",
|
22
|
+
"Version": "2010-12-01"
|
23
|
+
}
|
24
|
+
}
|
25
|
+
J
|
26
|
+
|
27
|
+
subject{
|
28
|
+
Email.new(DATA)
|
29
|
+
}
|
30
|
+
|
31
|
+
it 'works as expected' do
|
32
|
+
expect(subject.from).to eql("noreply@webspicy.io")
|
33
|
+
expect(subject.to).to eql(["someone@world.com"])
|
34
|
+
expect(subject.subject).to eql("Hey world, hello!")
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webspicy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.20.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bernard Lambeau
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -222,12 +222,19 @@ files:
|
|
222
222
|
- lib/webspicy/formaldoc.fio
|
223
223
|
- lib/webspicy/specification.rb
|
224
224
|
- lib/webspicy/specification/condition.rb
|
225
|
-
- lib/webspicy/specification/
|
225
|
+
- lib/webspicy/specification/err.rb
|
226
226
|
- lib/webspicy/specification/file_upload.rb
|
227
|
-
- lib/webspicy/specification/
|
228
|
-
- lib/webspicy/specification/
|
229
|
-
- lib/webspicy/specification/
|
230
|
-
- lib/webspicy/specification/
|
227
|
+
- lib/webspicy/specification/oldies.rb
|
228
|
+
- lib/webspicy/specification/oldies/bridge.rb
|
229
|
+
- lib/webspicy/specification/oldies/errcondition.rb
|
230
|
+
- lib/webspicy/specification/oldies/postcondition.rb
|
231
|
+
- lib/webspicy/specification/oldies/precondition.rb
|
232
|
+
- lib/webspicy/specification/post.rb
|
233
|
+
- lib/webspicy/specification/post/missing_condition_impl.rb
|
234
|
+
- lib/webspicy/specification/post/unexpected_condition_impl.rb
|
235
|
+
- lib/webspicy/specification/pre.rb
|
236
|
+
- lib/webspicy/specification/pre/global_request_headers.rb
|
237
|
+
- lib/webspicy/specification/pre/robust_to_invalid_input.rb
|
231
238
|
- lib/webspicy/specification/service.rb
|
232
239
|
- lib/webspicy/specification/test_case.rb
|
233
240
|
- lib/webspicy/support.rb
|
@@ -241,6 +248,10 @@ files:
|
|
241
248
|
- lib/webspicy/tester/assertions.rb
|
242
249
|
- lib/webspicy/tester/client.rb
|
243
250
|
- lib/webspicy/tester/failure.rb
|
251
|
+
- lib/webspicy/tester/fakeses.rb
|
252
|
+
- lib/webspicy/tester/fakeses/email.rb
|
253
|
+
- lib/webspicy/tester/fakesmtp.rb
|
254
|
+
- lib/webspicy/tester/fakesmtp/email.rb
|
244
255
|
- lib/webspicy/tester/file_checker.rb
|
245
256
|
- lib/webspicy/tester/invocation.rb
|
246
257
|
- lib/webspicy/tester/reporter.rb
|
@@ -257,6 +268,7 @@ files:
|
|
257
268
|
- lib/webspicy/tester/result/check.rb
|
258
269
|
- lib/webspicy/tester/result/errcondition_met.rb
|
259
270
|
- lib/webspicy/tester/result/error_schema_met.rb
|
271
|
+
- lib/webspicy/tester/result/invocation_succeeded.rb
|
260
272
|
- lib/webspicy/tester/result/output_schema_met.rb
|
261
273
|
- lib/webspicy/tester/result/postcondition_met.rb
|
262
274
|
- lib/webspicy/tester/result/response_header_met.rb
|
@@ -278,7 +290,7 @@ files:
|
|
278
290
|
- spec/unit/configuration/scope/test_each_specification.rb
|
279
291
|
- spec/unit/configuration/scope/test_expand_example.rb
|
280
292
|
- spec/unit/configuration/scope/test_to_real_url.rb
|
281
|
-
- spec/unit/specification/
|
293
|
+
- spec/unit/specification/pre/test_global_request_headers.rb
|
282
294
|
- spec/unit/specification/service/test_dress_params.rb
|
283
295
|
- spec/unit/specification/test_case/test_mutate.rb
|
284
296
|
- spec/unit/specification/test_condition.rb
|
@@ -294,6 +306,7 @@ files:
|
|
294
306
|
- spec/unit/support/world/fixtures/yaml.yml
|
295
307
|
- spec/unit/support/world/test_world.rb
|
296
308
|
- spec/unit/test_configuration.rb
|
309
|
+
- spec/unit/tester/fakeses/test_email.rb
|
297
310
|
- spec/unit/tester/test_asserter.rb
|
298
311
|
- spec/unit/tester/test_assertions.rb
|
299
312
|
- spec/unit/web/mocker/test_mocker.rb
|