hyperdrive 0.0.5 → 0.0.6

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +12 -4
  3. data/Gemfile +2 -0
  4. data/hyperdrive.gemspec +6 -2
  5. data/lib/hyperdrive/docs.rb +10 -11
  6. data/lib/hyperdrive/dsl/resource.rb +10 -15
  7. data/lib/hyperdrive/dsl.rb +49 -3
  8. data/lib/hyperdrive/endpoint.rb +65 -0
  9. data/lib/hyperdrive/errors/missing_required_param.rb +19 -0
  10. data/lib/hyperdrive/errors/not_acceptable.rb +18 -0
  11. data/lib/hyperdrive/errors.rb +13 -8
  12. data/lib/hyperdrive/filter.rb +20 -0
  13. data/lib/hyperdrive/hateoas.rb +45 -0
  14. data/lib/hyperdrive/middleware/accept.rb +16 -0
  15. data/lib/hyperdrive/middleware/content_negotiation.rb +19 -0
  16. data/lib/hyperdrive/middleware/cors.rb +36 -0
  17. data/lib/hyperdrive/middleware/error.rb +35 -0
  18. data/lib/hyperdrive/middleware/request_method.rb +31 -0
  19. data/lib/hyperdrive/middleware/required_params.rb +35 -0
  20. data/lib/hyperdrive/middleware/resource.rb +18 -0
  21. data/lib/hyperdrive/middleware/sanitize_params.rb +25 -0
  22. data/lib/hyperdrive/middleware.rb +10 -0
  23. data/lib/hyperdrive/param.rb +44 -0
  24. data/lib/hyperdrive/resource.rb +74 -30
  25. data/lib/hyperdrive/server.rb +16 -17
  26. data/lib/hyperdrive/utils.rb +27 -0
  27. data/lib/hyperdrive/values.rb +37 -3
  28. data/lib/hyperdrive/version.rb +1 -1
  29. data/lib/hyperdrive.rb +21 -7
  30. data/spec/hyperdrive/docs_spec.rb +14 -8
  31. data/spec/hyperdrive/dsl/resource_spec.rb +27 -37
  32. data/spec/hyperdrive/dsl_spec.rb +52 -0
  33. data/spec/hyperdrive/endpoint_spec.rb +22 -0
  34. data/spec/hyperdrive/filter_spec.rb +34 -0
  35. data/spec/hyperdrive/hateoas_spec.rb +42 -0
  36. data/spec/hyperdrive/middleware/accept_spec.rb +15 -0
  37. data/spec/hyperdrive/middleware/content_negotiation_spec.rb +29 -0
  38. data/spec/hyperdrive/middleware/cors_spec.rb +47 -0
  39. data/spec/hyperdrive/middleware/error_spec.rb +25 -0
  40. data/spec/hyperdrive/middleware/request_method_spec.rb +28 -0
  41. data/spec/hyperdrive/middleware/required_params_spec.rb +43 -0
  42. data/spec/hyperdrive/middleware/resource_spec.rb +15 -0
  43. data/spec/hyperdrive/middleware/sanitize_params_spec.rb +24 -0
  44. data/spec/hyperdrive/param_spec.rb +34 -0
  45. data/spec/hyperdrive/resource_spec.rb +87 -25
  46. data/spec/hyperdrive/server_spec.rb +48 -7
  47. data/spec/hyperdrive/utils_spec.rb +38 -0
  48. data/spec/hyperdrive/values_spec.rb +37 -0
  49. data/spec/spec_helper.rb +71 -12
  50. metadata +106 -11
  51. data/.coveralls.yml +0 -1
  52. data/lib/hyperdrive/dsl/main.rb +0 -19
  53. data/lib/hyperdrive/response.rb +0 -42
  54. data/spec/hyperdrive/dsl/main_spec.rb +0 -16
  55. data/spec/hyperdrive/response_spec.rb +0 -23
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ module Hyperdrive
4
+ class Param
5
+ attr_reader :name, :description, :required, :type, :constraints
6
+
7
+ def initialize(name, description, options = {})
8
+ @name = name.to_s
9
+ @description = description
10
+ options = default_options.merge(options)
11
+ @required = if options[:required] == true
12
+ %w(POST PUT PATCH)
13
+ elsif options[:required] == false
14
+ []
15
+ else
16
+ Array(options[:required])
17
+ end
18
+ @type = options[:type]
19
+ @constraints = "#{required_constraint} #{options[:constraints]}"
20
+ end
21
+
22
+ def required?(http_request_method)
23
+ @required.include? http_request_method
24
+ end
25
+
26
+ def to_hash
27
+ { name: @name, description: @description, type: @type, constraints: @constraints }
28
+ end
29
+
30
+ private
31
+
32
+ def default_options
33
+ { required: true, type: 'String', constraints: nil }
34
+ end
35
+
36
+ def required_constraint
37
+ if @required.empty?
38
+ ''
39
+ else
40
+ "Required for: #{@required.join(', ')}."
41
+ end
42
+ end
43
+ end
44
+ end
@@ -2,37 +2,62 @@
2
2
 
