committee 0.4.14 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ require_relative "test_helper"
2
+
3
+ describe Committee::RequestValidator do
4
+ before do
5
+ @schema =
6
+ JsonSchema.parse!(MultiJson.decode(File.read("./test/data/schema.json")))
7
+ # POST /apps/:id
8
+ @link = @link = @schema.properties["app"].links[0]
9
+ end
10
+
11
+ it "passes through a valid request" do
12
+ params = {
13
+ "name" => "heroku-api",
14
+ }
15
+ call(params)
16
+ end
17
+
18
+ it "detects a parameter of the wrong pattern" do
19
+ params = {
20
+ "name" => "%@!"
21
+ }
22
+ e = assert_raises(Committee::InvalidRequest) do
23
+ call(params)
24
+ end
25
+ message = %{Invalid request.\n\nAt "/schema/app": Expected string to match pattern "/^[a-z][a-z0-9-]{3,30}$/", value was: %@!.}
26
+ assert_equal message, e.message
27
+ end
28
+
29
+ private
30
+
31
+ def call(params)
32
+ Committee::RequestValidator.new.call(@link, params)
33
+ end
34
+ end
@@ -2,31 +2,31 @@ require_relative "test_helper"
2
2
 
3
3
  describe Committee::ResponseGenerator do
4
4
  before do
5
- @schema = Committee::Schema.new(File.read("./test/data/schema.json"))
6
- @generator = Committee::ResponseGenerator.new(
7
- @schema,
8
- @schema["app"],
9
- @schema["app"]["links"][0]
10
- )
5
+ @schema =
6
+ JsonSchema.parse!(MultiJson.decode(File.read("./test/data/schema.json")))
7
+ # GET /apps/:id
8
+ @get_link = @link = @schema.properties["app"].links[2]
9
+ # GET /apps
10
+ @list_link = @schema.properties["app"].links[3]
11
11
  end
12
12
 
13
13
  it "generates string properties" do
14
- data = @generator.call
14
+ data = call
15
15
  assert data["name"].is_a?(String)
16
16
  end
17
17
 
18
18
  it "generates non-string properties" do
19
- data = @generator.call
20
- assert [FalseClass, TrueClass].include?(data["maintenance"].class)
19
+ data = call
20
+ assert_includes [FalseClass, TrueClass], data["maintenance"].class
21
21
  end
22
22
 
23
23
  it "wraps list data in an array" do
24
- @generator = Committee::ResponseGenerator.new(
25
- @schema,
26
- @schema["app"],
27
- @schema["app"]["links"][3]
28
- )
29
- data = @generator.call
24
+ @link = @list_link
25
+ data = call
30
26
  assert data.is_a?(Array)
31
27
  end
28
+
29
+ def call
30
+ Committee::ResponseGenerator.new.call(@link)
31
+ end
32
32
  end
@@ -3,10 +3,16 @@ require_relative "test_helper"
3
3
  describe Committee::ResponseValidator do
4
4
  before do
5
5
  @data = ValidApp.dup
6
- @schema = Committee::Schema.new(File.read("./test/data/schema.json"))
6
+ @headers = {
7
+ "Content-Type" => "application/json"
8
+ }
9
+ @schema =
10
+ JsonSchema.parse!(MultiJson.decode(File.read("./test/data/schema.json")))
7
11
  # GET /apps/:id
8
- @link_schema = @schema["app"]["links"][2]
9
- @type_schema = @schema["app"]
12
+ @get_link = @link = @schema.properties["app"].links[2]
13
+ # GET /apps
14
+ @list_link = @schema.properties["app"].links[3]
15
+ @type_schema = @schema.properties["app"]
10
16
  end
11
17
 
12
18
  it "passes through a valid response" do
@@ -15,132 +21,35 @@ describe Committee::ResponseValidator do
15
21
 
16
22
  it "passes through a valid list response" do
17
23
  @data = [@data]
18
- # GET /apps
19
- @link_schema = @schema["app"]["links"][3]
20
- call
21
- end
22
-
23
- it "passes through a valid list response with non-default title" do
24
- @data = [@data]
25
- @link_schema = @schema["app"]["links"][4]
24
+ @link = @list_link
26
25
  call
27
26
  end
28
27
 
29
28
  it "detects an improperly formatted list response" do
30
- # GET /apps
31
- @link_schema = @schema["app"]["links"][3]
29
+ @link = @list_link
32
30
  e = assert_raises(Committee::InvalidResponse) { call }
33
31
  message = "List endpoints must return an array of objects."
34
32
  assert_equal message, e.message
35
33
  end
