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.
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