committee 2.5.1 → 3.0.0.alpha

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 (50) hide show
  1. checksums.yaml +5 -5
  2. data/lib/committee.rb +16 -5
  3. data/lib/committee/bin/committee_stub.rb +4 -0
  4. data/lib/committee/drivers.rb +12 -29
  5. data/lib/committee/drivers/hyper_schema.rb +9 -0
  6. data/lib/committee/drivers/open_api_2.rb +9 -20
  7. data/lib/committee/drivers/open_api_3.rb +70 -0
  8. data/lib/committee/errors.rb +3 -0
  9. data/lib/committee/middleware/base.rb +20 -62
  10. data/lib/committee/middleware/request_validation.rb +5 -62
  11. data/lib/committee/middleware/response_validation.rb +5 -19
  12. data/lib/committee/middleware/stub.rb +5 -1
  13. data/lib/committee/parameter_coercer.rb +1 -0
  14. data/lib/committee/request_unpacker.rb +2 -5
  15. data/lib/committee/schema_validator/hyper_schema.rb +92 -0
  16. data/lib/committee/{request_validator.rb → schema_validator/hyper_schema/request_validator.rb} +1 -1
  17. data/lib/committee/{response_generator.rb → schema_validator/hyper_schema/response_generator.rb} +1 -1
  18. data/lib/committee/{response_validator.rb → schema_validator/hyper_schema/response_validator.rb} +6 -6
  19. data/lib/committee/{router.rb → schema_validator/hyper_schema/router.rb} +10 -4
  20. data/lib/committee/{string_params_coercer.rb → schema_validator/hyper_schema/string_params_coercer.rb} +1 -1
  21. data/lib/committee/schema_validator/open_api_3.rb +67 -0
  22. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +98 -0
  23. data/lib/committee/schema_validator/open_api_3/request_validator.rb +17 -0
  24. data/lib/committee/schema_validator/open_api_3/response_validator.rb +35 -0
  25. data/lib/committee/schema_validator/open_api_3/router.rb +26 -0
  26. data/lib/committee/schema_validator/option.rb +31 -0
  27. data/lib/committee/test/methods.rb +14 -117
  28. data/test/bin/committee_stub_test.rb +6 -0
  29. data/test/drivers/open_api_2_test.rb +0 -81
  30. data/test/drivers/open_api_3_test.rb +81 -0
  31. data/test/drivers_test.rb +2 -42
  32. data/test/middleware/base_test.rb +42 -21
  33. data/test/middleware/request_validation_open_api_3_test.rb +499 -0
  34. data/test/middleware/request_validation_test.rb +18 -0
  35. data/test/middleware/response_validation_open_api_3_test.rb +96 -0
  36. data/test/middleware/response_validation_test.rb +7 -30
  37. data/test/middleware/stub_test.rb +9 -0
  38. data/test/request_unpacker_test.rb +55 -21
  39. data/test/{request_validator_test.rb → schema_validator/hyper_schema/request_validator_test.rb} +4 -4
  40. data/test/{response_generator_test.rb → schema_validator/hyper_schema/response_generator_test.rb} +11 -11
  41. data/test/{response_validator_test.rb → schema_validator/hyper_schema/response_validator_test.rb} +3 -3
  42. data/test/{router_test.rb → schema_validator/hyper_schema/router_test.rb} +8 -10
  43. data/test/{string_params_coercer_test.rb → schema_validator/hyper_schema/string_params_coercer_test.rb} +3 -3
  44. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +132 -0
  45. data/test/schema_validator/open_api_3/request_validator_test.rb +151 -0
  46. data/test/schema_validator/open_api_3/response_validator_test.rb +55 -0
  47. data/test/test/methods_new_version_test.rb +11 -20
  48. data/test/test/methods_test.rb +51 -55
  49. data/test/test_helper.rb +22 -8
  50. metadata +46 -18