36
34
 
37
- it "detects missing keys in response" do
38
- @data.delete("name")
39
- e = assert_raises(Committee::InvalidResponse) { call }
40
- message = %r{Missing keys in response: name.}
41
- assert_match message, e.message
42
- end
43
-
44
- it "detects extra keys in response" do
45
- @data.merge!("tier" => "important")
35
+ it "detects an invalid response Content-Type" do
36
+ @headers = {}
46
37
  e = assert_raises(Committee::InvalidResponse) { call }
47
- message = %r{Extra keys in response: tier.}
48
- assert_match message, e.message
49
- end
50
-
51
- it "detects mismatched types" do
52
- @data.merge!("maintenance" => "not-bool")
53
- e = assert_raises(Committee::InvalidType) { call }
54
- message = %{Invalid type at "maintenance": expected "not-bool" to be ["boolean"].}
55
- assert_equal message, e.message
56
- end
57
-
58
- it "detects bad formats" do
59
- @data.merge!("id" => "123")
60
- e = assert_raises(Committee::InvalidFormat) { call }
61
- message = %{Invalid format at "id": expected "123" to be "uuid".}
38
+ message =
39
+ %{"Content-Type" response header must be set to "application/json".}
62
40
  assert_equal message, e.message
63
41
  end
64
42
 
65
- it "detects bad patterns" do
43
+ it "raises errors generated by json_schema" do
66
44
  @data.merge!("name" => "%@!")
67
- e = assert_raises(Committee::InvalidPattern) { call }
68
- message = %{Invalid pattern at "name": expected %@! to match "(?-mix:^[a-z][a-z0-9-]{3,30}$)".}
69
- assert_equal message, e.message
70
- end
71
-
72
- it "accepts date-time format without milliseconds" do
73
- validator = Committee::ResponseValidator.new(
74
- @data,
75
- @schema,
76
- @link_schema,
77
- @schema["app"])
78
-
79
- value = "2014-03-10T00:00:00Z"
80
- assert_nil validator.check_format!("date-time", value, ["example"])
81
- end
82
-
83
- it "accepts date-time format with milliseconds" do
84
- validator = Committee::ResponseValidator.new(
85
- @data,
86
- @schema,
87
- @link_schema,
88
- @schema["app"])
89
-
90
- value = "2014-03-10T00:00:00.123Z"
91
- assert_nil validator.check_format!("date-time", value, ["example"])
92
- end
93
-
94
- it "accepts a simple array" do
95
- @data = ValidAccount.dup
96
- @data["flags"] = @data["flags"].dup
97
- @link_schema = @schema["account"]["links"][1]
98
- @type_schema = @schema["account"]
99
-
100
- call
101
- end
102
-
103
- it "validates an array of objects" do
104
- @data = ValidAccount.dup
105
- @data["credit_cards"] = @data["credit_cards"].dup
106
- @link_schema = @schema["account"]["links"][0]
107
- @type_schema = @schema["account"]
108
-
109
- call
110
- end
111
-
112
- it "detects a simple array with an item of the wrong type" do
113
- @data = ValidAccount.dup
114
- @data["flags"] = @data["flags"].dup
115
- @data["flags"] << false
116
- @link_schema = @schema["account"]["links"][1]
117
- @type_schema = @schema["account"]
118
-
119
- e = assert_raises(Committee::InvalidType) { call }
120
- message = %{Invalid type at "flags": expected false to be ["string"].}
121
- assert_equal message, e.message
122
- end
123
-
124
- it "rejects an invalid pattern match" do
125
- @data = ValidAccount.dup
126
- @data["credit_cards"] = @data["credit_cards"].dup
127
- @data["credit_cards"] << {"account_number" => "1234-1234-1234-HUGZ", "name" => "Rodney Mullen", "security_code" => 123}
128
- @link_schema = @schema["account"]["links"][0]
129
- @type_schema = @schema["account"]
130
-
131
- e = assert_raises(Committee::InvalidPattern) { call }
132
- message = %{Invalid pattern at "credit_cards:account_number": expected 1234-1234-1234-HUGZ to match "(?-mix:[0-9]{4}\\-[0-9]{4}\\-[0-9]{4}\\-[0-9]{4}$)".}
45
+ e = assert_raises(Committee::InvalidResponse) { call }
46
+ message = %{Invalid response.\n\nAt "/schema/app": Expected string to match pattern "/^[a-z][a-z0-9-]{3,30}$/", value was: %@!.}
133
47
  assert_equal message, e.message
134
48
  end
135
49
 
136
50
  private
137
51
 