3
3
  module Hyperdrive
4
4
  class Resource
5
- attr_reader :endpoint, :allowed_params, :filters, :request_handlers
6
- attr_accessor :name, :desc
5
+ include Hyperdrive::Values
6
+ attr_reader :id, :namespace, :endpoint, :params, :filters, :request_handlers, :version
7
+ attr_accessor :name, :description
7
8
 
8
- def initialize(key)
9
- @key = key
10
- @endpoint = "/#{@key.to_s.en.plural}"
11
- @allowed_params = default_allowed_params
9
+ def initialize(name, hyperdrive_config = hyperdrive.config)
10
+ @namespace = name.to_s.en.plural
11
+ @endpoint = "/#{namespace}"
12
+ @params = default_params
12
13
  @filters = default_filters
13
14
  @request_handlers = default_request_handlers
15
+ @config = hyperdrive_config
16
+ @id = [@config[:vendor], @namespace].join(':')
14
17
  end
15
18
 
16
- def register_param(key, description, options = {})
17
- options = default_param_options.merge(options)
18
- @allowed_params[key] = { desc: description }.merge(options)
19
+ def register_param(param, description, options = {})
20
+ @params[param] = Param.new(param, description, options)
19
21
  end
20
22
 
21
- def register_filter(key, description, options = {})
22
- options = default_filter_options.merge(options)
23
- @filters[key] = { desc: description }.merge(options)
23
+ def register_filter(filter, description, options = {})
24
+ @filters[filter] = Filter.new(filter, description, options)
24
25
  end
25
26
 
26
- def define_request_handler(request_method, block)
27
- @request_handlers[request_method] = block
27
+ def register_request_handler(request_method, request_handler, version = 'v1')
28
+ @request_handlers[request_method] ||= {}
29
+ @request_handlers[request_method].merge!({ version => request_handler })
28
30
  if request_method == :get
29
- @request_handlers[:head] = block
31
+ @request_handlers[:head] ||= {}
32
+ @request_handlers[:head].merge!({ version => @request_handlers[:get][version] })
30
33
  end
31
34
  end
32
35
 
33
- def request_handler(http_request_method)
34
- request_method = Hyperdrive::Values.request_methods_string_map[http_request_method]
35
- request_handlers[request_method]
36
+ def request_handler(http_request_method, version = nil)
37
+ version ||= latest_version(http_request_method)
38
+ request_method = http_request_methods[http_request_method]
39
+ request_handlers[request_method][version]
40
+ end
41
+
42
+ def acceptable_content_types(http_request_method)
43
+ content_types = []
44
+ @config[:media_types].each do |media_type|
45
+ available_versions(http_request_method).each do |version|
46
+ content_types << "application/vnd.#{@config[:vendor]}.#{namespace}.#{version}+#{media_type}"
47
+ end
48
+ content_types << "application/vnd.#{@config[:vendor]}.#{namespace}+#{media_type}"
49
+ content_types << "application/vnd.#{@config[:vendor]}+#{media_type}"
50
+ end
51
+ content_types
52
+ end
53
+
54
+ def available_versions(http_request_method)
55
+ request_method = http_request_methods[http_request_method]
56
+ @request_handlers[request_method].keys.sort.reverse
57
+ end
58
+
59
+ def latest_version(http_request_method)
60
+ available_versions(http_request_method).first
36
61
  end
