committee 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/committee/drivers.rb +28 -0
- data/lib/committee/middleware/base.rb +14 -8
- data/lib/committee/middleware/request_validation.rb +1 -0
- data/lib/committee/middleware/response_validation.rb +15 -5
- data/lib/committee/request_unpacker.rb +2 -1
- data/lib/committee/response_validator.rb +5 -5
- data/lib/committee/router.rb +8 -8
- data/lib/committee/test/methods.rb +54 -6
- data/test/drivers_test.rb +42 -0
- data/test/middleware/base_test.rb +13 -2
- data/test/middleware/response_validation_test.rb +54 -5
- data/test/request_unpacker_test.rb +24 -0
- data/test/router_test.rb +13 -0
- data/test/test/methods_new_version_test.rb +76 -0
- data/test/test/methods_test.rb +7 -2
- data/test/test_helper.rb +12 -10
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: eac1e86868d4ca6f8b7ef27f62ec87131ba1399985f48b0e7846a5ceda6d81cc
|
4
|
+
data.tar.gz: a0e76eea124800b0d8d328657ffb30efc3c683bbfd47707ce9c148500fc79307
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dcaa28428558ae7b9f6ce8679e826e62bdc0640228e35fb41a0998a1ccbbab4d505bc6e23ad052b907a45be6d2003152ab9803e132907a3c68b1a2fbf7fc5292
|
7
|
+
data.tar.gz: 0a1d640b9305e12a2ac6b7436b2971a8a1568322aa1d75a25f8ec4a25aed50229c5bd5c99e317c9c243d22099c1855ea93fbf17cc0692488a35a8f1ee03f8959
|
data/lib/committee/drivers.rb
CHANGED
@@ -13,6 +13,34 @@ module Committee
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
# load and build drive from JSON file
|
17
|
+
# @param [String] schema_path
|
18
|
+
# @return [Committee::Driver]
|
19
|
+
def self.load_from_json(schema_path)
|
20
|
+
json = JSON.parse(File.read(schema_path))
|
21
|
+
load_from_data(json)
|
22
|
+
end
|
23
|
+
|
24
|
+
# load and build drive from Hash object
|
25
|
+
# @param [Hash] hash
|
26
|
+
# @return [Committee::Driver]
|
27
|
+
def self.load_from_data(hash)
|
28
|
+
driver = if hash['swagger'] == '2.0'
|
29
|
+
Committee::Drivers::OpenAPI2.new
|
30
|
+
else
|
31
|
+
Committee::Drivers::HyperSchema.new
|
32
|
+
end
|
33
|
+
|
34
|
+
driver.parse(hash)
|
35
|
+
end
|
36
|
+
|
37
|
+
# load and build drive from file
|
38
|
+
# @param [String] schema_path
|
39
|
+
# @return [Committee::Driver]
|
40
|
+
def self.load_from_file(schema_path)
|
41
|
+
load_from_json(schema_path)
|
42
|
+
end
|
43
|
+
|
16
44
|
# Driver is a base class for driver implementations.
|
17
45
|
class Driver
|
18
46
|
# Whether parameters that were form-encoded will be coerced by default.
|
@@ -7,8 +7,7 @@ module Committee::Middleware
|
|
7
7
|
@params_key = options[:params_key] || "committee.params"
|
8
8
|
@headers_key = options[:headers_key] || "committee.headers"
|
9
9
|
@raise = options[:raise]
|
10
|
-
@schema = get_schema(options
|
11
|
-
raise(ArgumentError, "Committee: need option `schema`"))
|
10
|
+
@schema = get_schema(options)
|
12
11
|
|
13
12
|
@router = Committee::Router.new(@schema,
|
14
13
|
prefix: options[:prefix]
|
@@ -36,7 +35,14 @@ module Committee::Middleware
|
|
36
35
|
# input to be a string, a data hash, or a JsonSchema::Schema. In the former
|
37
36
|
# two cases we just parse as if we were sent hyper-schema. In the latter,
|
38
37
|
# we have the hyper-schema driver wrap it in a new Committee object.
|
39
|
-
def get_schema(
|
38
|
+
def get_schema(options)
|
39
|
+
schema = options[:schema]
|
40
|
+
unless schema
|
41
|
+
schema = Committee::Drivers::load_from_file(options[:schema_path]) if options[:schema_path]
|
42
|
+
|
43
|
+
raise(ArgumentError, "Committee: need option `schema` or schema_path") unless schema
|
44
|
+
end
|
45
|
+
|
40
46
|
# These are in a separately conditional ladder so that we only show the
|
41
47
|
# user one warning.
|
42
48
|
if schema.is_a?(String)
|
@@ -62,8 +68,8 @@ module Committee::Middleware
|
|
62
68
|
# Expect the type we want by now. If we don't have it, the user passed
|
63
69
|
# something else non-standard in.
|
64
70
|
if !schema.is_a?(Committee::Drivers::Schema)
|
65
|
-
raise ArgumentError, "Committee: schema expected to be
|
66
|
-
"
|
71
|
+
raise ArgumentError, "Committee: schema expected to be an instance of " \
|
72
|
+
"Committee::Drivers::Schema."
|
67
73
|
end
|
68
74
|
|
69
75
|
schema
|
@@ -71,17 +77,17 @@ module Committee::Middleware
|
|
71
77
|
|
72
78
|
def warn_json_schema_deprecated
|
73
79
|
Committee.warn_deprecated("Committee: passing a JsonSchema::Schema to schema " \
|
74
|
-
"option is deprecated; please send
|
80
|
+
"option is deprecated; please send an instance of Committee::Drivers::Schema instead.")
|
75
81
|
end
|
76
82
|
|
77
83
|
def warn_hash_deprecated
|
78
84
|
Committee.warn_deprecated("Committee: passing a hash to schema " \
|
79
|
-
"option is deprecated; please send
|
85
|
+
"option is deprecated; please send an instance of Committee::Drivers::Schema instead.")
|
80
86
|
end
|
81
87
|
|
82
88
|
def warn_string_deprecated
|
83
89
|
Committee.warn_deprecated("Committee: passing a string to schema " \
|
84
|
-
"option is deprecated; please send
|
90
|
+
"option is deprecated; please send an instance of Committee::Drivers::Schema instead.")
|
85
91
|
end
|
86
92
|
end
|
87
93
|
end
|
@@ -1,10 +1,18 @@
|
|
1
1
|
module Committee::Middleware
|
2
2
|
class ResponseValidation < Base
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :validate_success_only
|
4
4
|
|
5
5
|
def initialize(app, options = {})
|
6
6
|
super
|
7
|
-
@
|
7
|
+
@validate_success_only = options.fetch(:validate_success_only, true)
|
8
|
+
|
9
|
+
unless options[:validate_errors].nil?
|
10
|
+
@validate_success_only = !options[:validate_errors]
|
11
|
+
Committee.warn_deprecated("Committee: validate_errors option is deprecated; " \
|
12
|
+
"please use validate_success_only=#{@validate_success_only}.")
|
13
|
+
end
|
14
|
+
|
15
|
+
@error_handler = options[:error_handler]
|
8
16
|
end
|
9
17
|
|
10
18
|
def handle(request)
|
@@ -16,21 +24,23 @@ module Committee::Middleware
|
|
16
24
|
response.each do |chunk|
|
17
25
|
full_body << chunk
|
18
26
|
end
|
19
|
-
data = JSON.parse(full_body)
|
20
|
-
Committee::ResponseValidator.new(link,
|
27
|
+
data = full_body.empty? ? {} : JSON.parse(full_body)
|
28
|
+
Committee::ResponseValidator.new(link, validate_success_only: validate_success_only).call(status, headers, data)
|
21
29
|
end
|
22
30
|
|
23
31
|
[status, headers, response]
|
24
32
|
rescue Committee::InvalidResponse
|
33
|
+
@error_handler.call($!) if @error_handler
|
25
34
|
raise if @raise
|
26
35
|
@error_class.new(500, :invalid_response, $!.message).render
|
27
36
|
rescue JSON::ParserError
|
37
|
+
@error_handler.call($!) if @error_handler
|
28
38
|
raise Committee::InvalidResponse if @raise
|
29
39
|
@error_class.new(500, :invalid_response, "Response wasn't valid JSON.").render
|
30
40
|
end
|
31
41
|
|
32
42
|
def validate?(status)
|
33
|
-
Committee::ResponseValidator.validate?(status,
|
43
|
+
Committee::ResponseValidator.validate?(status, validate_success_only: validate_success_only)
|
34
44
|
end
|
35
45
|
end
|
36
46
|
end
|
@@ -7,6 +7,7 @@ module Committee
|
|
7
7
|
@allow_query_params = options[:allow_query_params]
|
8
8
|
@coerce_form_params = options[:coerce_form_params]
|
9
9
|
@optimistic_json = options[:optimistic_json]
|
10
|
+
@coerce_recursive = options[:coerce_recursive]
|
10
11
|
@schema = options[:schema]
|
11
12
|
end
|
12
13
|
|
@@ -31,7 +32,7 @@ module Committee
|
|
31
32
|
p = @request.POST
|
32
33
|
|
33
34
|
if @coerce_form_params && @schema
|
34
|
-
Committee::StringParamsCoercer.new(p, @schema).call!
|
35
|
+
Committee::StringParamsCoercer.new(p, @schema, coerce_recursive: @coerce_recursive).call!
|
35
36
|
end
|
36
37
|
|
37
38
|
p
|
@@ -1,18 +1,18 @@
|
|
1
1
|
module Committee
|
2
2
|
class ResponseValidator
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :validate_success_only
|
4
4
|
|
5
5
|
def initialize(link, options = {})
|
6
6
|
@link = link
|
7
|
-
@
|
7
|
+
@validate_success_only = options[:validate_success_only]
|
8
8
|
|
9
9
|
@validator = JsonSchema::Validator.new(target_schema(link))
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.validate?(status, options = {})
|
13
|
-
|
13
|
+
validate_success_only = options[:validate_success_only]
|
14
14
|
|
15
|
-
status != 204 and
|
15
|
+
status != 204 and !validate_success_only || (200...300).include?(status)
|
16
16
|
end
|
17
17
|
|
18
18
|
def call(status, headers, data)
|
@@ -41,7 +41,7 @@ module Committee
|
|
41
41
|
return if data == nil
|
42
42
|
end
|
43
43
|
|
44
|
-
if self.class.validate?(status,
|
44
|
+
if self.class.validate?(status, validate_success_only: validate_success_only) && !@validator.validate(data)
|
45
45
|
errors = JsonSchema::SchemaError.aggregate(@validator.errors).join("\n")
|
46
46
|
raise InvalidResponse, "Invalid response.\n\n#{errors}"
|
47
47
|
end
|
data/lib/committee/router.rb
CHANGED
@@ -16,15 +16,15 @@ module Committee
|
|
16
16
|
|
17
17
|
def find_link(method, path)
|
18
18
|
path = path.gsub(@prefix_regexp, "") if @prefix
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
link_with_matches = (@schema.routes[method] || []).map do |pattern, link|
|
20
|
+
if matches = pattern.match(path)
|
21
|
+
# prefer path which has fewer matches (eg. `/pets/dog` than `/pets/{uuid}` for path `/pets/dog` )
|
22
|
+
[matches.captures.size, link, Hash[matches.names.zip(matches.captures)]]
|
23
|
+
else
|
24
|
+
nil
|
25
25
|
end
|
26
|
-
end
|
27
|
-
nil
|
26
|
+
end.compact.sort_by(&:first).first
|
27
|
+
link_with_matches.nil? ? nil : link_with_matches.slice(1, 2)
|
28
28
|
end
|
29
29
|
|
30
30
|
def find_request_link(request)
|
@@ -35,25 +35,49 @@ module Committee::Test
|
|
35
35
|
@committee_router ||= Committee::Router.new(@committee_schema,
|
36
36
|
prefix: schema_url_prefix)
|
37
37
|
|
38
|
-
link, _ = @committee_router.find_request_link(
|
38
|
+
link, _ = @committee_router.find_request_link(request_object)
|
39
39
|
unless link
|
40
|
-
response = "`#{
|
40
|
+
response = "`#{request_object.request_method} #{request_object.path_info}` undefined in schema."
|
41
41
|
raise Committee::InvalidResponse.new(response)
|
42
42
|
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
status, headers, body = response_data
|
45
|
+
if validate?(status)
|
46
|
+
data = JSON.parse(body)
|
47
|
+
Committee::ResponseValidator.new(link).call(status, headers, data)
|
47
48
|
end
|
48
49
|
end
|
49
50
|
|
51
|
+
def request_object
|
52
|
+
last_request
|
53
|
+
end
|
54
|
+
|
55
|
+
def response_data
|
56
|
+
[last_response.status, last_response.headers, last_response.body]
|
57
|
+
end
|
58
|
+
|
50
59
|
def assert_schema_content_type
|
51
60
|
Committee.warn_deprecated("Committee: use of #assert_schema_content_type is deprecated; use #assert_schema_conform instead.")
|
52
61
|
end
|
53
62
|
|
63
|
+
# we use this method 3.0 or later
|
64
|
+
def committee_options
|
65
|
+
unless defined?(@call_committee_options_deprecated)
|
66
|
+
@call_committee_options_deprecated = true
|
67
|
+
Committee.warn_deprecated("Committee: committee 3.0 require overwrite committee options so please use this method.")
|
68
|
+
end
|
69
|
+
|
70
|
+
{}
|
71
|
+
end
|
72
|
+
|
54
73
|
# Can be overridden with a different driver name for other API definition
|
55
74
|
# formats.
|
56
75
|
def committee_schema
|
76
|
+
schema = committee_options[:schema]
|
77
|
+
return schema if schema
|
78
|
+
|
79
|
+
Committee.warn_deprecated("Committee: we'll remove committee_schema method in committee 3.0;" \
|
80
|
+
"please use committee_options.")
|
57
81
|
nil
|
58
82
|
end
|
59
83
|
|
@@ -61,14 +85,26 @@ module Committee::Test
|
|
61
85
|
# easier to access as a string
|
62
86
|
# blob
|
63
87
|
def schema_contents
|
88
|
+
Committee.warn_deprecated("Committee: we'll remove schema_contents method in committee 3.0;" \
|
89
|
+
"please use committee_options.")
|
64
90
|
JSON.parse(File.read(schema_path))
|
65
91
|
end
|
66
92
|
|
67
93
|
def schema_path
|
94
|
+
Committee.warn_deprecated("Committee: we'll remove schema_path method in committee 3.0;" \
|
95
|
+
"please use committee_options.")
|
68
96
|
raise "Please override #committee_schema."
|
69
97
|
end
|
70
98
|
|
71
99
|
def schema_url_prefix
|
100
|
+
prefix = committee_options[:prefix]
|
101
|
+
return prefix if prefix
|
102
|
+
|
103
|
+
schema = committee_options[:schema]
|
104
|
+
return nil if schema # committee_options set so we don't show warn message
|
105
|
+
|
106
|
+
Committee.warn_deprecated("Committee: we'll remove schema_url_prefix method in committee 3.0;" \
|
107
|
+
"please use committee_options.")
|
72
108
|
nil
|
73
109
|
end
|
74
110
|
|
@@ -85,7 +121,19 @@ module Committee::Test
|
|
85
121
|
end
|
86
122
|
|
87
123
|
def validate_response?(status)
|
88
|
-
Committee
|
124
|
+
Committee.warn_deprecated("Committee: w'll remove validate_response? method in committee 3.0")
|
125
|
+
|
126
|
+
Committee::ResponseValidator.validate?(status, validate_success_only: validate_success_only)
|
89
127
|
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def validate_success_only
|
132
|
+
committee_options.fetch(:validate_success_only, true)
|
133
|
+
end
|
134
|
+
|
135
|
+
def validate?(status)
|
136
|
+
Committee::ResponseValidator.validate?(status, validate_success_only: validate_success_only)
|
137
|
+
end
|
90
138
|
end
|
91
139
|
end
|
data/test/drivers_test.rb
CHANGED
@@ -19,6 +19,48 @@ describe Committee::Drivers do
|
|
19
19
|
end
|
20
20
|
assert_equal %{Committee: unknown driver "blueprint".}, e.message
|
21
21
|
end
|
22
|
+
|
23
|
+
describe 'load_from_file(schema_path)' do
|
24
|
+
it 'load OpenAPI2' do
|
25
|
+
s = Committee::Drivers.load_from_file(open_api_2_schema_path)
|
26
|
+
assert_kind_of Committee::Drivers::Schema, s
|
27
|
+
assert_kind_of Committee::Drivers::OpenAPI2::Schema, s
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'load Hyper-Schema' do
|
31
|
+
s = Committee::Drivers.load_from_file(hyper_schema_schema_path)
|
32
|
+
assert_kind_of Committee::Drivers::Schema, s
|
33
|
+
assert_kind_of Committee::Drivers::HyperSchema::Schema, s
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'load_from_json(schema_path)' do
|
38
|
+
it 'load OpenAPI2' do
|
39
|
+
s = Committee::Drivers.load_from_json(open_api_2_schema_path)
|
40
|
+
assert_kind_of Committee::Drivers::Schema, s
|
41
|
+
assert_kind_of Committee::Drivers::OpenAPI2::Schema, s
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'load Hyper-Schema' do
|
45
|
+
s = Committee::Drivers.load_from_json(hyper_schema_schema_path)
|
46
|
+
assert_kind_of Committee::Drivers::Schema, s
|
47
|
+
assert_kind_of Committee::Drivers::HyperSchema::Schema, s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'load_from_data(schema_path)' do
|
52
|
+
it 'load OpenAPI2' do
|
53
|
+
s = Committee::Drivers.load_from_data(open_api_2_data)
|
54
|
+
assert_kind_of Committee::Drivers::Schema, s
|
55
|
+
assert_kind_of Committee::Drivers::OpenAPI2::Schema, s
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'load Hyper-Schema' do
|
59
|
+
s = Committee::Drivers.load_from_data(hyper_schema_data)
|
60
|
+
assert_kind_of Committee::Drivers::Schema, s
|
61
|
+
assert_kind_of Committee::Drivers::HyperSchema::Schema, s
|
62
|
+
end
|
63
|
+
end
|
22
64
|
end
|
23
65
|
|
24
66
|
describe Committee::Drivers::Driver do
|
@@ -65,8 +65,19 @@ describe Committee::Middleware::Base do
|
|
65
65
|
e = assert_raises(ArgumentError) do
|
66
66
|
post "/apps"
|
67
67
|
end
|
68
|
-
assert_equal "Committee: schema expected to be
|
69
|
-
|
68
|
+
assert_equal "Committee: schema expected to be an instance of Committee::Drivers::Schema.", e.message
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'initialize option' do
|
72
|
+
it "schema_path option with hyper-schema" do
|
73
|
+
b = Committee::Middleware::Base.new(nil, schema_path: hyper_schema_schema_path)
|
74
|
+
assert_kind_of Committee::Drivers::HyperSchema::Schema, b.instance_variable_get(:@schema)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "schema_path option with OpenAPI2" do
|
78
|
+
b = Committee::Middleware::Base.new(nil, schema_path: open_api_2_schema_path)
|
79
|
+
assert_kind_of Committee::Drivers::OpenAPI2::Schema, b.instance_variable_get(:@schema)
|
80
|
+
end
|
70
81
|
end
|
71
82
|
|
72
83
|
private
|
@@ -13,6 +13,14 @@ describe Committee::Middleware::ResponseValidation do
|
|
13
13
|
assert_equal 200, last_response.status
|
14
14
|
end
|
15
15
|
|
16
|
+
it "doesn't call error_handler when response is valid" do
|
17
|
+
called = false
|
18
|
+
pr = ->(_e) { called = true }
|
19
|
+
@app = new_rack_app(JSON.generate([ValidApp]), {}, schema: hyper_schema, error_handler: pr)
|
20
|
+
get "/apps"
|
21
|
+
assert !called, "error_handler is called"
|
22
|
+
end
|
23
|
+
|
16
24
|
it "detects a response invalid due to schema" do
|
17
25
|
@app = new_rack_app("{}", {}, schema: hyper_schema)
|
18
26
|
get "/apps"
|
@@ -21,7 +29,7 @@ describe Committee::Middleware::ResponseValidation do
|
|
21
29
|
end
|
22
30
|
|
23
31
|
it "detects a response invalid due to not being JSON" do
|
24
|
-
@app = new_rack_app("", {}, schema: hyper_schema)
|
32
|
+
@app = new_rack_app("{_}", {}, schema: hyper_schema)
|
25
33
|
get "/apps"
|
26
34
|
assert_equal 500, last_response.status
|
27
35
|
assert_match(/valid JSON/i, last_response.body)
|
@@ -33,10 +41,26 @@ describe Committee::Middleware::ResponseValidation do
|
|
33
41
|
assert_equal 404, last_response.status
|
34
42
|
end
|
35
43
|
|
44
|
+
it "optionally validates non-2xx invalid responses and deprecated option" do
|
45
|
+
mock(Committee).warn_deprecated.with_any_args
|
46
|
+
|
47
|
+
@app = new_rack_app("{_}", {}, app_status: 404, validate_errors: true,
|
48
|
+
schema: hyper_schema)
|
49
|
+
|
50
|
+
get "/apps"
|
51
|
+
assert_equal 500, last_response.status
|
52
|
+
assert_match(/valid JSON/i, last_response.body)
|
53
|
+
end
|
54
|
+
|
36
55
|
it "optionally validates non-2xx invalid responses" do
|
37
|
-
@app = new_rack_app("", {}, app_status: 404,
|
38
|
-
|
56
|
+
@app = new_rack_app("", {}, app_status: 404, validate_success_only: false, schema: hyper_schema)
|
57
|
+
get "/apps"
|
58
|
+
assert_equal 500, last_response.status
|
59
|
+
assert_match(/Invalid response/i, last_response.body)
|
60
|
+
end
|
39
61
|
|
62
|
+
it "optionally validates non-2xx invalid responses with invalid json" do
|
63
|
+
@app = new_rack_app("{_}", {}, app_status: 404, validate_success_only: false, schema: hyper_schema)
|
40
64
|
get "/apps"
|
41
65
|
assert_equal 500, last_response.status
|
42
66
|
assert_match(/valid JSON/i, last_response.body)
|
@@ -48,13 +72,28 @@ describe Committee::Middleware::ResponseValidation do
|
|
48
72
|
assert_equal 204, last_response.status
|
49
73
|
end
|
50
74
|
|
75
|
+
it "skip validation when 4xx" do
|
76
|
+
@app = new_rack_app("[{x:y}]", {}, schema: hyper_schema, validate_success_only: true, app_status: 400)
|
77
|
+
get "/apps"
|
78
|
+
assert_equal 400, last_response.status
|
79
|
+
assert_match("[{x:y}]", last_response.body)
|
80
|
+
end
|
81
|
+
|
51
82
|
it "rescues JSON errors" do
|
52
|
-
@app = new_rack_app("[{x:y}]", {}, schema: hyper_schema)
|
83
|
+
@app = new_rack_app("[{x:y}]", {}, schema: hyper_schema, validate_success_only: false)
|
53
84
|
get "/apps"
|
54
85
|
assert_equal 500, last_response.status
|
55
86
|
assert_match(/valid json/i, last_response.body)
|
56
87
|
end
|
57
88
|
|
89
|
+
it "calls error_handler when it rescues JSON errors" do
|
90
|
+
called_err = nil
|
91
|
+
pr = ->(e) { called_err = e }
|
92
|
+
@app = new_rack_app("[{x:y}]", {}, schema: hyper_schema, error_handler: pr)
|
93
|
+
get "/apps"
|
94
|
+
assert_kind_of JSON::ParserError, called_err
|
95
|
+
end
|
96
|
+
|
58
97
|
it "takes a prefix" do
|
59
98
|
@app = new_rack_app(JSON.generate([ValidApp]), {}, prefix: "/v1",
|
60
99
|
schema: hyper_schema)
|
@@ -69,6 +108,16 @@ describe Committee::Middleware::ResponseValidation do
|
|
69
108
|
end
|
70
109
|
end
|
71
110
|
|
111
|
+
it "calls error_handler when it rescues JSON errors" do
|
112
|
+
called_err = nil
|
113
|
+
pr = ->(e) { called_err = e }
|
114
|
+
@app = new_rack_app("[{x:y}]", {}, raise: true, schema: hyper_schema, error_handler: pr)
|
115
|
+
assert_raises(Committee::InvalidResponse) do
|
116
|
+
get "/apps"
|
117
|
+
assert_kind_of JSON::ParserError, called_err
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
72
121
|
it "passes through a valid response for OpenAPI" do
|
73
122
|
@app = new_rack_app(JSON.generate([ValidPet]), {},
|
74
123
|
schema: open_api_2_schema)
|
@@ -77,7 +126,7 @@ describe Committee::Middleware::ResponseValidation do
|
|
77
126
|
end
|
78
127
|
|
79
128
|
it "detects an invalid response for OpenAPI" do
|
80
|
-
@app = new_rack_app("", {}, schema: open_api_2_schema)
|
129
|
+
@app = new_rack_app("{_}", {}, schema: open_api_2_schema)
|
81
130
|
get "/api/pets"
|
82
131
|
assert_equal 500, last_response.status
|
83
132
|
assert_match(/valid JSON/i, last_response.body)
|
@@ -113,6 +113,30 @@ describe Committee::RequestUnpacker do
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
+
it "coerces form params with coerce_form_params, coerce_recursive and a schema" do
|
117
|
+
%w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
|
118
|
+
schema = JsonSchema::Schema.new
|
119
|
+
schema.properties = { "x" => JsonSchema::Schema.new }
|
120
|
+
schema.properties["x"].type = ["object"]
|
121
|
+
schema.properties["x"].properties = { "y" => JsonSchema::Schema.new }
|
122
|
+
schema.properties["x"].properties["y"].type = ["integer"]
|
123
|
+
|
124
|
+
env = {
|
125
|
+
"CONTENT_TYPE" => content_type,
|
126
|
+
"rack.input" => StringIO.new("x[y]=1"),
|
127
|
+
}
|
128
|
+
request = Rack::Request.new(env)
|
129
|
+
params, _ = Committee::RequestUnpacker.new(
|
130
|
+
request,
|
131
|
+
allow_form_params: true,
|
132
|
+
coerce_form_params: true,
|
133
|
+
coerce_recursive: true,
|
134
|
+
schema: schema
|
135
|
+
).call
|
136
|
+
assert_equal({ "x" => { "y" => 1 } }, params)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
116
140
|
it "unpacks form & query params with allow_form_params and allow_query_params" do
|
117
141
|
%w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
|
118
142
|
env = {
|
data/test/router_test.rb
CHANGED
@@ -44,6 +44,7 @@ describe Committee::Router do
|
|
44
44
|
it "provides named parameters" do
|
45
45
|
link, param_matches = open_api_2_router.find_link("GET", "/api/pets/fido")
|
46
46
|
refute_nil link
|
47
|
+
assert_equal '/api/pets/{id}', link.href
|
47
48
|
assert_equal({ "id" => "fido" }, param_matches)
|
48
49
|
end
|
49
50
|
|
@@ -53,6 +54,18 @@ describe Committee::Router do
|
|
53
54
|
assert_equal({}, param_matches)
|
54
55
|
end
|
55
56
|
|
57
|
+
it "finds a path which has fewer matches" do
|
58
|
+
link, _ = open_api_2_router.find_link("GET", "/api/pets/dog")
|
59
|
+
refute_nil link
|
60
|
+
assert_equal '/api/pets/dog', link.href
|
61
|
+
end
|
62
|
+
|
63
|
+
it "fewer match not support in HyperSchema" do
|
64
|
+
link, _ = hyper_schema_router.find_link("GET", "/apps/abc")
|
65
|
+
refute_nil link
|
66
|
+
assert_equal '/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fname)}', link.href
|
67
|
+
end
|
68
|
+
|
56
69
|
def hyper_schema_router(options = {})
|
57
70
|
schema = Committee::Drivers::HyperSchema.new.parse(hyper_schema_data)
|
58
71
|
Committee::Router.new(schema, options)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative "../test_helper"
|
2
|
+
|
3
|
+
describe Committee::Test::Methods do
|
4
|
+
include Committee::Test::Methods
|
5
|
+
include Rack::Test::Methods
|
6
|
+
|
7
|
+
def app
|
8
|
+
@app
|
9
|
+
end
|
10
|
+
|
11
|
+
def committee_options
|
12
|
+
@committee_options
|
13
|
+
end
|
14
|
+
|
15
|
+
before do
|
16
|
+
# This is a little icky, but the test methods will cache router and schema
|
17
|
+
# values between tests. This makes sense in real life, but is harmful for
|
18
|
+
# our purposes here in testing the module.
|
19
|
+
@committee_router = nil
|
20
|
+
@committee_schema = nil
|
21
|
+
|
22
|
+
@committee_options = {schema: hyper_schema}
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#assert_schema_conform" do
|
26
|
+
it "passes through a valid response" do
|
27
|
+
@app = new_rack_app(JSON.generate([ValidApp]))
|
28
|
+
get "/apps"
|
29
|
+
assert_schema_conform
|
30
|
+
end
|
31
|
+
|
32
|
+
it "passes with prefix" do
|
33
|
+
@committee_options.merge!(prefix: "/v1")
|
34
|
+
|
35
|
+
@app = new_rack_app(JSON.generate([ValidApp]))
|
36
|
+
get "/v1/apps"
|
37
|
+
assert_schema_conform
|
38
|
+
end
|
39
|
+
|
40
|
+
it "detects an invalid response Content-Type" do
|
41
|
+
@app = new_rack_app(JSON.generate([ValidApp]), 200, {})
|
42
|
+
get "/apps"
|
43
|
+
e = assert_raises(Committee::InvalidResponse) do
|
44
|
+
assert_schema_conform
|
45
|
+
end
|
46
|
+
assert_match(/response header must be set to/i, e.message)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "detects an invalid response Content-Type but ignore because it's not success status code" do
|
50
|
+
@app = new_rack_app(JSON.generate([ValidApp]), 400, {})
|
51
|
+
get "/apps"
|
52
|
+
assert_schema_conform
|
53
|
+
end
|
54
|
+
|
55
|
+
it "detects an invalid response Content-Type and check all status code" do
|
56
|
+
@committee_options.merge!(validate_success_only: false)
|
57
|
+
|
58
|
+
@app = new_rack_app(JSON.generate([ValidApp]), 400, {})
|
59
|
+
get "/apps"
|
60
|
+
e = assert_raises(Committee::InvalidResponse) do
|
61
|
+
assert_schema_conform
|
62
|
+
end
|
63
|
+
assert_match(/response header must be set to/i, e.message)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def new_rack_app(response, status=200, headers={ "Content-Type" => "application/json" })
|
70
|
+
Rack::Builder.new {
|
71
|
+
run lambda { |_|
|
72
|
+
[status, headers, [response]]
|
73
|
+
}
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
data/test/test/methods_test.rb
CHANGED
@@ -29,12 +29,16 @@ describe Committee::Test::Methods do
|
|
29
29
|
|
30
30
|
describe "#assert_schema_conform" do
|
31
31
|
it "passes through a valid response" do
|
32
|
+
mock(Committee).warn_deprecated.with_any_args.times(2)
|
33
|
+
|
32
34
|
@app = new_rack_app(JSON.generate([ValidApp]))
|
33
35
|
get "/apps"
|
34
36
|
assert_schema_conform
|
35
37
|
end
|
36
38
|
|
37
39
|
it "detects an invalid response Content-Type" do
|
40
|
+
mock(Committee).warn_deprecated.with_any_args.times(2)
|
41
|
+
|
38
42
|
@app = new_rack_app(JSON.generate([ValidApp]), {})
|
39
43
|
get "/apps"
|
40
44
|
e = assert_raises(Committee::InvalidResponse) do
|
@@ -44,7 +48,7 @@ describe Committee::Test::Methods do
|
|
44
48
|
end
|
45
49
|
|
46
50
|
it "accepts schema string (legacy behavior)" do
|
47
|
-
mock(Committee).warn_deprecated.with_any_args
|
51
|
+
mock(Committee).warn_deprecated.with_any_args.times(3)
|
48
52
|
|
49
53
|
stub(self).committee_schema { nil }
|
50
54
|
stub(self).schema_contents { JSON.dump(hyper_schema_data) }
|
@@ -55,7 +59,7 @@ describe Committee::Test::Methods do
|
|
55
59
|
end
|
56
60
|
|
57
61
|
it "accepts schema hash (legacy behavior)" do
|
58
|
-
mock(Committee).warn_deprecated.with_any_args
|
62
|
+
mock(Committee).warn_deprecated.with_any_args.times(3)
|
59
63
|
|
60
64
|
stub(self).committee_schema { nil }
|
61
65
|
stub(self).schema_contents { hyper_schema_data }
|
@@ -69,6 +73,7 @@ describe Committee::Test::Methods do
|
|
69
73
|
# Note we don't warn here because this is a recent deprecation and
|
70
74
|
# passing a schema object will not be a huge performance hit. We should
|
71
75
|
# probably start warning on the next version.
|
76
|
+
mock(Committee).warn_deprecated.with_any_args.times(2)
|
72
77
|
|
73
78
|
stub(self).committee_schema { nil }
|
74
79
|
stub(self).schema_contents do
|
data/test/test_helper.rb
CHANGED
@@ -47,25 +47,27 @@ ValidPet = {
|
|
47
47
|
}.freeze
|
48
48
|
|
49
49
|
def hyper_schema
|
50
|
-
@hyper_schema ||=
|
51
|
-
driver = Committee::Drivers::HyperSchema.new
|
52
|
-
driver.parse(hyper_schema_data)
|
53
|
-
end
|
50
|
+
@hyper_schema ||= Committee::Drivers.load_from_file(hyper_schema_schema_path)
|
54
51
|
end
|
55
52
|
|
56
53
|
def open_api_2_schema
|
57
|
-
@open_api_2_schema ||=
|
58
|
-
driver = Committee::Drivers::OpenAPI2.new
|
59
|
-
driver.parse(open_api_2_data)
|
60
|
-
end
|
54
|
+
@open_api_2_schema ||= Committee::Drivers.load_from_data(open_api_2_data)
|
61
55
|
end
|
62
56
|
|
63
57
|
# Don't cache this because we'll often manipulate the created hash in tests.
|
64
58
|
def hyper_schema_data
|
65
|
-
JSON.parse(File.read(
|
59
|
+
JSON.parse(File.read(hyper_schema_schema_path))
|
66
60
|
end
|
67
61
|
|
68
62
|
# Don't cache this because we'll often manipulate the created hash in tests.
|
69
63
|
def open_api_2_data
|
70
|
-
JSON.parse(File.read(
|
64
|
+
JSON.parse(File.read(open_api_2_schema_path))
|
65
|
+
end
|
66
|
+
|
67
|
+
def hyper_schema_schema_path
|
68
|
+
"./test/data/hyperschema/paas.json"
|
69
|
+
end
|
70
|
+
|
71
|
+
def open_api_2_schema_path
|
72
|
+
"./test/data/openapi2/petstore-expanded.json"
|
71
73
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: committee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandur
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2019-01-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json_schema
|
@@ -189,6 +189,7 @@ files:
|
|
189
189
|
- test/response_validator_test.rb
|
190
190
|
- test/router_test.rb
|
191
191
|
- test/string_params_coercer_test.rb
|
192
|
+
- test/test/methods_new_version_test.rb
|
192
193
|
- test/test/methods_test.rb
|
193
194
|
- test/test_helper.rb
|
194
195
|
- test/validation_error_test.rb
|
@@ -212,7 +213,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
212
213
|
version: '0'
|
213
214
|
requirements: []
|
214
215
|
rubyforge_project:
|
215
|
-
rubygems_version: 2.
|
216
|
+
rubygems_version: 2.7.3
|
216
217
|
signing_key:
|
217
218
|
specification_version: 4
|
218
219
|
summary: A collection of Rack middleware to support JSON Schema.
|