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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c93972246b5dbf788152e0b307564f19bf9fd2ac
4
- data.tar.gz: b56949031b0ddff186b158b4346a71743de8c607
2
+ SHA256:
3
+ metadata.gz: eac1e86868d4ca6f8b7ef27f62ec87131ba1399985f48b0e7846a5ceda6d81cc
4
+ data.tar.gz: a0e76eea124800b0d8d328657ffb30efc3c683bbfd47707ce9c148500fc79307
5
5
  SHA512:
6
- metadata.gz: 76b74624c59f7af781abe8359df8db12ded7cc697ccea6cc9c73eb03be7e07813eec6c32b0436f54c2902c5fd4e995bc3d729ebc502c43745b7c59750dbc0919
7
- data.tar.gz: e82213545cd8918152a748d6637563fc2acd589f9e48b3f76d8b73d12b8f15f80a93f6ded6706a1d8383eaadd7cb14d0661ecf35dec1dde2cc9660387c0f43e5
6
+ metadata.gz: dcaa28428558ae7b9f6ce8679e826e62bdc0640228e35fb41a0998a1ccbbab4d505bc6e23ad052b907a45be6d2003152ab9803e132907a3c68b1a2fbf7fc5292
7
+ data.tar.gz: 0a1d640b9305e12a2ac6b7436b2971a8a1568322aa1d75a25f8ec4a25aed50229c5bd5c99e317c9c243d22099c1855ea93fbf17cc0692488a35a8f1ee03f8959
@@ -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[:schema] ||
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(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 a hash or " \
66
- "an instance of Committee::Drivers::Schema."
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 a driver object instead.")
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 a driver object instead.")
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 a driver object instead.")
90
+ "option is deprecated; please send an instance of Committee::Drivers::Schema instead.")
85
91
  end
86
92
  end
87
93
  end
@@ -46,6 +46,7 @@ module Committee::Middleware
46
46
  allow_query_params: @allow_query_params,
47
47
  coerce_form_params: @coerce_form_params,
48
48
  optimistic_json: @optimistic_json,
49
+ coerce_recursive: @coerce_recursive,
49
50
  schema: link ? link.schema : nil
50
51
  ).call
51
52
 
@@ -1,10 +1,18 @@
1
1
  module Committee::Middleware
2
2
  class ResponseValidation < Base
3
- attr_reader :validate_errors
3
+ attr_reader :validate_success_only
4
4
 
5
5
  def initialize(app, options = {})
6
6
  super
7
- @validate_errors = options[:validate_errors]
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, validate_errors: validate_errors).call(status, headers, data)
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, validate_errors: validate_errors)
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 :validate_errors
3
+ attr_reader :validate_success_only
4
4
 
5
5
  def initialize(link, options = {})
6
6
  @link = link
7
- @validate_errors = options[:validate_errors]
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
- validate_errors = options[:validate_errors]
13
+ validate_success_only = options[:validate_success_only]
14
14
 
15
- status != 204 and validate_errors || (200...300).include?(status)
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, validate_errors: validate_errors) && !@validator.validate(data)
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
@@ -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
- if method_routes = @schema.routes[method]
20
- method_routes.each do |pattern, link|
21
- if matches = pattern.match(path)
22
- hash = Hash[matches.names.zip(matches.captures)]
23
- return link, hash
24
- end
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(last_request)
38
+ link, _ = @committee_router.find_request_link(request_object)
39
39
  unless link
40
- response = "`#{last_request.request_method} #{last_request.path_info}` undefined in schema."
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
- if validate_response?(last_response.status)
45
- data = JSON.parse(last_response.body)
46
- Committee::ResponseValidator.new(link).call(last_response.status, last_response.headers, data)
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::ResponseValidator.validate?(status)
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
@@ -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 a hash or an instance " +
69
- "of Committee::Drivers::Schema.", e.message
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, validate_errors: true,
38
- schema: hyper_schema)
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 = {
@@ -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
@@ -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
@@ -47,25 +47,27 @@ ValidPet = {
47
47
  }.freeze
48
48
 
49
49
  def hyper_schema
50
- @hyper_schema ||= begin
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 ||= begin
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("./test/data/hyperschema/paas.json"))
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("./test/data/openapi2/petstore-expanded.json"))
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.3.0
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: 2018-11-15 00:00:00.000000000 Z
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.6.14
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.