rspec-api 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dc5683375460c14700afc17d12e045bb5acac528
4
- data.tar.gz: c218c3741e024db99c24862a87092b5b4fe54594
3
+ metadata.gz: f66889873ee973973a2f419e950bd9f405abe7e2
4
+ data.tar.gz: bbd9a7c7f60d30eaad0e46a28de310e538933bc3
5
5
  SHA512:
6
- metadata.gz: 1d9f72802f506ee2532df49b3d5e64b0cebb0535fc802f557b74e297c4996da73c80d1ded42dc6cdd44bd4e5a745c137df8823049ee1eabce14a96313f8f4050
7
- data.tar.gz: e2d6254c1fe55f4a2ae6ce6086d92a734e5cdd3bfd905ac8090423a37278e40f71eb58c7d56ef4ad6528a183a21048506ff4033658a287fb5c1ad3f5834ee6c8
6
+ metadata.gz: 0bfaa8bfff6796c6620802a92505c7c4d89c29bfac3283123ab8d9aec942bb8805c02030434646898f119a36f817ca838b4182fcf776cfe02484af0e624a138e
7
+ data.tar.gz: 6eb04e4cd39cbf6275a1a8ce4e850996e3d27417bdd004947830ca1a11d484633fcb742636ea9c25f3d7f571b634b72efabc4f69a4dc0b4029c8dfa9a3e4ebb1
data/README.md CHANGED
@@ -1,16 +1,102 @@
1
1
  RSpec API
2
2
  =========
3
3
 
