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
@@ -42,8 +42,13 @@ module Webspicy
42
42
  end
43
43
 
44
44
  def to_s
45
- @range.to_s
45
+ if @range.first == @range.last
46
+ @range.first.to_s
47
+ else
48
+ @range.to_s
49
+ end
46
50
  end
51
+ alias :inspect :to_s
47
52
 
48
53
  end # class StatusRange
49
54
  end # module Support
@@ -1,5 +1,6 @@
1
1
  module Webspicy
2
2
  class Tester
3
+ include Webspicy::Support::Colorize
3
4
 
4
5
  def initialize(config)
5
6
  @config = Configuration.dress(config)
@@ -33,8 +34,8 @@ module Webspicy
33
34
  end
34
35
  tester.config.each_scope do |scope|
35
36
  client = scope.get_client
36
- scope.each_resource do |resource|
37
- scope.each_service(resource) do |service|
37
+ scope.each_specification do |specification|
38
+ scope.each_service(specification) do |service|
38
39
  tester.rspec_service!(self, service, client, scope)
39
40
  end
40
41
  end
@@ -44,11 +45,11 @@ module Webspicy
44
45
  end
45
46
 
46
47
  def rspec_service!(on, service, client, scope)
47
- on.describe service do
48
- scope.each_testcase(service) do |test_case|
49
- describe test_case do
50
- include_examples 'a successful test case invocation', client, test_case
51
- end
48
+ scope.each_testcase(service) do |test_case|
49
+ str = "#{service} #{test_case}"
50
+ str = colorize_highlight(str)
51
+ on.describe(str) do
52
+ include_examples 'a successful test case invocation', client, test_case
52
53
  end
53
54
  end
54
55
  end
@@ -61,7 +62,8 @@ module Webspicy
61
62
  client.before(test_case)
62
63
  test_case.instrument(client)
63
64
  client.instrument(test_case)
64
- @invocation = client.call(test_case)
65
+ @response = client.call(test_case)
66
+ @invocation = Tester::Invocation.new(test_case, @response, client)
65
67
  example.run
66
68
  client.after(test_case, @invocation)
67
69
  @invocation
@@ -72,13 +74,16 @@ module Webspicy
72
74
  @invocation
73
75
  end
74
76
 
75
- it 'works' do
77
+ it "meets its specification" do
76
78
  raise "Test not ran" unless invocation.done?
77
- errors = invocation.errors
78
- raise "\n* " + errors.join("\n* ") + "\n" unless errors.empty?
79
+ invocation.rspec_assert!(self)
79
80
  end
80
81
  end
81
82
  end
82
83
 
83
84
  end # class Tester
84
85
  end # module Webspicy
86
+ require_relative 'tester/client'
87
+ require_relative 'tester/invocation'
88
+ require_relative 'tester/assertions'
89
+ require_relative 'tester/asserter'
@@ -54,7 +54,8 @@ module Webspicy
54
54
  def size(path, expected = NO_ARG)
55
55
  path, expected = '', path if expected == NO_ARG
56
56
  unless @assertions.size(@target, path, expected)
57
- _! "Expected #{_s(@target, path)} to have a size of #{expected}"
57
+ actual = @assertions.actual_size(@target, path)
58
+ _! "Expected #{_s(@target, path)} to have a size of #{expected}, actual size is: #{actual}"
58
59
  end
59
60
  end
60
61
 
@@ -84,7 +85,7 @@ module Webspicy
84
85
 
85
86
  def pathFD(path, expected)
86
87
  unless @assertions.pathFD(@target, path, expected)
87
- _! "Expected #{_s(@target, path)} to meet FD #{expected.inspect}"
88
+ _! "#{expected.inspect} vs. #{_s(@target, path)}"
88
89
  end
89
90
  end
90
91
 
@@ -37,8 +37,12 @@ module Webspicy
37
37
 
38
38
  def size(target, path, expected = NO_ARG)
39
39
  path, expected = '', path if expected == NO_ARG
40
+ actual_size(target, path) == expected
41
+ end
42
+
43
+ def actual_size(target, path)
40
44
  target = extract_path(target, path)
41
- respond_to!(target, :size).size == expected
45
+ respond_to!(target, :size).size
42
46
  end
