committee 1.15.0 → 2.0.0.pre

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/bin/committee-stub +10 -36
  3. data/lib/committee/bin/committee_stub.rb +61 -0
  4. data/lib/committee/drivers/hyper_schema.rb +151 -0
  5. data/lib/committee/drivers/open_api_2.rb +297 -0
  6. data/lib/committee/drivers.rb +57 -0
  7. data/lib/committee/middleware/base.rb +52 -13
  8. data/lib/committee/middleware/request_validation.rb +33 -10
  9. data/lib/committee/middleware/response_validation.rb +2 -1
  10. data/lib/committee/middleware/stub.rb +24 -8
  11. data/lib/committee/request_validator.rb +1 -1
  12. data/lib/committee/response_generator.rb +58 -13
  13. data/lib/committee/response_validator.rb +32 -8
  14. data/lib/committee/router.rb +5 -33
  15. data/lib/committee/{query_params_coercer.rb → string_params_coercer.rb} +11 -6
  16. data/lib/committee/test/methods.rb +49 -12
  17. data/lib/committee.rb +15 -1
  18. data/test/bin/committee_stub_test.rb +45 -0
  19. data/test/bin_test.rb +20 -0
  20. data/test/committee_test.rb +49 -0
  21. data/test/drivers/hyper_schema_test.rb +95 -0
  22. data/test/drivers/open_api_2_test.rb +255 -0
  23. data/test/drivers_test.rb +60 -0
  24. data/test/middleware/base_test.rb +49 -5
  25. data/test/middleware/request_validation_test.rb +39 -25
  26. data/test/middleware/response_validation_test.rb +32 -20
  27. data/test/middleware/stub_test.rb +50 -19
  28. data/test/request_unpacker_test.rb +10 -0
  29. data/test/request_validator_test.rb +4 -3
  30. data/test/response_generator_test.rb +50 -6
  31. data/test/response_validator_test.rb +29 -4
  32. data/test/router_test.rb +40 -13
  33. data/test/{query_params_coercer_test.rb → string_params_coercer_test.rb} +3 -4
  34. data/test/test/methods_test.rb +44 -5
  35. data/test/test_helper.rb +59 -1
  36. metadata +62 -10
@@ -6,19 +6,12 @@ module Committee::Middleware
6
6
  @error_class = options.fetch(:error_class, Committee::ValidationError)
7
7
  @params_key = options[:params_key] || "committee.params"
8
8
  @raise = options[:raise]
9
+ @schema = get_schema(options[:schema] ||
10
+ raise(ArgumentError, "Committee: need option `schema`"))
9
11
 
10
- schema = options[:schema] || raise("need option `schema`")
11
- if schema.is_a?(String)
12
- warn_string_deprecated
13
- schema = JSON.parse(schema)
14
- end
15
- if schema.is_a?(Hash)
16
- schema = JsonSchema.parse!(schema)
17
- schema.expand_references!
18
- end
19
- @schema = schema
20
-
21
- @router = Committee::Router.new(@schema, options)
12
+ @router = Committee::Router.new(@schema,
13
+ prefix: options[:prefix]
14
+ )
22
15
  end
23
16
 
24
17
  def call(env)
@@ -33,8 +26,54 @@ module Committee::Middleware
33
26
 
34
27
  private
35
28
 