138
52
  def call
139
- Committee::ResponseValidator.new(
140
- @data,
141
- @schema,
142
- @link_schema,
143
- @type_schema
144
- ).call
53
+ Committee::ResponseValidator.new(@link).call(@headers, @data)
145
54
  end
146
55
  end
data/test/router_test.rb CHANGED
@@ -2,25 +2,25 @@ require_relative "test_helper"
2
2
 
3
3
  describe Committee::Router do
4
4
  before do
5
- data = File.read("./test/data/schema.json")
6
- schema = Committee::Schema.new(data)
5
+ data = MultiJson.decode(File.read("./test/data/schema.json"))
6
+ schema = JsonSchema.parse!(data)
7
7
  @router = Committee::Router.new(schema)
8
8
  end
9
9
 
10
10
  it "builds routes without parameters" do
11
- refute_nil @router.routes?("GET", "/apps")[0]
11
+ refute_nil @router.routes?("GET", "/apps")
12
12
  end
13
13
 
14
14
  it "builds routes with parameters" do
15
- refute_nil @router.routes?("GET", "/apps/123")[0]
15
+ refute_nil @router.routes?("GET", "/apps/123")
16
16
  end
17
17
 
18
18
  it "doesn't match anything on a /" do
19
- assert_nil @router.routes?("GET", "/")[0]
19
+ assert_nil @router.routes?("GET", "/")
20
20
  end
21
21
 
22
22
  it "takes a prefix" do
23
23
  # this is a sociopathic example
24
- refute_nil @router.routes?("GET", "/kpi/apps/123", prefix: "/kpi")[0]
24
+ refute_nil @router.routes?("GET", "/kpi/apps/123", prefix: "/kpi")
25
25
  end
26
26
  end
@@ -13,20 +13,10 @@ describe Committee::Middleware::Stub do
13
13
  end
14
14
 
15
15
  describe "#assert_schema_content_type" do
16
- it "passes through a valid response" do
17
- @app = new_rack_app(MultiJson.encode([ValidApp]))
18
- get "/apps"
16
+ it "warns about deprecation" do
17
+ mock(Committee).warn_deprecated.with_any_args
19
18
  assert_schema_content_type
20
19
  end
21
-
22
- it "detects an invalid response Content-Type" do
23
- @app = new_rack_app(MultiJson.encode([ValidApp]), {})
24
- get "/apps"
25
- e = assert_raises(Committee::InvalidResponse) do
26
- assert_schema_content_type
27
- end
28
- assert_match /response header must be set to/i, e.message
29
- end
30
20
  end
31
21
 
32
22
  describe "#assert_schema_conform" do
@@ -45,15 +35,12 @@ describe Committee::Middleware::Stub do
45
35
  assert_match /response header must be set to/i, e.message
46
36
  end
47
37
 
48
- it "detects missing keys in response" do
49
- data = ValidApp.dup
50
- data.delete("name")
51
- @app = new_rack_app(MultiJson.encode([data]))
38
+ it "warns when sending a deprecated string" do
39
+ stub(self).schema_contents { File.read(schema_path) }
40
+ mock(Committee).warn_deprecated.with_any_args
41
+ @app = new_rack_app(MultiJson.encode([ValidApp]))
52
42
  get "/apps"
53
- e = assert_raises(Committee::InvalidResponse) do
54
- assert_schema_conform
55
- end
56
- assert_match /missing keys/i, e.message
43
+ assert_schema_conform
57
44
  end
58
45
  end
59
46
 
data/test/test_helper.rb CHANGED
@@ -1,48 +1,12 @@
1
1
  require "minitest"
2
2
  require "minitest/spec"
3
3
  require "minitest/autorun"
4
-
5
- require "bundler/setup"
6
- Bundler.require(:development)
4
+ require "rack/test"
5
+ require "rr"
7
6
 
8
7
  require_relative "../lib/committee"
9
8
 
