committee 0.4.14 → 1.0.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.
- data/bin/committee-stub +1 -1
- data/lib/committee/errors.rb +1 -10
- data/lib/committee/middleware/base.rb +9 -1
- data/lib/committee/middleware/request_validation.rb +5 -9
- data/lib/committee/middleware/response_validation.rb +3 -19
- data/lib/committee/middleware/stub.rb +4 -5
- data/lib/committee/request_validator.rb +28 -0
- data/lib/committee/response_generator.rb +19 -13
- data/lib/committee/response_validator.rb +21 -88
- data/lib/committee/router.rb +10 -9
- data/lib/committee/test/methods.rb +15 -17
- data/lib/committee.rb +10 -3
- data/test/middleware/request_validation_test.rb +23 -43
- data/test/middleware/response_validation_test.rb +9 -27
- data/test/middleware/stub_test.rb +10 -1
- data/test/request_validator_test.rb +34 -0
- data/test/response_generator_test.rb +15 -15
- data/test/response_validator_test.rb +19 -110
- data/test/router_test.rb +6 -6
- data/test/test/methods_test.rb +7 -20
- data/test/test_helper.rb +4 -40
- metadata +14 -17
- data/lib/committee/param_validator.rb +0 -106
- data/lib/committee/schema.rb +0 -56
- data/lib/committee/validation.rb +0 -83
- data/test/param_validator_test.rb +0 -127
- data/test/performance/request_validation.rb +0 -55
- data/test/schema_test.rb +0 -36
data/bin/committee-stub
CHANGED
@@ -32,8 +32,8 @@ schema = File.read(args[0])
|
|
32
32
|
|
33
33
|
app = Rack::Builder.new {
|
34
34
|
use Committee::Middleware::RequestValidation, schema: schema
|
35
|
-
use Committee::Middleware::Stub, schema: schema
|
36
35
|
use Committee::Middleware::ResponseValidation, schema: schema
|
36
|
+
use Committee::Middleware::Stub, schema: schema
|
37
37
|
run lambda { |_|
|
38
38
|
[404, {}, ["Not found"]]
|
39
39
|
}
|
data/lib/committee/errors.rb
CHANGED
@@ -5,16 +5,7 @@ module Committee
|
|
5
5
|
class BadRequest < Error
|
6
6
|
end
|
7
7
|
|
8
|
-
class
|
9
|
-
end
|
10
|
-
|
11
|
-
class InvalidFormat < Error
|
12
|
-
end
|
13
|
-
|
14
|
-
class InvalidType < Error
|
15
|
-
end
|
16
|
-
|
17
|
-
class InvalidParams < Error
|
8
|
+
class InvalidRequest < Error
|
18
9
|
end
|
19
10
|
|
20
11
|
class InvalidResponse < Error
|
@@ -5,7 +5,11 @@ module Committee::Middleware
|
|
5
5
|
|
6
6
|
@params_key = options[:params_key] || "committee.params"
|
7
7
|
data = options[:schema] || raise("need option `schema`")
|
8
|
-
|
8
|
+
if data.is_a?(String)
|
9
|
+
warn_string_deprecated
|
10
|
+
data = MultiJson.decode(data)
|
11
|
+
end
|
12
|
+
@schema = JsonSchema.parse!(data)
|
9
13
|
@router = Committee::Router.new(@schema)
|
10
14
|
end
|
11
15
|
|
@@ -15,5 +19,9 @@ module Committee::Middleware
|
|
15
19
|
[status, { "Content-Type" => "application/json" },
|
16
20
|
[MultiJson.encode({ id: id, error: message }, pretty: true)]]
|
17
21
|
end
|
22
|
+
|
23
|
+
def warn_string_deprecated
|
24
|
+
Committee.warn_deprecated("Committee: passing a string to `schema` option is deprecated; please send a deserialized hash instead.")
|
25
|
+
end
|
18
26
|
end
|
19
27
|
end
|
@@ -2,21 +2,17 @@ module Committee::Middleware
|
|
2
2
|
class RequestValidation < Base
|
3
3
|
def initialize(app, options={})
|
4
4
|
super
|
5
|
-
@allow_extra = options[:allow_extra]
|
6
5
|
@prefix = options[:prefix]
|
6
|
+
|
7
|
+
# deprecated
|
8
|
+
@allow_extra = options[:allow_extra]
|
7
9
|
end
|
8
10
|
|
9
11
|
def call(env)
|
10
12
|
request = Rack::Request.new(env)
|
11
13
|
env[@params_key] = Committee::RequestUnpacker.new(request).call
|
12
|
-
link
|
13
|
-
|
14
|
-
Committee::ParamValidator.new(
|
15
|
-
env[@params_key],
|
16
|
-
@schema,
|
17
|
-
link,
|
18
|
-
allow_extra: @allow_extra
|
19
|
-
).call
|
14
|
+
if link = @router.routes_request?(request, prefix: @prefix)
|
15
|
+
Committee::RequestValidator.new.call(link, env[@params_key])
|
20
16
|
end
|
21
17
|
@app.call(env)
|
22
18
|
rescue Committee::BadRequest
|
@@ -8,17 +8,10 @@ module Committee::Middleware
|
|
8
8
|
def call(env)
|
9
9
|
status, headers, response = @app.call(env)
|
10
10
|
request = Rack::Request.new(env)
|
11
|
-
|
12
|
-
@router.routes_request?(request, prefix: @prefix)
|
13
|
-
if type_schema
|
14
|
-
check_content_type!(headers)
|
11
|
+
if link = @router.routes_request?(request, prefix: @prefix)
|
15
12
|
str = response.reduce("") { |str, s| str << s }
|
16
|
-
|
17
|
-
|
18
|
-
@schema,
|
19
|
-
link_schema,
|
20
|
-
type_schema
|
21
|
-
).call
|
13
|
+
data = MultiJson.decode(str)
|
14
|
+
Committee::ResponseValidator.new(link).call(headers, data)
|
22
15
|
end
|
23
16
|
[status, headers, response]
|
24
17
|
rescue Committee::InvalidResponse
|
@@ -26,14 +19,5 @@ module Committee::Middleware
|
|
26
19
|
rescue MultiJson::LoadError
|
27
20
|
render_error(500, :invalid_response, "Response wasn't valid JSON.")
|
28
21
|
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def check_content_type!(headers)
|
33
|
-
unless headers["Content-Type"] =~ %r{application/json}
|
34
|
-
raise Committee::InvalidResponse,
|
35
|
-
%{"Content-Type" response header must be set to "application/json".}
|
36
|
-
end
|
37
|
-
end
|
38
22
|
end
|
39
23
|
end
|
@@ -9,11 +9,10 @@ module Committee::Middleware
|
|
9
9
|
|
10
10
|
def call(env)
|
11
11
|
request = Rack::Request.new(env)
|
12
|
-
|
13
|
-
if type_schema
|
12
|
+
if link = @router.routes_request?(request, prefix: @prefix)
|
14
13
|
headers = { "Content-Type" => "application/json" }
|
15
|
-
data = cache(
|
16
|
-
Committee::ResponseGenerator.new(
|
14
|
+
data = cache(link.method, link.href) do
|
15
|
+
Committee::ResponseGenerator.new.call(link)
|
17
16
|
end
|
18
17
|
if @call
|
19
18
|
env["committee.response"] = data
|
@@ -28,7 +27,7 @@ module Committee::Middleware
|
|
28
27
|
# made, and stub normally
|
29
28
|
headers.merge!(call_headers)
|
30
29
|
end
|
31
|
-
status =
|
30
|
+
status = link.rel == "create" ? 201 : 200
|
32
31
|
[status, headers, [MultiJson.encode(data, pretty: true)]]
|
33
32
|
else
|
34
33
|
@app.call(env)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Committee
|
2
|
+
class RequestValidator
|
3
|
+
def initialize(options = {})
|
4
|
+
end
|
5
|
+
|
6
|
+
def call(link, params)
|
7
|
+
if link.schema
|
8
|
+
valid, errors = link.schema.validate(params)
|
9
|
+
if !valid
|
10
|
+
errors = error_messages(errors).join("\n")
|
11
|
+
raise InvalidRequest, "Invalid request.\n\n#{errors}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def error_messages(errors)
|
19
|
+
errors.map do |error|
|
20
|
+
if error.schema
|
21
|
+
%{At "#{error.schema.uri}": #{error.message}}
|
22
|
+
else
|
23
|
+
error.message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,29 +1,35 @@
|
|
1
1
|
module Committee
|
2
2
|
class ResponseGenerator
|
3
|
-
def
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
def call(link)
|
4
|
+
data = generate_properties(link.parent)
|
5
|
+
|
6
|
+
# list is a special case; wrap data in an array
|
7
|
+
data = [data] if link.rel == "instances"
|
8
8
|
|
9
|
-
|
10
|
-
generate_properties(@type_schema)
|
9
|
+
data
|
11
10
|
end
|
12
11
|
|
13
12
|
private
|
14
13
|
|
15
14
|
def generate_properties(schema)
|
16
15
|
data = {}
|
17
|
-
schema
|
18
|
-
data[
|
16
|
+
schema.properties.each do |key, value|
|
17
|
+
data[key] = if !value.properties.empty?
|
19
18
|
generate_properties(value)
|
20
19
|
else
|
21
|
-
|
22
|
-
|
20
|
+
# special example attribute was included; use its value
|
21
|
+
if !value.data["example"].nil?
|
22
|
+
value.data["example"]
|
23
|
+
# null is allowed; use that
|
24
|
+
elsif value.type.include?("null")
|
25
|
+
nil
|
26
|
+
# otherwise we don't know what to do (we could eventually generate
|
27
|
+
# random data based on type/format
|
28
|
+
else
|
29
|
+
raise(%{At "#{schema.id}"/"#{key}": no "example" attribute and "null" is not allowed; don't know how to generate property.})
|
30
|
+
end
|
23
31
|
end
|
24
32
|
end
|
25
|
-
# list is a special case; wrap data in an array
|
26
|
-
data = [data] if @link_schema["title"] == "List"
|
27
33
|
data
|
28
34
|
end
|
29
35
|
end
|
@@ -1,111 +1,44 @@
|
|
1
1
|
module Committee
|
2
2
|
class ResponseValidator
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
@data = data
|
8
|
-
@schema = schema
|
9
|
-
@link_schema = link_schema
|
10
|
-
@type_schema = type_schema
|
3
|
+
def initialize(link)
|
4
|
+
@link = link
|
5
|
+
@validator = JsonSchema::Validator.new(link.parent)
|
11
6
|
end
|
12
7
|
|
13
|
-
def call
|
14
|
-
|
15
|
-
|
8
|
+
def call(headers, data)
|
9
|
+
check_content_type!(headers)
|
10
|
+
|
11
|
+
if @link.rel == "instances"
|
12
|
+
if !data.is_a?(Array)
|
16
13
|
raise InvalidResponse, "List endpoints must return an array of objects."
|
17
14
|
end
|
18
15
|
# only consider the first object during the validation from here on
|
19
|
-
|
20
|
-
else
|
21
|
-
@data
|
22
|
-
end
|
23
|
-
|
24
|
-
data_keys = build_data_keys(data)
|
25
|
-
schema_keys = build_schema_keys
|
26
|
-
|
27
|
-
extra = data_keys - schema_keys
|
28
|
-
missing = schema_keys - data_keys
|
29
|
-
|
30
|
-
errors = []
|
31
|
-
|
32
|
-
if extra.count > 0
|
33
|
-
errors << "Extra keys in response: #{extra.join(', ')}."
|
34
|
-
end
|
35
|
-
|
36
|
-
if missing.count > 0
|
37
|
-
errors << "Missing keys in response: #{missing.join(', ')}."
|
16
|
+
data = data[0]
|
38
17
|
end
|
39
18
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
check_data!(@type_schema, data, [])
|
45
|
-
end
|
46
|
-
|
47
|
-
def check_data!(schema, data, path)
|
48
|
-
schema["properties"].each do |key, value|
|
49
|
-
if value["properties"]
|
50
|
-
check_data!(value, data[key], path + [key])
|
51
|
-
elsif value["type"] == ["array"]
|
52
|
-
definition = @schema.find(value["items"]["$ref"])
|
53
|
-
data[key].each do |datum|
|
54
|
-
check_type!(definition["type"], datum, path + [key])
|
55
|
-
check_data!(definition, datum, path + [key]) if definition["type"] == ["object"]
|
56
|
-
unless definition["type"].include?("null") && datum.nil?
|
57
|
-
check_format!(definition["format"], datum, path + [key])
|
58
|
-
check_pattern!(definition["pattern"], datum, path + [key])
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
else
|
63
|
-
definition = @schema.find(value["$ref"])
|
64
|
-
check_type!(definition["type"], data[key], path + [key])
|
65
|
-
unless definition["type"].include?("null") && data[key].nil?
|
66
|
-
check_format!(definition["format"], data[key], path + [key])
|
67
|
-
check_pattern!(definition["pattern"], data[key], path + [key])
|
68
|
-
end
|
69
|
-
end
|
19
|
+
if !@validator.validate(data)
|
20
|
+
errors = error_messages(@validator.errors).join("\n")
|
21
|
+
raise InvalidResponse, "Invalid response.\n\n#{errors}"
|
70
22
|
end
|
71
23
|
end
|
72
24
|
|
73
25
|
private
|
74
26
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
keys += value.keys.map { |k| "#{key}:#{k}" }
|
80
|
-
else
|
81
|
-
keys << key
|
82
|
-
end
|
27
|
+
def check_content_type!(headers)
|
28
|
+
unless headers["Content-Type"] =~ %r{application/json}
|
29
|
+
raise Committee::InvalidResponse,
|
30
|
+
%{"Content-Type" response header must be set to "application/json".}
|
83
31
|
end
|
84
|
-
keys
|
85
32
|
end
|
86
33
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
info
|
92
|
-
elsif info["type"] == ["array"]
|
93
|
-
array_schema = @schema.find(info["items"]["$ref"])
|
94
|
-
unless array_schema["type"] == ["object"]
|
95
|
-
array_schema
|
96
|
-
else
|
97
|
-
{} # satisfy data['properties'] check below
|
98
|
-
end
|
99
|
-
elsif info["$ref"]
|
100
|
-
@schema.find(info["$ref"])
|
101
|
-
end
|
102
|
-
if data["properties"]
|
103
|
-
keys += data["properties"].keys.map { |k| "#{key}:#{k}" }
|
34
|
+
def error_messages(errors)
|
35
|
+
errors.map do |error|
|
36
|
+
if error.schema
|
37
|
+
%{At "#{error.schema.uri}": #{error.message}}
|
104
38
|
else
|
105
|
-
|
39
|
+
error.message
|
106
40
|
end
|
107
41
|
end
|
108
|
-
keys
|
109
42
|
end
|
110
43
|
end
|
111
44
|
end
|
data/lib/committee/router.rb
CHANGED
@@ -7,13 +7,13 @@ module Committee
|
|
7
7
|
def routes?(method, path, options = {})
|
8
8
|
path = path.gsub(/^#{options[:prefix]}/, "") if options[:prefix]
|
9
9
|
if method_routes = @routes[method]
|
10
|
-
method_routes.each do |pattern, link
|
10
|
+
method_routes.each do |pattern, link|
|
11
11
|
if path =~ pattern
|
12
|
-
return link
|
12
|
+
return link
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
16
|
+
nil
|
17
17
|
end
|
18
18
|
|
19
19
|
def routes_request?(request, options = {})
|
@@ -24,13 +24,14 @@ module Committee
|
|
24
24
|
|
25
25
|
def build_routes(schema)
|
26
26
|
routes = {}
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
# realistically, we should be examining links recursively at all levels
|
28
|
+
schema.properties.each do |_, type_schema|
|
29
|
+
type_schema.links.each do |link|
|
30
|
+
method = link.method.to_s.upcase
|
31
|
+
routes[method] ||= []
|
30
32
|
# /apps/{id} --> /apps/([^/]+)
|
31
|
-
href = link
|
32
|
-
routes[
|
33
|
-
[%r{^#{href}$}, link, type_schema]
|
33
|
+
href = link.href.gsub(/\{(.*?)\}/, "[^/]+")
|
34
|
+
routes[method] << [%r{^#{href}$}, link]
|
34
35
|
end
|
35
36
|
end
|
36
37
|
routes
|
@@ -1,48 +1,46 @@
|
|
1
1
|
module Committee::Test
|
2
2
|
module Methods
|
3
3
|
def assert_schema_conform
|
4
|
-
|
4
|
+
if (data = schema_contents).is_a?(String)
|
5
|
+
warn_string_deprecated
|
6
|
+
data = MultiJson.decode(data)
|
7
|
+
end
|
5
8
|
|
6
|
-
@schema ||=
|
9
|
+
@schema ||= JsonSchema.parse!(data)
|
7
10
|
@router ||= Committee::Router.new(@schema)
|
8
11
|
|
9
|
-
|
12
|
+
link =
|
10
13
|
@router.routes_request?(last_request, prefix: schema_url_prefix)
|
11
|
-
|
12
|
-
unless link_schema
|
14
|
+
unless link
|
13
15
|
response = "`#{last_request.request_method} #{last_request.path_info}` undefined in schema."
|
14
16
|
raise Committee::InvalidResponse.new(response)
|
15
17
|
end
|
16
18
|
|
17
19
|
data = MultiJson.decode(last_response.body)
|
18
|
-
Committee::ResponseValidator.new(
|
19
|
-
data,
|
20
|
-
@schema,
|
21
|
-
link_schema,
|
22
|
-
type_schema
|
23
|
-
).call
|
20
|
+
Committee::ResponseValidator.new(link).call(last_response.headers, data)
|
24
21
|
end
|
25
22
|
|
26
23
|
def assert_schema_content_type
|
27
|
-
|
28
|
-
raise Committee::InvalidResponse,
|
29
|
-
%{"Content-Type" response header must be set to "application/json".}
|
30
|
-
end
|
24
|
+
Committee.warn_deprecated("Use of #assert_schema_content_type is deprecated; use #assert_schema_conform instead.")
|
31
25
|
end
|
32
26
|
|
33
27
|
# can be overridden alternatively to #schema_path in case the schema is
|
34
28
|
# easier to access as a string
|
35
29
|
# blob
|
36
30
|
def schema_contents
|
37
|
-
File.read(schema_path)
|
31
|
+
MultiJson.decode(File.read(schema_path))
|
38
32
|
end
|
39
33
|
|
40
34
|
def schema_path
|
41
|
-
raise "Please override #schema_path."
|
35
|
+
raise "Please override #schema_contents or #schema_path."
|
42
36
|
end
|
43
37
|
|
44
38
|
def schema_url_prefix
|
45
39
|
nil
|
46
40
|
end
|
41
|
+
|
42
|
+
def warn_string_deprecated
|
43
|
+
Committee.warn_deprecated("Committee: returning a string from `#schema_contents` is deprecated; please return a deserialized hash instead.")
|
44
|
+
end
|
47
45
|
end
|
48
46
|
end
|
data/lib/committee.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
|
+
require "json_schema"
|
1
2
|
require "multi_json"
|
2
3
|
require "rack"
|
3
4
|
|
4
5
|
require_relative "committee/errors"
|
5
|
-
require_relative "committee/validation"
|
6
|
-
require_relative "committee/param_validator"
|
7
6
|
require_relative "committee/request_unpacker"
|
7
|
+
require_relative "committee/request_validator"
|
8
8
|
require_relative "committee/response_generator"
|
9
9
|
require_relative "committee/response_validator"
|
10
10
|
require_relative "committee/router"
|
11
|
-
require_relative "committee/schema"
|
12
11
|
|
13
12
|
require_relative "committee/middleware/base"
|
14
13
|
require_relative "committee/middleware/request_validation"
|
@@ -16,3 +15,11 @@ require_relative "committee/middleware/response_validation"
|
|
16
15
|
require_relative "committee/middleware/stub"
|
17
16
|
|
18
17
|
require_relative "committee/test/methods"
|
18
|
+
|
19
|
+
module Committee
|
20
|
+
def self.warn_deprecated(message)
|
21
|
+
if !$VERBOSE.nil?
|
22
|
+
$stderr.puts(message)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -7,61 +7,31 @@ describe Committee::Middleware::RequestValidation do
|
|
7
7
|
@app
|
8
8
|
end
|
9
9
|
|
10
|
-
it "detects an invalid Content-Type" do
|
11
|
-
@app = new_rack_app
|
12
|
-
header "Content-Type", "application/whats-this"
|
13
|
-
post "/account/app-transfers", "{}"
|
14
|
-
assert_equal 400, last_response.status
|
15
|
-
end
|
16
|
-
|
17
10
|
it "passes through a valid request" do
|
18
11
|
@app = new_rack_app
|
19
12
|
params = {
|
20
|
-
"
|
21
|
-
"recipient" => "owner@heroku.com",
|
13
|
+
"name" => "cloudnasium"
|
22
14
|
}
|
23
15
|
header "Content-Type", "application/json"
|
24
|
-
post "/
|
16
|
+
post "/apps", MultiJson.encode(params)
|
25
17
|
assert_equal 200, last_response.status
|
26
18
|
end
|
27
19
|
|
28
|
-
it "detects
|
29
|
-
@app = new_rack_app
|
30
|
-
header "Content-Type", "application/json"
|
31
|
-
post "/account/app-transfers", "{}"
|
32
|
-
assert_equal 422, last_response.status
|
33
|
-
assert_match /require params/i, last_response.body
|
34
|
-
end
|
35
|
-
|
36
|
-
it "detects an extra parameter" do
|
20
|
+
it "detects an invalid Content-Type" do
|
37
21
|
@app = new_rack_app
|
22
|
+
header "Content-Type", "application/whats-this"
|
38
23
|
params = {
|
39
|
-
"
|
40
|
-
"cloud" => "production",
|
41
|
-
"recipient" => "owner@heroku.com",
|
42
|
-
}
|
43
|
-
header "Content-Type", "application/json"
|
44
|
-
post "/account/app-transfers", MultiJson.encode(params)
|
45
|
-
assert_equal 422, last_response.status
|
46
|
-
assert_match /unknown params/i, last_response.body
|
47
|
-
end
|
48
|
-
|
49
|
-
it "doesn't error on an extra parameter with allow_extra" do
|
50
|
-
@app = new_rack_app(allow_extra: true)
|
51
|
-
params = {
|
52
|
-
"app" => "heroku-api",
|
53
|
-
"cloud" => "production",
|
54
|
-
"recipient" => "owner@heroku.com",
|
24
|
+
"name" => "cloudnasium"
|
55
25
|
}
|
56
|
-
|
57
|
-
|
58
|
-
|
26
|
+
post "/apps", MultiJson.encode(params)
|
27
|
+
assert_equal 400, last_response.status
|
28
|
+
assert_match /unsupported content-type/i, last_response.body
|
59
29
|
end
|
60
30
|
|
61
31
|
it "rescues JSON errors" do
|
62
32
|
@app = new_rack_app
|
63
33
|
header "Content-Type", "application/json"
|
64
|
-
post "/
|
34
|
+
post "/apps", "{x:y}"
|
65
35
|
assert_equal 400, last_response.status
|
66
36
|
assert_match /valid json/i, last_response.body
|
67
37
|
end
|
@@ -69,11 +39,21 @@ describe Committee::Middleware::RequestValidation do
|
|
69
39
|
it "takes a prefix" do
|
70
40
|
@app = new_rack_app(prefix: "/v1")
|
71
41
|
params = {
|
72
|
-
"
|
73
|
-
|
42
|
+
"name" => "cloudnasium"
|
43
|
+
}
|
44
|
+
header "Content-Type", "application/json"
|
45
|
+
post "/v1/apps", MultiJson.encode(params)
|
46
|
+
assert_equal 200, last_response.status
|
47
|
+
end
|
48
|
+
|
49
|
+
it "warns when sending a deprecated string" do
|
50
|
+
mock(Committee).warn_deprecated.with_any_args
|
51
|
+
@app = new_rack_app(schema: File.read("./test/data/schema.json"))
|
52
|
+
params = {
|
53
|
+
"name" => "cloudnasium"
|
74
54
|
}
|
75
55
|
header "Content-Type", "application/json"
|
76
|
-
post "/
|
56
|
+
post "/apps", MultiJson.encode(params)
|
77
57
|
assert_equal 200, last_response.status
|
78
58
|
end
|
79
59
|
|
@@ -81,7 +61,7 @@ describe Committee::Middleware::RequestValidation do
|
|
81
61
|
|
82
62
|
def new_rack_app(options = {})
|
83
63
|
options = {
|
84
|
-
schema: File.read("./test/data/schema.json")
|
64
|
+
schema: MultiJson.decode(File.read("./test/data/schema.json"))
|
85
65
|
}.merge(options)
|
86
66
|
Rack::Builder.new {
|
87
67
|
use Committee::Middleware::RequestValidation, options
|
@@ -13,14 +13,6 @@ describe Committee::Middleware::ResponseValidation do
|
|
13
13
|
assert_equal 200, last_response.status
|
14
14
|
end
|
15
15
|
|
16
|
-
it "detects an invalid response Content-Type" do
|
17
|
-
@app = new_rack_app(MultiJson.encode([ValidApp]),
|
18
|
-
{ "Content-Type" => "application/xml" })
|
19
|
-
get "/apps"
|
20
|
-
assert_equal 500, last_response.status
|
21
|
-
assert_match /response header must be set to/i, last_response.body
|
22
|
-
end
|
23
|
-
|
24
16
|
it "detects an invalid response" do
|
25
17
|
@app = new_rack_app("")
|
26
18
|
get "/apps"
|
@@ -28,24 +20,6 @@ describe Committee::Middleware::ResponseValidation do
|
|
28
20
|
assert_match /valid JSON/i, last_response.body
|
29
21
|
end
|
30
22
|
|
31
|
-
it "detects missing keys in response" do
|
32
|
-
data = ValidApp.dup
|
33
|
-
data.delete("name")
|
34
|
-
@app = new_rack_app(MultiJson.encode([data]))
|
35
|
-
get "/apps"
|
36
|
-
assert_equal 500, last_response.status
|
37
|
-
assert_match /missing keys/i, last_response.body
|
38
|
-
end
|
39
|
-
|
40
|
-
it "detects extra keys in response" do
|
41
|
-
data = ValidApp.dup
|
42
|
-
data.merge!("tier" => "important")
|
43
|
-
@app = new_rack_app(MultiJson.encode([data]))
|
44
|
-
get "/apps"
|
45
|
-
assert_equal 500, last_response.status
|
46
|
-
assert_match /extra keys/i, last_response.body
|
47
|
-
end
|
48
|
-
|
49
23
|
it "rescues JSON errors" do
|
50
24
|
@app = new_rack_app("[{x:y}]")
|
51
25
|
get "/apps"
|
@@ -59,6 +33,14 @@ describe Committee::Middleware::ResponseValidation do
|
|
59
33
|
assert_equal 200, last_response.status
|
60
34
|
end
|
61
35
|
|
36
|
+
it "warns when sending a deprecated string" do
|
37
|
+
mock(Committee).warn_deprecated.with_any_args
|
38
|
+
@app = new_rack_app(MultiJson.encode([ValidApp]), {},
|
39
|
+
schema: File.read("./test/data/schema.json"))
|
40
|
+
get "/apps"
|
41
|
+
assert_equal 200, last_response.status
|
42
|
+
end
|
43
|
+
|
62
44
|
private
|
63
45
|
|
64
46
|
def new_rack_app(response, headers = {}, options = {})
|
@@ -66,7 +48,7 @@ describe Committee::Middleware::ResponseValidation do
|
|
66
48
|
"Content-Type" => "application/json"
|
67
49
|
}.merge(headers)
|
68
50
|
options = {
|
69
|
-
schema: File.read("./test/data/schema.json")
|
51
|
+
schema: MultiJson.decode(File.read("./test/data/schema.json"))
|
70
52
|
}.merge(options)
|
71
53
|
Rack::Builder.new {
|
72
54
|
use Committee::Middleware::ResponseValidation, options
|
@@ -46,12 +46,21 @@ describe Committee::Middleware::Stub do
|
|
46
46
|
assert_equal ValidApp.keys.sort, data.keys.sort
|
47
47
|
end
|
48
48
|
|
49
|
+
it "warns when sending a deprecated string" do
|
50
|
+
mock(Committee).warn_deprecated.with_any_args
|
51
|
+
@app = new_rack_app(schema: File.read("./test/data/schema.json"))
|
52
|
+
get "/apps/heroku-api"
|
53
|
+
assert_equal 200, last_response.status
|
54
|
+
data = MultiJson.decode(last_response.body)
|
55
|
+
assert_equal ValidApp.keys.sort, data.keys.sort
|
56
|
+
end
|
57
|
+
|
49
58
|
private
|
50
59
|
|
51
60
|
def new_rack_app(options = {})
|
52
61
|
suppress = options.delete(:suppress)
|
53
62
|
options = {
|
54
|
-
schema: File.read("./test/data/schema.json")
|
63
|
+
schema: MultiJson.decode(File.read("./test/data/schema.json"))
|
55
64
|
}.merge(options)
|
56
65
|
Rack::Builder.new {
|
57
66
|
use Committee::Middleware::Stub, options
|