37
62
 
38
63
  def request_method_allowed?(http_request_method)
@@ -40,33 +65,52 @@ module Hyperdrive
40
65
  end
41
66
 
42
67
  def allowed_methods
43
- Hyperdrive::Values.request_methods_symbol_map.values_at(*request_handlers.keys)
68
+ request_methods.values_at(*request_handlers.keys)
44
69
  end
45
70
 
46
- private
71
+ def required_param?(param, http_request_method)
72
+ return false if %w(GET HEAD OPTIONS).include? http_request_method
73
+ params.key?(param) and params[param].required?(http_request_method)
74
+ end
47
75
 
48
- def default_allowed_params
76
+ def required_filter?(filter, http_request_method)
77
+ return false unless %w(GET HEAD OPTIONS).include? http_request_method
78
+ filters.key?(filter) and filters[filter].required?(http_request_method)
79
+ end
80
+
81
+ def required?(param, http_request_method)
82
+ required_param?(param, http_request_method) or required_filter?(param, http_request_method)
83
+ end
84
+
85
+ def to_hash
49
86
  {
50
- id: { desc: 'Resource Identifier', required: %w(PUT PATCH DELETE) }
87
+ _links: { 'self' => { href: endpoint } },
88
+ id: id,
89
+ name: name,
90
+ description: description,
91
+ methods: allowed_methods,
92
+ params: params.map { |_,param| param.to_hash },
93
+ filters: filters.map { |_,filter| filter.to_hash },
94
+ media_types: allowed_methods.map { |method| acceptable_content_types(method) }.uniq
51
95
  }
52
96
  end
53
97
 
54
- def default_param_options
55
- { required: true }.freeze
56
- end
98
+ private
57
99
 
58
- def default_filters
100
+ def default_params
59
101
  {
60
- id: { desc: 'Resource Identifier', required: false }
102
+ id: Param.new(:id, 'Identifier', required: %w(PUT PATCH DELETE))
61
103
  }
62
104
  end
63
105
 
64
- def default_filter_options
65
- { required: false }.freeze
106
+ def default_filters
107
+ {
108
+ id: Filter.new(:id, 'Resource Identifier', required: false)
109
+ }
66
110
  end
67
111
 
68
112
  def default_request_handlers
69
- { options: proc { '' } }
113
+ { options: { 'v1' => proc { |env| '' } } }
70
114
  end
71
115
  end
72
116
  end
@@ -10,30 +10,29 @@ module Hyperdrive
10
10
 
11
11
  def self.server
12
12
  Rack::Builder.new do
13
- use Rack::Runtime
14
13
  use Rack::Lint
14
+ use Rack::Runtime
15
+ use Rack::MethodOverride
15
16
  use Rack::Head
17
+ use Rack::ConditionalGet
18
+ use Hyperdrive::Middleware::Error
19
+ use Hyperdrive::Middleware::Accept
20
+ use Rack::Deflater
21
+ use Rack::ETag, "max-age=0,private,must-revalidate", "public,max-age=86400,s-maxage=86400"
16
22
 
17
23
  map '/' do
18
- info = ''
19
- hyperdrive.resources.each do |type, resource|
20
- info += %Q({"id":"#{resource.endpoint}","name":"#{resource.name}","desc":"#{resource.desc}","type":"#{type}"})
21
- end
22
-
23
- run ->(env) {
24
- [200, { 'Content-Type' => 'application/json', 'Allow' => Hyperdrive::Values.request_methods.join(",") }, ["[#{info}]"]]
25
- }
24
+ run Hyperdrive::HATEOAS
26
25
  end
27
26
 
28
- hyperdrive.resources.each do |key, resource|
27
+ hyperdrive.resources.each do |key, resource|
29
28
  map resource.endpoint do