29
+ # For modern use of the library a schema should be an instance of
30
+ # Committee::Drivers::Schema so that we know that all the computationally
31
+ # difficult parsing is already done by the time we try to handle any
32
+ # request.
33
+ #
34
+ # However, for reasons of backwards compatibility we also allow schema
35
+ # input to be a string, a data hash, or a JsonSchema::Schema. In the former
36
+ # two cases we just parse as if we were sent hyper-schema. In the latter,
37
+ # we have the hyper-schema driver wrap it in a new Committee object.
38
+ def get_schema(schema)
39
+ # These are in a separately conditional ladder so that we only show the
40
+ # user one warning.
41
+ if schema.is_a?(String)
42
+ warn_string_deprecated
43
+ elsif schema.is_a?(Hash)
44
+ warn_hash_deprecated
45
+ end
46
+
47
+ if schema.is_a?(String)
48
+ schema = JSON.parse(schema)
49
+ end
50
+
51
+ if schema.is_a?(Hash) || schema.is_a?(JsonSchema::Schema)
52
+ driver = Committee::Drivers::HyperSchema.new
53
+
54
+ # The driver itself has its own special cases to be able to parse
55
+ # either a hash or JsonSchema::Schema object.
56
+ schema = driver.parse(schema)
57
+ end
58
+
59
+ # Expect the type we want by now. If we don't have it, the user passed
60
+ # something else non-standard in.
61
+ if !schema.is_a?(Committee::Drivers::Schema)
62
+ raise ArgumentError, "Committee: schema expected to be a hash or " \
63
+ "an instance of Committee::Drivers::Schema."
64
+ end
65
+
66
+ schema
67
+ end
68
+
69
+ def warn_hash_deprecated
70
+ Committee.warn_deprecated("Committee: passing a hash to schema " \
71
+ "option is deprecated; please send a driver object instead.")
72
+ end
73
+
36
74
  def warn_string_deprecated
37
- Committee.warn_deprecated("Committee: passing a string to `schema` option is deprecated; please send a deserialized hash instead.")
75
+ Committee.warn_deprecated("Committee: passing a string to schema " \
76
+ "option is deprecated; please send a driver object instead.")
38
77
  end
39
78
  end
40
79
  end
@@ -2,27 +2,48 @@ module Committee::Middleware
2
2
  class RequestValidation < Base
3
3
  def initialize(app, options={})
4
4
  super
5
+
5
6
  @allow_form_params = options.fetch(:allow_form_params, true)
6
7
  @allow_query_params = options.fetch(:allow_query_params, true)
7
8
  @check_content_type = options.fetch(:check_content_type, true)
8
9
  @optimistic_json = options.fetch(:optimistic_json, false)
9
- @coerce_query_params = options.fetch(:coerce_query_params, false)
10
10
  @strict = options[:strict]
11
11
 
12
+ @coerce_path_params = options.fetch(:coerce_path_params,
13
+ @schema.driver.default_path_params)
14
+ @coerce_query_params = options.fetch(:coerce_query_params,
15
+ @schema.driver.default_query_params)
16
+
12
17
  # deprecated
13
18
  @allow_extra = options[:allow_extra]
14
19
  end
15
20
 
16
21
  def handle(request)
17
- link = @router.find_request_link(request)
18
-
19
- if link && @coerce_query_params && !request.GET.nil? && !link.schema.nil?
20
- request.env["rack.request.query_hash"].merge!(
21
- Committee::QueryParamsCoercer.new(
22
- request.GET,
23
- link.schema
24
- ).call
25
- )
22
+ link, param_matches = @router.find_request_link(request)
23
+ path_params = {}
24
+
25
+ if link
26
+ # Attempts to coerce parameters that appear in a link's URL to Ruby
27
+ # types that can be validated with a schema.
28
+ if @coerce_path_params
29
+ path_params = param_matches.merge(
30
+ Committee::StringParamsCoercer.new(
31
+ param_matches,
32
+ link.schema
33
+ ).call
34
+ )
35
+ end
36
+
37
+ # Attempts to coerce parameters that appear in a query string to Ruby
38
+ # types that can be validated with a schema.
39
+ if @coerce_query_params && !request.GET.nil? && !link.schema.nil?
40
+ request.env["rack.request.query_hash"].merge!(
41
+ Committee::StringParamsCoercer.new(
42
+ request.GET,
43
+ link.schema
44
+ ).call
45
+ )
46
+ end
26
47
  end
27
48
 
28
49
  request.env[@params_key] = Committee::RequestUnpacker.new(
@@ -32,6 +53,8 @@ module Committee::Middleware
32
53
  optimistic_json: @optimistic_json
33
54
  ).call
34
55
 