43
47
 
44
48
  def idIn(target, path, expected = NO_ARG)
@@ -0,0 +1,63 @@
1
+ module Webspicy
2
+ class Tester
3
+ class Client
4
+
5
+ def initialize(scope)
6
+ @scope = scope
7
+ end
8
+ attr_reader :scope
9
+
10
+ def config
11
+ scope.config
12
+ end
13
+
14
+ def around(*args, &bl)
15
+ args << self
16
+ ls = config.listeners(:around_each)
17
+ if ls.size == 0
18
+ bl.call
19
+ elsif ls.size > 1
20
+ _around(ls.first, ls[1..-1], args, &bl)
21
+ else
22
+ ls.first.call(*args, &bl)
23
+ end
24
+ end
25
+
26
+ def _around(head, tail, args, &bl)
27
+ head.call(*args) do
28
+ if tail.empty?
29
+ bl.call
30
+ else
31
+ _around(tail.first, tail[1..-1], args, &bl)
32
+ end
33
+ end
34
+ end
35
+ private :_around
36
+
37
+ def instrument(*args, &bl)
38
+ args << self
39
+ config.listeners(:instrument).each do |i|
40
+ i.call(*args, &bl)
41
+ end
42
+ end
43
+
44
+ def before(*args, &bl)
45
+ args << self
46
+ config.listeners(:before_each).each do |beach|
47
+ beach.call(*args, &bl)
48
+ end
49
+ end
50
+
51
+ def after(*args, &bl)
52
+ args << self
53
+ config.listeners(:after_each).each do |aeach|
54
+ aeach.call(*args, &bl)
55
+ end
56
+ end
57
+
58
+ end # class Client
59
+ end # class Tester
60
+ end # module Webspicy
61
+ require_relative 'client/support'
62
+ require_relative 'client/http_client'
63
+ require_relative 'client/rack_test_client'
@@ -0,0 +1,154 @@
1
+ module Webspicy
2
+ class Tester
3
+ class HttpClient < Client
4
+
5
+ class ::HTTP::Request
6
+
7
+ # We monkey patch the URI normalization on Http because
8
+ # we don't want it to interfere with URIs that are encoded
9
+ # in tests, especially security tests.
10
+ def normalize_uri(uri)
11
+ uri
12
+ end
13
+
14
+ end # class ::HTTP::Request
15
+
16
+ def initialize(scope)
17
+ super(scope)
18
+ @api = Api.new(scope)
19
+ end
20
+ attr_reader :api
21
+
22
+ def call(test_case)
23
+ service, specification = test_case.service, test_case.specification
24
+
25
+ # Instantiate the parameters
26
+ headers = test_case.headers
27
+ params = test_case.dress_params? ? service.dress_params(test_case.params) : test_case.params
28
+ body = test_case.body || test_case.located_file_upload
29
+
30
+ # Instantiate the url and strip parameters
31
+ url, params = specification.instantiate_url(params)
32
+
33
+ # Globalize the URL if required
34
+ url = scope.to_real_url(url, test_case)
35
+
36
+ # Invoke the service now
37
+ api.public_send(service.method.to_s.downcase.to_sym, url, params, headers, body)
38
+
39
+ # Return the response
40
+ api.last_response
41
+ end
42
+
43
+ class Api
44
+ include Client::Support
45
+
46
+ attr_reader :last_response
47
+
48
+ def initialize(scope)
49
+ @scope = scope
50
+ end
51
+
52
+ def config
53
+ @scope.config
54
+ end
55
+
56
+ def options(url, params = {}, headers = nil, body = nil)
57
+ info_request("OPTIONS", url, params, headers, body)
58
+
59
+ params = querystring_params(params)
60
+ @last_response = HTTP[headers || {}].options(url, params: params)
61
+
62
+ debug_response(@last_response)
63
+
64
+ @last_response
65
+ end
66
+
67
+ def get(url, params = {}, headers = nil, body = nil)
68
+ info_request("GET", url, params, headers, body)
69
+
70
+ params = querystring_params(params)
71
+ @last_response = HTTP[headers || {}].get(url, params: params)
72
+
73
+ debug_response(@last_response)
74
+
75
+ @last_response
76
+ end
77
+
78
+ def post(url, params = {}, headers = nil, body = nil)
79
+ info_request("POST", url, params, headers, body)
80
+
81
+ url = url + "?" + Rack::Utils.build_query(params) if body && !params.empty?
82
+
83
+ headers ||= {}
84
+
85
+ case body
86
+ when NilClass
87
+ headers['Content-Type'] ||= 'application/json'
88
+ @last_response = HTTP[headers].post(url, body: params.to_json)
89
+ when FileUpload
90
+ file = HTTP::FormData::File.new(body.path.to_s, {
91
+ content_type: body.content_type,
92
+ filename: body.path.basename.to_s
93
+ })
94
+ @last_response = HTTP[headers].post(url, form: {
95
+ body.param_name.to_sym => file
96
+ })
97
+ else
98
+ headers['Content-Type'] ||= 'application/json'
99
+ @last_response = HTTP[headers].post(url, body: body)
100
+ end
101
+
102
+ debug_response(@last_response)
103
+
104
+ @last_response
105
+ end
106
+
107
+ def patch(url, params = {}, headers = nil, body = nil)
108
+ info_request("PATCH", url, params, headers, body)
109
+
110
+ headers ||= {}
111
+ headers['Content-Type'] ||= 'application/json'
112
+ @last_response = HTTP[headers].patch(url, body: params.to_json)
113
+
114
+ debug_response(@last_response)
115
+
116
+ @last_response
117
+ end
118
+
119
+ def put(url, params = {}, headers = nil, body = nil)
120
+ info_request("PUT", url, params, headers, body)
121
+
122
+ headers ||= {}
123
+ headers['Content-Type'] ||= 'application/json'
124
+ @last_response = HTTP[headers].put(url, body: params.to_json)
125
+
126
+ debug_response(@last_response)
127
+
128
+ @last_response
129
+ end
130
+
131
+ def post_form(url, params = {}, headers = nil, body = nil)
132
+ info_request("POST", url, params, headers, body)
133
+
134
+ @last_response = HTTP[headers || {}].post(url, form: params)
135
+
136
+ debug_response(@last_response)
137
+
138
+ @last_response
139
+ end
140
+
141
+ def delete(url, params = {}, headers = nil, body = nil)
142
+ info_request("DELETE", url, params, headers, body)
143
+
144
+ @last_response = HTTP[headers || {}].delete(url, body: params.to_json)
145
+
146
+ debug_response(@last_response)
147
+
148
+ @last_response
149
+ end
150
+ end # class Api
151
+
152
+ end # class HttpClient
153
+ end # class Tester
154
+ end # module Webspicy
@@ -0,0 +1,188 @@
1
+ module Webspicy
2
+ class Tester
3
+ class RackTestClient < Client
4
+
5
+ def self.for(app)
6
+ Factory.new(app)
7
+ end
8
+
9
+ def initialize(scope, app)
10
+ super(scope)
11
+ @api = Api.new(scope, app)
12
+ end
13
+ attr_reader :api
14
+
15
+ def call(test_case)
16
+ service, specification = test_case.service, test_case.specification
17
+
18
+ # Instantiate the parameters
19
+ headers = test_case.headers.dup
20
+ params = test_case.dress_params? ? service.dress_params(test_case.params) : test_case.params
21
+ body = test_case.body || test_case.located_file_upload
22
+
23
+ # Instantiate the url and strip parameters
24
+ url, params = specification.instantiate_url(params)
25
+ url = scope.to_real_url(url, test_case){|u,_| u }
26
+
27
+ # Invoke the service now
28
+ api.public_send(service.method.to_s.downcase.to_sym, url, params, headers, body)
29
+
30
+ # Return the last response
31
+ api.last_response
32
+ end
33
+
34
+ class Factory
35
+
36
+ def initialize(app)
37
+ @app = app
38
+ end
39
+ attr_reader :app
40
+
41
+ def new(scope)
42
+ RackTestClient.new(scope, app)
43
+ end
44
+
45
+ end # class Factory
46
+
47
+ class RackHandler
48
+ include Rack::Test::Methods
49
+
50
+ def initialize(app)
51
+ @app = app
52
+ end
53
+ attr_reader :app
54
+
55
+ end # class RackHandler
56
+
57
+ class Api
58
+ include Client::Support
59
+
60
+ attr_reader :last_response
61
+
62
+ def initialize(scope, app)
63
+ @scope = scope
64
+ @app = app
65
+ end
66
+
67
+ def config
68
+ @scope.config
69
+ end
70
+
71
+ def options(url, params = {}, headers = nil, body = nil)
72
+ handler = get_handler(headers)
73
+
74
+ info_request("OPTIONS", url, params, headers, body)
75
+
76
+ handler.options(url, params)
77
+ @last_response = handler.last_response
78
+
79
+ debug_response(@last_response)
80
+
81
+ @last_response
82
+ end
83
+
84
+ def get(url, params = {}, headers = nil, body = nil)
85
+ handler = get_handler(headers)
86
+
87
+ params = Hash[params.map{|k,v| [k, v.nil? ? "" : v] }]
88
+ info_request("GET", url, params, headers, body)
89
+
90
+ handler.get(url, params)
91
+ @last_response = handler.last_response
92
+
93
+ debug_response(@last_response)
94
+
95
+ @last_response
96
+ end
97
+
98
+ def post(url, params = {}, headers = nil, body = nil)
99
+ handler = get_handler(headers)
100
+
101
+ url = url + "?" + Rack::Utils.build_query(params) if body && !params.empty?
102
+
103
+ case body
104
+ when NilClass
105
+ info_request("POST", url, params, headers, body)
106
+ handler.post(url, params.to_json, {"CONTENT_TYPE" => "application/json"})
107
+ when FileUpload
108
+ file = Rack::Test::UploadedFile.new(body.path, body.content_type)
109
+ info_request("POST", url, params, headers, body)
110
+ handler.post(url, body.param_name.to_sym => file)
111
+ else
112
+ info_request("POST", url, params, headers, body)
113
+ handler.post(url, body)
114
+ end
115
+ @last_response = handler.last_response
116
+
117
+ debug_response(@last_response)
118
+
119
+ @last_response
120
+ end
121
+
122
+ def patch(url, params = {}, headers = nil, body = nil)
123
+ handler = get_handler(headers)
124
+
125
+ info_request("PATCH", url, params, headers, body)
126
+
127
+ handler.patch(url, params.to_json, {"CONTENT_TYPE" => "application/json"})
128
+ @last_response = handler.last_response
129
+
130
+ debug_response(@last_response)
131
+
132
+ @last_response
133
+ end
134
+
135
+ def put(url, params = {}, headers = nil, body = nil)
136
+ handler = get_handler(headers)
137
+
138
+ info_request("PUT", url, params, headers, body)
139
+
140
+ handler.put(url, params.to_json, {"CONTENT_TYPE" => "application/json"})
141
+ @last_response = handler.last_response
142
+
143
+ debug_response(@last_response)
144
+
145
+ @last_response
146
+ end
147
+
148
+ def post_form(url, params = {}, headers = nil, body = nil)
149
+ handler = get_handler(headers)
150
+
151
+ info_request("POST", url, params, headers, body)
152
+
153
+ handler.post(url, params)
154
+ @last_response = handler.last_response
155
+
156
+ debug_response(@last_response)
157
+
158
+ @last_response
159
+ end
160
+
161
+ def delete(url, params = {}, headers = nil, body = nil)
162
+ handler = get_handler(headers)
163
+
164
+ info_request("DELETE", url, params, headers, body)
165
+
166
+ handler.delete(url, params.to_json, {"CONTENT_TYPE" => "application/json"})
167
+ @last_response = handler.last_response
168
+
169
+ debug_response(@last_response)
170
+
171
+ @last_response
172
+ end
173
+
174
+ private
175
+
176
+ def get_handler(hs)
177
+ handler = RackHandler.new(@app)
178
+ hs.each_pair do |k,v|
179
+ handler.header(k,v)
180
+ end if hs
181
+ handler
182
+ end
183
+
184
+ end # class Api
185
+
186
+ end # class RackTestClient
187
+ end # class Client
188
+ end # module Webspicy