committee 2.3.0 → 2.4.0
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.
- 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.
|