sitehub 0.5.0.alpha7 → 0.5.0.alpha8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/sitehub/builder.rb +10 -3
- data/lib/sitehub/candidate_routes.rb +5 -5
- data/lib/sitehub/collection.rb +2 -2
- data/lib/sitehub/collection/route_collection.rb +3 -0
- data/lib/sitehub/collection/split_route_collection.rb +1 -1
- data/lib/sitehub/config_server.rb +32 -0
- data/lib/sitehub/core.rb +2 -2
- data/lib/sitehub/middleware/candidate_route_mappings.rb +20 -0
- data/lib/sitehub/middleware/config_loader.rb +15 -18
- data/lib/sitehub/version.rb +1 -1
- data/spec/integration/access_logs_spec.rb +15 -4
- data/spec/integration/error_handling_spec.rb +77 -0
- data/spec/sitehub/builder_spec.rb +21 -0
- data/spec/sitehub/candidate_routes_spec.rb +10 -7
- data/spec/sitehub/collection_spec.rb +1 -1
- data/spec/sitehub/config_server_spec.rb +48 -0
- data/spec/sitehub/downstream_client_spec.rb +9 -0
- data/spec/sitehub/middleware/candidate_route_mappings_spec.rb +69 -2
- data/spec/sitehub/middleware/config_loader_spec.rb +76 -5
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2389132d290dc9bcff1fca5999cc75802e31af1a
|
4
|
+
data.tar.gz: dfca66250eac7e57b5ec53002d2c662ede650688
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27a39c2ac4aea35dcf4ae1b73a7ad8fd77c23f3f0fc1e22c4829a0052eec38cec2d1d4eaac32e84906e442297855eefa7afce5e6eccf1a33605a04ca84fcd001
|
7
|
+
data.tar.gz: fb3e3f8a9d9eb65ef2ad3b714bec96a716cbaf5d8b0fde70dd5e5418a887901e7f07403f4e63249e4f736ce4e10a7f723afe90d21b2312803e0c22de771c4341
|
data/Gemfile.lock
CHANGED
data/lib/sitehub/builder.rb
CHANGED
@@ -7,8 +7,15 @@ class SiteHub
|
|
7
7
|
include Middleware
|
8
8
|
extend GetterSetterMethods
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
DEFAULT_CACHING_OPTIONS = { expires_in: 30 }.freeze
|
11
|
+
|
12
|
+
attr_reader :core, :config_server_url, :config_server_caching_options
|
13
|
+
getter_setters :access_logger, :error_logger
|
14
|
+
|
15
|
+
def config_server(url, caching_options: DEFAULT_CACHING_OPTIONS)
|
16
|
+
@config_server_url = url
|
17
|
+
@config_server_caching_options = caching_options
|
18
|
+
end
|
12
19
|
|
13
20
|
def force_ssl(except: [])
|
14
21
|
@force_ssl = true
|
@@ -22,7 +29,7 @@ class SiteHub
|
|
22
29
|
|
23
30
|
def build
|
24
31
|
add_default_middleware
|
25
|
-
use ConfigLoader,
|
32
|
+
use ConfigLoader, config_server_url, caching_options: config_server_caching_options if config_server_url
|
26
33
|
apply_middleware(core.build)
|
27
34
|
end
|
28
35
|
|
@@ -15,7 +15,7 @@ require_relative 'downstream_client'
|
|
15
15
|
|
16
16
|
class SiteHub
|
17
17
|
class CandidateRoutes
|
18
|
-
class
|
18
|
+
class InvalidDefinitionError < StandardError
|
19
19
|
end
|
20
20
|
|
21
21
|
ROUTES_WITH_SPLITS_MSG = 'you cant register routes and splits at the same level'.freeze
|
@@ -37,11 +37,11 @@ class SiteHub
|
|
37
37
|
child_label = id.child_label(label)
|
38
38
|
|
39
39
|
route = if block
|
40
|
-
raise
|
40
|
+
raise InvalidDefinitionError, candidate_definition_msg unless percentage || rule
|
41
41
|
warn(IGNORING_URL_MSG) if url
|
42
42
|
new(rule: rule, id: child_label, &block).build
|
43
43
|
else
|
44
|
-
raise
|
44
|
+
raise InvalidDefinitionError, URL_REQUIRED_MSG unless url
|
45
45
|
forward_proxy(url: url, label: child_label, rule: rule)
|
46
46
|
end
|
47
47
|
|
@@ -56,7 +56,7 @@ class SiteHub
|
|
56
56
|
def candidates(collection = nil)
|
57
57
|
return @endpoints ||= Collection::RouteCollection.new unless collection
|
58
58
|
|
59
|
-
raise
|
59
|
+
raise InvalidDefinitionError, ROUTES_WITH_SPLITS_MSG if @endpoints && !@endpoints.equal?(collection)
|
60
60
|
@endpoints = collection
|
61
61
|
end
|
62
62
|
|
@@ -96,7 +96,7 @@ class SiteHub
|
|
96
96
|
return unless block_given?
|
97
97
|
|
98
98
|
instance_eval(&block)
|
99
|
-
raise
|
99
|
+
raise InvalidDefinitionError unless valid?
|
100
100
|
end
|
101
101
|
|
102
102
|
def method_missing(method, *args, &block)
|
data/lib/sitehub/collection.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
class SiteHub
|
2
2
|
class Collection < Hash
|
3
|
-
class
|
3
|
+
class DuplicateVersionError < StandardError
|
4
4
|
end
|
5
5
|
|
6
6
|
module ClassMethods
|
@@ -16,7 +16,7 @@ class SiteHub
|
|
16
16
|
alias_method :add_backup, :add
|
17
17
|
|
18
18
|
send(:define_method, :add) do |id, value, *args|
|
19
|
-
raise
|
19
|
+
raise DuplicateVersionError, UNIQUE_LABELS_MSG if key?(id)
|
20
20
|
add_backup id, value, *args
|
21
21
|
end
|
22
22
|
end
|
@@ -3,7 +3,7 @@ require_relative 'split_route_collection/split'
|
|
3
3
|
class SiteHub
|
4
4
|
class Collection < Hash
|
5
5
|
class SplitRouteCollection < Collection
|
6
|
-
class InvalidSplitException <
|
6
|
+
class InvalidSplitException < StandardError
|
7
7
|
end
|
8
8
|
|
9
9
|
FIXNUM_ERR_MSG = 'splits must be a Fixnum'.freeze
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class SiteHub
|
2
|
+
class ConfigServer
|
3
|
+
BAD_JSON_MSG = 'Illegal JSON returned from config server: %s'.freeze
|
4
|
+
UNABLE_TO_CONTACT_SERVER_MSG = 'Unabled to contact server: %s'.freeze
|
5
|
+
NON_200_RESPONSE_MSG = 'Config server did not respond with a 200, got %s'.freeze
|
6
|
+
|
7
|
+
class Error < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :server_url, :http_client
|
11
|
+
def initialize(url)
|
12
|
+
@server_url = url
|
13
|
+
@http_client = Faraday.new(ssl: { verify: false }) do |con|
|
14
|
+
con.adapter :em_synchrony
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def get
|
19
|
+
response = http_client.get(server_url)
|
20
|
+
raise Error, NON_200_RESPONSE_MSG % response.status unless response.status == 200
|
21
|
+
parse_response(response.body)
|
22
|
+
rescue Faraday::Error => e
|
23
|
+
raise Error, UNABLE_TO_CONTACT_SERVER_MSG % e.message
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_response(response_body)
|
27
|
+
JSON(response_body, symbolize_names: true)
|
28
|
+
rescue JSON::ParserError
|
29
|
+
raise Error, BAD_JSON_MSG % response_body
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/sitehub/core.rb
CHANGED
@@ -3,10 +3,10 @@ require 'sitehub/candidate_routes'
|
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
5
|
class SiteHub
|
6
|
-
class InvalidProxyDefinitionException <
|
6
|
+
class InvalidProxyDefinitionException < StandardError
|
7
7
|
end
|
8
8
|
|
9
|
-
class ConfigError <
|
9
|
+
class ConfigError < StandardError
|
10
10
|
end
|
11
11
|
|
12
12
|
class Core
|
@@ -9,6 +9,10 @@ require 'em-http'
|
|
9
9
|
class SiteHub
|
10
10
|
module Middleware
|
11
11
|
class CandidateRouteMappings < Hash
|
12
|
+
INVALID_PATH_MATCHER = 'Matcher for path (%s) was not a valid regexp: %s'.freeze
|
13
|
+
class InvalidPathMatcherError < StandardError
|
14
|
+
end
|
15
|
+
|
12
16
|
NIL_ROUTE = NilRoute.new
|
13
17
|
|
14
18
|
include Equality
|
@@ -39,6 +43,8 @@ class SiteHub
|
|
39
43
|
return
|
40
44
|
end
|
41
45
|
|
46
|
+
mapped_path = string_to_regexp(mapped_path) if string_containing_regexp?(mapped_path)
|
47
|
+
|
42
48
|
self[mapped_path] = CandidateRoutes.new(sitehub_cookie_name: sitehub_cookie_name,
|
43
49
|
mapped_path: mapped_path,
|
44
50
|
&block).tap do |builder|
|
@@ -60,6 +66,20 @@ class SiteHub
|
|
60
66
|
end
|
61
67
|
end
|
62
68
|
end
|
69
|
+
|
70
|
+
def string_containing_regexp?(obj)
|
71
|
+
return false unless obj.is_a?(String)
|
72
|
+
obj.start_with?('%r{') && obj.end_with?('}')
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def string_to_regexp(mapped_path)
|
78
|
+
regexp_string = mapped_path.to_s.sub(/^%r{/, '').sub(/}$/, '')
|
79
|
+
Regexp.compile(regexp_string)
|
80
|
+
rescue RegexpError => e
|
81
|
+
raise InvalidPathMatcherError, format(INVALID_PATH_MATCHER, regexp_string, e.message)
|
82
|
+
end
|
63
83
|
end
|
64
84
|
end
|
65
85
|
end
|
@@ -1,36 +1,33 @@
|
|
1
1
|
require 'active_support'
|
2
|
+
require 'sitehub/config_server'
|
2
3
|
class SiteHub
|
3
|
-
class ConfigServer
|
4
|
-
attr_reader :server_url, :http_client
|
5
|
-
def initialize(url)
|
6
|
-
@server_url = url
|
7
|
-
@http_client = Faraday.new(ssl: { verify: false }) do |con|
|
8
|
-
con.adapter :em_synchrony
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def get
|
13
|
-
JSON(http_client.get(server_url).body, symbolize_names: true)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
4
|
module Middleware
|
18
5
|
class ConfigLoader
|
19
|
-
attr_reader :config_server, :app, :cache
|
6
|
+
attr_reader :config_server, :app, :cache, :caching_options
|
20
7
|
|
21
|
-
def initialize(_app, config_server_url)
|
8
|
+
def initialize(_app, config_server_url, caching_options:)
|
22
9
|
@config_server = ConfigServer.new(config_server_url)
|
23
10
|
@cache = ActiveSupport::Cache::MemoryStore.new(size: 1.megabytes)
|
11
|
+
@caching_options = caching_options
|
24
12
|
end
|
25
13
|
|
26
14
|
def call(env)
|
27
|
-
|
15
|
+
begin
|
16
|
+
load_config
|
17
|
+
rescue ConfigServer::Error => e
|
18
|
+
if @app
|
19
|
+
env[ERRORS] << e.message
|
20
|
+
else
|
21
|
+
raise e unless @app
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
28
25
|
@app.call env
|
29
26
|
end
|
30
27
|
|
31
28
|
# TODO: handle errors connecting to the config server
|
32
29
|
def load_config
|
33
|
-
config = cache.fetch(:sitehub_config,
|
30
|
+
config = cache.fetch(:sitehub_config, caching_options) do
|
34
31
|
config_server.get
|
35
32
|
end
|
36
33
|
|
data/lib/sitehub/version.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'cgi'
|
2
|
+
|
2
3
|
describe 'access_logs' do
|
3
4
|
let(:downstream_url) { 'http://localhost:12345/experiment1' }
|
4
5
|
|
@@ -30,10 +31,13 @@ describe 'access_logs' do
|
|
30
31
|
let(:query_string) { '' }
|
31
32
|
let(:request_url) { "/endpoint#{query_string.empty? ? '' : "?#{query_string}"}" }
|
32
33
|
|
33
|
-
|
34
|
+
before do
|
34
35
|
query_string_hash = CGI.parse(query_string).collect { |key, value| [key, value.first] }.to_h
|
35
36
|
stub_request(:get, downstream_url).with(query: query_string_hash)
|
36
37
|
get(request_url)
|
38
|
+
end
|
39
|
+
|
40
|
+
subject do
|
37
41
|
access_logger.string
|
38
42
|
end
|
39
43
|
|
@@ -41,20 +45,28 @@ describe 'access_logs' do
|
|
41
45
|
context 'present' do
|
42
46
|
let(:query_string) { 'key=value' }
|
43
47
|
it 'logs it' do
|
44
|
-
expect(subject).to include(
|
48
|
+
expect(subject).to include("GET #{request_url} => #{downstream_url}")
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
48
52
|
context 'not present' do
|
49
53
|
let(:query_string) { '' }
|
50
54
|
it 'is not logged' do
|
55
|
+
expect(subject).to match(/"GET\s#{request_url}\s=>\s#{downstream_url}\s/)
|
51
56
|
expect(subject).to include(query_string)
|
52
57
|
end
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
61
|
+
module WebMock
|
62
|
+
def self.last_request
|
63
|
+
WebMock::RequestRegistry.instance.requested_signatures.hash.keys.first
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
56
67
|
it 'logs the transaction id' do
|
57
|
-
|
68
|
+
expected_id = WebMock.last_request.headers['Sitehub-Transaction-Id']
|
69
|
+
expect(subject).to match(/transaction_id:#{expected_id}/)
|
58
70
|
end
|
59
71
|
|
60
72
|
it 'logs the response status' do
|
@@ -69,7 +81,6 @@ describe 'access_logs' do
|
|
69
81
|
processing_time_matcher = '\d{1}\.\d{4}'
|
70
82
|
transaction_id_matcher = '[a-z\d]+'
|
71
83
|
expected_response_status = 200
|
72
|
-
puts subject
|
73
84
|
expect(subject).to match(/transaction_id:#{transaction_id_matcher}:\s"GET\s#{request_url}\s=>\s#{downstream_url}\s"\s#{expected_response_status}\s-\s#{processing_time_matcher}/)
|
74
85
|
end
|
75
86
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
describe 'error handling' do
|
3
|
+
context 'using config server' do
|
4
|
+
CONFIG_SERVER_URL = 'http://the.config.server'.freeze
|
5
|
+
DOWNSTREAM_URL = 'http://downstream.url'.freeze
|
6
|
+
ERROR_LOGGER = StringIO.new
|
7
|
+
ACCESS_LOGGER = StringIO.new
|
8
|
+
|
9
|
+
include_context :sitehub_json
|
10
|
+
|
11
|
+
let(:config) do
|
12
|
+
{ proxies: [
|
13
|
+
{
|
14
|
+
path: '/',
|
15
|
+
default: DOWNSTREAM_URL
|
16
|
+
}
|
17
|
+
] }
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:downstream_response) { 'downstream response' }
|
21
|
+
|
22
|
+
before do
|
23
|
+
stub_request(:get, DOWNSTREAM_URL).and_return(body: downstream_response)
|
24
|
+
end
|
25
|
+
let(:app) do
|
26
|
+
sitehub = SiteHub.build do
|
27
|
+
config_server(CONFIG_SERVER_URL, caching_options: { force: true })
|
28
|
+
error_logger ERROR_LOGGER
|
29
|
+
access_logger ACCESS_LOGGER
|
30
|
+
end
|
31
|
+
Async::Middleware.new(sitehub)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'reads the config for routing' do
|
35
|
+
stub_request(:get, CONFIG_SERVER_URL).and_return(body: config.to_json)
|
36
|
+
get('/')
|
37
|
+
expect(app.last_response.body).to eq([downstream_response])
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'config server is broken' do
|
41
|
+
let(:bad_status) { 500 }
|
42
|
+
context 'config has not been loaded' do
|
43
|
+
it 'returns an error' do
|
44
|
+
stub_request(:get, CONFIG_SERVER_URL).and_return(status: 500)
|
45
|
+
get('/')
|
46
|
+
expect(app.last_response.status).to eq(500)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'logs an error' do
|
50
|
+
bad_status = 500
|
51
|
+
stub_request(:get, CONFIG_SERVER_URL).and_return(status: bad_status)
|
52
|
+
get('/')
|
53
|
+
expect(ERROR_LOGGER.string).to include(SiteHub::ConfigServer::NON_200_RESPONSE_MSG % bad_status)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'config has been correctly loaded once' do
|
58
|
+
before do
|
59
|
+
stub_request(:get, CONFIG_SERVER_URL).and_return(body: config.to_json)
|
60
|
+
get('/')
|
61
|
+
|
62
|
+
stub_request(:get, CONFIG_SERVER_URL).and_return(status: bad_status)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'uses the old config' do
|
66
|
+
get('/')
|
67
|
+
expect(app.last_response.body).to eq([downstream_response])
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'logs the failure to read config' do
|
71
|
+
get('/')
|
72
|
+
expect(ERROR_LOGGER.string).to include(SiteHub::ConfigServer::NON_200_RESPONSE_MSG % bad_status)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -163,6 +163,27 @@ class SiteHub
|
|
163
163
|
|
164
164
|
expect(middleware_stack).to eq(expected_middleware)
|
165
165
|
end
|
166
|
+
|
167
|
+
it 'defaults caching policy' do
|
168
|
+
subject.config_server(:server_url)
|
169
|
+
|
170
|
+
sitehub = subject.build
|
171
|
+
|
172
|
+
config_loader = find_middleware(sitehub, Middleware::ConfigLoader)
|
173
|
+
expect(config_loader.caching_options).to eq(described_class::DEFAULT_CACHING_OPTIONS)
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'caching options specified' do
|
177
|
+
it 'uses them' do
|
178
|
+
caching_options = { force: true }
|
179
|
+
subject.config_server(:server_url, caching_options: caching_options)
|
180
|
+
|
181
|
+
sitehub = subject.build
|
182
|
+
|
183
|
+
config_loader = find_middleware(sitehub, Middleware::ConfigLoader)
|
184
|
+
expect(config_loader.caching_options).to eq(caching_options)
|
185
|
+
end
|
186
|
+
end
|
166
187
|
end
|
167
188
|
|
168
189
|
it 'adds them in the right order' do
|
@@ -148,7 +148,7 @@ class SiteHub
|
|
148
148
|
it 'raises an error' do
|
149
149
|
subject.candidates(Collection::SplitRouteCollection.new)
|
150
150
|
expect { subject.candidates(Collection::RouteCollection.new) }
|
151
|
-
.to raise_error(CandidateRoutes::
|
151
|
+
.to raise_error(CandidateRoutes::InvalidDefinitionError)
|
152
152
|
end
|
153
153
|
end
|
154
154
|
end
|
@@ -301,7 +301,7 @@ class SiteHub
|
|
301
301
|
subject.candidates(Collection::SplitRouteCollection.new)
|
302
302
|
expected_message = described_class::PERCENTAGE_NOT_SPECIFIED_MSG
|
303
303
|
expect { subject.add(label: :label) {} }
|
304
|
-
.to raise_exception described_class::
|
304
|
+
.to raise_exception described_class::InvalidDefinitionError, expected_message
|
305
305
|
end
|
306
306
|
end
|
307
307
|
|
@@ -310,7 +310,7 @@ class SiteHub
|
|
310
310
|
subject.candidates(Collection::RouteCollection.new)
|
311
311
|
expected_message = described_class::RULE_NOT_SPECIFIED_MSG
|
312
312
|
expect { subject.add(label: :label) {} }
|
313
|
-
.to raise_exception described_class::
|
313
|
+
.to raise_exception described_class::InvalidDefinitionError, expected_message
|
314
314
|
end
|
315
315
|
end
|
316
316
|
end
|
@@ -327,8 +327,11 @@ class SiteHub
|
|
327
327
|
rule = proc { true }
|
328
328
|
subject.add(rule: rule, label: :label, &block)
|
329
329
|
|
330
|
-
expected_endpoints = described_class.new(id: :label,
|
331
|
-
|
330
|
+
expected_endpoints = described_class.new(id: :label,
|
331
|
+
sitehub_cookie_name: :cookie_name,
|
332
|
+
rule: rule,
|
333
|
+
mapped_path: subject.mapped_path,
|
334
|
+
&block).tap do |builder|
|
332
335
|
builder.sitehub_cookie_name subject.sitehub_cookie_name
|
333
336
|
end.build
|
334
337
|
|
@@ -342,7 +345,7 @@ class SiteHub
|
|
342
345
|
subject.add rule: rule, label: :label do
|
343
346
|
split percentage: 20, url: :url, label: :label1
|
344
347
|
end
|
345
|
-
end.to raise_exception described_class::
|
348
|
+
end.to raise_exception described_class::InvalidDefinitionError
|
346
349
|
end
|
347
350
|
end
|
348
351
|
end
|
@@ -464,7 +467,7 @@ class SiteHub
|
|
464
467
|
context 'already set with a different collection' do
|
465
468
|
it 'raise an error' do
|
466
469
|
subject.candidates(:collection1)
|
467
|
-
expect { subject.candidates(:collection2) }.to raise_exception described_class::
|
470
|
+
expect { subject.candidates(:collection2) }.to raise_exception described_class::InvalidDefinitionError
|
468
471
|
end
|
469
472
|
end
|
470
473
|
end
|
@@ -32,7 +32,7 @@ class SiteHub
|
|
32
32
|
subject.add(duplicate.id, duplicate)
|
33
33
|
expected_message = described_class::ClassMethods::UNIQUE_LABELS_MSG
|
34
34
|
expect { subject.add(duplicate.id, duplicate) }
|
35
|
-
.to raise_exception described_class::
|
35
|
+
.to raise_exception described_class::DuplicateVersionError, expected_message
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'sitehub/config_server'
|
2
|
+
|
3
|
+
class SiteHub
|
4
|
+
describe ConfigServer do
|
5
|
+
include_context :sitehub_json
|
6
|
+
|
7
|
+
let(:server_url) { 'http://www.server.url' }
|
8
|
+
let(:config) { { config: 'value' } }
|
9
|
+
|
10
|
+
subject do
|
11
|
+
described_class.new(server_url)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#get' do
|
15
|
+
context 'non 200 returned' do
|
16
|
+
it 'raises an error' do
|
17
|
+
bad_response_code = 500
|
18
|
+
stub_request(:get, server_url).to_return(body: config.to_json, status: bad_response_code)
|
19
|
+
expected_message = described_class::NON_200_RESPONSE_MSG % bad_response_code
|
20
|
+
expect { subject.get }.to raise_error(described_class::Error, expected_message)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'exception thrown in client' do
|
25
|
+
it 'raises an error' do
|
26
|
+
error_msg = 'error from library'
|
27
|
+
expect_any_instance_of(Faraday::Connection).to receive(:get).and_raise(Faraday::ConnectionFailed, error_msg)
|
28
|
+
expected_message = described_class::UNABLE_TO_CONTACT_SERVER_MSG % error_msg
|
29
|
+
expect { subject.get }.to raise_error(described_class::Error, expected_message)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'malformed json returned' do
|
34
|
+
it 'returns an error' do
|
35
|
+
bad_json = 'bad json'
|
36
|
+
stub_request(:get, server_url).to_return(body: bad_json)
|
37
|
+
expected_message = described_class::BAD_JSON_MSG % bad_json
|
38
|
+
expect { subject.get }.to raise_error(described_class::Error, expected_message)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'returns config as a hash' do
|
43
|
+
stub_request(:get, server_url).to_return(body: config.to_json)
|
44
|
+
expect(subject.get).to eq(config)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -45,6 +45,15 @@ class SiteHub
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
context 'query string' do
|
49
|
+
let(:params) { { 'key' => 'value' } }
|
50
|
+
let(:env) { env_for(path: mapped_path, params_or_body: params) }
|
51
|
+
it 'preserves the query string when forwarding the request' do
|
52
|
+
stub_request(http_method, current_version_url).with(query: params)
|
53
|
+
subject.call(request)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
48
57
|
it 'preserves the headers when forwarding request' do
|
49
58
|
http_headers['HTTP_HEADER'] = 'value'
|
50
59
|
subject.call(request)
|
@@ -21,10 +21,10 @@ class SiteHub
|
|
21
21
|
end
|
22
22
|
|
23
23
|
before do
|
24
|
-
subject.init
|
24
|
+
# subject.init
|
25
25
|
end
|
26
26
|
|
27
|
-
describe '#
|
27
|
+
describe '#add_route' do
|
28
28
|
def route(app, id:)
|
29
29
|
Route.new(app,
|
30
30
|
id: id,
|
@@ -41,6 +41,51 @@ class SiteHub
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
context 'mapped_path' do
|
45
|
+
let(:expected_route) do
|
46
|
+
proxy = ForwardProxy.new(mapped_path: expected_mapping, mapped_url: base_url)
|
47
|
+
route(proxy, id: :current)
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'mapped_path is a string' do
|
51
|
+
let(:mapped_path) { 'string' }
|
52
|
+
let(:expected_mapping) { mapped_path }
|
53
|
+
it 'stores the route against that strubg' do
|
54
|
+
expect(subject[mapped_path][:current]).to eq(expected_route)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'mapped_path is a valid regex' do
|
59
|
+
let(:mapped_path) { /regular_expression/ }
|
60
|
+
let(:expected_mapping) { mapped_path }
|
61
|
+
it 'stores the route against that regexp' do
|
62
|
+
expect(subject[mapped_path][:current]).to eq(expected_route)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'is a string containing a valid regexp' do
|
67
|
+
let(:expected_mapping) { /regexp/ }
|
68
|
+
let(:mapped_path) { '%r{regexp}' }
|
69
|
+
it 'stores the route against the string coverted to a regexp' do
|
70
|
+
expect(subject[expected_mapping][:current]).to eq(expected_route)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'is a string containing malformed regexp' do
|
75
|
+
let(:mapped_path) { '%r{*}' }
|
76
|
+
|
77
|
+
it 'raises an error' do
|
78
|
+
expected_message = begin
|
79
|
+
Regexp.compile('*')
|
80
|
+
rescue RegexpError => e
|
81
|
+
format(described_class::INVALID_PATH_MATCHER, '*', e.message)
|
82
|
+
end
|
83
|
+
|
84
|
+
expect { subject }.to raise_error(described_class::InvalidPathMatcherError, expected_message)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
44
89
|
context 'url specified' do
|
45
90
|
let(:expected_route) do
|
46
91
|
proxy = ForwardProxy.new(mapped_path: mapped_path, mapped_url: :url)
|
@@ -145,6 +190,28 @@ class SiteHub
|
|
145
190
|
end
|
146
191
|
end
|
147
192
|
end
|
193
|
+
|
194
|
+
describe '#string_containing_regexp?' do
|
195
|
+
context 'parameter is not a string' do
|
196
|
+
it 'it returns false' do
|
197
|
+
expect(subject.string_containing_regexp?(//)).to eq(false)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'parameter is a string' do
|
202
|
+
context 'contains a regexp' do
|
203
|
+
it 'it returns true' do
|
204
|
+
expect(subject.string_containing_regexp?('%r{}')).to be(true)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
context 'does not contain a regexp' do
|
209
|
+
it 'returns false' do
|
210
|
+
expect(subject.string_containing_regexp?('')).to eq(false)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
148
215
|
end
|
149
216
|
end
|
150
217
|
end
|
@@ -26,24 +26,40 @@ class SiteHub
|
|
26
26
|
stub_request(:get, server_url).to_return(body: config.to_json)
|
27
27
|
end
|
28
28
|
|
29
|
+
let(:no_caching) { { force: true } }
|
29
30
|
subject do
|
30
|
-
described_class.new(:app, server_url)
|
31
|
+
described_class.new(:app, server_url, caching_options: no_caching)
|
31
32
|
end
|
32
33
|
|
33
34
|
describe '#load_config' do
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
subject do
|
36
|
+
caching_enabled = { expires_in: 30 }
|
37
|
+
described_class.new(:app, server_url, caching_options: caching_enabled)
|
38
|
+
end
|
37
39
|
|
38
|
-
|
40
|
+
let(:expected_core) do
|
41
|
+
Core.new do
|
39
42
|
sitehub_cookie_name 'sitehub.recorded_route'
|
40
43
|
proxy '/route_1' do
|
41
44
|
route label: :label_1, url: 'http://lvl-up.uk/'
|
42
45
|
end
|
43
46
|
end.build
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'loads config' do
|
50
|
+
expect(subject.app).to be_nil
|
51
|
+
subject.load_config
|
44
52
|
|
45
53
|
expect(subject.app).to eq(expected_core)
|
46
54
|
end
|
55
|
+
|
56
|
+
it 'loads it from cache' do
|
57
|
+
different_config = { proxies: [] }
|
58
|
+
subject.load_config
|
59
|
+
stub_request(:get, server_url).to_return(body: different_config.to_json)
|
60
|
+
subject.load_config
|
61
|
+
expect(subject.app).to eq(expected_core)
|
62
|
+
end
|
47
63
|
end
|
48
64
|
|
49
65
|
describe '#call' do
|
@@ -58,6 +74,61 @@ class SiteHub
|
|
58
74
|
|
59
75
|
expect(subject.call(:env)).to eq(response)
|
60
76
|
end
|
77
|
+
|
78
|
+
context 'config not retrievable' do
|
79
|
+
let(:config_server) do
|
80
|
+
instance_double(ConfigServer).tap do |config_server|
|
81
|
+
expect(ConfigServer).to receive(:new).and_return(config_server)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
let(:env) { { ERRORS => [] } }
|
86
|
+
|
87
|
+
context 'first call ever received' do
|
88
|
+
it 'raises an error' do
|
89
|
+
expected_message = 'error message'
|
90
|
+
expect(config_server).to receive(:get).and_raise(ConfigServer::Error, expected_message)
|
91
|
+
expect { subject.call(env) }.to raise_error(ConfigServer::Error, expected_message)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'does not write anything to errors' do
|
95
|
+
expect(config_server).to receive(:get).and_raise(ConfigServer::Error)
|
96
|
+
|
97
|
+
expect { subject.call(env) }.to raise_error(ConfigServer::Error)
|
98
|
+
expect(env[ERRORS]).to be_empty
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'config previously loaded' do
|
103
|
+
subject do
|
104
|
+
described_class.new(:app, server_url, caching_options: no_caching)
|
105
|
+
end
|
106
|
+
|
107
|
+
let(:response) { [200, {}, []] }
|
108
|
+
before do
|
109
|
+
app = proc do |_env|
|
110
|
+
response
|
111
|
+
end
|
112
|
+
|
113
|
+
expect(config_server).to receive(:get).and_return(config)
|
114
|
+
expect(Core).to receive(:from_hash).with(config).and_return(double(build: app))
|
115
|
+
subject.call(env)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'retains the original config' do
|
119
|
+
expect(config_server).to receive(:get).and_raise(ConfigServer::Error)
|
120
|
+
expect(subject.call(env)).to eq(response)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'writes an error to the error log' do
|
124
|
+
expected_error_message = 'error message'
|
125
|
+
expect(config_server).to receive(:get).and_raise(ConfigServer::Error, expected_error_message)
|
126
|
+
|
127
|
+
subject.call(env)
|
128
|
+
expect(env[ERRORS]).to include(expected_error_message)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
61
132
|
end
|
62
133
|
end
|
63
134
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sitehub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.0.
|
4
|
+
version: 0.5.0.alpha8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ladtech
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08
|
11
|
+
date: 2016-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -346,6 +346,7 @@ files:
|
|
346
346
|
- lib/sitehub/collection/split_route_collection.rb
|
347
347
|
- lib/sitehub/collection/split_route_collection/split.rb
|
348
348
|
- lib/sitehub/collection_methods.rb
|
349
|
+
- lib/sitehub/config_server.rb
|
349
350
|
- lib/sitehub/constants.rb
|
350
351
|
- lib/sitehub/constants/http_header_keys.rb
|
351
352
|
- lib/sitehub/constants/rack_http_header_keys.rb
|
@@ -390,6 +391,7 @@ files:
|
|
390
391
|
- sitehub.gemspec
|
391
392
|
- spec/equality_spec.rb
|
392
393
|
- spec/integration/access_logs_spec.rb
|
394
|
+
- spec/integration/error_handling_spec.rb
|
393
395
|
- spec/integration/middleware_spec.rb
|
394
396
|
- spec/integration/version_affinity_spec.rb
|
395
397
|
- spec/sitehub/builder_spec.rb
|
@@ -398,6 +400,7 @@ files:
|
|
398
400
|
- spec/sitehub/collection/split_route_collection/split_spec.rb
|
399
401
|
- spec/sitehub/collection/split_route_collection_spec.rb
|
400
402
|
- spec/sitehub/collection_spec.rb
|
403
|
+
- spec/sitehub/config_server_spec.rb
|
401
404
|
- spec/sitehub/cookie/attribute_spec.rb
|
402
405
|
- spec/sitehub/cookie/flag_spec.rb
|
403
406
|
- spec/sitehub/cookie/rewriting_spec.rb
|