webspicy 0.15.7 → 0.16.3

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +71 -24
  3. data/bin/webspicy +30 -14
  4. data/examples/restful/Gemfile +2 -2
  5. data/examples/restful/Gemfile.lock +53 -33
  6. data/examples/restful/Rakefile +0 -1
  7. data/examples/restful/app.rb +4 -1
  8. data/examples/restful/webspicy/config.rb +8 -0
  9. data/examples/restful/webspicy/rack.rb +1 -1
  10. data/examples/restful/webspicy/real.rb +1 -1
  11. data/examples/restful/webspicy/schema.fio +2 -2
  12. data/examples/restful/webspicy/support/must_be_authenticated.rb +2 -2
  13. data/examples/restful/webspicy/support/todo_removed.rb +18 -0
  14. data/examples/restful/webspicy/todo/deleteTodo.yml +4 -1
  15. data/examples/restful/webspicy/todo/getTodoSingleServiceFormat.yml +46 -0
  16. data/examples/restful/webspicy/todo/options.yml +1 -1
  17. data/examples/restful/webspicy/todo/patchTodo.yml +3 -0
  18. data/examples/restful/webspicy/todo/postFile.yml +1 -1
  19. data/examples/single_spec/spec.yml +59 -0
  20. data/examples/website/config.rb +2 -0
  21. data/examples/website/schema.fio +1 -0
  22. data/examples/website/specification/get-http.yml +30 -0
  23. data/examples/website/specification/get-https.yml +30 -0
  24. data/lib/finitio/webspicy/scalars.fio +25 -0
  25. data/lib/webspicy.rb +48 -17
  26. data/lib/webspicy/checker.rb +2 -2
  27. data/lib/webspicy/configuration.rb +70 -14
  28. data/lib/webspicy/configuration/scope.rb +162 -0
  29. data/lib/webspicy/configuration/single_url.rb +58 -0
  30. data/lib/webspicy/configuration/single_yml_file.rb +30 -0
  31. data/lib/webspicy/formaldoc.fio +23 -8
  32. data/lib/webspicy/mocker.rb +8 -8
  33. data/lib/webspicy/mocker/config.ru +5 -0
  34. data/lib/webspicy/openapi.rb +1 -0
  35. data/lib/webspicy/openapi/generator.rb +127 -0
  36. data/lib/webspicy/{resource.rb → specification.rb} +28 -5
  37. data/lib/webspicy/specification/file_upload.rb +37 -0
  38. data/lib/webspicy/specification/postcondition.rb +16 -0
  39. data/lib/webspicy/specification/precondition.rb +19 -0
  40. data/lib/webspicy/specification/precondition/global_request_headers.rb +35 -0
  41. data/lib/webspicy/specification/precondition/robust_to_invalid_input.rb +68 -0
  42. data/lib/webspicy/{resource → specification}/service.rb +11 -6
  43. data/lib/webspicy/specification/test_case.rb +139 -0
  44. data/lib/webspicy/support.rb +1 -0
  45. data/lib/webspicy/support/colorize.rb +28 -0
  46. data/lib/webspicy/support/status_range.rb +6 -1
  47. data/lib/webspicy/tester.rb +16 -11
  48. data/lib/webspicy/tester/asserter.rb +3 -2
  49. data/lib/webspicy/tester/assertions.rb +5 -1
  50. data/lib/webspicy/tester/client.rb +63 -0
  51. data/lib/webspicy/tester/client/http_client.rb +154 -0
  52. data/lib/webspicy/tester/client/rack_test_client.rb +188 -0
  53. data/lib/webspicy/tester/client/support.rb +65 -0
  54. data/lib/webspicy/tester/invocation.rb +218 -0
  55. data/lib/webspicy/tester/rspec_asserter.rb +108 -0
  56. data/lib/webspicy/tester/rspec_matchers.rb +104 -0
  57. data/lib/webspicy/version.rb +2 -2
  58. data/spec/{unit/spec_helper.rb → spec_helper.rb} +0 -0
  59. data/spec/unit/configuration/scope/test_each_service.rb +49 -0
  60. data/spec/unit/configuration/scope/test_each_specification.rb +68 -0
  61. data/spec/unit/configuration/scope/test_expand_example.rb +65 -0
  62. data/spec/unit/configuration/scope/test_to_real_url.rb +82 -0
  63. data/spec/unit/openapi/test_generator.rb +28 -0
  64. data/spec/unit/specification/precondition/test_global_request_headers.rb +42 -0
  65. data/spec/unit/{resource → specification}/service/test_dress_params.rb +2 -2
  66. data/spec/unit/specification/test_case/test_mutate.rb +24 -0
  67. data/spec/unit/{resource → specification}/test_instantiate_url.rb +5 -5
  68. data/spec/unit/{resource → specification}/test_url_placeholders.rb +4 -4
  69. data/spec/unit/test_configuration.rb +24 -7
  70. data/spec/unit/tester/client/test_around.rb +61 -0
  71. data/spec/unit/tester/test_asserter.rb +51 -0
  72. data/spec/unit/tester/test_assertions.rb +4 -4
  73. data/tasks/test.rake +3 -1
  74. metadata +88 -45
  75. data/LICENSE.md +0 -22
  76. data/lib/webspicy/client.rb +0 -61
  77. data/lib/webspicy/client/http_client.rb +0 -145
  78. data/lib/webspicy/client/rack_test_client.rb +0 -181
  79. data/lib/webspicy/client/support.rb +0 -48
  80. data/lib/webspicy/file_upload.rb +0 -35
  81. data/lib/webspicy/postcondition.rb +0 -14
  82. data/lib/webspicy/precondition.rb +0 -15
  83. data/lib/webspicy/resource/service/invocation.rb +0 -212
  84. data/lib/webspicy/resource/service/test_case.rb +0 -132
  85. data/lib/webspicy/scope.rb +0 -160
  86. data/spec/unit/client/test_around.rb +0 -59
  87. data/spec/unit/scope/test_each_resource.rb +0 -66
  88. data/spec/unit/scope/test_each_service.rb +0 -47
  89. data/spec/unit/scope/test_expand_example.rb +0 -63
  90. data/spec/unit/scope/test_to_real_url.rb +0 -80