10
9
  ValidApp = {
11
- "archived_at" => "2012-01-01T12:00:00Z",
12
- "buildpack_provided_description" => "Ruby/Rack",
13
- "created_at" => "2012-01-01T12:00:00Z",
14
- "git_url" => "git@heroku.com/example.git",
15
- "id" => "01234567-89ab-cdef-0123-456789abcdef",
16
- "maintenance" => false,
17
- "name" => "example",
18
- "owner" => {
19
- "email" => "username@example.com",
20
- "id" => "01234567-89ab-cdef-0123-456789abcdef"
21
- },
22
- "region" => {
23
- "id" => "01234567-89ab-cdef-0123-456789abcdef",
24
- "name" => "us"
25
- },
26
- "released_at" => "2012-01-01T12:00:00Z",
27
- "repo_size" => 0,
28
- "slug_size" => 0,
29
- "stack" => {
30
- "id" => "01234567-89ab-cdef-0123-456789abcdef",
31
- "name" => "cedar"
32
- },
33
- "updated_at" => "2012-01-01T12:00:00Z",
34
- "web_url" => "http://example.herokuapp.com"
35
- }.freeze
36
-
37
- ValidAccount = {
38
- "allow_tracking" => true,
39
- "beta" => true,
40
- "created_at" => "2012-01-01T12:00:00Z",
41
- "email" => "username@example.com",
42
- "id" => "01234567-89ab-cdef-0123-456789abcdef",
43
- "last_login" => "2012-01-01T12:00:00Z",
44
- "updated_at" => "2012-01-01T12:00:00Z",
45
- "verified" => true,
46
- "flags" => ["foo", "bar"],
47
- "credit_cards" => [{"account_number" => "1234-1234-1234-1234", "name" => "Rodney Mullen", "security_code" => 123}]
10
+ "maintenance" => false,
11
+ "name" => "example",
48
12
  }.freeze
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: 0.4.14
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,10 +10,10 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-05-12 00:00:00.000000000 Z
13
+ date: 2014-05-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: multi_json
16
+ name: json_schema
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
@@ -29,7 +29,7 @@ dependencies:
29
29
  - !ruby/object:Gem::Version
30
30
  version: '0.0'
31
31
  - !ruby/object:Gem::Dependency
32
- name: rack
32
+ name: multi_json
33
33
  requirement: !ruby/object:Gem::Requirement
34
34
  none: false
35
35
  requirements:
@@ -45,21 +45,21 @@ dependencies:
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0.0'
47
47
  - !ruby/object:Gem::Dependency
48
- name: minitest
48
+ name: rack
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
- - - ! '>='
52
+ - - ! '>'
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
55
- type: :development
54
+ version: '0.0'
55
+ type: :runtime
56
56
  prerelease: false
57
57
  version_requirements: !ruby/object:Gem::Requirement
58
58
  none: false
59
59
  requirements:
60
- - - ! '>='
60
+ - - ! '>'
61
61
  - !ruby/object:Gem::Version
62
- version: '0'
62
+ version: '0.0'
63
63
  - !ruby/object:Gem::Dependency
64
64
  name: rack-test
65
65
  requirement: !ruby/object:Gem::Requirement
@@ -77,7 +77,7 @@ dependencies:
77
77
  - !ruby/object:Gem::Version
78
78
  version: '0'
79
79
  - !ruby/object:Gem::Dependency
80
- name: rake
80
+ name: rr
81
81
  requirement: !ruby/object:Gem::Requirement
82
82
  none: false
83
83
  requirements:
@@ -106,25 +106,21 @@ files:
106
106
  - lib/committee/middleware/request_validation.rb
107
107
  - lib/committee/middleware/response_validation.rb
108
108
  - lib/committee/middleware/stub.rb
109
- - lib/committee/param_validator.rb
110
109
  - lib/committee/request_unpacker.rb
110
+ - lib/committee/request_validator.rb
111
111
  - lib/committee/response_generator.rb
112
112
  - lib/committee/response_validator.rb
113
113
  - lib/committee/router.rb
114
- - lib/committee/schema.rb
115
114
  - lib/committee/test/methods.rb
116
- - lib/committee/validation.rb
117
115
  - lib/committee.rb
118
116
  - test/middleware/request_validation_test.rb
119
117
  - test/middleware/response_validation_test.rb
120
118
  - test/middleware/stub_test.rb
121
- - test/param_validator_test.rb
122
- - test/performance/request_validation.rb
123
119
  - test/request_unpacker_test.rb
120
+ - test/request_validator_test.rb
124
121
  - test/response_generator_test.rb
125
122
  - test/response_validator_test.rb
126
123
  - test/router_test.rb
127
- - test/schema_test.rb
128
124
  - test/test/methods_test.rb
129
125
  - test/test_helper.rb
130
126
  - bin/committee-stub
@@ -154,3 +150,4 @@ signing_key:
154
150
  specification_version: 3
155
151
  summary: A collection of Rack middleware to support JSON Schema.
156
152
  test_files: []
