webspicy 0.19.0 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/webspicy.rb +0 -2
  3. data/lib/webspicy/specification.rb +4 -3
  4. data/lib/webspicy/specification/condition.rb +29 -4
  5. data/lib/webspicy/specification/err.rb +18 -0
  6. data/lib/webspicy/specification/oldies.rb +4 -0
  7. data/lib/webspicy/specification/oldies/bridge.rb +29 -0
  8. data/lib/webspicy/specification/{errcondition.rb → oldies/errcondition.rb} +5 -0
  9. data/lib/webspicy/specification/{postcondition.rb → oldies/postcondition.rb} +5 -0
  10. data/lib/webspicy/specification/{precondition.rb → oldies/precondition.rb} +5 -2
  11. data/lib/webspicy/specification/post.rb +20 -0
  12. data/lib/webspicy/specification/post/missing_condition_impl.rb +15 -0
  13. data/lib/webspicy/specification/post/unexpected_condition_impl.rb +15 -0
  14. data/lib/webspicy/specification/pre.rb +19 -0
  15. data/lib/webspicy/specification/{precondition → pre}/global_request_headers.rb +4 -4
  16. data/lib/webspicy/specification/{precondition → pre}/robust_to_invalid_input.rb +4 -4
  17. data/lib/webspicy/specification/service.rb +26 -5
  18. data/lib/webspicy/support.rb +12 -2
  19. data/lib/webspicy/tester.rb +72 -11
  20. data/lib/webspicy/tester/client.rb +0 -26
  21. data/lib/webspicy/tester/fakeses.rb +41 -0
  22. data/lib/webspicy/tester/fakeses/email.rb +38 -0
  23. data/lib/webspicy/tester/fakesmtp.rb +39 -0
  24. data/lib/webspicy/tester/fakesmtp/email.rb +27 -0
  25. data/lib/webspicy/tester/reporter/documentation.rb +7 -0
  26. data/lib/webspicy/tester/reporter/exceptions.rb +2 -0
  27. data/lib/webspicy/tester/reporter/file_progress.rb +3 -0
  28. data/lib/webspicy/tester/reporter/file_summary.rb +1 -0
  29. data/lib/webspicy/tester/reporter/progress.rb +2 -0
  30. data/lib/webspicy/tester/reporter/summary.rb +1 -0
  31. data/lib/webspicy/tester/result.rb +16 -13
  32. data/lib/webspicy/tester/result/errcondition_met.rb +1 -1
  33. data/lib/webspicy/tester/result/invocation_succeeded.rb +13 -0
  34. data/lib/webspicy/tester/result/postcondition_met.rb +1 -1
  35. data/lib/webspicy/version.rb +1 -1
  36. data/spec/unit/specification/{precondition → pre}/test_global_request_headers.rb +9 -4
  37. data/spec/unit/specification/test_condition.rb +18 -0
  38. data/spec/unit/tester/fakeses/test_email.rb +40 -0
  39. metadata +21 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 614e5904931375bd6f4df435d33736cde757e752d0d759fbc3af4e185b9cd9e4
4
- data.tar.gz: 8d63e07e93809bd5139300e5206f0a3b3204e86d77faea60a29302e5787413d1
3
+ metadata.gz: d0bfd23b321b3e1216316e09c2b9e2d761a9e3179c62830d4b6a99b13042f4cc
4
+ data.tar.gz: 0661ba60f10ac2433bb5952d3c17141d880363e1d0c763836877a08b9673a8d1
5
5
  SHA512:
6
- metadata.gz: 33d6a69e097e14a90f8b69edb4833a4a1325cec38513bf8f88d017b31847e08b310acce50dc7fb3d3cbdeefdff76b3497b489adbc2e965c7e8b12f0228a63524
7
- data.tar.gz: 0d6cf55d54a6bb94b48511f9c178a1e5e673772749784b3c7c720d9413670dc1e93029cfa0687efd8fced6f91eb699ea5cae4ab15e40a674dea7843712dec114
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/precondition'
98
- require_relative 'specification/postcondition'
99
- require_relative 'specification/errcondition'
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,4 @@
1
+ require_relative 'oldies/precondition'
2
+ require_relative 'oldies/postcondition'
3
+ require_relative 'oldies/errcondition'
4
+ require_relative 'oldies/bridge'
@@ -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 Err instead
3
4
  module Errcondition
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, descr)
7
12
  end
8
13
 
@@ -1,8 +1,13 @@
1
1
  module Webspicy