30
- run ->(env) {
31
- begin
32
- Hyperdrive::Response.new(env, resource).response
33
- rescue Hyperdrive::Errors::HTTPError => error
34
- [error.http_status_code, { 'Allow' => resource.allowed_methods.join(',') }, [error.message]]
35
- end
36
- }
29
+ use Hyperdrive::Middleware::Resource, resource
30
+ use Hyperdrive::Middleware::RequestMethod
31
+ use Hyperdrive::Middleware::SanitizeParams
32
+ use Hyperdrive::Middleware::RequiredParams
33
+ use Hyperdrive::Middleware::CORS, hyperdrive.config[:cors]
34
+ use Hyperdrive::Middleware::ContentNegotiation
35
+ run Hyperdrive::Endpoint
37
36
  end
38
37
  end
39
38
  end.to_app
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ module Hyperdrive
4
+ module Utils
5
+ def self.sanitize_keys(keys_to_keep, hash)
6
+ Hash[hash.select do |key, value|
7
+ keys_to_keep.include? key
8
+ end]
9
+ end
10
+
11
+ def self.symbolize_keys(hash)
12
+ hash.inject({}) do |result, (key, value)|
13
+ result.merge!(Hash[
14
+ case key
15
+ when String then key.to_sym
16
+ else key
17
+ end,
18
+ case value
19
+ when Hash then symbolize_keys(value)
20
+ when Array then value.map! { |v| v.is_a?(Hash) ? symbolize_keys(v) : v } ; value
21
+ else value
22
+ end
23
+ ])
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,10 +1,18 @@
1
+ # encoding: utf-8
2
+
1
3
  module Hyperdrive
2
4
  module Values
3
- def self.request_methods
5
+ module_function
6
+
7
+ def definable_request_methods
8
+ [:get, :post, :put, :patch, :delete].freeze
9
+ end
10
+
11
+ def supported_request_methods
4
12
  %w(GET HEAD OPTIONS POST PUT PATCH DELETE).freeze
5
13
  end
6
14
 
7
- def self.request_methods_symbol_map
15
+ def request_methods
8
16
  {
9
17
  get: 'GET',
10
18
  head: 'HEAD',
@@ -16,7 +24,7 @@ module Hyperdrive
16
24
  }.freeze
17
25
  end
18
26
 
19
- def self.request_methods_string_map
27
+ def http_request_methods
20
28
  {
21
29
  'GET' => :get,
22
30
  'HEAD' => :head,
@@ -27,5 +35,31 @@ module Hyperdrive
27
35
  'DELETE' => :delete
28
36
  }.freeze
29
37
  end
38
+
39
+ def default_cors_options
40
+ {
41
+ origins: '*',
42
+ allow_headers: 'Content-Type, Accept, Authorization, If-None-Match',
43
+ credentials: 'false',
44
+ expose_headers: 'Allow, Cache-Control, Content-Language, Content-Type, ETag',
45
+ max_age: 86400
46
+ }.freeze
47
+ end
48
+
49
+ def default_config
50
+ {
51
+ cors: default_cors_options,
52
+ name: 'Hyperdrive API',
53
+ description: "v#{Hyperdrive::VERSION}",
54
+ vendor: 'hyperdrive',
55
+ media_types: %w(hal+json json)
56
+ }.freeze
57
+ end
58
+
59
+ def default_headers
60
+ {
61
+ 'X-Powered-By' => "Hyperdrive (v#{Hyperdrive::VERSION})"
62
+ }
63
+ end
30
64
  end
31
65
  end
@@ -1,3 +1,3 @@
1
1
  module Hyperdrive
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
data/lib/hyperdrive.rb CHANGED
@@ -1,16 +1,30 @@
1
1
  # encoding: utf-8
2
2
 
3
- # stdlib
3
+ # prepare for hyperspace!
4
+
5
+ ## stdlib
4
6
  require 'rack'
5
- require 'linguistics'
6
- Linguistics.use(:en)
7
+ require 'rack/accept'
8
+ require 'linguistics'; Linguistics.use(:en)
9
+ require 'oj'
7
10
 