56
+ request.env[@params_key].merge!(path_params)
57
+
35
58
  if link
36
59
  validator = Committee::RequestValidator.new(link, check_content_type: @check_content_type)
37
60
  validator.call(request, request.env[@params_key])
@@ -10,7 +10,8 @@ module Committee::Middleware
10
10
  def handle(request)
11
11
  status, headers, response = @app.call(request.env)
12
12
 
13
- if validate?(status) && link = @router.find_request_link(request)
13
+ link, _ = @router.find_request_link(request)
14
+ if validate?(status) && link
14
15
  full_body = ""
15
16
  response.each do |chunk|
16
17
  full_body << chunk
@@ -2,16 +2,27 @@ module Committee::Middleware
2
2
  class Stub < Base
3
3
  def initialize(app, options={})
4
4
  super
5
- @cache = {}
6
- @call = options[:call]
5
+
6
+ # A bug in Committee's cache implementation meant that it wasn't working
7
+ # for a very long time, even for people who thought they were taking
8
+ # advantage of it. I repaired the caching feature, but have disable it by
9
+ # default so that we don't need to introduce any class-level variables
10
+ # that could have memory leaking implications. To enable caching, just
11
+ # pass an empty hash to this option.
12
+ @cache = options[:cache]
13
+
14
+ @call = options[:call]
7
15
  end
8
16
 
9
17
  def handle(request)
10
- if link = @router.find_request_link(request)
18
+ link, _ = @router.find_request_link(request)
19
+ if link
11
20
  headers = { "Content-Type" => "application/json" }
12
- data = cache(link.method, link.href) do
21
+
22
+ data = cache(link) do
13
23
  Committee::ResponseGenerator.new.call(link)
14
24
  end
25
+
15
26
  if @call
16
27
  request.env["committee.response"] = data
17
28
  call_status, call_headers, call_body = @app.call(request.env)
@@ -29,8 +40,8 @@ module Committee::Middleware
29
40
  # will be the same one that we set above)
30
41
  data = request.env["committee.response"]
31
42
  end
32
- status = link.rel == "create" ? 201 : 200
33
- [status, headers, [JSON.pretty_generate(data)]]
43
+
44
+ [link.status_success, headers, [JSON.pretty_generate(data)]]
34
45
  else
35
46
  @app.call(request.env)
36
47
  end
@@ -38,8 +49,13 @@ module Committee::Middleware
38
49
 
39
50
  private
40
51
 
41
- def cache(method, href)
42
- key = "#{method}+#{href}"
52
+ def cache(link)
53
+ return yield unless @cache
54
+
55
+ # Just the object ID is enough to uniquely identify the link, but store
56
+ # the method and href so that we can more easily introspect the cache if
57
+ # necessary.
58
+ key = "#{link.object_id}##{link.method}+#{link.href}"
43
59
  if @cache[key]
44
60
  @cache[key]
45
61
  else
@@ -24,7 +24,7 @@ module Committee
24
24
 
25
25
  def check_content_type!(request, data)
26
26
  content_type = request_media_type(request)
27
- if content_type && !empty_request?(request)
27
+ if content_type && @link.enc_type && !empty_request?(request)
28
28
  unless Rack::Mime.match?(content_type, @link.enc_type)
29
29
  raise Committee::InvalidRequest,
30
30
  %{"Content-Type" request header must be set to "#{@link.enc_type}".}
@@ -1,35 +1,80 @@
1
1
  module Committee
2
2
  class ResponseGenerator
3
3
  def call(link)
4
- data = generate_properties(link.target_schema || link.parent)
4
+ data = generate_properties(link, target_schema(link))
5
5
 
6
- # list is a special case; wrap data in an array
7
- data = [data] if link.rel == "instances"
6
+ # List is a special case; wrap data in an array.
7
+ #
8
+ # This is poor form that's here so as not to introduce breaking behavior.
9
+ # The "instances" value of "rel" is a Heroku-ism and was originally
10
+ # introduced before we understood how to use "targetSchema". It's not
11
+ # meaningful with the context of the hyper-schema specification and
12
+ # should be eventually be removed.
13
+ if legacy_hyper_schema_rel?(link)
14
+ data = [data]
15
+ end
8
16
 