2
2
  class Specification
3
+ # Deprecated, use Post instead
3
4
  module Postcondition
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, descr)
7
12
  end
8
13
 
@@ -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 Precondition
3
+ module Pre
4
4
  class GlobalRequestHeaders
5
- include Precondition
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(test_case, client)
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 Precondition
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 Precondition
3
+ module Pre
4
4
  class RobustToInvalidInput
5
- include Precondition
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 Precondition
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
- conditions.map{|c|
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
- if instance && instance.respond_to?(:matching_description=)
121
- instance.matching_description = descr
122
- end
123
- instance
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
@@ -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
- until (r = yield) || left == 0
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'
@@ -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
- private
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(@scope, @client)
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(@scope, @client)
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(test_case, client) do
127
+ hooks.fire_around(self) do
111
128
  reporter.before_each
112
- hooks.fire_before_each(test_case, client)
129
+ hooks.fire_before_each(self)
113
130
  reporter.before_each_done
114
131
 
115
132
  reporter.before_instrument
116
- client.instrument(test_case)
133
+ instrument_test_case
117
134
  reporter.instrument_done
118
135
 
119
- reporter.before_invocation
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(test_case, @invocation, client)
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
@@ -33,6 +33,7 @@ module Webspicy
33
33
  io.puts e
34
34
  end
35
35
  io.puts
36
+ io.flush
36
37
  end
37
38
 
38
39
  def report_failed_results
@@ -52,6 +53,7 @@ module Webspicy
52
53
  io.puts
53
54
  end
54
55
  io.puts
56
+ io.flush
55
57
  end
56
58
 
57
59
  end # class Exceptions
@@ -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
@@ -28,6 +28,7 @@ module Webspicy
28
28
  end
29
29
  io.puts(msg)
30
30
  io.puts
31
+ io.flush
31
32
  end
32
33
 
33
34
  private
@@ -15,11 +15,13 @@ module Webspicy
15
15
  elsif result.error?
16
16
  io.print colorize_error("E")
17
17
  end
18
+ io.flush
18
19
  end
19
20
 
20
21
  def report
21
22
  io.puts
22
23
  io.puts
24
+ io.flush
23
25
  end
24
26
 
25
27
  end # class Progress
@@ -48,6 +48,7 @@ module Webspicy
48
48
  end
49
49
  io.puts(msg)
50
50
  io.puts
51
+ io.flush
51
52
  end
52
53
 
53
54
  private
@@ -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
- check!
17
+ if @invocation
18
+ check!
19
+ else
20
+ @errors << [InvocationSuceeded.new(self), tester.invocation_error]
21
+ end
13
22
  end
14
- attr_reader :tester, :invocation
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
- :scope,
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"
@@ -18,7 +18,7 @@ module Webspicy
18
18
  end
19
19
 
20
20
  def call
21
- if err = post.check(invocation)
21
+ if err = post.check!
22
22
  _! err
23
23
  end
24
24
  end
@@ -0,0 +1,13 @@
1
+ module Webspicy
2
+ class Tester
3
+ class Result
4
+ class InvocationSuceeded < Check
5
+
6
+ def initialize(result)
7
+ super(result)
8
+ end
9
+
10
+ end # class InvocationSuceeded
11
+ end # class Result
12
+ end # class Tester
13
+ end # module Webspicy
@@ -18,7 +18,7 @@ module Webspicy
18
18
  end
19
19
 
20
20
  def call
21
- if err = post.check(invocation)
21
+ if err = post.check!
22
22
  _! err
23
23
  end
24
24
  end
@@ -1,7 +1,7 @@
1
1
  module Webspicy
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 19
4
+ MINOR = 20
5
5
  TINY = 0
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
@@ -2,16 +2,21 @@ require 'spec_helper'
2
2
 
3
3
  module Webspicy
4
4
  class Specification
5
- module Precondition
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
- gbr.instrument(tc, nil)
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
- gbr.instrument(tc, nil)
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
- gbr.instrument(tc, nil)
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.19.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-09 00:00:00.000000000 Z
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/errcondition.rb
225
+ - lib/webspicy/specification/err.rb
226
226
  - lib/webspicy/specification/file_upload.rb
227
- - lib/webspicy/specification/postcondition.rb
228
- - lib/webspicy/specification/precondition.rb
229
- - lib/webspicy/specification/precondition/global_request_headers.rb
230
- - lib/webspicy/specification/precondition/robust_to_invalid_input.rb
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/precondition/test_global_request_headers.rb
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