apill 2.9.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/apill/errors/invalid_api_request_error.rb +9 -7
- data/lib/apill/errors/invalid_subdomain_error.rb +9 -7
- data/lib/apill/parameters/filter.rb +23 -0
- data/lib/apill/parameters/index.rb +17 -0
- data/lib/apill/parameters/page.rb +22 -0
- data/lib/apill/parameters/sort.rb +27 -0
- data/lib/apill/resource/model.rb +42 -0
- data/lib/apill/resource/processors/filtering.rb +63 -0
- data/lib/apill/resource/processors/indexing.rb +32 -0
- data/lib/apill/resource/processors/paging.rb +39 -0
- data/lib/apill/resource/processors/sorting.rb +35 -0
- data/lib/apill/resource.rb +68 -0
- data/lib/apill/version.rb +1 -1
- data/lib/apill.rb +1 -6
- data/spec/apill/errors/invalid_api_request_error_spec.rb +8 -17
- data/spec/apill/errors/invalid_subdomain_error_spec.rb +6 -15
- data/spec/apill/invalid_subdomain_response_spec.rb +31 -35
- data/spec/apill/middleware/api_request_spec.rb +47 -54
- data/spec/apill/resource/model_spec.rb +66 -0
- data/spec/apill/resource/processors/filtering_spec.rb +84 -0
- data/spec/apill/resource/processors/indexing_spec.rb +11 -0
- data/spec/apill/resource/processors/paging_spec.rb +54 -0
- data/spec/apill/resource/processors/sorting_spec.rb +74 -0
- metadata +24 -22
- data/lib/apill/mixins/indexable.rb +0 -49
- data/lib/apill/mixins/pageable.rb +0 -51
- data/lib/apill/mixins/queryable.rb +0 -86
- data/lib/apill/mixins/sortable.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c68aa89f529dc07063d53a0f2e53e210b606c1bb
|
4
|
+
data.tar.gz: 6fc4b20a268b92b45d8e2e3f0b6ae195b118827d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 02544a5f552ca8205ccd6aff687b60460f001c06b49db580ebfc9ad2cc841e5af9af65440af2494d1ba3f4baf6c56b002d68ce1afc720b90c5b0192d62d7c509
|
7
|
+
data.tar.gz: 9ac237ebc9b635993885a286f8b22353c98c655efc68fde7c50056dba9cbc332298465e328bcc80c8cf23cc6a0f44eab59c21aa381943c06a43c54993fe8a4a5
|
@@ -2,25 +2,27 @@ require 'human_error'
|
|
2
2
|
|
3
3
|
module Apill
|
4
4
|
module Errors
|
5
|
-
class InvalidApiRequestError <
|
5
|
+
class InvalidApiRequestError < RuntimeError
|
6
|
+
include HumanError::Error
|
7
|
+
|
6
8
|
attr_accessor :accept_header
|
7
9
|
|
8
10
|
def http_status
|
9
11
|
400
|
10
12
|
end
|
11
13
|
|
12
|
-
def
|
14
|
+
def title
|
15
|
+
'Invalid API Request'
|
16
|
+
end
|
17
|
+
|
18
|
+
def detail
|
13
19
|
'The accept header that you passed in the request cannot be parsed, ' \
|
14
20
|
'please refer to the documentation to verify.'
|
15
21
|
end
|
16
22
|
|
17
|
-
def
|
23
|
+
def source
|
18
24
|
{ accept_header: accept_header }
|
19
25
|
end
|
20
|
-
|
21
|
-
def friendly_message
|
22
|
-
"Sorry! We couldn't understand what you were trying to ask us to do."
|
23
|
-
end
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
@@ -2,25 +2,27 @@ require 'human_error'
|
|
2
2
|
|
3
3
|
module Apill
|
4
4
|
module Errors
|
5
|
-
class InvalidSubdomainError <
|
5
|
+
class InvalidSubdomainError < RuntimeError
|
6
|
+
include HumanError::Error
|
7
|
+
|
6
8
|
attr_accessor :http_host
|
7
9
|
|
8
10
|
def http_status
|
9
11
|
404
|
10
12
|
end
|
11
13
|
|
12
|
-
def
|
14
|
+
def title
|
15
|
+
'Invalid Subdomain'
|
16
|
+
end
|
17
|
+
|
18
|
+
def detail
|
13
19
|
'The resource you attempted to access is either not authorized for the ' \
|
14
20
|
'authenticated user or does not exist.'
|
15
21
|
end
|
16
22
|
|
17
|
-
def
|
23
|
+
def source
|
18
24
|
{ http_host: http_host }
|
19
25
|
end
|
20
|
-
|
21
|
-
def friendly_message
|
22
|
-
'Sorry! The resource you tried to access does not exist.'
|
23
|
-
end
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Apill
|
2
|
+
class Parameters
|
3
|
+
class Filter
|
4
|
+
attr_accessor :raw_parameters
|
5
|
+
|
6
|
+
def initialize(raw_parameters)
|
7
|
+
self.raw_parameters = raw_parameters
|
8
|
+
end
|
9
|
+
|
10
|
+
def each_with_object(memoized)
|
11
|
+
raw_parameters.each do |raw_parameter|
|
12
|
+
next if raw_parameter[0] == 'query' ||
|
13
|
+
raw_parameter[1] == '' ||
|
14
|
+
raw_parameter[1].nil?
|
15
|
+
|
16
|
+
memoized = yield raw_parameter[0], raw_parameter[1], memoized
|
17
|
+
end
|
18
|
+
|
19
|
+
memoized
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Apill
|
2
|
+
class Parameters
|
3
|
+
class Index
|
4
|
+
DEFAULT_QUERY = '*'
|
5
|
+
|
6
|
+
attr_accessor :raw_parameters
|
7
|
+
|
8
|
+
def initialize(raw_parameters)
|
9
|
+
self.raw_parameters = raw_parameters
|
10
|
+
end
|
11
|
+
|
12
|
+
def query
|
13
|
+
raw_parameters['query'] || raw_parameters['q']
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Apill
|
2
|
+
class Parameters
|
3
|
+
class Page
|
4
|
+
DEFAULT_STARTING_PAGE = 1
|
5
|
+
DEFAULT_PAGE_SIZE = 25
|
6
|
+
|
7
|
+
attr_accessor :raw_parameters
|
8
|
+
|
9
|
+
def initialize(raw_parameters)
|
10
|
+
self.raw_parameters = raw_parameters
|
11
|
+
end
|
12
|
+
|
13
|
+
def page_number
|
14
|
+
raw_parameters['number'] || DEFAULT_STARTING_PAGE
|
15
|
+
end
|
16
|
+
|
17
|
+
def per_page
|
18
|
+
raw_parameters['size'] || DEFAULT_PAGE_SIZE
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Apill
|
2
|
+
class Parameters
|
3
|
+
class Sort
|
4
|
+
DESCENDING_PREFIX = '-'
|
5
|
+
|
6
|
+
attr_accessor :raw_parameters
|
7
|
+
|
8
|
+
def initialize(raw_parameters)
|
9
|
+
self.raw_parameters = raw_parameters ? raw_parameters.split(',') : ['-created_at']
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_h
|
13
|
+
@to_h ||= Hash[to_a]
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_a
|
17
|
+
@to_a ||= raw_parameters.map do |raw_parameter|
|
18
|
+
if raw_parameter.start_with?(DESCENDING_PREFIX)
|
19
|
+
[raw_parameter[1..-1], 'desc']
|
20
|
+
else
|
21
|
+
[raw_parameter, 'asc']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'apill/resource/processors/filtering'
|
2
|
+
require 'apill/resource/processors/sorting'
|
3
|
+
require 'apill/resource/processors/paging'
|
4
|
+
require 'apill/resource/processors/indexing'
|
5
|
+
|
6
|
+
module Apill
|
7
|
+
module Resource
|
8
|
+
class Model
|
9
|
+
DEFAULT_PROCESSORS = %w{filtering sorting paging indexing}
|
10
|
+
|
11
|
+
attr_accessor :resource,
|
12
|
+
:parameters,
|
13
|
+
:processors
|
14
|
+
|
15
|
+
def initialize(resource:, parameters:, **options)
|
16
|
+
self.resource = resource
|
17
|
+
self.parameters = parameters
|
18
|
+
self.processors = options.fetch(:processors, DEFAULT_PROCESSORS)
|
19
|
+
end
|
20
|
+
|
21
|
+
def processed
|
22
|
+
@processed ||= \
|
23
|
+
processors.inject(resource) do |processed_resource, processor|
|
24
|
+
processor.processed(processed_resource, parameters)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def meta
|
29
|
+
@meta ||= \
|
30
|
+
processors.inject({}) do |metadata, processor|
|
31
|
+
metadata.merge processor.meta(processed, parameters)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def processors=(other)
|
36
|
+
@processors = other.map do |processor|
|
37
|
+
Object.const_get "::Apill::Resource::Processors::#{processor.capitalize}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'apill/parameters/filter'
|
2
|
+
|
3
|
+
module Apill
|
4
|
+
module Resource
|
5
|
+
module Processors
|
6
|
+
class Filtering
|
7
|
+
attr_accessor :resource,
|
8
|
+
:parameters
|
9
|
+
|
10
|
+
def initialize(resource, parameters = {})
|
11
|
+
self.resource = resource
|
12
|
+
self.parameters = Parameters::Filter.new(parameters['filter'] || {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.processed(*attrs)
|
16
|
+
new(*attrs).processed
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.meta(*_attrs)
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
|
23
|
+
def processed
|
24
|
+
parameters.each_with_object(resource) do |name, value, filtered_resource|
|
25
|
+
filter_method = filter_method_for(name)
|
26
|
+
|
27
|
+
if !filter_method
|
28
|
+
filtered_resource
|
29
|
+
elsif filter_method.arity == 0
|
30
|
+
filtered_resource.public_send(filter_method.name)
|
31
|
+
else
|
32
|
+
filtered_resource.public_send(filter_method.name, value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def filter_method_for(filter_item)
|
40
|
+
filter_method_name = filter_method_name_for(filter_item)
|
41
|
+
|
42
|
+
resource_class.method(filter_method_name) if filter_method_name
|
43
|
+
end
|
44
|
+
|
45
|
+
def filter_method_name_for(filter_item)
|
46
|
+
if resource_class.respond_to? "for_#{filter_item}"
|
47
|
+
"for_#{filter_item}"
|
48
|
+
elsif resource_class.respond_to? filter_item
|
49
|
+
filter_item
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def resource_class
|
54
|
+
@resource_class ||= if resource.respond_to? :klass
|
55
|
+
resource.klass
|
56
|
+
else
|
57
|
+
resource
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'apill/parameters/index'
|
2
|
+
|
3
|
+
module Apill
|
4
|
+
module Resource
|
5
|
+
module Processors
|
6
|
+
class Indexing
|
7
|
+
attr_accessor :resource,
|
8
|
+
:parameters
|
9
|
+
|
10
|
+
def initialize(resource, parameters)
|
11
|
+
self.resource = resource
|
12
|
+
self.parameters = Parameters::Index.new(parameters['filter'] || {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.processed(*attrs)
|
16
|
+
new(*attrs).processed
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.meta(*_attrs)
|
20
|
+
{}
|
21
|
+
end
|
22
|
+
|
23
|
+
def processed
|
24
|
+
return resource unless resource.respond_to?(:for_query) &&
|
25
|
+
parameters.query
|
26
|
+
|
27
|
+
resource.for_query(parameters.query)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'apill/parameters/page'
|
2
|
+
|
3
|
+
module Apill
|
4
|
+
module Resource
|
5
|
+
module Processors
|
6
|
+
class Paging
|
7
|
+
attr_accessor :resource,
|
8
|
+
:parameters
|
9
|
+
|
10
|
+
def initialize(resource, parameters = {})
|
11
|
+
self.resource = resource
|
12
|
+
self.parameters = Parameters::Page.new(parameters['page'] || {})
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.processed(*attrs)
|
16
|
+
new(*attrs).processed
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.meta(*attrs)
|
20
|
+
new(*attrs).meta
|
21
|
+
end
|
22
|
+
|
23
|
+
def processed
|
24
|
+
resource.page(parameters.page_number).
|
25
|
+
per(parameters.per_page)
|
26
|
+
end
|
27
|
+
|
28
|
+
def meta
|
29
|
+
{
|
30
|
+
'total-pages' => processed.total_pages,
|
31
|
+
'current-page' => processed.current_page,
|
32
|
+
'previous-page' => processed.prev_page,
|
33
|
+
'next-page' => processed.next_page,
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'apill/parameters/sort'
|
2
|
+
|
3
|
+
module Apill
|
4
|
+
module Resource
|
5
|
+
module Processors
|
6
|
+
class Sorting
|
7
|
+
attr_accessor :resource,
|
8
|
+
:parameters
|
9
|
+
|
10
|
+
def initialize(resource, parameters = {})
|
11
|
+
self.resource = resource
|
12
|
+
self.parameters = Parameters::Sort.new(parameters['sort'])
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.processed(*attrs)
|
16
|
+
new(*attrs).processed
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.meta(*attrs)
|
20
|
+
new(*attrs).meta
|
21
|
+
end
|
22
|
+
|
23
|
+
def processed
|
24
|
+
resource.order(parameters.to_h)
|
25
|
+
end
|
26
|
+
|
27
|
+
def meta
|
28
|
+
{
|
29
|
+
'sort' => parameters.to_h,
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'apill/resource/model'
|
2
|
+
require 'human_error/rescuable_resource'
|
3
|
+
|
4
|
+
module Apill
|
5
|
+
module Resource
|
6
|
+
module ClassMethods
|
7
|
+
def plural_resource_name
|
8
|
+
name[/(\w+)Controller\z/, 1].
|
9
|
+
underscore.
|
10
|
+
pluralize.
|
11
|
+
downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
def singular_resource_name
|
15
|
+
name[/(\w+)Controller\z/, 1].
|
16
|
+
underscore.
|
17
|
+
singularize.
|
18
|
+
downcase
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.included(base)
|
23
|
+
base.extend ClassMethods
|
24
|
+
base.include HumanError::RescuableResource
|
25
|
+
base.include HumanError::VerifiableResource
|
26
|
+
end
|
27
|
+
|
28
|
+
def api_resource
|
29
|
+
@resource ||= Resource::Model.new(
|
30
|
+
resource: public_send(self.class.plural_resource_name),
|
31
|
+
parameters: api_resource_params)
|
32
|
+
end
|
33
|
+
|
34
|
+
def api_resource_params
|
35
|
+
params.permit(sort: String,
|
36
|
+
page: %i{
|
37
|
+
number
|
38
|
+
size
|
39
|
+
offset
|
40
|
+
limit
|
41
|
+
cursor
|
42
|
+
},
|
43
|
+
filter: api_filterable_parameters)
|
44
|
+
end
|
45
|
+
|
46
|
+
def api_filterable_parameters
|
47
|
+
@api_filterable_parameters ||= begin
|
48
|
+
filter_params = params.fetch(:filter, {})
|
49
|
+
scalar_params = [:query]
|
50
|
+
array_params = {}
|
51
|
+
|
52
|
+
api_filterable_attributes.each do |api_filterable_attribute|
|
53
|
+
if filter_params[api_filterable_attribute].class == Array
|
54
|
+
array_params[api_filterable_attribute] = []
|
55
|
+
else
|
56
|
+
scalar_params << api_filterable_attribute
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
scalar_params << array_params
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def api_filterable_attributes
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/apill/version.rb
CHANGED
data/lib/apill.rb
CHANGED
@@ -1,15 +1,10 @@
|
|
1
|
-
require 'kaminari'
|
2
|
-
|
3
1
|
require 'apill/version'
|
4
2
|
|
5
3
|
require 'apill/configuration'
|
6
4
|
require 'apill/matchers/accept_header_matcher'
|
7
5
|
require 'apill/matchers/subdomain_matcher'
|
8
6
|
require 'apill/matchers/version_matcher'
|
9
|
-
require 'apill/
|
10
|
-
require 'apill/mixins/sortable'
|
11
|
-
require 'apill/mixins/queryable'
|
12
|
-
require 'apill/mixins/pageable'
|
7
|
+
require 'apill/resource'
|
13
8
|
require 'apill/serializers/json_api'
|
14
9
|
|
15
10
|
require 'apill/middleware/api_request'
|
@@ -10,29 +10,20 @@ describe InvalidApiRequestError do
|
|
10
10
|
expect(error.http_status).to eql 400
|
11
11
|
end
|
12
12
|
|
13
|
-
it 'has a code
|
14
|
-
expect(error.code).to eql
|
13
|
+
it 'has a code' do
|
14
|
+
expect(error.code).to eql 'errors.invalid_api_request_error'
|
15
15
|
end
|
16
16
|
|
17
|
-
it '
|
18
|
-
expect(error.
|
17
|
+
it 'can output the detail' do
|
18
|
+
expect(error.detail).to eql 'The accept header that you passed in the ' \
|
19
|
+
'request cannot be parsed, please refer to ' \
|
20
|
+
'the documentation to verify.'
|
19
21
|
end
|
20
22
|
|
21
|
-
it 'can output the
|
22
|
-
expect(error.developer_message).to eql 'The accept header that you passed in the ' \
|
23
|
-
'request cannot be parsed, please refer to ' \
|
24
|
-
'the documentation to verify.'
|
25
|
-
end
|
26
|
-
|
27
|
-
it 'can output the developer details' do
|
23
|
+
it 'can output the source' do
|
28
24
|
error = InvalidApiRequestError.new accept_header: 'foo'
|
29
25
|
|
30
|
-
expect(error.
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'can output the friendly message' do
|
34
|
-
expect(error.friendly_message).to eql "Sorry! We couldn't understand what you were " \
|
35
|
-
'trying to ask us to do.'
|
26
|
+
expect(error.source).to eql(accept_header: 'foo')
|
36
27
|
end
|
37
28
|
end
|
38
29
|
end
|
@@ -10,29 +10,20 @@ describe InvalidSubdomainError do
|
|
10
10
|
expect(error.http_status).to eql 404
|
11
11
|
end
|
12
12
|
|
13
|
-
it 'has a code
|
14
|
-
expect(error.code).to eql
|
13
|
+
it 'has a code' do
|
14
|
+
expect(error.code).to eql 'errors.invalid_subdomain_error'
|
15
15
|
end
|
16
16
|
|
17
|
-
it '
|
18
|
-
expect(error.
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'can output the developer message' do
|
22
|
-
expect(error.developer_message).to eql \
|
17
|
+
it 'can output the detail' do
|
18
|
+
expect(error.detail).to eql \
|
23
19
|
'The resource you attempted to access is either not authorized for the ' \
|
24
20
|
'authenticated user or does not exist.'
|
25
21
|
end
|
26
22
|
|
27
|
-
it 'can output the
|
23
|
+
it 'can output the source' do
|
28
24
|
error = InvalidSubdomainError.new http_host: 'foo'
|
29
25
|
|
30
|
-
expect(error.
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'can output the friendly message' do
|
34
|
-
expect(error.friendly_message).to eql \
|
35
|
-
'Sorry! The resource you tried to access does not exist.'
|
26
|
+
expect(error.source).to eql(http_host: 'foo')
|
36
27
|
end
|
37
28
|
end
|
38
29
|
end
|
@@ -3,43 +3,39 @@ require 'apill/responses/invalid_subdomain_response'
|
|
3
3
|
|
4
4
|
module Apill
|
5
5
|
module Responses
|
6
|
-
describe InvalidSubdomainResponse do
|
7
|
-
before(:each) do
|
8
|
-
HumanError.configuration.api_error_documentation_url = 'http://error.com'
|
9
|
-
HumanError.configuration.knowledgebase_url = 'http://knowledge.com'
|
10
|
-
HumanError.configuration.api_version = '1'
|
11
|
-
end
|
12
|
-
|
6
|
+
describe InvalidSubdomainResponse, singletons: HumanError::Configuration do
|
13
7
|
it 'returns the proper response' do
|
14
|
-
|
15
|
-
|
8
|
+
HumanError.configuration.url_mappings = {
|
9
|
+
'external_documentation_urls' => {
|
10
|
+
'errors.invalid_subdomain_response' => 'http://example.com/foo',
|
11
|
+
},
|
12
|
+
'developer_documentation_urls' => {
|
13
|
+
'errors.invalid_subdomain_response' => 'http://example.com/foo',
|
14
|
+
},
|
15
|
+
}
|
16
|
+
|
17
|
+
request = { 'HTTP_HOST' => 'api.example.com' }
|
18
|
+
status, headers, response = InvalidSubdomainResponse.call(request)
|
16
19
|
|
17
|
-
expect(
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
'
|
23
|
-
|
24
|
-
'
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
'},' \
|
37
|
-
'"friendly_message_key":"errors.invalid.subdomain.error.friendly",' \
|
38
|
-
'"friendly_message":"Sorry! The resource you tried to access does not ' \
|
39
|
-
'exist."' \
|
40
|
-
'}' \
|
41
|
-
'}',
|
42
|
-
],
|
20
|
+
expect(status).to eql 404
|
21
|
+
expect(headers).to eql({})
|
22
|
+
expect(JSON.load(response[0])).to include(
|
23
|
+
'errors' => [
|
24
|
+
include(
|
25
|
+
'id' => match(/[a-f0-9\-]+/),
|
26
|
+
'links' => {
|
27
|
+
'about' => nil,
|
28
|
+
'documentation' => nil,
|
29
|
+
},
|
30
|
+
'status' => 404,
|
31
|
+
'code' => 'errors.invalid_subdomain_error',
|
32
|
+
'title' => 'Invalid Subdomain',
|
33
|
+
'detail' => 'The resource you attempted to access is either not authorized ' \
|
34
|
+
'for the authenticated user or does not exist.',
|
35
|
+
'source' => {
|
36
|
+
'http_host' => 'api.example.com',
|
37
|
+
},
|
38
|
+
),
|
43
39
|
],
|
44
40
|
)
|
45
41
|
end
|