8
- # prepare for hyperspace!
11
+ ## immutable values
12
+ require 'hyperdrive/values'
13
+ require 'hyperdrive/version'
14
+
15
+ ## helpers
9
16
  require 'hyperdrive/docs'
17
+ require 'hyperdrive/utils'
18
+
19
+ ## sugary syntax and state mangagement
10
20
  require 'hyperdrive/dsl'
11
21
  require 'hyperdrive/errors'
22
+ require 'hyperdrive/param'
23
+ require 'hyperdrive/filter' # must come after param
12
24
  require 'hyperdrive/resource'
13
- require 'hyperdrive/response'
25
+
26
+ ## rack apps and middleware
27
+ require 'hyperdrive/endpoint'
28
+ require 'hyperdrive/hateoas'
29
+ require 'hyperdrive/middleware'
14
30
  require 'hyperdrive/server'
15
- require 'hyperdrive/values'
16
- require 'hyperdrive/version'
@@ -4,17 +4,20 @@ require 'spec_helper'
4
4
 
5
5
  describe Hyperdrive::Docs do
6
6
  before do
7
- sample_api
8
- @docs = Hyperdrive::Docs.new(hyperdrive.resources)
7
+ @resources = { :thing => default_resource }
8
+ @docs = Hyperdrive::Docs.new(@resources)
9
9
  end
10
10
 
11
11
  it 'generates a header with size 1 as default' do
12
12
  @docs.header('Thing Resource').must_equal "\n\n# Thing Resource\n\n"
13
13
  end
14
14
 
15
- it 'generates a header only between size 1 and 6' do
16
- proc {@docs.header('Thing Resource', 0)}.must_raise ArgumentError
17
- proc {@docs.header('Thing Resource', 8)}.must_raise ArgumentError
15
+ it 'raises an error if header size is less than 1' do
16
+ proc { @docs.header('Thing Resource', 0) }.must_raise ArgumentError
17
+ end
18
+
19
+ it 'raises an error if header size is greater than 6' do
20
+ proc { @docs.header('Thing Resource', 8) }.must_raise ArgumentError
18
21
  end
19
22
 
20
23
  it 'generates a paragraph' do
@@ -33,11 +36,14 @@ describe Hyperdrive::Docs do
33
36
  @docs.bullet('test').must_equal " - test\n"
34
37
  end
35
38
 
36
- it 'generates a bullet with nest level between 1 and 3' do
37
- proc {@docs.bullet('test', 4)}.must_raise ArgumentError
39
+ it 'raises an error if bullet indention size is less than 1' do
38
40
  proc {@docs.bullet('test', 0)}.must_raise ArgumentError
39
41
  end
40
42
 
43
+ it 'raises an error if bullet indention size is greater than 3' do
44
+ proc {@docs.bullet('test', 4)}.must_raise ArgumentError
45
+ end
46
+
41
47
  it 'generates a nested bulleted list' do
42
48
  @docs.bullet('test', 2).must_equal " - test\n"
43
49
  end
@@ -53,4 +59,4 @@ describe Hyperdrive::Docs do
53
59
  it 'outputs a string of the completed doc' do
54
60
  @docs.output.must_be_kind_of String
55
61
  end
56
- end
62
+ end
@@ -3,56 +3,46 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Hyperdrive::DSL::Resource do
6
-
7
- it "sets the name of the endpoint" do
6
+ before do
8
7
  hyperdrive do
9
8
  resource(:thing) do
10
- name "Thing Resource"
9
+ name 'Thing Resource'
10
+ description 'Thing Description'
11
+ param :name, 'Thing Name'
12
+ filter :parent_id, "Parent ID"
13
+ request(:get) do
14
+ 'ok'
15
+ end
11
16
  end
12
- end.resources[:thing].name.must_equal 'Thing Resource'
17
+ end
13
18
  end
14
19
 
