committee 2.5.1 → 3.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
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