routemaster-drain 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.env.test +0 -1
- data/.travis.yml +4 -0
- data/Appraisals +11 -0
- data/Gemfile +7 -3
- data/Gemfile.lock +12 -7
- data/README.md +60 -11
- data/gemfiles/rails_3.gemfile +24 -0
- data/gemfiles/rails_3.gemfile.lock +224 -0
- data/gemfiles/rails_4.gemfile +24 -0
- data/gemfiles/rails_4.gemfile.lock +224 -0
- data/gemfiles/rails_5.gemfile +24 -0
- data/gemfiles/rails_5.gemfile.lock +250 -0
- data/lib/routemaster/{fetcher.rb → api_client.rb} +34 -20
- data/lib/routemaster/cache.rb +5 -5
- data/lib/routemaster/config.rb +14 -0
- data/lib/routemaster/drain.rb +1 -1
- data/lib/routemaster/errors.rb +73 -0
- data/lib/routemaster/middleware/error_handling.rb +28 -0
- data/lib/routemaster/middleware/{caching.rb → response_caching.rb} +23 -11
- data/lib/routemaster/resources/rest_resource.rb +26 -0
- data/lib/routemaster/responses/hateoas_response.rb +51 -0
- data/routemaster-drain.gemspec +1 -1
- data/spec/routemaster/{fetcher_spec.rb → api_client_spec.rb} +33 -2
- data/spec/routemaster/cache_spec.rb +7 -7
- data/spec/routemaster/integration/api_client_spec.rb +76 -0
- data/spec/routemaster/integration/cache_spec.rb +10 -3
- data/spec/routemaster/middleware/authenticate_spec.rb +2 -2
- data/spec/routemaster/resources/rest_resource_spec.rb +35 -0
- data/spec/routemaster/responses/hateoas_response_spec.rb +50 -0
- metadata +25 -8
@@ -0,0 +1,73 @@
|
|
1
|
+
module Routemaster
|
2
|
+
module Errors
|
3
|
+
class BaseError < RuntimeError
|
4
|
+
attr_reader :env
|
5
|
+
|
6
|
+
def initialize(env)
|
7
|
+
@env = env
|
8
|
+
super(message)
|
9
|
+
end
|
10
|
+
|
11
|
+
def errors
|
12
|
+
body['errors']
|
13
|
+
end
|
14
|
+
|
15
|
+
def message
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def body
|
20
|
+
@body ||= deserialized_body
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def deserialized_body
|
26
|
+
@env.body.empty? ? {} : JSON.parse(@env.body)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class UnauthorizedResourceAccessError < BaseError
|
31
|
+
def message
|
32
|
+
"Unauthorized Resource Access Error"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class InvalidResourceError < BaseError
|
37
|
+
def message
|
38
|
+
"Invalid Resource Error"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class ResourceNotFoundError < BaseError
|
43
|
+
def message
|
44
|
+
"Resource Not Found Error"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class FatalResourceError < BaseError
|
49
|
+
def message
|
50
|
+
"Fatal Resource Error. body: #{body}, url: #{env.url}, method: #{env.method}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class ConflictResourceError < BaseError
|
55
|
+
def message
|
56
|
+
"ConflictResourceError Resource Error"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class IncompatibleVersionError < BaseError
|
61
|
+
def message
|
62
|
+
headers = env.request_headers.select { |k, _| k != 'Authorization' }
|
63
|
+
"Incompatible Version Error. headers: #{headers}, url: #{env.url}, method: #{env.method}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class ResourceThrottlingError < BaseError
|
68
|
+
def message
|
69
|
+
"Resource Throttling Error"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'faraday_middleware'
|
2
|
+
require 'routemaster/errors'
|
3
|
+
|
4
|
+
module Routemaster
|
5
|
+
module Middleware
|
6
|
+
class ErrorHandling < Faraday::Response::Middleware
|
7
|
+
ERRORS_MAPPING = {
|
8
|
+
(400..400) => Errors::InvalidResourceError,
|
9
|
+
(401..401) => Errors::UnauthorizedResourceAccessError,
|
10
|
+
(403..403) => Errors::UnauthorizedResourceAccessError,
|
11
|
+
(404..404) => Errors::ResourceNotFoundError,
|
12
|
+
(409..409) => Errors::ConflictResourceError,
|
13
|
+
(412..412) => Errors::IncompatibleVersionError,
|
14
|
+
(413..413) => Errors::InvalidResourceError,
|
15
|
+
(429..429) => Errors::ResourceThrottlingError,
|
16
|
+
(407..500) => Errors::FatalResourceError
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
def on_complete(env)
|
20
|
+
ERRORS_MAPPING.each do |range, error_class|
|
21
|
+
if range.include?(env[:status])
|
22
|
+
raise error_class.new(env)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -2,9 +2,10 @@ require 'wisper'
|
|
2
2
|
|
3
3
|
module Routemaster
|
4
4
|
module Middleware
|
5
|
-
class
|
5
|
+
class ResponseCaching
|
6
6
|
KEY_TEMPLATE = 'cache:{url}'
|
7
|
-
|
7
|
+
BODY_FIELD_TEMPLATE = 'v:{version},l:{locale},body'
|
8
|
+
HEADERS_FIELD_TEMPLATE = 'v:{version},l:{locale},headers'
|
8
9
|
VERSION_REGEX = /application\/json;v=(?<version>\S*)/.freeze
|
9
10
|
|
10
11
|
def initialize(app, cache: Config.cache_redis, listener: nil)
|
@@ -27,27 +28,38 @@ module Routemaster
|
|
27
28
|
response = response_env.response
|
28
29
|
|
29
30
|
if response.success?
|
30
|
-
@cache.
|
31
|
-
|
31
|
+
@cache.multi do |multi|
|
32
|
+
multi.hset(cache_key(env), body_cache_field(env), response.body)
|
33
|
+
multi.hset(cache_key(env), headers_cache_field(env), Marshal.dump(response.headers))
|
34
|
+
multi.expire(cache_key(env), @expiry)
|
35
|
+
end
|
32
36
|
@listener._publish(:cache_miss, url(env)) if @listener
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
36
40
|
|
37
41
|
def fetch_from_cache(env)
|
38
|
-
|
42
|
+
body = @cache.hget(cache_key(env), body_cache_field(env))
|
43
|
+
headers = @cache.hget(cache_key(env), headers_cache_field(env))
|
39
44
|
|
40
|
-
if
|
45
|
+
if body && headers
|
41
46
|
@listener._publish(:cache_hit, url(env)) if @listener
|
42
47
|
Faraday::Response.new(status: 200,
|
43
|
-
body:
|
44
|
-
response_headers:
|
48
|
+
body: body,
|
49
|
+
response_headers: Marshal.load(headers),
|
50
|
+
request: {})
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
54
|
+
def body_cache_field(env)
|
55
|
+
BODY_FIELD_TEMPLATE
|
56
|
+
.gsub('{version}', version(env).to_s)
|
57
|
+
.gsub('{locale}', locale(env).to_s)
|
58
|
+
end
|
59
|
+
|
60
|
+
def headers_cache_field(env)
|
61
|
+
HEADERS_FIELD_TEMPLATE
|
62
|
+
.gsub('{version}', version(env).to_s)
|
51
63
|
.gsub('{locale}', locale(env).to_s)
|
52
64
|
end
|
53
65
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'routemaster/api_client'
|
2
|
+
|
3
|
+
module Routemaster
|
4
|
+
module Resources
|
5
|
+
class RestResource
|
6
|
+
attr_reader :url
|
7
|
+
|
8
|
+
def initialize(url, client: nil)
|
9
|
+
@url = url
|
10
|
+
@client = client || Routemaster::APIClient.new(response_class: Responses::HateoasResponse)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create(params)
|
14
|
+
@client.post(@url, body: params)
|
15
|
+
end
|
16
|
+
|
17
|
+
def show(id=nil)
|
18
|
+
@client.get(@url.gsub('{id}', id.to_s))
|
19
|
+
end
|
20
|
+
|
21
|
+
def index
|
22
|
+
@client.get(@url)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'faraday_middleware'
|
2
|
+
require 'routemaster/api_client'
|
3
|
+
require 'routemaster/responses/hateoas_response'
|
4
|
+
require 'routemaster/resources/rest_resource'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module Routemaster
|
9
|
+
module Responses
|
10
|
+
class HateoasResponse
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
attr_reader :response
|
14
|
+
def_delegators :@response, :body, :status, :headers, :success?
|
15
|
+
|
16
|
+
def initialize(response, client: nil)
|
17
|
+
@response = response
|
18
|
+
@client = client || Routemaster::APIClient.new(response_class: Routemaster::Responses::HateoasResponse)
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(m, *args, &block)
|
22
|
+
method_name = m.to_s
|
23
|
+
normalized_method_name = method_name == '_self' ? 'self' : method_name
|
24
|
+
|
25
|
+
if _links.keys.include?(normalized_method_name)
|
26
|
+
unless respond_to?(method_name)
|
27
|
+
resource = Resources::RestResource.new(_links[normalized_method_name]['href'], client: @client)
|
28
|
+
|
29
|
+
self.class.send(:define_method, method_name) do |*m_args|
|
30
|
+
resource
|
31
|
+
end
|
32
|
+
|
33
|
+
resource
|
34
|
+
end
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def body_without_links
|
41
|
+
body.reject { |key, _| ['_links'].include?(key) }
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def _links
|
47
|
+
@links ||= @response.body.fetch('_links', {})
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/routemaster-drain.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.add_runtime_dependency 'faraday', '>= 0.9.0'
|
20
20
|
spec.add_runtime_dependency 'faraday_middleware'
|
21
21
|
spec.add_runtime_dependency 'net-http-persistent', '< 3' # 3.x is currently incompatible with faraday
|
22
|
-
spec.add_runtime_dependency 'rack', '>= 1.
|
22
|
+
spec.add_runtime_dependency 'rack', '>= 1.4.5'
|
23
23
|
spec.add_runtime_dependency 'wisper', '~> 1.6.1'
|
24
24
|
spec.add_runtime_dependency 'hashie'
|
25
25
|
spec.add_runtime_dependency 'redis-namespace'
|
@@ -2,10 +2,10 @@ require 'spec_helper'
|
|
2
2
|
require 'spec/support/uses_dotenv'
|
3
3
|
require 'spec/support/uses_redis'
|
4
4
|
require 'spec/support/uses_webmock'
|
5
|
-
require 'routemaster/
|
5
|
+
require 'routemaster/api_client'
|
6
6
|
require 'json'
|
7
7
|
|
8
|
-
describe Routemaster::
|
8
|
+
describe Routemaster::APIClient do
|
9
9
|
uses_dotenv
|
10
10
|
uses_redis
|
11
11
|
uses_webmock
|
@@ -24,6 +24,14 @@ describe Routemaster::Fetcher do
|
|
24
24
|
'content-type' => 'application/json;v=1'
|
25
25
|
}
|
26
26
|
)
|
27
|
+
|
28
|
+
@post_req = stub_request(:post, /example\.com/).to_return(
|
29
|
+
status: 200,
|
30
|
+
body: { id: 132, type: 'widget' }.to_json,
|
31
|
+
headers: {
|
32
|
+
'content-type' => 'application/json;v=1'
|
33
|
+
}
|
34
|
+
)
|
27
35
|
end
|
28
36
|
|
29
37
|
it 'GETs from the URL' do
|
@@ -31,6 +39,15 @@ describe Routemaster::Fetcher do
|
|
31
39
|
expect(@req).to have_been_requested
|
32
40
|
end
|
33
41
|
|
42
|
+
context 'POST request' do
|
43
|
+
subject { fetcher.post(url, body: {}, headers: headers) }
|
44
|
+
|
45
|
+
it 'POSTs from the URL' do
|
46
|
+
subject
|
47
|
+
expect(@post_req).to have_been_requested
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
34
51
|
it 'has :status, :headers, :body' do
|
35
52
|
expect(subject.status).to eq(200)
|
36
53
|
expect(subject.headers).to have_key('content-type')
|
@@ -56,5 +73,19 @@ describe Routemaster::Fetcher do
|
|
56
73
|
expect(req.headers).to include('X-Custom-Header')
|
57
74
|
end
|
58
75
|
end
|
76
|
+
|
77
|
+
context 'when response_class is present' do
|
78
|
+
before do
|
79
|
+
class DummyResponse
|
80
|
+
def initialize(res, client: nil); end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
let(:fetcher) { described_class.new(response_class: DummyResponse) }
|
85
|
+
|
86
|
+
it 'returns a response_class instance as a response' do
|
87
|
+
expect(subject).to be_an_instance_of(DummyResponse)
|
88
|
+
end
|
89
|
+
end
|
59
90
|
end
|
60
91
|
end
|
@@ -3,7 +3,7 @@ require 'spec/support/uses_redis'
|
|
3
3
|
require 'spec/support/uses_dotenv'
|
4
4
|
require 'spec/support/uses_webmock'
|
5
5
|
require 'routemaster/cache'
|
6
|
-
require 'routemaster/
|
6
|
+
require 'routemaster/api_client'
|
7
7
|
|
8
8
|
module Routemaster
|
9
9
|
describe Cache do
|
@@ -27,8 +27,8 @@ module Routemaster
|
|
27
27
|
context 'with no options' do
|
28
28
|
let(:options) { {} }
|
29
29
|
|
30
|
-
it 'calls get on the
|
31
|
-
expect_any_instance_of(
|
30
|
+
it 'calls get on the api client with no version and locale headers' do
|
31
|
+
expect_any_instance_of(APIClient)
|
32
32
|
.to receive(:get)
|
33
33
|
.with(url, headers: { 'Accept' => 'application/json' })
|
34
34
|
.and_call_original
|
@@ -40,8 +40,8 @@ module Routemaster
|
|
40
40
|
context 'with a specific version' do
|
41
41
|
let(:options) { { version: 2 } }
|
42
42
|
|
43
|
-
it 'calls get on the
|
44
|
-
expect_any_instance_of(
|
43
|
+
it 'calls get on the api client with version header' do
|
44
|
+
expect_any_instance_of(APIClient)
|
45
45
|
.to receive(:get)
|
46
46
|
.with(url, headers: { 'Accept' => 'application/json;v=2' })
|
47
47
|
.and_call_original
|
@@ -53,8 +53,8 @@ module Routemaster
|
|
53
53
|
context 'with a specific locale' do
|
54
54
|
let(:options) { { locale: 'fr' } }
|
55
55
|
|
56
|
-
it 'calls get on the
|
57
|
-
expect_any_instance_of(
|
56
|
+
it 'calls get on the api client with locale header' do
|
57
|
+
expect_any_instance_of(APIClient)
|
58
58
|
.to receive(:get)
|
59
59
|
.with(url, headers: { 'Accept' => 'application/json', 'Accept-Language' => 'fr' })
|
60
60
|
.and_call_original
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spec/support/uses_redis'
|
3
|
+
require 'spec/support/uses_dotenv'
|
4
|
+
require 'routemaster/api_client'
|
5
|
+
require 'webrick'
|
6
|
+
|
7
|
+
RSpec.describe 'Api client integration specs' do
|
8
|
+
uses_dotenv
|
9
|
+
uses_redis
|
10
|
+
|
11
|
+
let!(:log) { WEBrick::Log.new '/dev/null' }
|
12
|
+
let(:service) do
|
13
|
+
WEBrick::HTTPServer.new(Port: 8000, DocumentRoot: Dir.pwd, Logger: log).tap do |server|
|
14
|
+
[400, 401, 403, 404, 409, 412, 413, 429, 500].each do |status_code|
|
15
|
+
server.mount_proc "/#{status_code}" do |req, res|
|
16
|
+
res.status = status_code
|
17
|
+
res.body = { field: 'test' }.to_json
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
@pid = fork do
|
25
|
+
trap 'INT' do service.shutdown end
|
26
|
+
service.start
|
27
|
+
end
|
28
|
+
sleep(0.5) # leave sometime for the previous webrick to teardown
|
29
|
+
end
|
30
|
+
|
31
|
+
after do
|
32
|
+
Process.kill('KILL', @pid)
|
33
|
+
Process.wait(@pid)
|
34
|
+
end
|
35
|
+
|
36
|
+
subject { Routemaster::APIClient.new }
|
37
|
+
let(:host) { 'http://localhost:8000' }
|
38
|
+
|
39
|
+
describe 'error handling' do
|
40
|
+
it 'raises an ResourceNotFoundError on 404' do
|
41
|
+
expect { subject.get(host + '/404') }.to raise_error(Routemaster::Errors::ResourceNotFoundError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'raises an InvalidResourceError on 400' do
|
45
|
+
expect { subject.get(host + '/400') }.to raise_error(Routemaster::Errors::InvalidResourceError)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'raises an UnauthorizedResourceAccessError on 401' do
|
49
|
+
expect { subject.get(host + '/401') }.to raise_error(Routemaster::Errors::UnauthorizedResourceAccessError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'raises an UnauthorizedResourceAccessError on 403' do
|
53
|
+
expect { subject.get(host + '/403') }.to raise_error(Routemaster::Errors::UnauthorizedResourceAccessError)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'raises an ConflictResourceError on 409' do
|
57
|
+
expect { subject.get(host + '/409') }.to raise_error(Routemaster::Errors::ConflictResourceError)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'raises an IncompatibleVersionError on 412' do
|
61
|
+
expect { subject.get(host + '/412') }.to raise_error(Routemaster::Errors::IncompatibleVersionError)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'raises an InvalidResourceError on 413' do
|
65
|
+
expect { subject.get(host + '/413') }.to raise_error(Routemaster::Errors::InvalidResourceError)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'raises an ResourceThrottlingError on 429' do
|
69
|
+
expect { subject.get(host + '/429') }.to raise_error(Routemaster::Errors::ResourceThrottlingError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'raises an FatalResourceError on 500' do
|
73
|
+
expect { subject.get(host + '/500') }.to raise_error(Routemaster::Errors::FatalResourceError)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -33,7 +33,8 @@ RSpec.describe 'Requests with caching' do
|
|
33
33
|
subject { Routemaster::Cache.new }
|
34
34
|
|
35
35
|
describe 'GET request' do
|
36
|
-
let(:
|
36
|
+
let(:body_cache_keys) { ["cache:#{url}", "v:,l:,body"] }
|
37
|
+
let(:headers_cache_keys) { ["cache:#{url}", "v:,l:,headers"] }
|
37
38
|
let(:url) { 'http://localhost:8000/test' }
|
38
39
|
|
39
40
|
context 'when there is no previous cached response' do
|
@@ -44,10 +45,16 @@ RSpec.describe 'Requests with caching' do
|
|
44
45
|
|
45
46
|
it 'sets the new response onto the cache' do
|
46
47
|
expect { subject.get(url) }
|
47
|
-
.to change { Routemaster::Config.cache_redis.hget(*
|
48
|
+
.to change { Routemaster::Config.cache_redis.hget(*body_cache_keys)}
|
48
49
|
.from(nil)
|
49
50
|
.to({ field: 'test'}.to_json)
|
50
51
|
end
|
52
|
+
|
53
|
+
it 'sets the response headers onto the cache' do
|
54
|
+
expect { subject.get(url) }
|
55
|
+
.to change { Routemaster::Config.cache_redis.hget(*headers_cache_keys)}
|
56
|
+
.from(nil)
|
57
|
+
end
|
51
58
|
end
|
52
59
|
|
53
60
|
context 'when there is a previous cached response' do
|
@@ -61,7 +68,7 @@ RSpec.describe 'Requests with caching' do
|
|
61
68
|
|
62
69
|
it 'does not make an http call' do
|
63
70
|
response = subject.get(url)
|
64
|
-
expect(response.
|
71
|
+
expect(response.env.request).to be_empty
|
65
72
|
end
|
66
73
|
end
|
67
74
|
end
|
@@ -8,11 +8,11 @@ describe Routemaster::Middleware::Authenticate do
|
|
8
8
|
let(:app) { described_class.new(ErrorRackApp.new, options) }
|
9
9
|
let(:listener) { double 'listener', on_authenticate: nil }
|
10
10
|
let(:options) {{ uuid: 'demo' }}
|
11
|
-
|
11
|
+
|
12
12
|
def perform
|
13
13
|
post '/whatever'
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
before { Wisper.subscribe(listener, scope: described_class.name, prefix: true) }
|
17
17
|
after { Wisper::GlobalListeners.clear }
|
18
18
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'routemaster/resources/rest_resource'
|
3
|
+
|
4
|
+
module Routemaster
|
5
|
+
module Resources
|
6
|
+
RSpec.describe RestResource do
|
7
|
+
let(:url) { 'test_url' }
|
8
|
+
let(:client) { double('Client') }
|
9
|
+
let(:params) { {} }
|
10
|
+
|
11
|
+
subject { described_class.new(url, client: client) }
|
12
|
+
|
13
|
+
describe '#create' do
|
14
|
+
it 'posts to the given url' do
|
15
|
+
expect(client).to receive(:post).with(url, body: params)
|
16
|
+
subject.create(params)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#show' do
|
21
|
+
it 'gets to the given url' do
|
22
|
+
expect(client).to receive(:get).with(url)
|
23
|
+
subject.show(1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#index' do
|
28
|
+
it 'gets to the given url' do
|
29
|
+
expect(client).to receive(:get).with(url)
|
30
|
+
subject.index
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'routemaster/responses/hateoas_response'
|
3
|
+
|
4
|
+
module Routemaster
|
5
|
+
module Responses
|
6
|
+
RSpec.describe HateoasResponse do
|
7
|
+
let(:response) { double('Response', status: status, body: body, headers: headers) }
|
8
|
+
let(:status) { 200 }
|
9
|
+
let(:body) { {}.to_json }
|
10
|
+
let(:headers) { {} }
|
11
|
+
|
12
|
+
subject { described_class.new(response) }
|
13
|
+
|
14
|
+
context 'link traversal' do
|
15
|
+
let(:body) do
|
16
|
+
{
|
17
|
+
'_links' => {
|
18
|
+
'self' => { 'href' => 'self_url' },
|
19
|
+
'resource_a' => { 'href' => 'resource_a_url' },
|
20
|
+
'resource_b' => { 'href' => 'resource_b_url' }
|
21
|
+
}
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'creates a method for every key in _links attribute' do
|
26
|
+
expect(subject.resource_a.url).to eq('resource_a_url')
|
27
|
+
expect(subject.resource_b.url).to eq('resource_b_url')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'creates a _self method if there is a link with name self' do
|
31
|
+
expect(subject._self.url).to eq('self_url')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'raise an exception when requested link does not exist' do
|
35
|
+
expect { subject.some_unsupported_link }.to raise_error(NoMethodError)
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#body_without_links' do
|
39
|
+
before do
|
40
|
+
body.merge!('foo' => 'bar')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns the body without the _links key' do
|
44
|
+
expect(subject.body_without_links).to eq({ 'foo' => 'bar' })
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|