@@ -1,6 +1,6 @@
1
- require_relative "test_helper"
1
+ require_relative "../../test_helper"
2
2
 
3
- describe Committee::StringParamsCoercer do
3
+ describe Committee::SchemaValidator::HyperSchema::StringParamsCoercer do
4
4
  before do
5
5
  @schema = JsonSchema.parse!(hyper_schema_data)
6
6
  @schema.expand_references!
@@ -130,6 +130,6 @@ describe Committee::StringParamsCoercer do
130
130
  end
131
131
 
132
132
  def call(data, options={})
133
- Committee::StringParamsCoercer.new(data, @link.schema, options).call!
133
+ Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(data, @link.schema, options).call!
134
134
  end
135
135
  end
@@ -0,0 +1,132 @@
1
+ require_relative "../../test_helper"
2
+
3
+ require "stringio"
4
+
5
+ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
6
+ describe 'validate' do
7
+ before do
8
+ @path = '/validate'
9
+ @method = 'post'
10
+ @validator_option = Committee::SchemaValidator::Option.new({}, open_api_3_schema, :open_api_3)
11
+ end
12
+
13
+ def operation_object
14
+ open_api_3_schema.operation_object(@path, @method)
15
+ end
16
+
17
+ SCHEMA_PROPERTIES_PAIR = [
18
+ ['string', 'str'],
19
+ ['integer', 1],
20
+ ['boolean', true],
21
+ ['boolean', false],
22
+ ['number', 0.1],
23
+ ]
24
+
25
+ it 'correct data' do
26
+ operation_object.validate_request_params(SCHEMA_PROPERTIES_PAIR.to_h, @validator_option)
27
+ assert true
28
+ end
29
+
30
+ it 'correct object data' do
31
+ operation_object.validate_request_params({
32
+ "object_1" =>
33
+ {
34
+ "string_1" => nil,
35
+ "integer_1" => nil,
36
+ "boolean_1" => nil,
37
+ "number_1" => nil
38
+ }
39
+ },
40
+ @validator_option)
41
+
42
+ assert true
43
+ end
44
+
45
+ it 'invalid params' do
46
+ e = assert_raises(Committee::InvalidRequest) {
47
+ operation_object.validate_request_params({"string" => 1}, @validator_option)
48
+ }
49
+
50
+ # FIXME: when ruby 2.3 dropped, fix because ruby 2.3 return Fixnum, ruby 2.4 or later return Integer
51
+ assert e.message.start_with?("1 class is #{1.class} but it's not valid")
52
+ end
53
+
54
+ it 'support put method' do
55
+ @method = "put"
56
+ operation_object.validate_request_params({"string" => "str"}, @validator_option)
57
+
58
+ e = assert_raises(Committee::InvalidRequest) {
59
+ operation_object.validate_request_params({"string" => 1}, @validator_option)
60
+ }
61
+
62
+ # FIXME: when ruby 2.3 dropped, fix because ruby 2.3 return Fixnum, ruby 2.4 or later return Integer
63
+ assert e.message.start_with?("1 class is #{1.class} but it's not valid")
64
+ end
65
+
66
+ it 'support patch method' do
67
+ @method = "patch"
68
+ operation_object.validate_request_params({"integer" => 1}, @validator_option)
69
+
70
+ e = assert_raises(Committee::InvalidRequest) {
71
+ operation_object.validate_request_params({"integer" => "str"}, @validator_option)
72
+ }
73
+
74
+ assert e.message.start_with?("str class is String but it's not valid")
75
+ end
76
+
77
+ it 'unknown param' do
78
+ operation_object.validate_request_params({"unknown" => 1}, @validator_option)
79
+ end
80
+
81
+ describe 'support get method' do
82
+ before do
83
+ @method = "get"
84
+ end
85
+
86
+ it 'correct' do
87
+ operation_object.validate_request_params({"query_string" => "query", "query_integer_list" => [1, 2]}, @validator_option)
88
+ operation_object.validate_request_params({"query_string" => "query", "query_integer_list" => [1, 2], "optional_integer" => 1}, @validator_option)
89
+
90
+ assert true
91
+ end
92
+
93
+ it 'not exist required' do
94
+ e = assert_raises(Committee::InvalidRequest) {
95
+ operation_object.validate_request_params({"query_integer_list" => [1, 2]}, @validator_option)
96
+ }
97
+
98
+ assert e.message.start_with?("required parameters query_string not exist in")
99
+ end
100
+
101
+ it 'invalid type' do
102
+ e = assert_raises(Committee::InvalidRequest) {
103
+ operation_object.validate_request_params({"query_string" => 1, "query_integer_list" => [1, 2], "optional_integer" => 1}, @validator_option)
104
+ }
105
+
106
+ # FIXME: when ruby 2.3 dropped, fix because ruby 2.3 return Fixnum, ruby 2.4 or later return Integer
107
+ assert e.message.start_with?("1 class is #{1.class} but")
108
+ end
109
+ end
110
+
111
+ describe 'support delete method' do
112
+ before do
113
+ @path = '/characters'
114
+ @method = "delete"
115
+ end
116
+
117
+ it 'correct' do
118
+ operation_object.validate_request_params({"limit" => "1"}, @validator_option)
119
+
120
+ assert true
121
+ end
122
+
123
+ it 'invalid type' do
124
+ e = assert_raises(Committee::InvalidRequest) {
125
+ operation_object.validate_request_params({"limit" => "a"}, @validator_option)
126
+ }
127
+
128
+ assert e.message.start_with?("a class is String but")
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,151 @@
1
+ require_relative "../../test_helper"
2
+
3
+ require "stringio"
4
+
5
+ # TODO: fix
6
+
7
+ describe Committee::SchemaValidator::OpenAPI3::RequestValidator do
8
+ describe 'open_api_3' do
9
+ before do
10
+ @schema = JsonSchema.parse!(hyper_schema_data)
11
+ @schema.expand_references!
12
+ # POST /apps/:id
13
+ @link = @schema.properties["app"].links[0]
14
+ @request = Rack::Request.new({
15
+ "CONTENT_TYPE" => "application/json",
16
+ "rack.input" => StringIO.new("{}"),
17
+ "REQUEST_METHOD" => "POST"
18
+ })
19
+ end
20
+
21
+ it "passes through a valid request with Content-Type options" do
22
+ @headers = { "Content-Type" => "application/json; charset=utf-8" }
23
+ call({})
24
+ end
25
+
26
+ it "passes through a valid request" do
27
+ data = {
28
+ "name" => "heroku-api",
29
+ }
30
+ call(data)
31
+ end
32
+
33
+ it "passes through a valid request with Content-Type options" do
34
+ @request =
35
+ Rack::Request.new({
36
+ "CONTENT_TYPE" => "application/json; charset=utf-8",
37
+ "rack.input" => StringIO.new("{}"),
38
+ })
39
+ data = {
40
+ "name" => "heroku-api",
41
+ }
42
+ call(data)
43
+ end
44
+
45
+ it "detects an invalid request Content-Type" do
46
+ e = assert_raises(Committee::InvalidRequest) {
47
+ @request =
48
+ Rack::Request.new({
49
+ "CONTENT_TYPE" => "application/x-www-form-urlencoded",
50
+ "rack.input" => StringIO.new("{}"),
51
+ })
52
+ call({})
53
+ }
54
+ message =
55
+ %{"Content-Type" request header must be set to "application/json".}
56
+ assert_equal message, e.message
57
+ end
58
+
59
+ it "allows skipping content_type check" do
60
+ @request =
61
+ Rack::Request.new({
62
+ "CONTENT_TYPE" => "application/x-www-form-urlencoded",
63
+ "rack.input" => StringIO.new("{}"),
64
+ })
65
+ call({}, {}, check_content_type: false)
66
+ end
67
+
68
+ it "detects an missing parameter in GET requests" do
69
+ # GET /apps/search?query=...
70
+ @link = @schema.properties["app"].links[5]
71
+ @request = Rack::Request.new({})
72
+ e = assert_raises(Committee::InvalidRequest) do
73
+ call({})
74
+ end
75
+ message =
76
+ %{Invalid request.\n\n#: failed schema #/definitions/app/links/5/schema: "query" wasn't supplied.}
77
+ assert_equal message, e.message
78
+ end
79
+
80
+ it "allows an invalid Content-Type with an empty body" do
81
+ @request =
82
+ Rack::Request.new({
83
+ "CONTENT_TYPE" => "application/x-www-form-urlencoded",
84
+ "rack.input" => StringIO.new(""),
85
+ })
86
+ call({})
87
+ end
88
+
89
+ it "detects a parameter of the wrong pattern" do
90
+ data = {
91
+ "name" => "%@!"
92
+ }
93
+ e = assert_raises(Committee::InvalidRequest) do
94
+ call(data)
95
+ end
96
+ message = %{Invalid request.\n\n#/name: failed schema #/definitions/app/links/0/schema/properties/name: %@! does not match /^[a-z][a-z0-9-]{3,30}$/.}
97
+ assert_equal message, e.message
98
+ end
99
+
100
+ private
101
+
102
+ def call(data, headers={}, options={})
103
+ # hyper-schema link should be dropped into driver wrapper before it's used
104
+ link = Committee::Drivers::HyperSchema::Link.new(@link)
105
+ Committee::SchemaValidator::HyperSchema::RequestValidator.new(link, options).call(@request, data, headers)
106
+ end
107
+ end
108
+
109
+ describe 'OpenAPI 2' do
110
+ before do
111
+ @schema = open_api_2_schema
112
+ @link = @schema.routes['GET'][0][1]
113
+ @request = Rack::Request.new({
114
+ "CONTENT_TYPE" => "application/json",
115
+ "rack.input" => StringIO.new("{}"),
116
+ "REQUEST_METHOD" => "POST"
117
+ })
118
+ end
119
+
120
+
121
+ it "passes through a valid request" do
122
+ headers = {
123
+ "AUTH-TOKEN" => "xxx"
124
+ }
125
+ call({}, headers)
126
+ end
127
+
128
+ it "detects an missing header parameter in GET requests" do
129
+ @link = @schema.routes['GET'][0][1]
130
+ @request = Rack::Request.new({})
131
+ e = assert_raises(Committee::InvalidRequest) do
132
+ call({}, {})
133
+ end
134
+ message = %{Invalid request.\n\n#: failed schema : "AUTH-TOKEN" wasn't supplied.}
135
+ assert_equal message, e.message
136
+ end
137
+
138
+ it "allows skipping header schema check" do
139
+ @link = @schema.routes['GET'][0][1]
140
+ @request = Rack::Request.new({})
141
+ call({}, {}, { check_header: false })
142
+ end
143
+
144
+ private
145
+
146
+ def call(data, headers={}, options={})
147
+ # hyper-schema link should be dropped into driver wrapper before it's used
148
+ Committee::SchemaValidator::HyperSchema::RequestValidator.new(@link, options).call(@request, data, headers)
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,55 @@
1
+ require_relative "../../test_helper"
2
+
3
+ describe Committee::SchemaValidator::OpenAPI3::ResponseValidator do
4
+ before do
5
+ @status = 200
6
+ @headers = {
7
+ "Content-Type" => "application/json"
8
+ }
9
+ @data = {"string" => "Honoka.Kousaka"}
10
+
11
+ @path = '/validate'
12
+ @method = 'post'
13
+ @validator_option = Committee::SchemaValidator::Option.new({}, open_api_3_schema, :open_api_3)
14
+ end
15
+
16
+ it "passes through a valid response" do
17
+ call_response_validator
18
+ end
19
+
20
+ it "passes through a valid response with Content-Type options" do
21
+ @headers = { "Content-Type" => "application/json; charset=utf-8" }
22
+ call_response_validator
23
+ end
24
+
25
+ # TODO: raise error option
26
+ it "passes through a valid response with no registered Content-Type" do
27
+ @headers = { "Content-Type" => "application/xml" }
28
+ call_response_validator
29
+ end
30
+
31
+ # TODO: raise error option
32
+ it "passes through a valid response with no Content-Type" do
33
+ @headers = {}
34
+ call_response_validator
35
+ end
36
+
37
+ it "passes through a valid list response" do
38
+ @path = '/validate_response_array'
39
+ @method = 'get'
40
+ @data = ["Mari.Ohara"]
41
+ call_response_validator
42
+ end
43
+
44
+ it "passes through a 204 No Content response" do
45
+ @status, @headers, @data = 204, {}, nil
46
+ call_response_validator
47
+ end
48
+
49
+ private
50
+
51
+ def call_response_validator
52
+ @operation_object = open_api_3_schema.operation_object(@path, @method)
53
+ Committee::SchemaValidator::OpenAPI3::ResponseValidator.new(@operation_object, @validator_option).call(@status, @headers, @data)
54
+ end
55
+ end
@@ -12,6 +12,14 @@ describe Committee::Test::Methods do
12
12
  @committee_options
13
13
  end
14
14
 
15
+ def request_object
16
+ last_request
17
+ end
18
+
19
+ def response_data
20
+ [last_response.status, last_response.headers, last_response.body]
21
+ end
22
+
15
23
  before do
16
24
  # This is a little icky, but the test methods will cache router and schema
17
25
  # values between tests. This makes sense in real life, but is harmful for
@@ -38,24 +46,7 @@ describe Committee::Test::Methods do
38
46
  end
39
47
 
40
48
  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, {})
49
+ @app = new_rack_app(JSON.generate([ValidApp]), {})
59
50
  get "/apps"
60
51
  e = assert_raises(Committee::InvalidResponse) do
61
52
  assert_schema_conform
@@ -66,10 +57,10 @@ describe Committee::Test::Methods do
66
57
 
67
58
  private
68
59
 
69
- def new_rack_app(response, status=200, headers={ "Content-Type" => "application/json" })
60
+ def new_rack_app(response, headers={ "Content-Type" => "application/json" })
70
61
  Rack::Builder.new {
71
62
  run lambda { |_|
72
- [status, headers, [response]]
63
+ [200, headers, [response]]
73
64
  }
74
65
  }
75
66
  end
@@ -8,8 +8,16 @@ describe Committee::Test::Methods do
8
8
  @app
9
9
  end
10
10
 
11
- def committee_schema
12
- hyper_schema
11
+ def committee_options
12
+ @committee_options
13
+ end
14
+
15
+ def request_object
16
+ last_request
17
+ end
18
+
19
+ def response_data
20
+ [last_response.status, last_response.headers, last_response.body]
13
21
  end
14
22
 
15
23
  before do
@@ -18,73 +26,61 @@ describe Committee::Test::Methods do
18
26
  # our purposes here in testing the module.
19
27
  @committee_router = nil
20
28
  @committee_schema = nil
29
+ @committee_options = nil
30
+ @validate_errors = nil
21
31
  end
22
32
 
23
- describe "#assert_schema_content_type" do
24
- it "warns about deprecation" do
25
- mock(Committee).warn_deprecated.with_any_args
26
- assert_schema_content_type
33
+ describe "Hyper-Schema" do
34
+ before do
35
+ sc = JsonSchema.parse!(hyper_schema_data)
36
+ sc.expand_references!
37
+ s = Committee::Drivers::HyperSchema.new.parse(sc)
38
+ @committee_options = {schema: s}
27
39
  end
28
- end
29
-
30
- describe "#assert_schema_conform" do
31
- it "passes through a valid response" do
32
- mock(Committee).warn_deprecated.with_any_args.times(2)
33
-
34
- @app = new_rack_app(JSON.generate([ValidApp]))
35
- get "/apps"
36
- assert_schema_conform
37
- end
38
-
39
- it "detects an invalid response Content-Type" do
40
- mock(Committee).warn_deprecated.with_any_args.times(2)
41
40
 
42
- @app = new_rack_app(JSON.generate([ValidApp]), {})
43
- get "/apps"
44
- e = assert_raises(Committee::InvalidResponse) do
41
+ describe "#assert_schema_conform" do
42
+ it "passes through a valid response" do
43
+ @app = new_rack_app(JSON.generate([ValidApp]))
44
+ get "/apps"
45
45
  assert_schema_conform
46
46
  end
47
- assert_match(/response header must be set to/i, e.message)
48
- end
49
-
50
- it "accepts schema string (legacy behavior)" do
51
- mock(Committee).warn_deprecated.with_any_args.times(3)
52
-
53
- stub(self).committee_schema { nil }
54
- stub(self).schema_contents { JSON.dump(hyper_schema_data) }
55
47
 
56
- @app = new_rack_app(JSON.generate([ValidApp]))
57
- get "/apps"
58
- assert_schema_conform
48
+ it "detects an invalid response Content-Type" do
49
+ @app = new_rack_app(JSON.generate([ValidApp]), {})
50
+ get "/apps"
51
+ e = assert_raises(Committee::InvalidResponse) do
52
+ assert_schema_conform
53
+ end
54
+ assert_match(/response header must be set to/i, e.message)
55
+ end
59
56
  end
57
+ end
60
58
 
61
- it "accepts schema hash (legacy behavior)" do
62
- mock(Committee).warn_deprecated.with_any_args.times(3)
63
-
64
- stub(self).committee_schema { nil }
65
- stub(self).schema_contents { hyper_schema_data }
59
+ describe "OpenAPI3" do
60
+ before do
61
+ @committee_options = {open_api_3: open_api_3_schema}
66
62
 
67
- @app = new_rack_app(JSON.generate([ValidApp]))
68
- get "/apps"
69
- assert_schema_conform
63
+ @correct_response = { string_1: :honoka }
70
64
  end
71
65
 
72
- it "accepts schema JsonSchema::Schema object (legacy behavior)" do
73
- # Note we don't warn here because this is a recent deprecation and
74
- # passing a schema object will not be a huge performance hit. We should
75
- # probably start warning on the next version.
76
- mock(Committee).warn_deprecated.with_any_args.times(2)
77
-
78
- stub(self).committee_schema { nil }
79
- stub(self).schema_contents do
80
- schema = JsonSchema.parse!(hyper_schema_data)
81
- schema.expand_references!
82
- schema
66
+ describe "#assert_schema_conform" do
67
+ it "passes through a valid response" do
68
+ @app = new_rack_app(JSON.generate(@correct_response))
69
+ get "/characters"
70
+ assert_schema_conform
83
71
  end
84
72
 
85
- @app = new_rack_app(JSON.generate([ValidApp]))
86
- get "/apps"
87
- assert_schema_conform
73
+ # TODO: raise don't exist content-type error option
74
+ =begin
75
+ it "detects an invalid response Content-Type" do
76
+ @app = new_rack_app(JSON.generate([@correct_response]), {})
77
+ get "/characters"
78
+ e = assert_raises(Committee::InvalidResponse) do
79
+ assert_schema_conform
80
+ end
81
+ assert_match(/response header must be set to/i, e.message)
82
+ end
83
+ =end
88
84
  end
89
85
  end
90
86