committee 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,15 @@ module Committee
2
2
  class BadRequest < StandardError
3
3
  end
4
4
 
5
+ class InvalidPattern < StandardError
6
+ end
7
+
8
+ class InvalidFormat < StandardError
9
+ end
10
+
11
+ class InvalidType < StandardError
12
+ end
13
+
5
14
  class InvalidParams < StandardError
6
15
  end
7
16
 
@@ -1,5 +1,7 @@
1
1
  module Committee
2
2
  class ParamValidator
3
+ include Validation
4
+
3
5
  def initialize(params, schema, link_schema)
4
6
  @params = params
5
7
  @schema = schema
@@ -31,79 +33,27 @@ module Committee
31
33
  try_match(key, @params[key], definitions)
32
34
  else
33
35
  # only assume one possible array definition for now
34
- array_definition = find_definitions(value["items"]["$ref"])[0]
36
+ definitions = find_definitions(value["items"]["$ref"])
37
+ array_definition = definitions[0]
35
38
  @params[key].each do |item|
36
- array_definition["properties"].each do |array_key, array_value|
37
- return unless item.key?(array_key)
38
-
39
- # @todo: this should really be recursive; only one array level is
40
- # supported for now
41
- definitions = find_definitions(array_value["$ref"])
42
- try_match(array_key, item[array_key], definitions)
39
+ # separate logic for a complex object that includes properties
40
+ if array_definition.key?("properties")
41
+ array_definition["properties"].each do |array_key, array_value|
42
+ return unless item.key?(array_key)
43
+
44
+ # @todo: this should really be recursive; only one array level is
45
+ # supported for now
46
+ item_definitions = find_definitions(array_value["$ref"])
47
+ try_match(array_key, item[array_key], item_definitions)
48
+ end
49
+ else
50
+ try_match(key, item, definitions)
43
51
  end
44
52
  end
45
53
  end
46
54
  end
47
55
  end
48
56
 
