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.
@@ -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