9
17
  data
10
18
  end
11
19
 
12
20
  private
13
21
 
14
- def generate_properties(schema)
22
+ # These are basic types that are part of the JSON schema for which we'll
23
+ # emit zero values when generating a response. For a schema that allows
24
+ # multiple of the types in the list, types are preferred in the order in
25
+ # which they're defined.
26
+ SCALAR_TYPES = {
27
+ "boolean" => false,
28
+ "integer" => 0,
29
+ "number" => 0.0,
30
+ "string" => "",
31
+
32
+ # Prefer null last.
33
+ "null" => nil,
34
+ }.freeze
35
+
36
+ def generate_properties(link, schema)
15
37
  # special example attribute was included; use its value
16
- if !schema.data["example"].nil?
38
+ if schema.data && !schema.data["example"].nil?
17
39
  schema.data["example"]
18
- # null is allowed; use that
19
- elsif schema.type.include?("null")
20
- nil
21
- elsif schema.type.include?("array") && !schema.items.nil?
22
- [generate_properties(schema.items)]
23
- elsif !schema.properties.empty?
40
+ elsif !schema.all_of.empty? || !schema.properties.empty?
24
41
  data = {}
42
+ schema.all_of.each do |subschema|
43
+ data.merge!(generate_properties(link, subschema))
44
+ end
25
45
  schema.properties.map do |key, value|
26
- data[key] = generate_properties(value)
46
+ data[key] = generate_properties(link, value)
27
47
  end
28
48
  data
49
+ elsif schema.type.include?("array") && !schema.items.nil?
50
+ [generate_properties(link, schema.items)]
51
+ elsif schema.type.any? { |t| SCALAR_TYPES.include?(t) }
52
+ SCALAR_TYPES.each do |k, v|
53
+ break(v) if schema.type.include?(k)
54
+ end
29
55
  else