49
- def check_format(format, value, key)
50
- case format
51
- when "date-time"
52
- value =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(Z|[+-](\d{2})\:(\d{2}))$/
53
- when "email"
54
- value =~ /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i
55
- when "uuid"
56
- value =~ /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/
57
- else
58
- true
59
- end
60
- end
61
-
62
- def check_format!(format, value, key)
63
- unless check_format(format, value, key)
64
- raise InvalidParams,
65
- %{Invalid format for key "#{key}": expected "#{value}" to be "#{format}".}
66
- end
67
- end
68
-
69
- def check_pattern(pattern, value, key)
70
- !pattern || value =~ pattern
71
- end
72
-
73
- def check_pattern!(pattern, value, key)
74
- unless check_pattern(pattern, value, key)
75
- raise InvalidParams,
76
- %{Invalid pattern for key "#{key}": expected #{value} to match "#{pattern}".}
77
- end
78
- end
79
-
80
- def check_type(allowed_types, value, key)
81
- types = case value
82
- when NilClass
83
- ["null"]
84
- when TrueClass, FalseClass
85
- ["boolean"]
86
- when Bignum, Fixnum
87
- ["integer", "number"]
88
- when Float
89
- ["number"]
90
- when Hash
91
- ["object"]
92
- when String
93
- ["string"]
94
- else
95
- ["unknown"]
96
- end
97
- !(allowed_types & types).empty?
98
- end
99
-
100
- def check_type!(types, value, key)
101
- unless check_type(types, value, key)
102
- raise InvalidParams,
103
- %{Invalid type for key "#{key}": expected #{value} to be #{types}.}
104
- end
105
- end
106
-
107
57
  def detect_extra!
108
58
  extra = @params.keys - all_keys
109
59
  if extra.count > 0
@@ -1,6 +1,9 @@
1
1
  module Committee
2
2
  class ResponseValidator
3
+ include Validation
4
+
3
5
  def initialize(data, schema, link_schema, type_schema)
6
+
4
7
  @data = data
5
8
  @schema = schema
6
9
  @link_schema = link_schema
@@ -8,7 +11,7 @@ module Committee
8
11
  end
9
12
 
10
13
  def call
11
- data = if @link_schema["title"] == "List"
14
+ data = if @link_schema["rel"] == "instances"
12
15
  if !@data.is_a?(Array)
13
16
  raise InvalidResponse, "List endpoints must return an array of objects."
14
17
  end
@@ -45,6 +48,16 @@ module Committee
45
48
  schema["properties"].each do |key, value|
46
49
  if value["properties"]
47
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
+ unless definition["type"].include?("null") && datum.nil?
56
+ check_format!(definition["format"], datum, path + [key])
57
+ check_pattern!(definition["pattern"], datum, path + [key])
58
+ end
59
+ end
60
+
48
61
  else
49
62
  definition = @schema.find(value["$ref"])
50
63
  check_type!(definition["type"], data[key], path + [key])
@@ -56,54 +69,6 @@ module Committee
56
69
  end
57
70
  end
58
71
 
59
- def check_format!(format, value, path)
60
- return if !format
61
- valid = case format
62
- when "date-time"
63
- value =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(Z|[+-](\d{2})\:(\d{2}))$/
64
- when "email"
65
- value =~ /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i
66
- when "uuid"
67
- value =~ /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/
68
- else
69
- true
70
- end
71
- unless valid
72
- raise InvalidResponse,
73
- %{Invalid format at "#{path.join(":")}": expected "#{value}" to be "#{format}".}
74
- end
75
- end
76
-
77
- def check_pattern!(pattern, value, path)
78
- if pattern && !(value =~ pattern)
79
- raise InvalidResponse,
80
- %{Invalid pattern at "#{path.join(":")}": expected #{value} to match "#{pattern}".}
81
- end
82
- end
83
-
84
- def check_type!(allowed_types, value, path)
85
- types = case value
86
- when NilClass
87
- ["null"]
88
- when TrueClass, FalseClass
89
- ["boolean"]
90
- when Bignum, Fixnum
91
- ["integer", "number"]
92
- when Float
93
- ["number"]
94
- when Hash
95
- ["object"]
96
- when String
97
- ["string"]
98
- else
99
- ["unknown"]
100
- end
101
- if (allowed_types & types).empty?
102
- raise InvalidResponse,
103
- %{Invalid type at "#{path.join(":")}": expected #{value} to be #{allowed_types} (was: #{types}).}
104
- end
105
- end
106
-
107
72
  private
108
73
 
109
74
  def build_data_keys(data)
@@ -123,6 +88,8 @@ module Committee
123
88
  @type_schema["properties"].each do |key, info|
124
89
  data = if info["properties"]
125
90
  info
91
+ elsif info["type"] == ["array"]
92
+ @schema.find(info["items"]["$ref"])
126
93
  elsif info["$ref"]
127
94
  @schema.find(info["$ref"])
128
95
  end
@@ -0,0 +1,36 @@
1
+ module Committee::Test
2
+ module Methods
3
+ def assert_schema_conform
4
+ assert_schema_content_type
5
+
6
+ @schema ||= Committee::Schema.new(File.read(schema_path))
7
+ @router ||= Committee::Router.new(@schema)
8
+
9
+ link_schema, type_schema = @router.routes_request?(last_request)
10
+
11
+ unless link_schema
12
+ response = "`#{last_request.request_method} #{last_request.path_info}` undefined in schema."
13
+ raise Committee::InvalidResponse.new(response)
14
+ end
15
+
16
+ data = MultiJson.decode(last_response.body)
17
+ Committee::ResponseValidator.new(
18
+ data,
19
+ @schema,
20
+ link_schema,
21
+ type_schema
22
+ ).call
23
+ end
24
+
25
+ def assert_schema_content_type
26
+ unless last_response.headers["Content-Type"] =~ %r{application/json}
27
+ raise Committee::InvalidResponse,
28
+ %{"Content-Type" response header must be set to "application/json".}
29
+ end
30
+ end
31
+
32
+ def schema_path
33
+ raise "Please override #schema_path."
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,81 @@
1
+ module Committee
2
+ module Validation
3
+ def check_format!(format, value, identifier)
4
+ return if !format
5
+ return if check_format(format, value, identifier)
6
+
7
+ description = case identifier
8
+ when String
9
+ %{Invalid format for key "#{identifier}": expected "#{value}" to be "#{format}".}
10
+ when Array
11
+ %{Invalid format at "#{identifier.join(":")}": expected "#{value}" to be "#{format}".}
12
+ end
13
+
14
+ raise InvalidFormat, description
15
+ end
16
+
17
+ def check_format(format, value, identifier)
18
+ case format
19
+ when "date-time"
20
+ value =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})(\.\d{1,})?(Z|[+-](\d{2})\:(\d{2}))$/
21
+ when "email"
22
+ value =~ /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i
23
+ when "uuid"
24
+ value =~ /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/
25
+ else
26
+ true
27
+ end
28
+ end
29
+
30
+ def check_type!(allowed_types, value, identifier)
31
+ return if check_type(allowed_types, value, identifier)
32
+
33
+ description = case identifier
34
+ when String
35
+ %{Invalid type for key "#{identifier}": expected #{value} to be #{allowed_types}.}
36
+ when Array
37
+ %{Invalid type at "#{identifier.join(":")}": expected #{value} to be #{allowed_types}.}
38
+ end
39
+
40
+ raise InvalidType, description
41
+ end
42
+
43
+ def check_type(allowed_types, value, identifier)
44
+ types = case value
45
+ when NilClass
46
+ ["null"]
47
+ when TrueClass, FalseClass
48
+ ["boolean"]
49
+ when Bignum, Fixnum
50
+ ["integer", "number"]
51
+ when Float
52
+ ["number"]
53
+ when Hash
54
+ ["object"]
55
+ when String
56
+ ["string"]
57
+ else
58
+ ["unknown"]
59
+ end
60
+
61
+ !(allowed_types & types).empty?
62
+ end
63
+
64
+ def check_pattern!(pattern, value, identifier)
65
+ return if check_pattern(pattern, value, identifier)
66
+
67
+ description = case identifier
68
+ when String
69
+ %{Invalid pattern for key "#{identifier}": expected #{value} to match "#{pattern}".}
70
+ when Array
71
+ %{Invalid pattern at "#{identifier.join(":")}": expected #{value} to match "#{pattern}".}
72
+ end
73
+
74
+ raise InvalidPattern, description
75
+ end
76
+
77
+ def check_pattern(pattern, value, identifier)
78
+ !pattern || value =~ pattern
79
+ end
80
+ end
81
+ end
data/lib/committee.rb CHANGED
@@ -2,6 +2,7 @@ require "multi_json"
2
2
  require "rack"
3
3
 
4
4
  require_relative "committee/errors"
5
+ require_relative "committee/validation"
5
6
  require_relative "committee/param_validator"
6
7
  require_relative "committee/request_unpacker"
7
8
  require_relative "committee/response_generator"
@@ -13,3 +14,5 @@ require_relative "committee/middleware/base"
13
14
  require_relative "committee/middleware/request_validation"
14
15
  require_relative "committee/middleware/response_validation"
15
16
  require_relative "committee/middleware/stub"
17
+
18
+ require_relative "committee/test/methods"
@@ -41,7 +41,7 @@ describe Committee::ParamValidator do
41
41
  "app" => "heroku-api",
42
42
  "recipient" => 123,
43
43
  }
44
- e = assert_raises(Committee::InvalidParams) do
44
+ e = assert_raises(Committee::InvalidType) do
45
45
  Committee::ParamValidator.new(params, @schema, @link_schema).call
46
46
  end
47
47
  message = %{Invalid type for key "recipient": expected 123 to be ["string"].}
@@ -53,7 +53,7 @@ describe Committee::ParamValidator do
53
53
  "app" => "heroku-api",
54
54
  "recipient" => "not-email",
55
55
  }
56
- e = assert_raises(Committee::InvalidParams) do
56
+ e = assert_raises(Committee::InvalidFormat) do
57
57
  Committee::ParamValidator.new(params, @schema, @link_schema).call
58
58
  end
59
59
  message = %{Invalid format for key "recipient": expected "not-email" to be "email".}
@@ -65,35 +65,61 @@ describe Committee::ParamValidator do
65
65
  "name" => "%@!"
66
66
  }
67
67
  link_schema = @schema["app"]["links"][0]
68
- e = assert_raises(Committee::InvalidParams) do
68
+ e = assert_raises(Committee::InvalidPattern) do
69
69
  Committee::ParamValidator.new(params, @schema, link_schema).call
70
70
  end
71
71
  message = %{Invalid pattern for key "name": expected %@! to match "(?-mix:^[a-z][a-z0-9-]{3,30}$)".}
72
72
  assert_equal message, e.message
73
73
  end
74
74
 
75
- it "passes an array parameter" do
76
- params = {
77
- "updates" => [
78
- { "name" => "bamboo", "state" => "private" },
79
- { "name" => "cedar", "state" => "public" },
80
- ]
81
- }
82
- link_schema = @schema["stack"]["links"][2]
83
- Committee::ParamValidator.new(params, @schema, link_schema).call
75
+ describe "complex arrays" do
76
+ it "passes an array parameter" do
77
+ params = {
78
+ "updates" => [
79
+ { "name" => "bamboo", "state" => "private" },
80
+ { "name" => "cedar", "state" => "public" },
81
+ ]
82
+ }
83
+ link_schema = @schema["stack"]["links"][2]
84
+ Committee::ParamValidator.new(params, @schema, link_schema).call
85
+ end
86
+
87
+ it "detects an array item with a parameter of the wrong type" do
88
+ params = {
89
+ "updates" => [
90
+ { "name" => "bamboo", "state" => 123 },
91
+ ]
92
+ }
93
+ link_schema = @schema["stack"]["links"][2]
94
+ e = assert_raises(Committee::InvalidType) do
95
+ Committee::ParamValidator.new(params, @schema, link_schema).call
96
+ end
97
+ message = %{Invalid type for key "state": expected 123 to be ["string"].}
98
+ assert_equal message, e.message
99
+ end
84
100
  end
85
101
 
86
- it "detects an array item with a parameter of the wrong type" do
87
- params = {
88
- "updates" => [
89
- { "name" => "bamboo", "state" => 123 },
90
- ]
91
- }
92
- link_schema = @schema["stack"]["links"][2]
93
- e = assert_raises(Committee::InvalidParams) do
102
+ describe "simple arrays" do
103
+ it "passes an array parameter" do
104
+ params = {
105
+ "password" => "1234",
106
+ "flags" => [ "vip", "customer" ]
107
+ }
108
+ link_schema = @schema["account"]["links"][1]
94
109
  Committee::ParamValidator.new(params, @schema, link_schema).call
95
110
  end
96
- message = %{Invalid type for key "state": expected 123 to be ["string"].}
97
- assert_equal message, e.message
111
+
112
+ it "detects an array item with a parameter of the wrong type" do
113
+ params = {
114
+ "password" => "1234",
115
+ "flags" => [ "vip", "customer", 999 ]
116
+ }
117
+ link_schema = @schema["account"]["links"][1]
118
+ e = assert_raises(Committee::InvalidType) do
119
+ Committee::ParamValidator.new(params, @schema, link_schema).call
120
+ end
121
+ message = %{Invalid type for key "flags": expected 999 to be ["string"].}
122
+ assert_equal message, e.message
123
+ end
98
124
  end
99
125
  end
@@ -6,6 +6,7 @@ describe Committee::ResponseValidator do
6
6
  @schema = Committee::Schema.new(File.read("./test/data/schema.json"))
7
7
  # GET /apps/:id
8
8
  @link_schema = @schema["app"]["links"][2]
9
+ @type_schema = @schema["app"]
9
10
  end
10
11
 
11
12
  it "passes through a valid response" do
@@ -19,6 +20,12 @@ describe Committee::ResponseValidator do
19
20
  call
20
21
  end
21
22
 
23
+ it "passes through a valid list response with non-default title" do
24
+ @data = [@data]
25
+ @link_schema = @schema["app"]["links"][4]
26
+ call
27
+ end
28
+
22
29
  it "detects an improperly formatted list response" do
23
30
  # GET /apps
24
31
  @link_schema = @schema["app"]["links"][3]
@@ -43,25 +50,68 @@ describe Committee::ResponseValidator do
43
50
 
44
51
  it "detects mismatched types" do
45
52
  @data.merge!("maintenance" => "not-bool")
46
- e = assert_raises(Committee::InvalidResponse) { call }
47
- message = %{Invalid type at "maintenance": expected not-bool to be ["boolean"] (was: ["string"]).}
53
+ e = assert_raises(Committee::InvalidType) { call }
54
+ message = %{Invalid type at "maintenance": expected not-bool to be ["boolean"].}
48
55
  assert_equal message, e.message
49
56
  end
50
57
 
51
58
  it "detects bad formats" do
52
59
  @data.merge!("id" => "123")
53
- e = assert_raises(Committee::InvalidResponse) { call }
60
+ e = assert_raises(Committee::InvalidFormat) { call }
54
61
  message = %{Invalid format at "id": expected "123" to be "uuid".}
55
62
  assert_equal message, e.message
56
63
  end
57
64
 
58
65
  it "detects bad patterns" do
59
66
  @data.merge!("name" => "%@!")
60
- e = assert_raises(Committee::InvalidResponse) { call }
67
+ e = assert_raises(Committee::InvalidPattern) { call }
61
68
  message = %{Invalid pattern at "name": expected %@! to match "(?-mix:^[a-z][a-z0-9-]{3,30}$)".}
62
69
  assert_equal message, e.message
63
70
  end
64
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 "detects a simple array with an item of the wrong type" do
104
+ @data = ValidAccount.dup
105
+ @data["flags"] = @data["flags"].dup
106
+ @data["flags"] << false
107
+ @link_schema = @schema["account"]["links"][1]
108
+ @type_schema = @schema["account"]
109
+
110
+ e = assert_raises(Committee::InvalidType) { call }
111
+ message = %{Invalid type at "flags": expected false to be ["string"].}
112
+ assert_equal message, e.message
113
+ end
114
+
65
115
  private
66
116
 
67
117
  def call
@@ -69,7 +119,7 @@ describe Committee::ResponseValidator do
69
119
  @data,
70
120
  @schema,
71
121
  @link_schema,
72
- @schema["app"]
122
+ @type_schema
73
123
  ).call
74
124
  end
75
125
  end
@@ -0,0 +1,69 @@
1
+ require_relative "../test_helper"
2
+
3
+ describe Committee::Middleware::Stub do
4
+ include Committee::Test::Methods
5
+ include Rack::Test::Methods
6
+
7
+ def app
8
+ @app
9
+ end
10
+
11
+ def schema_path
12
+ "./test/data/schema.json"
13
+ end
14
+
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"
19
+ assert_schema_content_type
20
+ 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
+ end
31
+
32
+ describe "#assert_schema_conform" do
33
+ it "passes through a valid response" do
34
+ @app = new_rack_app(MultiJson.encode([ValidApp]))
35
+ get "/apps"
36
+ assert_schema_conform
37
+ end
38
+
39
+ it "detects an invalid response Content-Type" do
40
+ @app = new_rack_app(MultiJson.encode([ValidApp]), {})
41
+ get "/apps"
42
+ e = assert_raises(Committee::InvalidResponse) do
43
+ assert_schema_conform
44
+ end
45
+ assert_match /response header must be set to/i, e.message
46
+ end
47
+
48
+ it "detects missing keys in response" do
49
+ data = ValidApp.dup
50
+ data.delete("name")
51
+ @app = new_rack_app(MultiJson.encode([data]))
52
+ get "/apps"
53
+ e = assert_raises(Committee::InvalidResponse) do
54
+ assert_schema_conform
55
+ end
56
+ assert_match /missing keys/i, e.message
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def new_rack_app(response, headers={ "Content-Type" => "application/json" })
63
+ Rack::Builder.new {
64
+ run lambda { |_|
65
+ [200, headers, [response]]
66
+ }
67
+ }
68
+ end
69
+ end
data/test/test_helper.rb CHANGED
@@ -34,3 +34,15 @@ ValidApp = {
34
34
  "updated_at" => "2012-01-01T12:00:00Z",
35
35
  "web_url" => "http://example.herokuapp.com"
36
36
  }.freeze
37
+
38
+ ValidAccount = {
39
+ "allow_tracking" => true,
40
+ "beta" => true,
41
+ "created_at" => "2012-01-01T12:00:00Z",
42
+ "email" => "username@example.com",
43
+ "id" => "01234567-89ab-cdef-0123-456789abcdef",
44
+ "last_login" => "2012-01-01T12:00:00Z",
45
+ "updated_at" => "2012-01-01T12:00:00Z",
46
+ "verified" => true,
47
+ "flags" => ["foo", "bar"]
48
+ }.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.2
4
+ version: 0.4.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-02-06 00:00:00.000000000 Z
13
+ date: 2014-04-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: multi_json
@@ -112,6 +112,8 @@ files:
112
112
  - lib/committee/response_validator.rb
113
113
  - lib/committee/router.rb
114
114
  - lib/committee/schema.rb
115
+ - lib/committee/test/methods.rb
116
+ - lib/committee/validation.rb
115
117
  - lib/committee.rb
116
118
  - test/middleware/request_validation_test.rb
117
119
  - test/middleware/response_validation_test.rb
@@ -122,6 +124,7 @@ files:
122
124
  - test/response_validator_test.rb
123
125
  - test/router_test.rb
124
126
  - test/schema_test.rb
127
+ - test/test/methods_test.rb
125
128
  - test/test_helper.rb
126
129
  - bin/committee-stub
127
130
  homepage: https://github.com/heroku/rack-committee