15
- it "describes the endpoint" do
16
- hyperdrive do
17
- resource(:thing) do
18
- desc "Thing Description"
19
- end
20
- end.resources[:thing].desc.must_equal 'Thing Description'
20
+ after do
21
+ hyperdrive.send(:reset!)
21
22
  end
22
23
 
23
- it "registers an allowed param on a resource" do
24
- hyperdrive do
25
- resource(:thing) do
26
- param :name, "Thing's Name"
27
- end
28
- end.resources[:thing].allowed_params[:name][:desc].must_equal "Thing's Name"
24
+ it "sets the name of the resource" do
25
+ hyperdrive.resources[:thing].name.must_equal 'Thing Resource'
29
26
  end
30
27
 
31
- it "registers an allowed param on a resource" do
32
- hyperdrive do
33
- resource(:thing) do
34
- filter :parent_id, "Thing's Parent ID"
35
- end
36
- end.resources[:thing].filters[:parent_id][:desc].must_equal "Thing's Parent ID"
28
+ it "sets the description of the resource" do
29
+ hyperdrive.resources[:thing].description.must_equal 'Thing Description'
30
+ end
31
+
32
+ it "registers a param for the resource" do
33
+ hyperdrive.resources[:thing].params[:name].description.must_equal "Thing Name"
34
+ end
35
+
36
+ it "registers a filter for the resource" do
37
+ hyperdrive.resources[:thing].filters[:parent_id].description.must_equal "Parent ID"
37
38
  end
38
39
 
39
40
  it "defines how requests are handled" do
40
- hyperdrive do
41
- resource(:thing) do
42
- request(:get) do
43
- 'ok'
44
- end
45
- end
46
- end.resources[:thing].request_handlers[:get].call.must_equal 'ok'
41
+ hyperdrive.resources[:thing].request_handlers[:get]['v1'].must_be :===, Proc
47
42
  end
48
43
 
49
- it "raises an exception if request method argument is unknown" do
50
- proc do
51
- hyperdrive do
52
- resource(:thing) do
53
- request(:verb)
54
- end
55
- end
56
- end.must_raise Hyperdrive::Errors::DSL::UnknownArgument
44
+ it "throws an error if request method is unknown" do
45
+ bad_resource = -> { hyperdrive { resource(:thing) { request(:verb) } } }
46
+ bad_resource.must_raise Hyperdrive::Errors::DSL::UnknownArgument
57
47
  end
58
48
  end
