rspec-api 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/rspec-api/active_resource.rb +85 -0
- data/lib/rspec-api/dsl.rb +20 -8
- data/lib/rspec-api/dsl/get.rb +93 -0
- data/lib/rspec-api/dsl/request.rb +62 -0
- data/lib/rspec-api/dsl/request/body.rb +78 -0
- data/lib/rspec-api/dsl/request/headers.rb +24 -0
- data/lib/rspec-api/dsl/request/status.rb +13 -0
- data/lib/rspec-api/dsl/resource.rb +47 -0
- data/lib/rspec-api/http/rack_test.rb +40 -0
- data/lib/rspec-api/matchers.rb +9 -0
- data/lib/rspec-api/matchers/attributes.rb +33 -0
- data/lib/rspec-api/matchers/body.rb +13 -0
- data/lib/rspec-api/matchers/content_type.rb +13 -0
- data/lib/rspec-api/matchers/fields.rb +32 -0
- data/lib/rspec-api/matchers/filter.rb +21 -0
- data/lib/rspec-api/matchers/fixtures.rb +16 -0
- data/lib/rspec-api/matchers/page.rb +20 -0
- data/lib/rspec-api/matchers/sort.rb +25 -0
- data/lib/rspec-api/matchers/status.rb +13 -0
- data/lib/rspec-api/version.rb +1 -1
- metadata +22 -9
- data/lib/rspec-api/accept_helper.rb +0 -61
- data/lib/rspec-api/api_helper.rb +0 -66
- data/lib/rspec-api/attributes_helper.rb +0 -53
- data/lib/rspec-api/description_helper.rb +0 -55
- data/lib/rspec-api/instances_helper.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5408552b4ac63e6bdbbd372c3c900c963f14ffbe
|
4
|
+
data.tar.gz: 53fa90f938352139f11677ae6b414fc32535ea47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b86970f33d662320ac5189e82aa1117f1cf44d0ffe583a30c0cae3d4440181769e96cff3cfa7766fd75126d5227423931ffcbc0e3e9801bf123d776bbf53c58
|
7
|
+
data.tar.gz: dddc04c7ced68aa44e82856ce4b73187af7671cc7b6be7b33f1a72ff84f264895356e85542d6eaa3cf6a9974c18b62c2d30c72f8545ef4585b6a4ddd87860429
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module DSL
|
4
|
+
module ActiveResource
|
5
|
+
module Route
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def send_request(verb, route, body)
|
9
|
+
conn = Faraday.new 'https://api.github.com/' do |c|
|
10
|
+
c.use Faraday::Response::Logger, Logger.new('log/faraday.log')
|
11
|
+
c.use Faraday::Adapter::NetHttp
|
12
|
+
end
|
13
|
+
|
14
|
+
conn.headers[:user_agent] = 'RSpec API for Github'
|
15
|
+
conn.authorization *authorization.flatten
|
16
|
+
|
17
|
+
@last_response = conn.send verb, route, (body.to_json if body.present?)
|
18
|
+
end
|
19
|
+
|
20
|
+
def authorization
|
21
|
+
# TODO: Any other way to access metadata in a before(:all) ?
|
22
|
+
self.class.metadata[:rspec_api][:authorization]
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
|
27
|
+
def setup_fixtures
|
28
|
+
# nothing to do for now...
|
29
|
+
end
|
30
|
+
|
31
|
+
def existing(field)
|
32
|
+
case field
|
33
|
+
when :user then 'claudiob'
|
34
|
+
when :gist_id then '0d7b597d822102148810'
|
35
|
+
when :id then '921225'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def unknown(field)
|
40
|
+
case field
|
41
|
+
when :user then 'not-a-valid-user'
|
42
|
+
when :gist_id then 'not-a-valid-gist-id'
|
43
|
+
when :id then 'not-a-valid-id'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
module DSL
|
53
|
+
module ActiveResource
|
54
|
+
module Resource
|
55
|
+
extend ActiveSupport::Concern
|
56
|
+
|
57
|
+
module ClassMethods
|
58
|
+
def authorize_with(options = {})
|
59
|
+
rspec_api[:authorization] = options
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
module DSL
|
68
|
+
module ActiveResource
|
69
|
+
module Request
|
70
|
+
extend ActiveSupport::Concern
|
71
|
+
|
72
|
+
def response
|
73
|
+
@last_response
|
74
|
+
end
|
75
|
+
|
76
|
+
def request_params
|
77
|
+
# TO DO
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
RSpec.configuration.include DSL::ActiveResource::Resource, rspec_api_dsl: :resource
|
84
|
+
RSpec.configuration.include DSL::ActiveResource::Request, rspec_api_dsl: :request
|
85
|
+
RSpec.configuration.include DSL::ActiveResource::Route, rspec_api_dsl: :route
|
data/lib/rspec-api/dsl.rb
CHANGED
@@ -1,8 +1,20 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
require 'rspec-api/dsl/resource'
|
2
|
+
require 'rspec-api/dsl/get'
|
3
|
+
require 'rspec-api/dsl/request'
|
4
|
+
|
5
|
+
module DSL
|
6
|
+
end
|
7
|
+
|
8
|
+
def resource(name, args = {}, &block)
|
9
|
+
args.merge! rspec_api_dsl: :resource, rspec_api: {resource_name: name}
|
10
|
+
describe name, args, &block
|
11
|
+
end
|
12
|
+
|
13
|
+
def rspec_api
|
14
|
+
metadata[:rspec_api]
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.configuration.include DSL::Resource, rspec_api_dsl: :resource
|
18
|
+
RSpec.configuration.include DSL::Route, rspec_api_dsl: :route
|
19
|
+
RSpec.configuration.include DSL::Request, rspec_api_dsl: :request
|
20
|
+
# requires rspec >= 2.14 : RSpec.configuration.backtrace_exclusion_patterns << %r{lib/rspec-api/dsl\.rb}
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module DSL
|
2
|
+
module Route
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def send_request(verb, route, body)
|
6
|
+
# To be overriden by more specific modules
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def request(*args, &block)
|
11
|
+
text, values = parse_request_arguments args
|
12
|
+
extra_parameters.each do |params|
|
13
|
+
request_with_extra_params text, values.merge(params), &block
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup_fixtures
|
18
|
+
# To be overriden by more specific modules
|
19
|
+
end
|
20
|
+
|
21
|
+
def existing(field)
|
22
|
+
# To be overriden by more specific modules
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def request_with_extra_params(text, values = {}, &block)
|
28
|
+
context request_description(text, values), rspec_api_dsl: :request do
|
29
|
+
# NOTE: Having setup_fixtures inside the context sets up different
|
30
|
+
# fixtures for each `request` inside the same `get`. This might be
|
31
|
+
# a little slower on the DB, but ensures that two `request`s do not
|
32
|
+
# conflict. For instance, if you have two `request` inside a `delete`
|
33
|
+
# and the first deletes an instance, the second `request` is no
|
34
|
+
# affected.
|
35
|
+
setup_fixtures
|
36
|
+
setup_request rspec_api[:verb], rspec_api[:route], values
|
37
|
+
instance_eval(&block) if block_given?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def extra_parameters
|
42
|
+
[].tap do |optional_params|
|
43
|
+
optional_params << {} # default: no extra params
|
44
|
+
if rspec_api[:array]
|
45
|
+
if rspec_api[:sort]
|
46
|
+
optional_params << {sort: rspec_api[:sort][:parameter]}
|
47
|
+
optional_params << {sort: "-#{rspec_api[:sort][:parameter]}"}
|
48
|
+
end
|
49
|
+
if rspec_api[:page]
|
50
|
+
optional_params << {page: 2}
|
51
|
+
end
|
52
|
+
if rspec_api[:filter]
|
53
|
+
optional_params << {rspec_api[:filter][:parameter] => existing(rspec_api[:filter][:attribute])}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def setup_request(verb, route, values)
|
60
|
+
request = Proc.new {
|
61
|
+
interpolated_route, body = route.dup, values.dup
|
62
|
+
body.keys.each do |key|
|
63
|
+
if interpolated_route[":#{key}"]
|
64
|
+
value = body.delete(key)
|
65
|
+
value = value.call if value.is_a?(Proc)
|
66
|
+
interpolated_route[":#{key}"] = value.to_s
|
67
|
+
(@request_params ||= {})[key] = value
|
68
|
+
else
|
69
|
+
body[key] = body[key].call if body[key].is_a?(Proc)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
[interpolated_route, body]
|
73
|
+
}
|
74
|
+
before(:all) { send_request verb, *instance_eval(&request) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def request_description(text, values)
|
78
|
+
if values.empty?
|
79
|
+
'by default'
|
80
|
+
else
|
81
|
+
text = "with" unless text.present?
|
82
|
+
"#{text} #{values.map{|k,v| "#{k}#{" #{v}" unless v.is_a?(Proc)}"}.to_sentence}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_request_arguments(args)
|
87
|
+
text = args.first.is_a?(String) ? args.first : ''
|
88
|
+
values = args.first.is_a?(String) ? args.second : args.first
|
89
|
+
[text, values || {}] # NOTE: In Ruby 2.0 we could write values.to_h
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rspec-api/dsl/request/status'
|
2
|
+
require 'rspec-api/dsl/request/headers'
|
3
|
+
require 'rspec-api/dsl/request/body'
|
4
|
+
|
5
|
+
module DSL
|
6
|
+
module Request
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
def response
|
10
|
+
# To be overriden by more specific modules
|
11
|
+
OpenStruct.new # body: nil, status: nil, headers: {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def response_body
|
15
|
+
JSON response.body
|
16
|
+
rescue JSON::ParserError, JSON::GeneratorError
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def response_headers
|
21
|
+
response.headers || {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def response_status
|
25
|
+
response.status
|
26
|
+
end
|
27
|
+
|
28
|
+
def request_params
|
29
|
+
{} # To be overriden by more specific modules
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
def respond_with(status_symbol, &block)
|
34
|
+
status_code = to_code status_symbol
|
35
|
+
|
36
|
+
context 'responds with a status code that' do
|
37
|
+
should_match_status_expectations(status_code)
|
38
|
+
end
|
39
|
+
context 'responds with headers that' do
|
40
|
+
should_match_headers_expectations(status_code)
|
41
|
+
end
|
42
|
+
context 'responds with a body that' do
|
43
|
+
should_match_body_expectations(status_code, &block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def to_code(status_symbol)
|
50
|
+
Rack::Utils.status_code status_symbol
|
51
|
+
end
|
52
|
+
|
53
|
+
def has_entity_body?(status_code)
|
54
|
+
Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.exclude? status_code
|
55
|
+
end
|
56
|
+
|
57
|
+
def success?(status_code)
|
58
|
+
has_entity_body?(status_code) && status_code < 400
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'rspec-api/matchers'
|
2
|
+
|
3
|
+
module DSL
|
4
|
+
module Request
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def should_match_body_expectations(status_code, &block)
|
9
|
+
should_return_a_json rspec_api[:array] if success? status_code
|
10
|
+
should_include_attributes rspec_api.fetch(:attributes, {}) if success? status_code
|
11
|
+
if rspec_api[:array]
|
12
|
+
should_include_fixture_data unless rspec_api[:page]
|
13
|
+
should_be_sorted_by(rspec_api[:sort]) if rspec_api[:sort]
|
14
|
+
should_be_filtered_by(rspec_api[:filter]) if rspec_api[:filter]
|
15
|
+
end
|
16
|
+
should_satisfy_expectations_in &block if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def should_return_a_json(is_array)
|
22
|
+
it { expect(response_body).to be_a_json(is_array ? Array : Hash) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def should_include_fixture_data
|
26
|
+
it { expect(response_body).to include_fixture_data }
|
27
|
+
end
|
28
|
+
|
29
|
+
def should_be_sorted_by(sort_options)
|
30
|
+
it {
|
31
|
+
if sort_options[:parameter].to_s == request_params['sort']
|
32
|
+
expect(response_body).to be_sorted_by(sort_options[:attribute], verse: :asc)
|
33
|
+
elsif "-#{sort_options[:parameter].to_s}" == request_params['sort']
|
34
|
+
expect(response_body).to be_sorted_by(sort_options[:attribute], verse: :desc)
|
35
|
+
else
|
36
|
+
expect(response_body).to be_sorted_by(nil)
|
37
|
+
end
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def should_be_filtered_by(filter_options)
|
42
|
+
it {
|
43
|
+
if json_value = request_params[filter_options[:parameter].to_s]
|
44
|
+
expect(response_body).to be_filtered_by(filter_options[:attribute], json_value)
|
45
|
+
else
|
46
|
+
expect(response_body).to be_filtered_by(nil)
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def should_satisfy_expectations_in(&block)
|
52
|
+
it { instance_exec(response_body, @request_params, &block) }
|
53
|
+
end
|
54
|
+
|
55
|
+
## Attributes... might clean them up
|
56
|
+
def should_include_attributes(attributes, ancestors = [], can_be_nil=false)
|
57
|
+
attributes.each do |name, options = {}|
|
58
|
+
should_match_attributes name, options, ancestors, can_be_nil
|
59
|
+
should_include_nested_attributes name, options, ancestors
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def should_match_attributes(name, options, ancestors, can_be_nil)
|
64
|
+
it {
|
65
|
+
parent = ancestors.inject(response_body) do |chain, ancestor|
|
66
|
+
Array.wrap(chain).map{|item| item[ancestor.to_s]}.flatten
|
67
|
+
end
|
68
|
+
expect(parent).to have_attribute(name, options.merge(parent_can_be_nil: can_be_nil, parent_can_be_empty: true))
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def should_include_nested_attributes(name, options, ancestors)
|
73
|
+
attributes = options.fetch :attributes, {}
|
74
|
+
should_include_attributes attributes, ancestors + [name], options[:can_be_nil]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rspec-api/matchers'
|
2
|
+
|
3
|
+
module DSL
|
4
|
+
module Request
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
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]) if rspec_api[:array] && rspec_api[:page]
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def should_have_json_content_type
|
16
|
+
it { expect(response_headers).to have_json_content_type }
|
17
|
+
end
|
18
|
+
|
19
|
+
def should_be_paginated(page_parameter)
|
20
|
+
it { expect(response_headers).to have_pagination_links(request_params[page_parameter.to_s]) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module DSL
|
2
|
+
module Resource
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def self.define_action(verb)
|
7
|
+
define_method verb do |route, args = {}, &block|
|
8
|
+
rspec_api.merge! array: args.delete(:array), verb: verb, route: route
|
9
|
+
args.merge! rspec_api_dsl: :route
|
10
|
+
describe("#{verb.upcase} #{route}", args, &block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
define_action :get
|
15
|
+
define_action :put
|
16
|
+
define_action :post
|
17
|
+
define_action :delete
|
18
|
+
|
19
|
+
def has_attribute(name, type, options = {})
|
20
|
+
parent = (@attribute_ancestors || []).inject(rspec_api) {|chain, step| chain[:attributes][step]}
|
21
|
+
(parent[:attributes] ||= {})[name] = options.merge(type: type)
|
22
|
+
nested_attribute(name, &Proc.new) if block_given?
|
23
|
+
end
|
24
|
+
|
25
|
+
def accepts_page(page_parameter)
|
26
|
+
rspec_api[:page] = page_parameter
|
27
|
+
end
|
28
|
+
|
29
|
+
def accepts_sort(sort_parameter, options={})
|
30
|
+
rspec_api[:sort] = {parameter: sort_parameter, attribute: options[:on]}
|
31
|
+
end
|
32
|
+
|
33
|
+
# TODO: the second 'accepts_filter' should not override the first, but add
|
34
|
+
def accepts_filter(filter_parameter, options={})
|
35
|
+
rspec_api[:filter] = {parameter: filter_parameter, attribute: options[:on]}
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def nested_attribute(name)
|
41
|
+
(@attribute_ancestors ||= []).push name
|
42
|
+
yield
|
43
|
+
@attribute_ancestors.pop
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rack/test'
|
2
|
+
|
3
|
+
def app
|
4
|
+
Rails.application
|
5
|
+
end
|
6
|
+
|
7
|
+
module DSL
|
8
|
+
module RackTest
|
9
|
+
module Route
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
def send_request(verb, route, body)
|
13
|
+
header 'Accept', 'application/json'
|
14
|
+
send verb, route, body
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
module DSL
|
22
|
+
module RackTest
|
23
|
+
module Request
|
24
|
+
extend ActiveSupport::Concern
|
25
|
+
|
26
|
+
def response
|
27
|
+
last_response
|
28
|
+
end
|
29
|
+
|
30
|
+
def request_params
|
31
|
+
last_request.params
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
RSpec.configuration.include Rack::Test::Methods, rspec_api_dsl: :route
|
39
|
+
RSpec.configuration.include DSL::RackTest::Route, rspec_api_dsl: :route
|
40
|
+
RSpec.configuration.include DSL::RackTest::Request, rspec_api_dsl: :request
|
@@ -0,0 +1,9 @@
|
|
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'
|
@@ -0,0 +1,33 @@
|
|
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
|
@@ -0,0 +1,13 @@
|
|
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
|
@@ -0,0 +1,13 @@
|
|
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
|
@@ -0,0 +1,32 @@
|
|
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
|
@@ -0,0 +1,21 @@
|
|
1
|
+
RSpec::Matchers.define :be_filtered_by do |filtered_attribute, json_value=nil|
|
2
|
+
match do |items|
|
3
|
+
if filtered_attribute.nil?
|
4
|
+
true
|
5
|
+
else
|
6
|
+
items.all?{|item| item[filtered_attribute.to_s].to_s == json_value}
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
description do
|
11
|
+
if filtered_attribute.nil?
|
12
|
+
%Q(not be filtered by any specific attribute)
|
13
|
+
else
|
14
|
+
%Q(be filtered by #{filtered_attribute.to_json} => #{json_value})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
failure_message_for_should do |items|
|
19
|
+
%Q(should #{description}, but is #{items})
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
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
|
@@ -0,0 +1,20 @@
|
|
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'] || '' # https://github.com/lostisland/faraday/pull/306
|
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
|
20
|
+
|
@@ -0,0 +1,25 @@
|
|
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
|
data/lib/rspec-api/version.rb
CHANGED
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.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-
|
11
|
+
date: 2013-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: faraday
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - '>='
|
@@ -31,16 +31,29 @@ executables: []
|
|
31
31
|
extensions: []
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
|
-
- lib/rspec-api/
|
35
|
-
- lib/rspec-api/
|
36
|
-
- lib/rspec-api/
|
37
|
-
- lib/rspec-api/
|
34
|
+
- lib/rspec-api/active_resource.rb
|
35
|
+
- lib/rspec-api/dsl/get.rb
|
36
|
+
- lib/rspec-api/dsl/request/body.rb
|
37
|
+
- lib/rspec-api/dsl/request/headers.rb
|
38
|
+
- lib/rspec-api/dsl/request/status.rb
|
39
|
+
- lib/rspec-api/dsl/request.rb
|
40
|
+
- lib/rspec-api/dsl/resource.rb
|
38
41
|
- lib/rspec-api/dsl.rb
|
39
|
-
- lib/rspec-api/
|
42
|
+
- lib/rspec-api/http/rack_test.rb
|
43
|
+
- lib/rspec-api/matchers/attributes.rb
|
44
|
+
- lib/rspec-api/matchers/body.rb
|
45
|
+
- lib/rspec-api/matchers/content_type.rb
|
46
|
+
- lib/rspec-api/matchers/fields.rb
|
47
|
+
- lib/rspec-api/matchers/filter.rb
|
48
|
+
- lib/rspec-api/matchers/fixtures.rb
|
49
|
+
- lib/rspec-api/matchers/page.rb
|
50
|
+
- lib/rspec-api/matchers/sort.rb
|
51
|
+
- lib/rspec-api/matchers/status.rb
|
52
|
+
- lib/rspec-api/matchers.rb
|
40
53
|
- lib/rspec-api/version.rb
|
41
54
|
- MIT-LICENSE
|
42
55
|
- README.md
|
43
|
-
homepage: https://github.com/
|
56
|
+
homepage: https://github.com/rspec-api/rspec-api
|
44
57
|
licenses:
|
45
58
|
- MIT
|
46
59
|
metadata: {}
|
@@ -1,61 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
def accepts(options = {}, &block)
|
4
|
-
parameters = block_given? ? options.merge(block: block) : options
|
5
|
-
(metadata[:query_parameters] ||= []).push parameters
|
6
|
-
end
|
7
|
-
|
8
|
-
RSpec::Matchers.define :be_sorted_by do |attribute|
|
9
|
-
match do |items|
|
10
|
-
values = items.map{|item| item[attribute.to_s]}
|
11
|
-
values.reverse! if example.metadata[:request_params][:sort][0] == '-'
|
12
|
-
values == values.sort
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def assert_pagination_links
|
17
|
-
expect(response_headers).to have_key 'Link'
|
18
|
-
links = response_headers['Link'].split(',')
|
19
|
-
rels = links.map{|link| link[/<.+?>; rel="(.*)"$/, 1]}
|
20
|
-
expect(rels).to match_array ['last', 'next']
|
21
|
-
end
|
22
|
-
|
23
|
-
def query_parameters_requests
|
24
|
-
metadata.fetch(:query_parameters, []).map do |params|
|
25
|
-
if params.has_key? :filter
|
26
|
-
filter_parameters_requests params
|
27
|
-
elsif params.has_key? :sort
|
28
|
-
sort_parameters_requests params
|
29
|
-
elsif params.has_key? :page
|
30
|
-
page_parameters_requests params
|
31
|
-
end
|
32
|
-
end.flatten
|
33
|
-
end
|
34
|
-
|
35
|
-
def filter_parameters_requests(params)
|
36
|
-
params.except(:given, :block).tap do |req|
|
37
|
-
value = params.fetch :given, apply(:as_json, to: existing(params[:on]))
|
38
|
-
req[:description] = " filtered by #{params[:filter]}"
|
39
|
-
req[:request_params] = {params[:filter] => value}
|
40
|
-
req[:block] = params[:block]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def sort_parameters_requests(params)
|
45
|
-
[true, false].map do |ascending|
|
46
|
-
params.except(:block).tap do |req|
|
47
|
-
req[:description] = " sorted by #{params[:sort]} #{ascending ? '↑' : '↓'}"
|
48
|
-
req[:request_params] = {sort: "#{ascending ? '' : '-'}#{params[:sort]}"}
|
49
|
-
req[:block] = params[:block]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def page_parameters_requests(params)
|
55
|
-
{}.tap do |req|
|
56
|
-
req[:description] = " paginated by #{params[:page]}"
|
57
|
-
(req[:request_params] = {})[params[:page]] = 1
|
58
|
-
req[:min_pages] = 2
|
59
|
-
req[:block] = -> _ { assert_pagination_links }
|
60
|
-
end
|
61
|
-
end
|
data/lib/rspec-api/api_helper.rb
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
shared_context 'accept_json', accepts: :json do
|
2
|
-
header 'Accept', 'application/json'
|
3
|
-
end
|
4
|
-
|
5
|
-
shared_context 'return_json', returns: :json do
|
6
|
-
after { expect(json_response?) }
|
7
|
-
end
|
8
|
-
|
9
|
-
def request(description, request_params = {})
|
10
|
-
default_request = {}
|
11
|
-
example_requests = [default_request]
|
12
|
-
example_requests.concat query_parameters_requests if metadata[:array]
|
13
|
-
|
14
|
-
example_requests.each do |request_metadata|
|
15
|
-
(request_metadata[:description] ||= '').prepend description
|
16
|
-
(request_metadata[:request_params] ||= {}).merge! request_params
|
17
|
-
metadata.merge! request_metadata
|
18
|
-
yield if block_given?
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def request_params
|
23
|
-
example.metadata[:request_params]
|
24
|
-
end
|
25
|
-
|
26
|
-
def respond_with(expected_status, &block)
|
27
|
-
description = metadata[:description]
|
28
|
-
example description do
|
29
|
-
setup_instances
|
30
|
-
evaluate_request_params!
|
31
|
-
do_request request_params.dup
|
32
|
-
assert_response expected_status, &example.metadata.fetch(:block, block)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def assert_response(expected_status, &block)
|
37
|
-
assert_status expected_status
|
38
|
-
if block_given? || success? && returns_content?
|
39
|
-
json = JSON response_body
|
40
|
-
assert_attributes json if success?
|
41
|
-
assert_instances json
|
42
|
-
instance_exec(json, &block) if block_given?
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def evaluate_request_params!
|
47
|
-
request_params.each do |name, value|
|
48
|
-
request_params[name] = instance_exec(&value) if value.is_a? Proc
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def success?
|
53
|
-
status < 400
|
54
|
-
end
|
55
|
-
|
56
|
-
def returns_content?
|
57
|
-
[100, 101, 102, 204, 205, 304].exclude? status
|
58
|
-
end
|
59
|
-
|
60
|
-
def assert_status(expected_status)
|
61
|
-
expect(status).to be Rack::Utils.status_code(expected_status)
|
62
|
-
end
|
63
|
-
|
64
|
-
def json_response?
|
65
|
-
response_headers['Content-Type'] == 'application/json; charset=utf-8'
|
66
|
-
end
|
@@ -1,53 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'rspec_api_documentation/dsl'
|
3
|
-
|
4
|
-
def has_attribute(name, type, options = {})
|
5
|
-
(metadata[:attributes] ||= {})[name] = options.merge(type: type)
|
6
|
-
end
|
7
|
-
|
8
|
-
def assert_attributes(json)
|
9
|
-
expect(json).to be_a (example.metadata[:array] ? Array : Hash)
|
10
|
-
example.metadata[:attributes].each do |name, options|
|
11
|
-
values = Array.wrap(json).map{|item| item[name.to_s]}
|
12
|
-
assert_attribute_types(values, options[:type], options[:can_be_nil])
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def random_values_for_attributes
|
17
|
-
{}.tap do |values|
|
18
|
-
example.metadata[:attributes].each do |name, options|
|
19
|
-
can_be_nil = options[:can_be_nil] && (name != example.metadata[:on])
|
20
|
-
values[name] = random_attribute_value options.merge(can_be_nil: can_be_nil)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def random_attribute_value(options)
|
26
|
-
if options[:can_be_nil] && [true, false].sample
|
27
|
-
nil
|
28
|
-
else
|
29
|
-
case options[:type]
|
30
|
-
when :string then [*('a'..'z'), *('A'..'Z')].sample(Random.rand 32).join
|
31
|
-
when :integer then Random.rand(2**16)
|
32
|
-
when :url then "http://example.com/#{SecureRandom.urlsafe_base64}"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def assert_attribute_types(values, expected_type, can_be_nil)
|
38
|
-
values.compact! if can_be_nil
|
39
|
-
expect(values).to all_match_type expected_type
|
40
|
-
end
|
41
|
-
|
42
|
-
def matches_type?(value, type)
|
43
|
-
case type
|
44
|
-
when :url then value =~ URI::regexp
|
45
|
-
else value.is_a? type.to_s.classify.constantize
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
RSpec::Matchers.define :all_match_type do |type|
|
50
|
-
match do |values|
|
51
|
-
values.all? {|value| matches_type? value, type}
|
52
|
-
end
|
53
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
def existing(key)
|
2
|
-
metadata[:description_attribute] = 'an existing'
|
3
|
-
-> { instances.any key }
|
4
|
-
end
|
5
|
-
|
6
|
-
def unknown(key)
|
7
|
-
metadata[:description_attribute] = 'an unknown'
|
8
|
-
-> { instances.none key }
|
9
|
-
end
|
10
|
-
|
11
|
-
def apply(method_name, options = {})
|
12
|
-
proc = options[:to]
|
13
|
-
-> { proc.call.send method_name }
|
14
|
-
end
|
15
|
-
|
16
|
-
def with(request_params = {})
|
17
|
-
request_params[:attribute] ||= metadata.delete :description_attribute
|
18
|
-
request description_for(request_params), request_params, &Proc.new
|
19
|
-
end
|
20
|
-
|
21
|
-
def no_params
|
22
|
-
{}
|
23
|
-
end
|
24
|
-
|
25
|
-
def valid(request_params)
|
26
|
-
request_params.merge attribute: 'an valid'
|
27
|
-
end
|
28
|
-
|
29
|
-
def invalid(request_params)
|
30
|
-
request_params.merge attribute: 'an invalid'
|
31
|
-
end
|
32
|
-
|
33
|
-
def description_for(request_params = {})
|
34
|
-
[description_verb, description_object(request_params)].join ' '
|
35
|
-
end
|
36
|
-
|
37
|
-
def description_verb
|
38
|
-
case metadata[:method]
|
39
|
-
when :get then 'Getting'
|
40
|
-
when :post then 'Creating'
|
41
|
-
when :put then 'Updating'
|
42
|
-
when :delete then 'Deleting'
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def description_object(request_params = {})
|
47
|
-
attribute = request_params.delete :attribute
|
48
|
-
if metadata[:array]
|
49
|
-
"a list of #{metadata[:resource_name]}".tap do |objects|
|
50
|
-
objects << " by #{request_params.keys.join(', ')}" if request_params.any?
|
51
|
-
end
|
52
|
-
else
|
53
|
-
[attribute, metadata[:resource_name].singularize].join ' '
|
54
|
-
end.downcase
|
55
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
def setup_instances
|
2
|
-
instances.create random_values_for_attributes
|
3
|
-
instances.create random_values_for_attributes
|
4
|
-
stub_instances_total_pages example.metadata[:min_pages]
|
5
|
-
end
|
6
|
-
|
7
|
-
def instances
|
8
|
-
example.metadata[:resource_name].singularize.constantize
|
9
|
-
end
|
10
|
-
|
11
|
-
def assert_instances(json)
|
12
|
-
expect(json).not_to be_empty
|
13
|
-
end
|
14
|
-
|
15
|
-
def existing(key)
|
16
|
-
-> { instances.pluck(key).first }
|
17
|
-
end
|
18
|
-
|
19
|
-
def unknown(key)
|
20
|
-
keys = 0.downto(-Float::INFINITY).lazy
|
21
|
-
-> { keys.reject {|value| instances.exists? key => value}.first }
|
22
|
-
end
|
23
|
-
|
24
|
-
def apply(method_name, options = {})
|
25
|
-
proc = options[:to]
|
26
|
-
-> { proc.call.send method_name }
|
27
|
-
end
|
28
|
-
|
29
|
-
def stub_instances_total_pages(total_pages)
|
30
|
-
return unless instances.respond_to?(:page) and total_pages.present?
|
31
|
-
page_method = instances.method :page
|
32
|
-
instances.stub(:page) do |page|
|
33
|
-
page_method.call(page).tap do |proxy|
|
34
|
-
proxy.stub(:total_pages).and_return total_pages
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|