rspec-api 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|