@@ -0,0 +1,52 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hyperdrive::DSL do
6
+ before do
7
+ hyperdrive do
8
+ name 'Example'
9
+ description 'Example Description'
10
+ vendor 'example'
11
+ media_types %w(json)
12
+ cors({ origins: '*', allow_headers: %w(Accept), test: 'test'})
13
+ resource(:thing) {}
14
+ end
15
+ end
16
+
17
+ after do
18
+ hyperdrive.send(:reset!)
19
+ end
20
+
21
+ it "has a name" do
22
+ hyperdrive.config[:name].must_equal 'Example'
23
+ end
24
+
25
+ it "has a description" do
26
+ hyperdrive.config[:description].must_equal 'Example Description'
27
+ end
28
+
29
+ it "has a vendor" do
30
+ hyperdrive.config[:vendor].must_equal 'example'
31
+ end
32
+
33
+ it "has media types" do
34
+ hyperdrive.config[:media_types].must_equal ['json']
35
+ end
36
+
37
+ it "registers a resource" do
38
+ hyperdrive.resources[:thing].must_be_instance_of ::Hyperdrive::Resource
39
+ end
40
+
41
+ it "can configure cors options" do
42
+ hyperdrive.config[:cors][:allow_headers].must_equal ['Accept']
43
+ end
44
+
45
+ it "ensures missing options have default values" do
46
+ hyperdrive.config[:cors][:credentials].must_equal 'false'
47
+ end
48
+
49
+ it "removes unsupported cors options" do
50
+ hyperdrive.config[:cors].key?(:test).must_equal false
51
+ end
52
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hyperdrive::Endpoint do
6
+ def app
7
+ Hyperdrive::Endpoint
8
+ end
9
+
10
+ before do
11
+ sample_api
12
+ end
13
+
14
+ after do
15
+ hyperdrive.send(:reset!)
16
+ end
17
+
18
+ it "responds to requests" do
19
+ get '/', {}, default_rack_env(hyperdrive.resources[:thing])
20
+ last_response.successful?.must_equal true
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hyperdrive::Filter do
6
+ before do
7
+ @filter = default_filter
8
+ end
9
+
10
+ it "has a name" do
11
+ @filter.name.must_equal 'parent_id'
12
+ end
13
+
14
+ it "has a description" do
15
+ @filter.description.must_equal 'Parent Identifier'
16
+ end
17
+
18
+ it "returns an array of HTTP methods it's required for" do
19
+ @filter.required.must_equal %w(GET HEAD)
20
+ end
21
+
22
+ it "returns true if the param is required for the given HTTP method" do
23
+ @filter.required?('GET').must_equal true
24
+ end
25
+
26
+ it "returns false if the param is not required for the given HTTP method" do
27
+ @filter.required?('OPTIONS').must_equal false
28
+ end
29
+
30
+ it "converts itself as a hash" do
31
+ constraints = { name: 'parent_id', description: 'Parent Identifier', type: 'String', constraints: 'Required for: GET, HEAD. Must be a valid BSON Object ID.' }
32
+ @filter.to_hash.must_equal constraints
33
+ end
34
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hyperdrive::HATEOAS do
6
+ def app
7
+ Hyperdrive::HATEOAS
8
+ end
9
+
10
+ context 'without Resources' do
11
+ it "throws a Not Found error" do
12
+ ->{ get '/' }.must_raise Hyperdrive::Errors::NotFound
13
+ end
14
+ end
15
+
16
+ context 'with Resources' do
17
+ before do
18
+ sample_api
19
+ get '/', {}, default_rack_env
20
+ end
21
+
22
+ after do
23
+ hyperdrive.send(:reset!)
24
+ end
25
+
26
+ it "responds successfully" do
27
+ last_response.successful?.must_equal true
28
+ end
29
+
30
+ it "sets the content type" do
31
+ last_response.headers['Content-Type'].must_equal 'application/json'
32
+ end
33
+
34
+ it "sets the allow header" do
35
+ last_response['Allow'].must_equal 'GET, HEAD, OPTIONS'
36
+ end
37
+
38
+ it "responds with a description of all resources" do
39
+ last_response.body.must_equal %Q({"_links":{"self":{"href":"/"}},"name":"Hyperdrive API","description":"v0.0.5","vendor":"hyperdrive","resources":[{"_links":{"self":{"href":"/things"}},"id":"hyperdrive:things","name":"Thing Resource","description":"Description of Thing Resource","methods":["OPTIONS","GET","HEAD","POST","PUT","PATCH","DELETE"],"params":[{"name":"id","description":"Identifier","type":"String","constraints":"Required for: PUT, PATCH, DELETE. "},{"name":"name","description":"50 Chars or less","type":"String","constraints":"Required for: POST, PUT, PATCH. "}],"filters":[{"name":"id","description":"Resource Identifier","type":"String","constraints":" "},{"name":"parent_id","description":"Parent ID of Thing","type":"String","constraints":"Required for: GET, HEAD. "}],"media_types":[["application/vnd.hyperdrive.things.v1+hal+json","application/vnd.hyperdrive.things+hal+json","application/vnd.hyperdrive+hal+json","application/vnd.hyperdrive.things.v1+json","application/vnd.hyperdrive.things+json","application/vnd.hyperdrive+json"]]}]})
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hyperdrive::Middleware::Accept do
6
+ def app
7
+ inner_app = ->(env) { [200, {}, env['hyperdrive.accept'].values.join(", ")] }
8
+ Hyperdrive::Middleware::Accept.new(inner_app)
9
+ end
10
+
11
+ it "parses the accept header" do
12
+ get '/', {}, default_rack_env
13
+ last_response.body.must_equal 'application/vnd.hyperdrive.things+hal+json, application/json'
14
+ end
15
+ end