30
- raise(%{At "#{schema.pointer}": no "example" attribute and "null" } +
56
+ raise(%{At "#{link.method} #{link.href}" "#{schema.pointer}": no } +
57
+ %{"example" attribute and "null" } +
31
58
  %{is not allowed; don't know how to generate property.})
32
59
  end
33
60
  end
61
+
62
+ def legacy_hyper_schema_rel?(link)
63
+ link.is_a?(Committee::Drivers::HyperSchema::Link) &&
64
+ link.rel == "instances" &&
65
+ !link.target_schema
66
+ end
67
+
68
+ # Gets the target schema of a link. This is normally just the standard
69
+ # response schema, but we allow some legacy behavior for hyper-schema links
70
+ # tagged with rel=instances to instead use the schema of their parent
71
+ # resource.
72
+ def target_schema(link)
73
+ if link.target_schema
74
+ link.target_schema
75
+ elsif legacy_hyper_schema_rel?(link)
76
+ link.parent
77
+ end
78
+ end
34
79
  end
35
80
  end
@@ -6,10 +6,7 @@ module Committee
6
6
  @link = link
7
7
  @validate_errors = options[:validate_errors]
8
8
 
9
- # we should eventually move off of validating against parent schema too
10
- # ... this is a Herokuism and not in the specification
11
- schema = link.target_schema || link.parent
12
- @validator = JsonSchema::Validator.new(schema)
9
+ @validator = JsonSchema::Validator.new(target_schema(link))
13
10
  end
14
11
 
15
12
  def self.validate?(status, options = {})
@@ -24,7 +21,14 @@ module Committee
24
21
  check_content_type!(response)
25
22
  end
26
23
 
27
- if @link.rel == "instances" && !@link.target_schema
24
+ # List is a special case; expect data in an array.
25
+ #
26
+ # This is poor form that's here so as not to introduce breaking behavior.
27
+ # The "instances" value of "rel" is a Heroku-ism and was originally
28
+ # introduced before we understood how to use "targetSchema". It's not
29
+ # meaningful with the context of the hyper-schema specification and
30
+ # should be eventually be removed.
31
+ if legacy_hyper_schema_rel?(@link)
28
32
  if !data.is_a?(Array)
29
33
  raise InvalidResponse, "List endpoints must return an array of objects."
30
34
  end
@@ -50,9 +54,29 @@ module Committee
50
54
  end
51
55
 
52
56
  def check_content_type!(response)
53
- unless Rack::Mime.match?(response_media_type(response), @link.media_type)
54
- raise Committee::InvalidResponse,
55
- %{"Content-Type" response header must be set to "#{@link.media_type}".}
57
+ if @link.media_type
58
+ unless Rack::Mime.match?(response_media_type(response), @link.media_type)
59
+ raise Committee::InvalidResponse,
60
+ %{"Content-Type" response header must be set to "#{@link.media_type}".}
61
+ end
62
+ end
63
+ end
64
+
65
+ def legacy_hyper_schema_rel?(link)
66
+ link.is_a?(Committee::Drivers::HyperSchema::Link) &&
67
+ link.rel == "instances" &&
68
+ !link.target_schema
69
+ end
70
+
71
+ # Gets the target schema of a link. This is normally just the standard
72
+ # response schema, but we allow some legacy behavior for hyper-schema links
73
+ # tagged with rel=instances to instead use the schema of their parent
74
+ # resource.
75
+ def target_schema(link)
76
+ if link.target_schema
77
+ link.target_schema
78
+ elsif legacy_hyper_schema_rel?(link)
79
+ link.parent
56
80
  end
57
81
  end
58
82
  end
@@ -1,9 +1,9 @@
1
1
  module Committee
2
2
  class Router
3
3
  def initialize(schema, options = {})
4
- @routes = build_routes(schema)
5
4
  @prefix = options[:prefix]
6
5
  @prefix_regexp = /\A#{Regexp.escape(@prefix)}/.freeze if @prefix
6
+ @schema = schema
7
7
  end
8
8
 
9
9
  def includes?(path)
@@ -16,10 +16,11 @@ module Committee
16
16
 
17
17
  def find_link(method, path)
18
18
  path = path.gsub(@prefix_regexp, "") if @prefix
19
- if method_routes = @routes[method]
19
+ if method_routes = @schema.routes[method]
20
20
  method_routes.each do |pattern, link|
21
- if path =~ pattern
22
- return link
21
+ if matches = pattern.match(path)
22
+ hash = Hash[matches.names.zip(matches.captures)]
23
+ return link, hash
23
24
  end
24
25
  end
25
26
  end
@@ -29,34 +30,5 @@ module Committee
29
30
  def find_request_link(request)
30
31
  find_link(request.request_method, request.path_info)
31
32
  end
32
-
33
- private
34
-
35
- def build_routes(schema)
36
- routes = {}
37
-
38
- schema.links.each do |link|
39
- method, href = parse_link(link)
40
- next unless method
41
- routes[method] ||= []
42
- routes[method] << [%r{^#{href}$}, link]
43
- end
44
-
45
- # recursively iterate through all `properties` subschemas to build a
46
- # complete routing table
47
- schema.properties.each do |_, subschema|
48
- routes.merge!(build_routes(subschema)) { |_, r1, r2| r1 + r2 }
49
- end
50
-
51
- routes
52
- end
53
-
54
- def parse_link(link)
55
- return nil, nil if !link.method || !link.href
56
- method = link.method.to_s.upcase
57
- # /apps/{id} --> /apps/([^/]+)
58
- href = link.href.gsub(/\{(.*?)\}/, "[^/]+")
59
- [method, href]
60
- end
61
33
  end
62
34
  end
@@ -1,10 +1,15 @@
1
- # Attempts to coerce params given in the query hash (which are all strings) into
2
- # the types specified by the schema.
3
- # Currently supported types: null, integer, number and boolean.
4
- # +call+ returns a hash of all params which could be coerced - coercion errors
5
- # are simply ignored and expected to be handled later by schema validation.
6
1
  module Committee
7
- class QueryParamsCoercer
2
+ # StringParamsCoercer takes parameters that are specified over a medium that
3
+ # can only accept strings (for example in a URL path or in query parameters)
4
+ # and attempts to coerce them into known types based of a link's schema
5
+ # definition.
6
+ #
7
+ # Currently supported types: null, integer, number and boolean.
8
+ #
9
+ # +call+ returns a hash of all params which could be coerced - coercion
10
+ # errors are simply ignored and expected to be handled later by schema
11
+ # validation.
12
+ class StringParamsCoercer
8
13
  def initialize(query_hash, schema)
9
14
  @query_hash = query_hash
10
15
  @schema = schema
@@ -1,19 +1,42 @@
1
1
  module Committee::Test
2
2
  module Methods
3
3
  def assert_schema_conform
4
- if (data = schema_contents).is_a?(String)
5
- warn_string_deprecated
6
- data = JSON.parse(data)
7
- end
4
+ @committee_schema ||= begin
5
+ # The preferred option. The user has already parsed a schema elsewhere
6
+ # and we therefore don't have to worry about any performance
7
+ # implications of having to do it for every single test suite.
8
+ if committee_schema
9
+ committee_schema
10
+ else
11
+ schema = schema_contents
12
+
13
+ if schema.is_a?(String)
14
+ warn_string_deprecated
15
+ elsif schema.is_a?(Hash)
16
+ warn_hash_deprecated
17
+ end
18
+
19
+ if schema.is_a?(String)
20
+ schema = JSON.parse(schema)
21
+ end
8
22
 
9
- @schema ||= begin
10
- schema = JsonSchema.parse!(data)
11
- schema.expand_references!
12
- schema
23
+ if schema.is_a?(Hash) || schema.is_a?(JsonSchema::Schema)
24
+ driver = Committee::Drivers::HyperSchema.new
25
+
26
+ # The driver itself has its own special cases to be able to parse
27
+ # either a hash or JsonSchema::Schema object.
28
+ schema = driver.parse(schema)
29
+ end
30
+
31
+ schema
32
+ end
13
33
  end
14
- @router ||= Committee::Router.new(@schema, prefix: schema_url_prefix)
15
34
 
16
- unless link = @router.find_request_link(last_request)
35
+ @committee_router ||= Committee::Router.new(@committee_schema,
36
+ prefix: schema_url_prefix)
37
+
38
+ link, _ = @committee_router.find_request_link(last_request)
39
+ unless link
17
40
  response = "`#{last_request.request_method} #{last_request.path_info}` undefined in schema."
18
41
  raise Committee::InvalidResponse.new(response)
19
42
  end
@@ -28,6 +51,12 @@ module Committee::Test
28
51
  Committee.warn_deprecated("Committee: use of #assert_schema_content_type is deprecated; use #assert_schema_conform instead.")
29
52
  end
30
53
 
54
+ # Can be overridden with a different driver name for other API definition
55
+ # formats.
56
+ def committee_schema
57
+ nil
58
+ end
59
+
31
60
  # can be overridden alternatively to #schema_path in case the schema is
32
61
  # easier to access as a string
33
62
  # blob
@@ -36,15 +65,23 @@ module Committee::Test
36
65
  end
37
66
 
38
67
  def schema_path
39
- raise "Please override #schema_contents or #schema_path."
68
+ raise "Please override #commitee_schema."
40
69
  end
41
70
 
42
71
  def schema_url_prefix
43
72
  nil
44
73
  end
45
74
 
75
+ def warn_hash_deprecated
76
+ Committee.warn_deprecated("Committee: returning a hash from " \
77
+ "#schema_contents and using #schema_path is deprecated; please " \
78
+ "override #committee_schema instead.")
79
+ end
80
+
46
81
  def warn_string_deprecated
47
- Committee.warn_deprecated("Committee: returning a string from `#schema_contents` is deprecated; please return a deserialized hash instead.")
82
+ Committee.warn_deprecated("Committee: returning a string from " \
83
+ "#schema_contents is deprecated; please override #committee_schema " \
84
+ "instead.")
48
85
  end
49
86
 
50
87
  def validate_response?(status)
data/lib/committee.rb CHANGED
@@ -3,7 +3,7 @@ require "json_schema"
3
3
  require "rack"
4
4
 
5
5
  require_relative "committee/errors"
6
- require_relative "committee/query_params_coercer"
6
+ require_relative "committee/string_params_coercer"
7
7
  require_relative "committee/request_unpacker"
8
8
  require_relative "committee/request_validator"
9
9
  require_relative "committee/response_generator"
@@ -11,14 +11,28 @@ require_relative "committee/response_validator"
11
11
  require_relative "committee/router"
12
12
  require_relative "committee/validation_error"
13
13
 
14
+ require_relative "committee/drivers"
15
+ require_relative "committee/drivers/hyper_schema"
16
+ require_relative "committee/drivers/open_api_2"
17
+
14
18
  require_relative "committee/middleware/base"
15
19
  require_relative "committee/middleware/request_validation"
16
20
  require_relative "committee/middleware/response_validation"
17
21
  require_relative "committee/middleware/stub"
18
22
 
23
+ require_relative "committee/bin/committee_stub"
24
+
19
25
  require_relative "committee/test/methods"
20
26
 
21
27
  module Committee
28
+ def self.debug?
29
+ ENV["COMMITTEE_DEBUG"]
30
+ end
31
+
32
+ def self.log_debug(message)
33
+ $stderr.puts(message) if debug?
34
+ end
35
+
22
36
  def self.warn_deprecated(message)
23
37
  if !$VERBOSE.nil?
24
38
  $stderr.puts(message)
@@ -0,0 +1,45 @@
1
+ require_relative "../test_helper"
2
+
3
+ describe Committee::Bin::CommitteeStub do
4
+ before do
5
+ @bin = Committee::Bin::CommitteeStub.new
6
+ end
7
+
8
+ it "produces a Rack app" do
9
+ app = @bin.get_app(hyper_schema, {})
10
+ assert_kind_of Rack::Builder, app
11
+ end
12
+
13
+ it "parses command line options" do
14
+ options, parser = @bin.get_options_parser
15
+
16
+ parser.parse!(["--help"])
17
+ assert_equal true, options[:help]
18
+
19
+ parser.parse!([
20
+ "--driver", "open_api_2",
21
+ "--tolerant", "true",
22
+ "--port", "1234"
23
+ ])
24
+ assert_equal :open_api_2, options[:driver]
25
+ assert_equal true, options[:tolerant]
26
+ assert_equal "1234", options[:port]
27
+ end
28
+ end
29
+
30
+ describe Committee::Bin::CommitteeStub, "app" do
31
+ include Rack::Test::Methods
32
+
33
+ before do
34
+ @bin = Committee::Bin::CommitteeStub.new
35
+ end
36
+
37
+ def app
38
+ @bin.get_app(hyper_schema, {})
39
+ end
40
+
41
+ it "defaults to a 404" do
42
+ get "/foos"
43
+ assert_equal 404, last_response.status
44
+ end
45
+ end
data/test/bin_test.rb ADDED
@@ -0,0 +1,20 @@
1
+ require_relative "test_helper"
2
+
3
+ #
4
+ # The purpose of this sets of tests is just to include our Ruby executables
5
+ # where possible so that we can get very basic sanity checks on their syntax
6
+ # (which is something that of course Ruby can't do by default).
7
+ #
8
+ # We can do this without actually executing them because they're gated by `if
9
+ # $0 == __FILE__` statements.
10
+ #
11
+
12
+ describe "executables in bin/" do
13
+ before do
14
+ @bin_dir = File.expand_path("../../bin", __FILE__)
15
+ end
16
+
17
+ it "has roughly valid Ruby structure for committee-stub" do
18
+ load File.join(@bin_dir, "committee-stub")
19
+ end
20
+ end