sitehub 0.5.0.alpha7 → 0.5.0.alpha8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|