webspicy 0.19.0 → 0.20.4

Sign up to get free protection for your applications and to get access to all the features.
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