153
+ has_rdoc:
@@ -1,106 +0,0 @@
1
- module Committee
2
- class ParamValidator
3
- include Validation
4
-
5
- def initialize(params, schema, link_schema, options = {})
6
- @params = params
7
- @schema = schema
8
- @link_schema = link_schema
9
- @allow_extra = options[:allow_extra]
10
- end
11
-
12
- def call
13
- detect_missing!
14
- detect_extra! if !@allow_extra
15
- check_data!
16
- end
17
-
18
- private
19
-
20
- def all_keys
21
- properties = @link_schema["schema"] && @link_schema["schema"]["properties"]
22
- properties && properties.keys || []
23
- end
24
-
25
- def check_data!
26
- return if !@link_schema["schema"] || !@link_schema["schema"]["properties"]
27
-
28
- @link_schema["schema"]["properties"].each do |key, value|
29
- # don't try to check this unless it was actually specificed
30
- next unless @params.key?(key)
31
-
32
- if value["type"] != ["array"]
33
- definitions = find_definitions(value["$ref"])
34
- try_match(key, @params[key], definitions)
35
- else
36
- # only assume one possible array definition for now
37
- definitions = find_definitions(value["items"]["$ref"])
38
- array_definition = definitions[0]
39
- @params[key].each do |item|
40
- # separate logic for a complex object that includes properties
41
- if array_definition.key?("properties")
42
- array_definition["properties"].each do |array_key, array_value|
43
- return unless item.key?(array_key)
44
-
45
- # @todo: this should really be recursive; only one array level is
46
- # supported for now
47
- item_definitions = find_definitions(array_value["$ref"])
48
- try_match(array_key, item[array_key], item_definitions)
49
- end
50
- else
51
- try_match(key, item, definitions)
52
- end
53
- end
54
- end
55
- end
56
- end
57
-
58
- def detect_extra!
59
- extra = @params.keys - all_keys
60
- if extra.count > 0
61
- raise InvalidParams.new("Unknown params: #{extra.join(', ')}.")
62
- end
63
- end
64
-
65
- def detect_missing!
66
- missing = required_keys - @params.keys
67
- if missing.count > 0
68
- raise InvalidParams.new("Require params: #{missing.join(', ')}.")
69
- end
70
- end
71
-
72
- def find_definitions(ref)
73
- definition = @schema.find(ref)
74
- if definition["anyOf"]
75
- definition["anyOf"].map { |r| @schema.find(r["$ref"]) }
76
- else
77
- [definition]
78
- end
79
- end
80
-
81
- def required_keys
82
- (@link_schema["schema"] && @link_schema["schema"]["required"]) || []
83
- end
84
-
85
- def try_match(key, value, definitions)
86
- match = false
87
-
88
- # try to match data against any possible definition
89
- definitions.each do |definition|
90
- if check_type(definition["type"], value, key) &&
91
- check_format(definition["format"], value, key) &&
92
- check_pattern(definition["pattern"], value, key)
93
- match = true
94
- next
95
- end
96
- end
97
-
98
- # if nothing was matched, throw error according to first definition
99
- if !match && definition = definitions.first
100
- check_type!(definition["type"], value, key)
101
- check_format!(definition["format"], value, key)
102
- check_pattern!(definition["pattern"], value, key)
103
- end
104
- end
105
- end
106
- end
@@ -1,56 +0,0 @@
1
- module Committee
2
- class Schema
3
- include Enumerable
4
-
5
- def initialize(data)
6
- @cache = {}
7
- @schema = MultiJson.decode(data)
8
- manifest_regex(@schema)
9
- end
10
-
11
- def [](type)
12
- find(@schema["properties"][type]['$ref'])
13
- end
14
-
15
- def each
16
- @schema["properties"].each do |type, ref|
17
- yield(type, find(ref['$ref']))
18
- end
19
- end
20
-
21
- def find(ref)
22
- cache(ref) do
23
- parts = ref.split("/")
24
- parts.shift if parts.first == "#"
25
- pointer = @schema
26
- parts.each do |p|
27
- next unless pointer
28
- pointer = pointer[p]
29
- end
30
- raise ReferenceNotFound, "Reference not found: #{ref}." if !pointer
31
- pointer
32
- end
33
- end
34
-
35
- private
36
-
37
- def cache(key)
38
- if @cache[key]
39
- @cache[key]
40
- else
41
- @cache[key] = yield
42
- end
43
- end
44
-
45
- def manifest_regex(schema_part)
46
- schema_part["definitions"].each do |_, type_schema|
47
- if type_schema.has_key?("definitions")
48
- manifest_regex(type_schema)
49
- end
50
- if pattern = type_schema["pattern"]
51
- type_schema["pattern"] = Regexp.new(pattern)
52
- end
53
- end
54
- end
55
- end
56
- end