@@ -1,35 +0,0 @@
1
- module Webspicy
2
- class FileUpload
3
-
4
- def initialize(raw)
5
- @path = raw[:path]
6
- @content_type = raw[:content_type]
7
- @param_name = raw[:param_name] || "file"
8
- end
9
-
10
- attr_reader :path, :content_type, :param_name
11
-
12
- def self.info(raw)
13
- new(raw)
14
- end
15
-
16
- def locate(resource)
17
- FileUpload.new({
18
- path: resource.locate(path),
19
- content_type: content_type
20
- })
21
- end
22
-
23
- def to_info
24
- { path: path.to_s,
25
- content_type: content_type,
26
- param_name: param_name }
27
- end
28
-
29
- def to_s
30
- "FileUpload(#{to_info})"
31
- end
32
- alias :inspect :to_s
33
-
34
- end # class FileUpload
35
- end # module Webspicy
@@ -1,14 +0,0 @@
1
- module Webspicy
2
- module Postcondition
3
-
4
- def self.match(service, descr)
5
- end
6
-
7
- def instrument(test_case, client)
8
- end
9
-
10
- def check(invocation)
11
- end
12
-
13
- end # module Postcondition
14
- end # module Webspicy
@@ -1,15 +0,0 @@
1
- module Webspicy
2
- module Precondition
3
-
4
- def self.match(service, pre)
5
- end
6
-
7
- def instrument(test_case, client)
8
- end
9
-
10
- def counterexamples(service)
11
- []
12
- end
13
-
14
- end # module Precondition
15
- end # module Webspicy
@@ -1,212 +0,0 @@
1
- module Webspicy
2
- class Resource
3
- class Service
4
- class Invocation
5
-
6
- def initialize(service, test_case, response, client)
7
- @service = service
8
- @test_case = test_case
9
- @response = response
10
- @client = client
11
- end
12
-
13
- attr_reader :service, :test_case, :response, :client
14
-
15
- def errors
16
- @errors ||= begin
17
- errs = [
18
- [:expected_status_unmet, true],
19
- [:expected_content_type_unmet, !test_case.is_expected_status?(204)],
20
- [:expected_headers_unmet, test_case.has_expected_headers?],
21
- [:expected_schema_unmet, !test_case.is_expected_status?(204)],
22
- [:assertions_unmet, test_case.has_assertions?],
23
- [:postconditions_unmet, test_case.service.has_postconditions? && !test_case.counterexample?],
24
- [:expected_error_unmet, test_case.has_expected_error?]
25
- ].map do |(expectation,only_if)|
26
- next unless only_if
27
- begin
28
- self.send(expectation)
29
- rescue => ex
30
- ex.message
31
- end
32
- end
33
- errs.compact
34
- end
35
- end
36
-
37
- def has_error?
38
- !errors.empty?
39
- end
40
-
41
- ### Getters on response
42
-
43
- def response_code
44
- code = response.status
45
- code = code.code unless code.is_a?(Integer)
46
- code
47
- end
48
-
49
- ### Query methods
50
-
51
- def done?
52
- !response.nil?
53
- end
54
-
55
- def is_expected_success?
56
- test_case.expected_status.to_i >= 200 && test_case.expected_status.to_i < 300
57
- end
58
-
59
- def is_success?
60
- response_code >= 200 && response_code < 300
61
- end
62
-
63
- def is_empty_response?
64
- response_code == 204
65
- end
66
-
67
- def is_redirect?
68
- response_code >= 300 && response_code < 400
69
- end
70
-
71
- ### Check of HTTP status
72
-
73
- def expected_status_unmet
74
- expected = test_case.expected_status
75
- got = response.status
76
- expected === got ? nil : "#{expected} !== #{got}"
77
- end
78
-
79
- def meets_expected_status?
80
- expected_status_unmet.nil?
81
- end
82
-
83
- ### Check of the expected output type
84
-
85
- def expected_content_type_unmet
86
- ect = test_case.expected_content_type
87
- got = response.content_type
88
- got = got.mime_type if got.respond_to?(:mime_type)
89
- if ect.nil?
90
- got.nil? ? nil : "#{ect} != #{got}"
91
- else
92
- got.to_s.start_with?(ect.to_s) ? nil : "#{ect} != #{got}"
93
- end
94
- end
95
-
96
- def meets_expected_content_type?
97
- expected_content_type_unmet.nil?
98
- end
99
-
100
- ### Check of output schema
101
-
102
- def expected_schema_unmet
103
- if is_empty_response?
104
- body = response.body.to_s.strip
105
- body.empty? ? nil : "Expected empty body, got #{body}"
106
- elsif is_redirect?
107
- else
108
- case dressed_body
109
- when Finitio::TypeError
110
- rc = dressed_body.root_cause
111
- "#{rc.message} (#{rc.location ? rc.location : 'unknown location'})"
112
- when StandardError
113
- dressed_body.message
114
- else nil
115
- end
116
- end
117
- end
118
-
119
- def meets_expected_schema?
120
- expected_schema_unmet.nil?
121
- end
122
-
123
- ### Check of assertions
124
-
125
- def assertions_unmet
126
- unmet = []
127
- asserter = Tester::Asserter.new(dressed_body)
128
- test_case.assert.each do |assert|
129
- begin
130
- asserter.instance_eval(assert)
131
- rescue => ex
132
- unmet << ex.message
133
- end
134
- end
135
- unmet.empty? ? nil : unmet.join("\n")
136
- end
137
-
138
- def value_equal(exp, got)
139
- case exp
140
- when Hash
141
- exp.all?{|(k,v)|
142
- got[k] == v
143
- }
144
- else
145
- exp == got
146
- end
147
- end
148
-
149
- ### Check of expected error message
150
-
151
- def expected_error_unmet
152
- expected = test_case.expected_error
153
- case test_case.expected_content_type
154
- when %r{json}
155
- got = meets_expected_schema? ? dressed_body[:description] : response.body
156
- expected == got ? nil : "`#{expected}` vs. `#{got}`"
157
- else
158
- dressed_body.include?(expected) ? nil : "#{expected} not found" unless expected.nil?
159
- end
160
- end
161
-
162
- ### Check of expected headers
163
-
164
- def expected_headers_unmet
165
- unmet = []
166
- expected = test_case.expected_headers
167
- expected.each_pair do |k,v|
168
- got = response.headers[k]
169
- unmet << "#{v} expected for #{k}, got #{got}" unless (got == v)
170
- end
171
- unmet.empty? ? nil : unmet.join("\n")
172
- end
173
-
174
- ### Check of postconditions
175
-
176
- def postconditions_unmet
177
- failures = service.postconditions.map{|post|
178
- post.check(self)
179
- }.compact
180
- failures.empty? ? nil : failures.join("\n")
181
- end
182
-
183
- private
184
-
185
- def loaded_body
186
- case test_case.expected_content_type
187
- when %r{json}
188
- raise "Body empty while expected" if response.body.to_s.empty?
189
- @loaded_body ||= ::JSON.parse(response.body)
190
- else
191
- response.body.to_s
192
- end
193
- end
194
-
195
- def dressed_body
196
- @dressed_body ||= case test_case.expected_content_type
197
- when %r{json}
198
- schema = is_expected_success? ? service.output_schema : service.error_schema
199
- begin
200
- schema.dress(loaded_body)
201
- rescue Finitio::TypeError => ex
202
- ex
203
- end
204
- else
205
- loaded_body
206
- end
207
- end
208
-
209
- end # class Invocation
210
- end # class Service
211
- end # class Resource
212
- end # module Webspicy
@@ -1,132 +0,0 @@
1
- module Webspicy
2
- class Resource
3
- class Service
4
- class TestCase
5
-
6
- def initialize(raw)
7
- @raw = raw
8
- @counterexample = nil
9
- end
10
- attr_reader :service
11
- attr_reader :counterexample
12
-
13
- def bind(service, counterexample)
14
- @service = service
15
- @counterexample = counterexample
16
- self
17
- end
18
-
19
- def counterexample?
20
- !!@counterexample
21
- end
22
-
23
- def resource
24
- service.resource
25
- end
26
-
27
- def self.info(raw)
28
- new(raw)
29
- end
30
-
31
- def description
32
- @raw[:description]
33
- end
34
-
35
- def seeds
36
- @raw[:seeds]
37
- end
38
-
39
- def headers
40
- @raw[:headers] ||= {}
41
- end
42
-
43
- def metadata
44
- @raw[:metadata] ||= {}
45
- end
46
-
47
- def tags
48
- @raw[:tags] ||= []
49
- end
50
-
51
- def dress_params
52
- @raw.fetch(:dress_params){ true }
53
- end
54
- alias :dress_params? :dress_params
55
-
56
- def params
57
- @raw[:params] || {}
58
- end
59
-
60
- def body
61
- @raw[:body]
62
- end
63
-
64
- def file_upload
65
- @raw[:file_upload]
66
- end
67
-
68
- def located_file_upload
69
- file_upload ? file_upload.locate(resource) : nil
70
- end
71
-
72
- def expected
73
- @raw[:expected] || {}
74
- end
75
-
76
- def expected_content_type
77
- expected.fetch(:content_type){ 'application/json' }
78
- end
79
-
80
- def expected_status
81
- expected[:status]
82
- end
83
-
84
- def is_expected_status?(status)
85
- expected_status === status
86
- end
87
-
88
- def expected_error
89
- expected[:error]
90
- end
91
-
92
- def has_expected_error?
93
- !expected_error.nil?
94
- end
95
-
96
- def expected_headers
97
- expected[:headers] || {}
98
- end
99
-
100
- def has_expected_headers?
101
- !expected_headers.empty?
102
- end
103
-
104
- def assert
105
- @raw[:assert] || []
106
- end
107
-
108
- def has_assertions?
109
- !assert.empty?
110
- end
111
-
112
- def to_info
113
- @raw
114
- end
115
-
116
- def instrument(client)
117
- service.preconditions.each do |pre|
118
- pre.instrument(self, client)
119
- end
120
- service.postconditions.each do |post|
121
- post.instrument(self, client) if post.respond_to?(:instrument)
122
- end
123
- end
124
-
125
- def to_s
126
- description
127
- end
128
-
129
- end # class TestCase
130
- end # class Service
131
- end # class Resource
132
- end # module Webspicy
@@ -1,160 +0,0 @@
1
- module Webspicy
2
- class Scope
3
-
4
- def initialize(config)
5
- @config = config
6
- end
7
- attr_reader :config
8
-
9
- def preconditions
10
- config.preconditions
11
- end
12
-
13
- def postconditions
14
- config.postconditions
15
- end
16
-
17
- ###
18
- ### Eachers -- Allow navigating the web service definitions
19
- ###
20
-
21
- # Yields each resource file in the current scope
22
- def each_resource_file(&bl)
23
- return enum_for(:each_resource_file) unless block_given?
24
- _each_resource_file(config, &bl)
25
- end
26
-
27
- # Recursive implementation of `each_resource_file` for each
28
- # folder in the configuration.
29
- def _each_resource_file(config)
30
- folder = config.folder
31
- folder.glob("**/*.yml").select(&to_filter_proc(config.file_filter)).each do |file|
32
- yield file, folder
33
- end
34
- end
35
- private :_each_resource_file
36
-
37
- # Yields each resource in the current scope in turn.
38
- def each_resource(&bl)
39
- return enum_for(:each_resource) unless block_given?
40
- each_resource_file do |file, folder|
41
- yield Webspicy.resource(file.load, file, self)
42
- end
43
- end
44
-
45
- def each_service(resource, &bl)
46
- resource.services.select(&to_filter_proc(config.service_filter)).each(&bl)
47
- end
48
-
49
- def each_example(service, &bl)
50
- service.examples
51
- .map{|e| expand_example(service, e) }
52
- .select(&to_filter_proc(config.test_case_filter))
53
- .each(&bl) if config.run_examples?
54
- end
55
-
56
- def each_counterexamples(service, &bl)
57
- service.counterexamples
58
- .map{|e| expand_example(service, e) }
59
- .select(&to_filter_proc(config.test_case_filter))
60
- .each(&bl) if config.run_counterexamples?
61
- end
62
-
63
- def each_generated_counterexamples(service, &bl)
64
- Webspicy.with_scope(self) do
65
- service.generated_counterexamples
66
- .map{|e| expand_example(service, e) }
67
- .select(&to_filter_proc(config.test_case_filter))
68
- .each(&bl) if config.run_counterexamples?
69
- end if config.run_counterexamples?
70
- end
71
-
72
- def each_testcase(service, &bl)
73
- each_example(service, &bl)
74
- each_counterexamples(service, &bl)
75
- each_generated_counterexamples(service, &bl)
76
- end
77
-
78
- ###
79
- ### Schemas -- For parsing input and output data schemas found in
80
- ### web service definitions
81
- ###
82
-
83
- # Parses a Finitio schema based on the data system.
84
- def parse_schema(fio)
85
- data_system.parse(fio)
86
- end
87
-
88
- # Returns the Data system to use for parsing schemas
89
- def data_system
90
- @data_system ||= config.data_system
91
- end
92
-
93
-
94
- ###
95
- ### Service invocation: abstract the configuration about what client is
96
- ### used and how to instantiate it
97
- ###
98
-
99
- # Returns an instance of the client to use to invoke web services
100
- def get_client
101
- config.client.new(self)
102
- end
103
-
104
- # Convert an instantiated URL found in a webservice definition
105
- # to a real URL, using the configuration host.
106
- #
107
- # When no host resolved on the configuration and the url is not
108
- # already an absolute URL, yields the block if given, or raise
109
- # an exception.
110
- def to_real_url(url, test_case = nil, &bl)
111
- case config.host
112
- when Proc
113
- config.host.call(url, test_case)
114
- when String
115
- url =~ /^http/ ? url : "#{config.host}#{url}"
116
- else
117
- return url if url =~ /^http/
118
- return yield(url) if block_given?
119
- raise "Unable to resolve `#{url}` : no host resolver provided\nSee `Configuration#host="
120
- end
121
- end
122
-
123
- ###
124
- ### Private methods
125
- ###
126
-
127
- private
128
-
129
- def expand_example(service, example)
130
- return example unless service.default_example
131
- h1 = service.default_example.to_info
132
- h2 = example.to_info
133
- ex = Resource::Service::TestCase.new(merge_maps(h1, h2))
134
- ex.bind(service, example.counterexample?)
135
- end
136
-
137
- def merge_maps(h1, h2)
138
- h1.merge(h2) do |k,v1,v2|
139
- case v1
140
- when Hash then merge_maps(v1, v2)
141
- when Array then v1 + v2
142
- else v2
143
- end
144
- end
145
- end
146
-
147
- # Returns a proc that implements file_filter strategy according to the
148
- # type of filter installed
149
- def to_filter_proc(filter)
150
- case ff = filter
151
- when NilClass then ->(f){ true }
152
- when Proc then ff
153
- when Regexp then ->(f){ ff =~ f.to_s }
154
- else
155
- ->(f){ ff === f }
156
- end
157
- end
158
-
159
- end
160
- end