4
- More info at [http://rspec-api.github.io](http://rspec-api.github.io)
4
+ RSpec API aims to make it easy to document and test [pragmatic RESTful web APIs](http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api).
5
5
 
6
- [![Build Status](https://travis-ci.org/rspec-api/rspec-api.png)](https://travis-ci.org/rspec-api/rspec-api)
6
+ It is still under development, and you can follow its progress by checking out the code on Github.
7
+
8
+ More documentation and examples are available at [http://rspec-api.github.io](http://rspec-api.github.io)
9
+
10
+ [![Build Status](https://travis-ci.org/rspec-api/rspec-api.png?branch=master)](https://travis-ci.org/rspec-api/rspec-api)
7
11
  [![Code Climate](https://codeclimate.com/github/rspec-api/rspec-api.png)](https://codeclimate.com/github/rspec-api/rspec-api)
8
12
  [![Coverage Status](https://coveralls.io/repos/rspec-api/rspec-api/badge.png)](https://coveralls.io/r/rspec-api/rspec-api)
9
13
  [![Dependency Status](https://gemnasium.com/rspec-api/rspec-api.png)](https://gemnasium.com/rspec-api/rspec-api)
10
14
 
11
- How to test
12
- ===========
15
+ A basic example
16
+ ---------------
17
+
18
+ RSpec API can help develop and document your own web APIs.
19
+ A basic example of running RSpec API locally is provided, comprised of:
20
+
21
+ * a Ruby on Rails *app* that provides a RESTful API for concerts (in [spec/dummy](https://github.com/rspec-api/rspec-api/tree/master/spec/dummy))
22
+ * a *test suite* that verifies the expected behavior of the API (in [spec/features/local/](https://github.com/rspec-api/rspec-api/blob/master/spec/features/local/gigs/gigs_spec.rb))
23
+
24
+ Run the basic example with the following commands:
25
+
26
+ git clone https://github.com/rspec-api/rspec-api.git
27
+ cd rspec-api
28
+ bundle
29
+ bundle exec rake db:migrate
30
+ bundle exec rspec spec/features/local
31
+
32
+ And you should see all the successful promises matched by the concerts API:
33
+
34
+ Concerts
35
+ GET /concerts
36
+ by default
37
+ responds with a status code that
38
+ should be 200
39
+ responds with headers that
40
+ should include 'Content-Type': 'application/json; charset=utf-8'
41
+ should include 'Link' (for pagination)
42
+ ...
43
+ DELETE /concerts/:id
44
+ given an existing id
45
+ responds with a status code that
46
+ should be 204
47
+
48
+ Finished in 0.73864 seconds
49
+ 151 examples, 0 failures
50
+
51
+
52
+ The GitHub API example
53
+ ----------------------
54
+
55
+ RSpec API can help specify and verify promises for remote APIs.
56
+ An example of running RSpec API for a remote API is provided in [spec/features/remote](https://github.com/rspec-api/rspec-api/blob/master/spec/features/remote).
57
+ The code verifies the expected behavior of a number of endpoints of the [GitHub API](http://developer.github.com):
58
+
59
+ * Activity resources (events, feeds, notifications, starring, watching)
60
+ * Gists resources (gists, gist comments)
61
+ * Git data resources (blobs, commits)
62
+ * Repository resources (repos)
63
+
64
+ Before running the example, get a GitHub Personal Access Token:
65
+
66
+ * Browse to your [GitHub settings](https://github.com/settings/applications)
67
+ * Click on 'Create new token' under 'Personal Access Token' (name it as you want)
68
+ * Copy the generated token and store it on your machine as the environment variable called `RSPEC_API_GITHUB_TOKEN`:
69
+ * On OSX and bash, accomplish this by running the command `export RSPEC_API_GITHUB_TOKEN=` followed by your pasted key (no spaces after `=`)
70
+
71
+ Now, run the GitHub API example with the following commands:
72
+
73
+ git clone https://github.com/rspec-api/rspec-api.git
74
+ cd rspec-api
75
+ bundle
76
+ bundle exec rspec spec/features/remote
77
+
78
+ And you should see all the successful promises matched by the GitHub API:
79
+
80
+ Events
81
+ GET https://api.github.com/events
82
+ by default
83
+ responds with a status code that
84
+ should be 200
85
+ responds with headers that
86
+ should include 'Content-Type': 'application/json; charset=utf-8'
87
+ should include 'Link' (for pagination)
88
+ ...
89
+ DELETE https://api.github.com/gists/:id/star
90
+ given an existing id 0d7b597d822102148810
91
+ responds with a status code that
92
+ should be 204
93
+
94
+ Finished in 1 minute 19.74 seconds
95
+ 1237 examples, 1 failure, 4 pending
96
+
97
+ How to contribute
98
+ =================
99
+
100
+ Don’t hesitate to send me code comments, issues or pull requests through GitHub!
101
+ All feedback is appreciated. Thanks :)
13
102
 
14
- * Create a new [GitHub personal authentication token](https://github.com/settings/applications)
15
- * Store it in an environment variable called `RSPEC_API_GITHUB_TOKEN` (e.g., on OS X, type `export RSPEC_API_GITHUB_TOKEN=...`)
16
- * Run `rspec`
@@ -5,6 +5,11 @@ require 'rspec-api/dsl/request'
5
5
  module DSL
6
6
  end
7
7
 
8
+ # Just like RSpec Core’s `describe` method, `resource` generates a subclass of
9
+ # {ExampleGroup} and is available at the top-level namespace.
10
+ # The difference is that examples declared inside a `resource` block have access
11
+ # to RSpec API own methods, defined in DSL::Resource, such as `has_attribute`,
12
+ # `accepts_filter`, `get`, `post`, and so on.
8
13
  def resource(name, args = {}, &block)
9
14
  args.merge! rspec_api_dsl: :resource, rspec_api: {resource_name: name}
10
15
  describe name, args, &block
@@ -13,4 +18,7 @@ end
13
18
  RSpec.configuration.include DSL::Resource, rspec_api_dsl: :resource
14
19
  RSpec.configuration.include DSL::Route, rspec_api_dsl: :route
15
20
  RSpec.configuration.include DSL::Request, rspec_api_dsl: :request
16
- # requires rspec >= 2.14 : RSpec.configuration.backtrace_exclusion_patterns << %r{lib/rspec-api/dsl\.rb}
21
+
22
+ if RSpec::Core::Version::STRING >= '2.14'
23
+ RSpec.configuration.backtrace_exclusion_patterns << %r{lib/rspec-api/dsl\.rb}
24
+ end
@@ -1,72 +1,5 @@
1
- require 'rack/utils'
1
+ require 'rspec-api/dsl/request/request'
2
+ require 'rspec-api/dsl/request/response'
2
3
  require 'rspec-api/dsl/request/status'
3
4
  require 'rspec-api/dsl/request/headers'
4
- require 'rspec-api/dsl/request/body'
5
-
6
- module DSL
7
- module Request
8
- extend ActiveSupport::Concern
9
-
10
- def response
11
- # To be overriden by more specific modules
12
- OpenStruct.new # body: nil, status: nil, headers: {}
13
- end
14
-
15
- def response_body
16
- JSON response_body_without_callbacks
17
- rescue JSON::ParserError, JSON::GeneratorError
18
- nil
19
- end
20
-
21
- def response_headers
22
- response.headers || {}
23
- end
24
-
25
- def response_status
26
- response.status
27
- end
28
-
29
- def request_params
30
- {} # To be overriden by more specific modules
31
- end
32
-
33
- module ClassMethods
34
- def respond_with(status_symbol, &block)
35
- status_code = to_code status_symbol
36
-
37
- context 'responds with a status code that' do
38
- should_match_status_expectations(status_code)
39
- end
40
- context 'responds with headers that' do
41
- should_match_headers_expectations(status_code)
42
- end
43
- context 'responds with a body that' do
44
- should_match_body_expectations(status_code, &block)
45
- end
46
- end
47
-
48
- private
49
-
50
- def to_code(status_symbol)
51
- Rack::Utils.status_code status_symbol
52
- end
53
-
54
- def has_entity_body?(status_code)
55
- !Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status_code
56
- end
57
-
58
- def success?(status_code)
59
- has_entity_body?(status_code) && status_code < 400
60
- end
61
- end
62
-
63
- private
64
-
65
- def response_body_without_callbacks
66
- body = response.body
67
- # TODO: extract the 'a_callback' constant
68
- callback_pattern = %r[a_callback\((.*?)\)]
69
- body =~ callback_pattern ? body.match(callback_pattern)[1] : body
70
- end
71
- end
72
- end
5
+ require 'rspec-api/dsl/request/body'
@@ -1,3 +1,4 @@
1
+ require 'active_support'
1
2
  require 'rspec-api/matchers'
2
3
 
3
4
  module DSL
@@ -5,85 +6,80 @@ module DSL
5
6
  extend ActiveSupport::Concern
6
7
 
7
8
  module ClassMethods
8
- def should_match_body_expectations(status_code, &block)
9
- should_return_a_jsonp rspec_api[:callback] if rspec_api[:callback]
10
- should_return_a_json rspec_api[:array] if success? status_code
11
- should_include_attributes rspec_api.fetch(:attributes, {}) if success? status_code
12
- if rspec_api[:array]
13
- should_include_fixture_data unless rspec_api[:page]
14
- should_be_sorted_by(rspec_api[:sort]) if rspec_api[:sort]
15
- should_be_filtered_by(rspec_api[:filter]) if rspec_api[:filter]
9
+ # Creates an example group for expectations on the response body of the
10
+ # last API request and runs it to verify that it matches best practices:
11
+ # * if response is succesful and has a body
12
+ # - the body should be a JSON-marshalled Array or Hash
13
+ # - if request has a callback parameter, the body should be JSONP
14
+ # - if request has a sort parameter, the body should be sorted
15
+ # - if request has a filter parameter, the body should be filtered
16
+ # - if custom expectations are passed, they should pass
17
+ # - if some attributes are expected, the body should include them
18
+ def should_respond_with_expected_body(options = {})
19
+ context 'responds with a body that' do
20
+ it { should_be_valid_json options[:type] }
21
+ it { should_be_wrapped_by options[:callbacks] }
22
+ it { should_be_sorted_by options[:sorts] }
23
+ it { should_be_filtered_by options[:filters] }
24
+ it { should_have_attributes options[:attributes] }
16
25
  end
17
- should_satisfy_expectations_in &block if block_given?
18
- end
19
-
20
- private
21
-
22
- def should_return_a_jsonp(callback_options)
23
- it {
24
- if callback_options[:value] == request_params[callback_options[:name].to_s]
25
- expect(response_body).to be_a_jsonp(callback_options[:value])
26
- else
27
- expect(response_body).to be_a_jsonp(nil)
28
- end
29
- }
30
26
  end
27
+ end
31
28
 
32
- def should_return_a_json(is_array)
33
- it { expect(response_body).to be_a_json(is_array ? Array : Hash) }
34
- end
29
+ def should_be_valid_json(type)
30
+ expect(response).to be_valid_json_if response_is_successful?, type
31
+ end
35
32
 
36
- def should_include_fixture_data
37
- it { expect(response_body).to include_fixture_data }
38
- end
33
+ # If the request had a 'callback' query parameter, then the body should be
34
+ # JSONP, otherwise it should not. For instance if the request was
35
+ # `GET /?method=alert` and the request `accepts_callback :method`, then
36
+ # the body must be a JSON wrapped in the alert(...) callback
37
+ # The +callback+ param says how the request might have been made, e.g.
38
+ # name: 'method', value: 'alert'... however to make sure that it was
39
+ # really made, we need to check that request_params['method'] is present
40
+ # and that is actually 'alert'
41
+ def should_be_wrapped_by(callback_params_sets)
42
+ callback_params = response_is_successful? && get_request_param_for_list(callback_params_sets)
43
+ value = callback_params[:value] if callback_params
44
+ expect(response).to be_a_jsonp_if callback_params, value
45
+ end
39
46
 
40
- def should_be_sorted_by(sort_options)
41
- it {
42
- if sort_options[:parameter].to_s == request_params['sort']
43
- expect(response_body).to be_sorted_by(sort_options[:attribute], verse: :asc)
44
- elsif "-#{sort_options[:parameter].to_s}" == request_params['sort']
45
- expect(response_body).to be_sorted_by(sort_options[:attribute], verse: :desc)
46
- else
47
- expect(response_body).to be_sorted_by(nil)
48
- end
49
- }
50
- end
47
+ def should_be_sorted_by(sort_params_sets)
48
+ sort_params = response_is_successful? && get_request_param_for_list(sort_params_sets)
49
+ options = sort_params ? sort_params.slice(:by, :verse) : {}
50
+ expect(response).to be_sorted_if sort_params, options
51
+ end
51
52
 
52
- def should_be_filtered_by(filter_options)
53
- it {
54
- if json_value = request_params[filter_options[:name].to_s]
55
- expect(response_body).to be_filtered_by(json_value, filter_options)
56
- else
57
- expect(response_body).to be_filtered_by(nil)
58
- end
59
- }
53
+ def should_be_filtered_by(filter_params_sets)
54
+ # TODO: The following is just so the condition does not match if it's nil
55
+ # but this should be fixed in get_request_param_for_list
56
+ if filter_params_sets
57
+ filter_params_sets = filter_params_sets.dup
58
+ filter_params_sets.each{|x| x[:value] = request_params.fetch(x[:name].to_s, :something_nil)}
60
59
  end
60
+ filter_params = response_is_successful? && get_request_param_for_list(filter_params_sets)
61
+ value = filter_params[:value] if filter_params
62
+ options = filter_params ? filter_params.slice(:by, :comparing_with) : {}
63
+ expect(response).to be_filtered_if filter_params, value, options
64
+ end
61
65
 
62
- def should_satisfy_expectations_in(&block)
63
- it { instance_exec(response_body, @request_params, &block) }
64
- end
66
+ def should_have_attributes(attributes)
67
+ expect(response).to have_attributes_if response_is_successful?, attributes
68
+ end
65
69
 
66
- ## Attributes... might clean them up
67
- def should_include_attributes(attributes, ancestors = [], can_be_nil=false)
68
- attributes.each do |name, options = {}|
69
- should_match_attributes name, options, ancestors, can_be_nil
70
- should_include_nested_attributes name, options, ancestors
70
+ def get_request_param_for_list(params_sets)
71
+ (params_sets || []).find do |params|
72
+ conditions = []
73
+ conditions << (request_params[params[:name].to_s] == params[:value])
74
+ params.fetch(:extra_fields, {}).each do |name, value|
75
+ conditions << (request_params[name.to_s] == value)
71
76
  end
77
+ conditions.all?
72
78
  end
79
+ end
73
80
 
74
- def should_match_attributes(name, options, ancestors, can_be_nil)
75
- it {
76
- parent = ancestors.inject(response_body) do |chain, ancestor|
77
- Array.wrap(chain).map{|item| item[ancestor.to_s]}.flatten
78
- end
79
- expect(parent).to have_attribute(name, options.merge(parent_can_be_nil: can_be_nil, parent_can_be_empty: true))
80
- }
81
- end
82
-
83
- def should_include_nested_attributes(name, options, ancestors)
84
- attributes = options.fetch :attributes, {}
85
- should_include_attributes attributes, ancestors + [name], options[:can_be_nil]
86
- end
81
+ def response_is_successful?
82
+ response.status < 400 && !Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(response.status)
87
83
  end
88
84
  end
89
85
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support'
2
+ require 'rack/utils'
1
3
  require 'rspec-api/matchers'
2
4
 
3
5
  module DSL
@@ -5,20 +7,34 @@ module DSL
5
7
  extend ActiveSupport::Concern
6
8
 
7
9
  module ClassMethods
8
- def should_match_headers_expectations(status_code)
9
- should_have_json_content_type if has_entity_body? status_code
10
- should_be_paginated(rspec_api[:page][:name]) if rspec_api[:array] && rspec_api[:page]
10
+ # Creates an example group for expectations on the response headers of
11
+ # last API request and runs it to verify that it matches best practices:
12
+ # * if request has entity body, the Content-Type header should be JSON
13
+ # * if request has pages, the Link header should have a 'rel=prev' link
14
+ def should_respond_with_expected_headers(options = {})
15
+ context 'responds with headers that' do
16
+ it { should_include_content_type :json }
17
+ it { should_have_prev_page_link options[:page] }
18
+ end
11
19
  end
20
+ end
12
21
 
13
- private
22
+ private
14
23
 
15
- def should_have_json_content_type
16
- it { expect(response_headers).to have_json_content_type }
17
- end
24
+ def should_include_content_type(type)
25
+ expect(response).to include_content_type_if response_has_body?, type
26
+ end
18
27
 
19
- def should_be_paginated(page_parameter)
20
- it { expect(response_headers).to have_pagination_links(request_params[page_parameter.to_s]) }
21
- end
28
+ def should_have_prev_page_link(page)
29
+ expect(response).to have_prev_page_link_if request_is_paginated?(page)
30
+ end
31
+
32
+ def response_has_body?
33
+ !Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(response.status)
34
+ end
35
+
36
+ def request_is_paginated?(page_parameter)
37
+ request_params.key? page_parameter.to_s
22
38
  end
23
39
  end
24
40
  end
@@ -0,0 +1,14 @@
1
+ require 'active_support'
2
+
3
+ module DSL
4
+ module Request
5
+ extend ::ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ end
9
+
10
+ def request_params
11
+ {} # To be overriden by more specific modules
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,86 @@
1
+ require 'active_support'
2
+
3
+ module DSL
4
+ module Request
5
+ extend ::ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # Runs a set of expectations on the result of the last API request.
9
+ # The most basic way to call `respond_with` is with a status code:
10
+ #
11
+ # respond_with :ok
12
+ #
13
+ # This simple line will run a number of expectations, based on best
14
+ # practices that any RESTful API is expected to match:
15
+ # * the returned HTTP status will be matched against 200 OK
16
+ # * the response headers will be checked for Content-Type etc.
17
+ # * the respone body will be checked for attributes etc.
18
+ #
19
+ # Additionally, the user can specify custom expectations for the
20
+ # response by passing a block to the method:
21
+ #
22
+ # respond_with :ok do |response|
23
+ # expect(response).to be_successful?
24
+ # end
25
+ #
26
+ # In this case, after all the *implicit* expectations are run, the
27
+ # JSON-parsed response body is passed to the block to make sure that
28
+ # (in the example above), the body is a hash with a 'color' key and
29
+ # the 'green' value
30
+ def respond_with(status_symbol, &block)
31
+ should_respond_with_status status_symbol
32
+ should_respond_with_expected_headers headers_options
33
+ should_respond_with_expected_body body_options
34
+ should_match_custom_response_expectations &block if block_given?
35
+ end
36
+
37
+ private
38
+
39
+ def headers_options
40
+ {page: rspec_api.fetch(:page, {})[:name].to_s}
41
+ end
42
+
43
+ def body_options
44
+ {
45
+ type: rspec_api[:array] ? Array : Hash,
46
+ callbacks: rspec_api.fetch(:callbacks, []),
47
+ sorts: rspec_api.fetch(:sorts, []),
48
+ filters: rspec_api.fetch(:filters, []),
49
+ attributes: rspec_api.fetch(:attributes, {})
50
+ }
51
+ end
52
+
53
+ def should_match_custom_response_expectations(&block)
54
+ it { instance_exec response, @url_params, &block }
55
+ end
56
+ end
57
+
58
+ def response
59
+ # To be overriden by more specific modules
60
+ OpenStruct.new # body: nil, status: nil, headers: {}
61
+ end
62
+
63
+ def response_body
64
+ JSON response_body_without_callbacks
65
+ rescue JSON::ParserError, JSON::GeneratorError
66
+ nil
67
+ end
68
+
69
+ def response_headers
70
+ response.headers || {}
71
+ end
72
+
73
+ def response_status
74
+ response.status
75
+ end
76
+
77
+ private
78
+
79
+ def response_body_without_callbacks
80
+ body = response.body
81
+ # TODO: extract the 'a_callback' constant
82
+ callback_pattern = %r[a_callback\((.*?)\)]
83
+ body =~ callback_pattern ? body.match(callback_pattern)[1] : body
84
+ end
85
+ end
86
+ end
@@ -1,12 +1,17 @@
1
+ require 'active_support'
1
2
  require 'rspec-api/matchers'
2
3
 
3
4
  module DSL
4
5
  module Request
5
- extend ActiveSupport::Concern
6
+ extend ::ActiveSupport::Concern
6
7
 
7
8
  module ClassMethods
8
- def should_match_status_expectations(status_code)
9
- it { expect(response_status).to be_status status_code }
9
+ # Creates an example group for expectations on the HTTP status code of the
10
+ # last API request and runs it to verify that it matches +status+.
11
+ def should_respond_with_status(status)
12
+ context 'responds with a status code that' do
13
+ it { expect(response).to have_status status }
14
+ end
10
15
  end
11
16
  end
12
17
  end
@@ -22,10 +22,30 @@ module DSL
22
22
  define_action :post
23
23
  define_action :delete
24
24
 
25
- def has_attribute(name, type, options = {})
26
- parent = (@attribute_ancestors || []).inject(rspec_api) {|chain, step| chain[:attributes][step]}
27
- (parent[:attributes] ||= {})[name] = options.merge(type: type)
28
- nested_attribute(name, &Proc.new) if block_given?
25
+
26
+ def has_attribute(name, options = {}, &block)
27
+ if block_given?
28
+ # options[:type] can be symbol, hash or array
29
+ # but if you have a block we must make it a hash
30
+ options[:type] = Hash[*Array.wrap(options[:type]).map{|x| x.is_a?(Hash) ? [x.keys.first, x.values.first] : [x, {}]}.flatten] unless options[:type].is_a? Hash
31
+ # we only set the block as the new format of Object and Array
32
+ nest_attributes(options[:type], &Proc.new)
33
+ end
34
+ if @attribute_ancestors.present?
35
+ hash = @attribute_ancestors.last
36
+ hash.slice(:object, :array).each do |type, _|
37
+ (hash[type] ||= {})[name] = options
38
+ end
39
+ else
40
+ hash = (rspec_api[:attributes] ||= {})
41
+ hash[name] = options
42
+ end
43
+ end
44
+
45
+ def nest_attributes(hash, &block)
46
+ (@attribute_ancestors ||= []).push hash
47
+ yield
48
+ @attribute_ancestors.pop
29
49
  end
30
50
 
31
51
  def accepts_page(page_parameter)
@@ -33,25 +53,16 @@ module DSL
33
53
  end
34
54
 
35
55
  def accepts_sort(sort_parameter, options={})
36
- rspec_api[:sort] = {name: sort_parameter, attribute: options[:on]}
56
+ (rspec_api[:sorts] ||= []) << options.merge(name: 'sort', value: sort_parameter)
37
57
  end
38
58
 
39
- # TODO: the second 'accepts_filter' should not override the first, but add
40
59
  def accepts_filter(filter_parameter, options={})
41
- rspec_api[:filter] = options.merge(name: filter_parameter)
60
+ (rspec_api[:filters] ||= []) << options.merge(name: filter_parameter)
42
61
  end
43
62
 
44
63
  def accepts_callback(callback_parameter)
45
- rspec_api[:callback] = {name: callback_parameter, value: 'a_callback'}
46
- end
47
-
48
- private
49
-
50
- def nested_attribute(name)
51
- (@attribute_ancestors ||= []).push name
52
- yield
53
- @attribute_ancestors.pop
64
+ (rspec_api[:callbacks] ||= []) << {name: callback_parameter.to_s, value: 'a_callback'}
54
65
  end
55
66
  end
56
67
  end
57
- end
68
+ end
@@ -44,12 +44,25 @@ module DSL
44
44
  def sets_of_parameters
45
45
  [].tap do |sets_of_params|
46
46
  sets_of_params.push no_params
47
- sets_of_params.push callback_params if rspec_api[:callback]
47
+ if rspec_api[:callbacks]
48
+ rspec_api[:callbacks].each do |callback|
49
+ sets_of_params.push callback_params(callback)
50
+ end
51
+ end
48
52
  if rspec_api[:array]
49
- sets_of_params.push sort_params(verse: :asc) if rspec_api[:sort]
50
- sets_of_params.push sort_params(verse: :desc) if rspec_api[:sort]
51
- sets_of_params.push page_params if rspec_api[:page]
52
- sets_of_params.push filter_params if rspec_api[:filter]
53
+ if rspec_api[:sorts]
54
+ rspec_api[:sorts].each do |sort|
55
+ sets_of_params.push sort_params(sort)
56
+ end
57
+ end
58
+ if rspec_api[:filters]
59
+ rspec_api[:filters].each do |filter|
60
+ sets_of_params.push filter_params(filter)
61
+ end
62
+ end
63
+ if rspec_api[:page]
64
+ sets_of_params.push page_params
65
+ end
53
66
  end
54
67
  end
55
68
  end
@@ -58,10 +71,13 @@ module DSL
58
71
  {} # always send the original request without extra parameters
59
72
  end
60
73
 
61
- def sort_params(options = {})
62
- ascending = options[:verse] == :asc
63
- sort = rspec_api[:sort][:name]
64
- {sort: ascending ? "#{sort}" : "-#{sort}"}
74
+ def sort_params(sort)
75
+ {}.tap do |params|
76
+ params[sort[:name]] = sort[:value]
77
+ sort.fetch(:extra_fields, {}).each do |name, value|
78
+ params[name] = value
79
+ end
80
+ end
65
81
  end
66
82
 
67
83
  def page_params
@@ -70,15 +86,15 @@ module DSL
70
86
  end
71
87
  end
72
88
 
73
- def filter_params
89
+ def filter_params(filter)
74
90
  {}.tap do |params|
75
- params[rspec_api[:filter][:name]] = existing rspec_api[:filter][:on]
91
+ params[filter[:name]] = existing filter[:by]
76
92
  end
77
93
  end
78
94
 
79
- def callback_params
95
+ def callback_params(callback)
80
96
  {}.tap do |params|
81
- params[rspec_api[:callback][:name]] = rspec_api[:callback][:value]
97
+ params[callback[:name]] = callback[:value]
82
98
  end
83
99
  end
84
100
 
@@ -90,7 +106,7 @@ module DSL
90
106
  value = body.delete(key)
91
107
  value = value.call if value.is_a?(Proc)
92
108
  interpolated_route[":#{key}"] = value.to_s
93
- (@request_params ||= {})[key] = value
109
+ (@url_params ||= {})[key] = value
94
110
  else
95
111
  body[key] = body[key].call if body[key].is_a?(Proc)
96
112
  end
@@ -1,3 +1,3 @@
1
1
  module RspecApi
2
- VERSION = '0.2.0'
2
+ VERSION = '0.4.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-21 00:00:00.000000000 Z
11
+ date: 2013-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec-api-matchers
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rack
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -245,6 +259,8 @@ extra_rdoc_files: []
245
259
  files:
246
260
  - lib/rspec-api/dsl/request/body.rb
247
261
  - lib/rspec-api/dsl/request/headers.rb
262
+ - lib/rspec-api/dsl/request/request.rb
263
+ - lib/rspec-api/dsl/request/response.rb
248
264
  - lib/rspec-api/dsl/request/status.rb
249
265
  - lib/rspec-api/dsl/request.rb
250
266
  - lib/rspec-api/dsl/resource.rb
@@ -257,17 +273,6 @@ files:
257
273
  - lib/rspec-api/http/remote/resource.rb
258
274
  - lib/rspec-api/http/remote/route.rb
259
275
  - lib/rspec-api/http/remote.rb
260
- - lib/rspec-api/matchers/attributes.rb
261
- - lib/rspec-api/matchers/body.rb
262
- - lib/rspec-api/matchers/content_type.rb
263
- - lib/rspec-api/matchers/fields.rb
264
- - lib/rspec-api/matchers/filter.rb
265
- - lib/rspec-api/matchers/fixtures.rb
266
- - lib/rspec-api/matchers/jsonp.rb
267
- - lib/rspec-api/matchers/page.rb
268
- - lib/rspec-api/matchers/sort.rb
269
- - lib/rspec-api/matchers/status.rb
270
- - lib/rspec-api/matchers.rb
271
276
  - lib/rspec-api/version.rb
272
277
  - MIT-LICENSE
273
278
  - README.md
@@ -291,7 +296,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
291
296
  version: 1.3.6
292
297
  requirements: []
293
298
  rubyforge_project:
294
- rubygems_version: 2.1.5
299
+ rubygems_version: 2.1.10
295
300
  signing_key:
296
301
  specification_version: 4
297
302
  summary: Methods to write more compact and meaningful, auto-documented specs for web
@@ -1,10 +0,0 @@
1
- require_relative 'matchers/status'
2
- require_relative 'matchers/body'
3
- require_relative 'matchers/content_type'
4
- require_relative 'matchers/fields'
5
- require_relative 'matchers/attributes'
6
- require_relative 'matchers/fixtures'
7
- require_relative 'matchers/page'
8
- require_relative 'matchers/sort'
9
- require_relative 'matchers/filter'
10
- require_relative 'matchers/jsonp'
@@ -1,33 +0,0 @@
1
- RSpec::Matchers.define :have_attribute do |name, options = {}|
2
- name, can_be_nil, type = name.to_s, options[:can_be_nil], options[:type]
3
-
4
- match do |json|
5
- Array.wrap(json).all? do |item|
6
- if (options[:parent_can_be_nil] and item.nil?) || (options[:parent_can_be_empty] and item.empty?)
7
- true
8
- elsif can_be_nil
9
- item.key?(name)
10
- else
11
- matches_type?(item[name], type)
12
- end
13
- end
14
- end
15
-
16
- description do # TODO: add parent name
17
- type = "#{options[:type]}#{' or nil' if can_be_nil}"
18
- %Q(include the field #{name.to_json} of type #{type})
19
- end
20
-
21
- failure_message_for_should do |json|
22
- %Q(should #{description}, but is #{json})
23
- end
24
- end
25
-
26
- def matches_type?(value, type)
27
- case type
28
- when :url then value =~ URI::regexp
29
- when :timestamp then DateTime.iso8601 value rescue false
30
- when :boolean then [TrueClass, FalseClass].include? value.class
31
- else value.is_a? type.to_s.classify.constantize
32
- end
33
- end
@@ -1,13 +0,0 @@
1
- RSpec::Matchers.define :be_a_json do |expected_type|
2
- match do |json|
3
- json.is_a? expected_type
4
- end
5
-
6
- description do
7
- "be a JSON #{expected_type}"
8
- end
9
-
10
- failure_message_for_should do |json|
11
- %Q(should #{description}, but is #{json})
12
- end
13
- end
@@ -1,13 +0,0 @@
1
- RSpec::Matchers.define :have_json_content_type do
2
- match do |response|
3
- response_headers['Content-Type'] == 'application/json; charset=utf-8'
4
- end
5
-
6
- description do
7
- "include 'Content-Type': 'application/json; charset=utf-8'"
8
- end
9
-
10
- failure_message_for_should do |item|
11
- %Q(should #{description}, but are #{response_headers})
12
- end
13
- end
@@ -1,32 +0,0 @@
1
- RSpec::Matchers.define :have_field do |key, options = {}|
2
- value = options[:value]
3
-
4
- match do |item|
5
- item[key.to_s] == value
6
- end
7
-
8
- description do
9
- %Q(have the value #{value.to_json} in the field #{key})
10
- end
11
-
12
- failure_message_for_should do |item|
13
- %Q(should #{description}, but got #{item})
14
- end
15
- end
16
-
17
- RSpec::Matchers.define :have_fields do |key, options = {}|
18
- value = options[:value]
19
- after = options[:after]
20
-
21
- match do |items|
22
- items.all?{|item| item[key.to_s].send(after) == value }
23
- end
24
-
25
- description do
26
- %Q(have the value #{value.to_json} in the field #{key} after #{after})
27
- end
28
-
29
- failure_message_for_should do |items|
30
- %Q(should #{description}, but got #{items})
31
- end
32
- end
@@ -1,28 +0,0 @@
1
- RSpec::Matchers.define :be_filtered_by do |json_value, options = {}|
2
- filtered_attribute = options[:on]
3
- compare = options[:comparing_with] || Proc.new{|x,y| x == y}
4
-
5
- match do |items|
6
- if filtered_attribute.nil?
7
- true
8
- else
9
- items.all? do |item|
10
- # TODO: Don't always use string
11
- compare.call json_value, item[filtered_attribute.to_s].to_s
12
- end
13
- end
14
- end
15
-
16
- description do
17
- if filtered_attribute.nil?
18
- %Q(not be filtered by any specific attribute)
19
- else
20
- # TODO: Change description based on operator
21
- %Q(be filtered by #{filtered_attribute.to_json} => #{json_value})
22
- end
23
- end
24
-
25
- failure_message_for_should do |items|
26
- %Q(should #{description}, but is #{items})
27
- end
28
- end
@@ -1,16 +0,0 @@
1
- RSpec::Matchers.define :include_fixture_data do
2
- match do |body|
3
- if body.empty? #&& did_not_declare_fixtures
4
- pending 'Make your tests more meaningful by declaring fixtures!'
5
- end
6
- !body.empty?
7
- end
8
-
9
- description do
10
- %Q(have some values from the fixtures)
11
- end
12
-
13
- failure_message_for_should do |response|
14
- %Q(should #{description}, but got an empty array)
15
- end
16
- end
@@ -1,17 +0,0 @@
1
- RSpec::Matchers.define :be_a_jsonp do |callback_name|
2
- match do |response_body|
3
- if callback_name.nil?
4
- true
5
- else
6
- response.body =~ %r[^#{callback_name}\((.*?)\)$]
7
- end
8
- end
9
-
10
- description do
11
- %Q(be a JSONP callback)
12
- end
13
-
14
- failure_message_for_should do |response_body|
15
- %Q(should #{description}, but is #{response_body})
16
- end
17
- end
@@ -1,19 +0,0 @@
1
- RSpec::Matchers.define :have_pagination_links do |page|
2
- match do |response_headers|
3
- if page.nil?
4
- true
5
- else
6
- links = response_headers['Link'] || '' # see http://git.io/CUz3-Q
7
- rels = links.split(',').map{|link| link[/<.+?>; rel="(.*)"$/, 1]}
8
- rels.sort == ['first', 'prev']
9
- end
10
- end
11
-
12
- description do
13
- %Q(include 'Link' (for pagination))
14
- end
15
-
16
- failure_message_for_should do |response_headers|
17
- %Q(should #{description}, but are #{response_headers})
18
- end
19
- end
@@ -1,25 +0,0 @@
1
- RSpec::Matchers.define :be_sorted_by do |sorting_field, options = {}|
2
- match do |items|
3
- if sorting_field.nil?
4
- true
5
- else
6
- values = items.map{|item| item[sorting_field.to_s]}
7
- values.reverse! if options[:verse] == :desc
8
- values == values.sort
9
- end
10
- end
11
-
12
- description do
13
- # NOTE: Since `accepts_sort random: nil` is acceptable, this description
14
- # should say "you should not expect any sorting by any specific field"
15
- if sorting_field.nil?
16
- %Q(not be sorted by any specific attribute)
17
- else
18
- %Q(be sorted by #{sorting_field.to_json} #{options[:verse]})
19
- end
20
- end
21
-
22
- failure_message_for_should do |items|
23
- %Q(should #{description}, but is #{items})
24
- end
25
- end
@@ -1,13 +0,0 @@
1
- RSpec::Matchers.define :be_status do |expected|
2
- match do |actual|
3
- actual == expected
4
- end
5
-
6
- description do
7
- %Q(be #{expected})
8
- end
9
-
10
- failure_message_for_should do |actual|
11
- %Q(should #{description}, but is #{actual})
12
- end
13
- end