webspicy 0.19.0 → 0.20.4

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