boltless 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +8 -0
- data/Guardfile +44 -0
- data/Makefile +138 -0
- data/Rakefile +26 -0
- data/docker-compose.yml +19 -0
- data/lib/boltless/configuration.rb +69 -0
- data/lib/boltless/errors/invalid_json_error.rb +9 -0
- data/lib/boltless/errors/request_error.rb +24 -0
- data/lib/boltless/errors/response_error.rb +30 -0
- data/lib/boltless/errors/transaction_begin_error.rb +9 -0
- data/lib/boltless/errors/transaction_in_bad_state_error.rb +11 -0
- data/lib/boltless/errors/transaction_not_found_error.rb +11 -0
- data/lib/boltless/errors/transaction_rollback_error.rb +26 -0
- data/lib/boltless/extensions/configuration_handling.rb +37 -0
- data/lib/boltless/extensions/connection_pool.rb +127 -0
- data/lib/boltless/extensions/operations.rb +175 -0
- data/lib/boltless/extensions/transactions.rb +301 -0
- data/lib/boltless/extensions/utilities.rb +187 -0
- data/lib/boltless/request.rb +386 -0
- data/lib/boltless/result.rb +98 -0
- data/lib/boltless/result_row.rb +90 -0
- data/lib/boltless/statement_collector.rb +40 -0
- data/lib/boltless/transaction.rb +234 -0
- data/lib/boltless/version.rb +23 -0
- data/lib/boltless.rb +36 -0
- data/spec/benchmark/transfer.rb +57 -0
- data/spec/boltless/extensions/configuration_handling_spec.rb +39 -0
- data/spec/boltless/extensions/connection_pool_spec.rb +131 -0
- data/spec/boltless/extensions/operations_spec.rb +189 -0
- data/spec/boltless/extensions/transactions_spec.rb +418 -0
- data/spec/boltless/extensions/utilities_spec.rb +546 -0
- data/spec/boltless/request_spec.rb +946 -0
- data/spec/boltless/result_row_spec.rb +161 -0
- data/spec/boltless/result_spec.rb +127 -0
- data/spec/boltless/statement_collector_spec.rb +45 -0
- data/spec/boltless/transaction_spec.rb +601 -0
- data/spec/boltless_spec.rb +11 -0
- data/spec/fixtures/files/raw_result.yml +21 -0
- data/spec/fixtures/files/raw_result_with_graph_result.yml +48 -0
- data/spec/fixtures/files/raw_result_with_meta.yml +11 -0
- data/spec/fixtures/files/raw_result_with_stats.yml +26 -0
- data/spec/spec_helper.rb +89 -0
- metadata +384 -0
@@ -0,0 +1,234 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Boltless
|
4
|
+
# A single neo4j transaction representation.
|
5
|
+
#
|
6
|
+
# When passing Cypher statements you can tweak some HTTP API result options
|
7
|
+
# while passing the following keys to the Cypher parameters (they wont be
|
8
|
+
# sent to neo4j):
|
9
|
+
#
|
10
|
+
# * +with_stats: true|false+: whenever to include statement
|
11
|
+
# statistics, or not (see: https://bit.ly/3SKXfC8)
|
12
|
+
# * +result_as_graph: true|false+: whenever to return the result as a graph
|
13
|
+
# structure that can be visualized (see: https://bit.ly/3doJw3Z)
|
14
|
+
#
|
15
|
+
# Error handling details (see: https://bit.ly/3pdqTCy):
|
16
|
+
#
|
17
|
+
# > If there is an error in a request, the server will roll back the
|
18
|
+
# > transaction. You can tell if the transaction is still open by inspecting
|
19
|
+
# > the response for the presence/absence of the transaction key.
|
20
|
+
class Transaction
|
21
|
+
# We allow to read some internal configurations
|
22
|
+
attr_reader :access_mode, :id, :raw_state
|
23
|
+
|
24
|
+
# We allow to access helpful utilities straigth from here
|
25
|
+
delegate :build_cypher, :prepare_label, :prepare_type, :prepare_string,
|
26
|
+
:to_options, :resolve_cypher,
|
27
|
+
to: Boltless
|
28
|
+
|
29
|
+
# Setup a new neo4j transaction management instance.
|
30
|
+
#
|
31
|
+
# @param connection [HTTP::Client] a ready to use persistent
|
32
|
+
# connection object
|
33
|
+
# @param database [String, Symbol] the neo4j database to use
|
34
|
+
# @param access_mode [String, Symbol] the neo4j
|
35
|
+
# transaction mode (+:read+, or +:write+)
|
36
|
+
# @param raw_results [Boolean] whenever to return the plain HTTP API JSON
|
37
|
+
# results (as plain +Hash{Symbol => Mixed}/Array+ data), or not (then we
|
38
|
+
# return +Array<Boltless::Result>+ structs
|
39
|
+
def initialize(connection, database: Boltless.configuration.default_db,
|
40
|
+
access_mode: :write, raw_results: false)
|
41
|
+
@request = Request.new(connection, access_mode: access_mode,
|
42
|
+
database: database,
|
43
|
+
raw_results: raw_results)
|
44
|
+
@access_mode = access_mode
|
45
|
+
@raw_state = :not_yet_started
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return the transaction state as +ActiveSupport::StringInquirer+
|
49
|
+
# for convenience.
|
50
|
+
#
|
51
|
+
# @return [ActiveSupport::StringInquirer] the transaction state
|
52
|
+
def state
|
53
|
+
ActiveSupport::StringInquirer.new(@raw_state.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Begin a new transaction. No exceptions will be rescued.
|
57
|
+
#
|
58
|
+
# @return [TrueClass] when the transaction was successfully started
|
59
|
+
#
|
60
|
+
# @raise [Errors::RequestError] when an error occurs, see request object
|
61
|
+
# for fine-grained details
|
62
|
+
def begin!
|
63
|
+
# We do not allow messing around in wrong states
|
64
|
+
unless @raw_state == :not_yet_started
|
65
|
+
raise Errors::TransactionInBadStateError,
|
66
|
+
"Transaction already #{@raw_state}"
|
67
|
+
end
|
68
|
+
|
69
|
+
@id = @request.begin_transaction
|
70
|
+
@raw_state = :open
|
71
|
+
true
|
72
|
+
end
|
73
|
+
|
74
|
+
# Begin a new transaction. We rescue all errors transparently.
|
75
|
+
#
|
76
|
+
# @return [Boolean] whenever the transaction was successfully started,
|
77
|
+
# or not
|
78
|
+
def begin
|
79
|
+
handle_errors(false) { begin! }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Run a single Cypher statement inside the transaction. This results
|
83
|
+
# in a single HTTP API request for the statement.
|
84
|
+
#
|
85
|
+
# @param cypher [String] the Cypher statement to run
|
86
|
+
# @param args [Hash{Symbol => Mixed}] the additional Cypher parameters
|
87
|
+
# @return [Hash{Symbol => Mixed}] the raw neo4j results
|
88
|
+
#
|
89
|
+
# @raise [Errors::RequestError] when an error occurs, see request object
|
90
|
+
# for fine-grained details
|
91
|
+
def run!(cypher, **args)
|
92
|
+
# We do not allow messing around in wrong states
|
93
|
+
raise Errors::TransactionInBadStateError, 'Transaction not open' \
|
94
|
+
unless @raw_state == :open
|
95
|
+
|
96
|
+
@request.run_query(@id, Request.statement_payload(cypher, **args)).first
|
97
|
+
end
|
98
|
+
|
99
|
+
# Run a single Cypher statement inside the transaction. This results in a
|
100
|
+
# single HTTP API request for the statement. We rescue all errors
|
101
|
+
# transparently.
|
102
|
+
#
|
103
|
+
# @param cypher [String] the Cypher statement to run
|
104
|
+
# @param args [Hash{Symbol => Mixed}] the additional Cypher parameters
|
105
|
+
# @return [Array<Hash{Symbol => Mixed}>, nil] the raw neo4j results,
|
106
|
+
# or +nil+ in case of errors
|
107
|
+
def run(cypher, **args)
|
108
|
+
handle_errors { run!(cypher, **args) }
|
109
|
+
end
|
110
|
+
|
111
|
+
# Run a multiple Cypher statement inside the transaction. This results
|
112
|
+
# in a single HTTP API request for all the statements.
|
113
|
+
#
|
114
|
+
# @param statements [Array<Hash>] the Cypher statements to run
|
115
|
+
# @return [Array<Hash{Symbol => Mixed}>] the raw neo4j results
|
116
|
+
#
|
117
|
+
# @raise [Errors::RequestError] when an error occurs, see request object
|
118
|
+
# for fine-grained details
|
119
|
+
def run_in_batch!(*statements)
|
120
|
+
# We do not allow messing around in wrong states
|
121
|
+
raise Errors::TransactionInBadStateError, 'Transaction not open' \
|
122
|
+
unless @raw_state == :open
|
123
|
+
|
124
|
+
@request.run_query(@id, *Request.statement_payloads(*statements))
|
125
|
+
end
|
126
|
+
|
127
|
+
# Run a multiple Cypher statement inside the transaction. This results
|
128
|
+
# in a single HTTP API request for all the statements. We rescue all errors
|
129
|
+
# transparently.
|
130
|
+
#
|
131
|
+
# @param statements [Array<Hash>] the Cypher statements to run
|
132
|
+
# @return [Array<Hash{Symbol => Mixed}>, nil] the raw neo4j results,
|
133
|
+
# or +nil+ in case of errors
|
134
|
+
#
|
135
|
+
# @raise [Errors::RequestError] when an error occurs, see request object
|
136
|
+
# for fine-grained details
|
137
|
+
def run_in_batch(*statements)
|
138
|
+
handle_errors { run_in_batch!(*statements) }
|
139
|
+
end
|
140
|
+
|
141
|
+
# Commit the transaction, while also sending finalizing Cypher
|
142
|
+
# statement(s). This results in a single HTTP API request for all the
|
143
|
+
# statement(s). You can also omit the statement(s) in order to just commit
|
144
|
+
# the transaction.
|
145
|
+
#
|
146
|
+
# @param statements [Array<Hash>] the Cypher statements to run
|
147
|
+
# @return [Array<Hash{Symbol => Mixed}>] the raw neo4j results
|
148
|
+
#
|
149
|
+
# @raise [Errors::RequestError] when an error occurs, see request object
|
150
|
+
# for fine-grained details
|
151
|
+
def commit!(*statements)
|
152
|
+
# We do not allow messing around in wrong states
|
153
|
+
raise Errors::TransactionInBadStateError, 'Transaction not open' \
|
154
|
+
unless @raw_state == :open
|
155
|
+
|
156
|
+
@request.commit_transaction(
|
157
|
+
@id,
|
158
|
+
*Request.statement_payloads(*statements)
|
159
|
+
).tap { @raw_state = :closed }
|
160
|
+
end
|
161
|
+
|
162
|
+
# Commit the transaction, while also sending finalizing Cypher
|
163
|
+
# statement(s). This results in a single HTTP API request for all the
|
164
|
+
# statement(s). You can also omit the statement(s) in order to just commit
|
165
|
+
# the transaction. We rescue all errors transparently.
|
166
|
+
#
|
167
|
+
# @param statements [Array<Hash>] the Cypher statements to run
|
168
|
+
# @return [Array<Hash{Symbol => Mixed}>, nil] the raw neo4j results,
|
169
|
+
# or +nil+ in case of errors
|
170
|
+
#
|
171
|
+
# @raise [Errors::RequestError] when an error occurs, see request object
|
172
|
+
# for fine-grained details
|
173
|
+
def commit(*statements)
|
174
|
+
handle_errors { commit!(*statements) }
|
175
|
+
end
|
176
|
+
|
177
|
+
# Rollback this transaction. No exceptions will be rescued.
|
178
|
+
#
|
179
|
+
# @return [TrueClass] when the transaction was successfully rolled back
|
180
|
+
#
|
181
|
+
# @raise [Errors::RequestError] when an error occurs, see request object
|
182
|
+
# for fine-grained details
|
183
|
+
def rollback!
|
184
|
+
# We do not allow messing around in wrong states
|
185
|
+
raise Errors::TransactionInBadStateError, 'Transaction not open' \
|
186
|
+
unless @raw_state == :open
|
187
|
+
|
188
|
+
@request.rollback_transaction(@id)
|
189
|
+
@raw_state = :closed
|
190
|
+
true
|
191
|
+
end
|
192
|
+
|
193
|
+
# Rollback this transaction. We rescue all errors transparently.
|
194
|
+
#
|
195
|
+
# @return [Boolean] whenever the transaction was successfully rolled back,
|
196
|
+
# or not
|
197
|
+
def rollback
|
198
|
+
handle_errors(false) { rollback! }
|
199
|
+
end
|
200
|
+
|
201
|
+
# Handle all request/response errors of the low-level connection for
|
202
|
+
# our non-bang methods in a generic way.
|
203
|
+
#
|
204
|
+
# @param error_result [Proc, Mixed] the object to return on errors, when a
|
205
|
+
# proc is given, we call it with the actual exception object as parameter
|
206
|
+
# and use the result of the proc as return value
|
207
|
+
# @yield the given block
|
208
|
+
# @return [Mixed] the result of the block, or on exceptions the
|
209
|
+
# given +error_result+
|
210
|
+
def handle_errors(error_result = nil)
|
211
|
+
yield
|
212
|
+
rescue Errors::RequestError, Errors::ResponseError,
|
213
|
+
Errors::TransactionInBadStateError => e
|
214
|
+
# When an error occured, the transaction is automatically rolled back by
|
215
|
+
# neo4j, so we cannot handle any further interaction
|
216
|
+
cleanup
|
217
|
+
@raw_state = :closed
|
218
|
+
|
219
|
+
# When we got a proc/lambda as error result, call it
|
220
|
+
return error_result.call(e) if error_result.is_a? Proc
|
221
|
+
|
222
|
+
# Otherwise use the error result as it is
|
223
|
+
error_result
|
224
|
+
end
|
225
|
+
|
226
|
+
# Clean the transaction, in order to make it unusable for further
|
227
|
+
# interaction. This prevents users from leaking the transaction context and
|
228
|
+
# mess around with the connection pool.
|
229
|
+
def cleanup
|
230
|
+
@request = nil
|
231
|
+
@raw_state = :cleaned
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The gem version details.
|
4
|
+
module Boltless
|
5
|
+
# The version of the +boltless+ gem
|
6
|
+
VERSION = '1.0.0'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# Returns the version of gem as a string.
|
10
|
+
#
|
11
|
+
# @return [String] the gem version as string
|
12
|
+
def version
|
13
|
+
VERSION
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the version of the gem as a +Gem::Version+.
|
17
|
+
#
|
18
|
+
# @return [Gem::Version] the gem version as object
|
19
|
+
def gem_version
|
20
|
+
Gem::Version.new VERSION
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/boltless.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zeitwerk'
|
4
|
+
require 'http'
|
5
|
+
require 'connection_pool'
|
6
|
+
require 'oj'
|
7
|
+
require 'fast_jsonparser'
|
8
|
+
require 'colorize'
|
9
|
+
require 'active_support'
|
10
|
+
require 'active_support/concern'
|
11
|
+
require 'active_support/core_ext/module/delegation'
|
12
|
+
require 'active_support/core_ext/numeric/time'
|
13
|
+
require 'active_support/core_ext/enumerable'
|
14
|
+
require 'pp'
|
15
|
+
|
16
|
+
# The gem root namespace. Everything is bundled here.
|
17
|
+
module Boltless
|
18
|
+
# Setup a Zeitwerk autoloader instance and configure it
|
19
|
+
loader = Zeitwerk::Loader.for_gem
|
20
|
+
|
21
|
+
# Finish the auto loader configuration
|
22
|
+
loader.setup
|
23
|
+
|
24
|
+
# Load standalone code
|
25
|
+
require 'boltless/version'
|
26
|
+
|
27
|
+
# Include top-level features
|
28
|
+
include Extensions::ConfigurationHandling
|
29
|
+
include Extensions::ConnectionPool
|
30
|
+
include Extensions::Transactions
|
31
|
+
include Extensions::Operations
|
32
|
+
include Extensions::Utilities
|
33
|
+
|
34
|
+
# Make sure to eager load all SDK constants
|
35
|
+
loader.eager_load
|
36
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'boltless'
|
6
|
+
require 'benchmark'
|
7
|
+
|
8
|
+
Boltless.configure do |conf|
|
9
|
+
conf.base_url = 'http://172.17.0.4:7474'
|
10
|
+
end
|
11
|
+
|
12
|
+
Benchmark.bm do |x|
|
13
|
+
cycles = 150
|
14
|
+
|
15
|
+
cypher = <<~CYPHER
|
16
|
+
MATCH (n:User { id: $subject }) -[:ROLE*]-> () -[r:READ]-> (o)
|
17
|
+
WITH
|
18
|
+
o.id AS id,
|
19
|
+
null AS all,
|
20
|
+
r.through AS through,
|
21
|
+
r.condition_keys AS keys,
|
22
|
+
r.condition_values AS values
|
23
|
+
RETURN DISTINCT id, all, through, keys, values
|
24
|
+
CYPHER
|
25
|
+
|
26
|
+
x.report('.transaction (raw results)') do
|
27
|
+
cycles.times do
|
28
|
+
Boltless.transaction(raw_results: true) do |tx|
|
29
|
+
tx.run(cypher, subject: '2d07b107-2a11-436e-a66d-6e0e0272d78e')
|
30
|
+
end.first[:data].count
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
x.report('.transaction') do
|
35
|
+
cycles.times do
|
36
|
+
Boltless.transaction(raw_results: false) do |tx|
|
37
|
+
tx.run(cypher, subject: '2d07b107-2a11-436e-a66d-6e0e0272d78e')
|
38
|
+
end.first.count
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# == Old hard-mapping
|
44
|
+
# user system total real
|
45
|
+
# .transaction (raw results) 66.999341 22.444895 89.444236 (227.354975)
|
46
|
+
# .transaction 133.277385 23.208360 156.485745 (291.491150)
|
47
|
+
#
|
48
|
+
# .transaction (raw results) avg(real/150) 1.51569s
|
49
|
+
# .transaction avg(real/150) 1.94327s (1.3x slower)
|
50
|
+
|
51
|
+
# == New lazy mapping
|
52
|
+
# user system total real
|
53
|
+
# .transaction (raw results) 67.460811 22.520042 89.980853 (229.456833)
|
54
|
+
# .transaction 73.468645 22.909531 96.378176 (237.652516)
|
55
|
+
#
|
56
|
+
# .transaction (raw results) avg(real/150) 1.52971222s
|
57
|
+
# .transaction avg(real/150) 1.58435011s (±1.0x same-ish)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Boltless::Extensions::ConfigurationHandling do
|
6
|
+
let(:described_class) { Boltless }
|
7
|
+
|
8
|
+
before { described_class.reset_configuration! }
|
9
|
+
|
10
|
+
it 'allows the access of the configuration' do
|
11
|
+
expect(described_class.configuration).not_to be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '.configure' do
|
15
|
+
it 'yields the configuration' do
|
16
|
+
expect do |block|
|
17
|
+
described_class.configure(&block)
|
18
|
+
end.to yield_with_args(described_class.configuration)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '.reset_configuration!' do
|
23
|
+
it 'resets the configuration to its defaults' do
|
24
|
+
described_class.configuration.request_timeout = 100
|
25
|
+
expect { described_class.reset_configuration! }.to \
|
26
|
+
change { described_class.configuration.request_timeout }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '.logger' do
|
31
|
+
it 'returns a Logger instance' do
|
32
|
+
expect(described_class.logger).to be_a(Logger)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns a logger with the default info level' do
|
36
|
+
expect(described_class.logger.level).to be_eql(Logger::INFO)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Boltless::Extensions::ConnectionPool do
|
6
|
+
let(:described_class) { Boltless }
|
7
|
+
let(:reload) do
|
8
|
+
proc do
|
9
|
+
described_class.connection_pool.shutdown(&:close)
|
10
|
+
described_class.instance_variable_set(:@connection_pool, nil)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
Boltless.reset_configuration!
|
16
|
+
reload.call
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.connection_pool' do
|
20
|
+
let(:action) { described_class.connection_pool }
|
21
|
+
let(:options) { action.checkout.default_options }
|
22
|
+
let(:auth) do
|
23
|
+
Base64.decode64(
|
24
|
+
options.headers['Authorization'].split(' ').last
|
25
|
+
).split(':')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns a memoized connection pool instance' do
|
29
|
+
expect(described_class.connection_pool).to \
|
30
|
+
be(described_class.connection_pool)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'returns a HTTP client instance when requested' do
|
34
|
+
expect(action.checkout).to be_a(HTTP::Client)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'returns a configured HTTP client (pool size)' do
|
38
|
+
Boltless.configuration.connection_pool_size = 1
|
39
|
+
reload.call
|
40
|
+
expect(action.size).to be_eql(1)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns a configured HTTP client (connection aquire timeout)' do
|
44
|
+
Boltless.configuration.connection_pool_timeout = 1
|
45
|
+
reload.call
|
46
|
+
expect(action.instance_variable_get(:@timeout)).to be_eql(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'returns a configured HTTP client (persistent base URL)' do
|
50
|
+
Boltless.configuration.base_url = 'http://test:1234'
|
51
|
+
reload.call
|
52
|
+
expect(options.persistent).to be_eql('http://test:1234')
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns a configured HTTP client (username)' do
|
56
|
+
Boltless.configuration.username = 'test'
|
57
|
+
reload.call
|
58
|
+
expect(auth.first).to be_eql('test')
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'returns a configured HTTP client (password)' do
|
62
|
+
Boltless.configuration.password = 'test'
|
63
|
+
reload.call
|
64
|
+
expect(auth.last).to be_eql('test')
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns a configured HTTP client (request timeout)' do
|
68
|
+
Boltless.configuration.request_timeout = 7
|
69
|
+
reload.call
|
70
|
+
expect(options.timeout_options[:global_timeout]).to be_eql(7)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'allows send requests' do
|
74
|
+
expect(action.checkout.get('/').to_s).to include('neo4j')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '.wait_for_server!' do
|
79
|
+
let(:connection) { described_class.connection_pool.checkout }
|
80
|
+
let(:action) { described_class.wait_for_server!(connection) }
|
81
|
+
let(:request_timeout) { 2.seconds }
|
82
|
+
let(:wait_for_upstream_server) { 2.seconds }
|
83
|
+
let(:logger) { Logger.new(log) }
|
84
|
+
let(:log) { StringIO.new }
|
85
|
+
|
86
|
+
before do
|
87
|
+
Boltless.configure do |conf|
|
88
|
+
conf.request_timeout = request_timeout
|
89
|
+
conf.wait_for_upstream_server = wait_for_upstream_server
|
90
|
+
conf.logger = logger
|
91
|
+
end
|
92
|
+
Boltless.instance_variable_set(:@upstream_is_ready, nil)
|
93
|
+
Boltless.instance_variable_set(:@upstream_retry_count, nil)
|
94
|
+
reload.call
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'when the server is up and running' do
|
98
|
+
it 'returns the given connection' do
|
99
|
+
expect(action).to be(connection)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'memoizes the check' do
|
103
|
+
action
|
104
|
+
expect(connection).not_to receive(:get)
|
105
|
+
action
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'when the server is not available' do
|
110
|
+
before do
|
111
|
+
Boltless.configuration.base_url = 'http://localhost:8751'
|
112
|
+
reload.call
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'raises a HTTP::ConnectionError' do
|
116
|
+
expect { action }.to raise_error(HTTP::ConnectionError)
|
117
|
+
end
|
118
|
+
|
119
|
+
describe 'logging' do
|
120
|
+
let(:wait_for_upstream_server) { 2.1.seconds }
|
121
|
+
|
122
|
+
it 'logs a retry' do
|
123
|
+
suppress(HTTP::ConnectionError) { action }
|
124
|
+
expect(log.string).to \
|
125
|
+
include('neo4j is unavailable, retry in 2 seconds ' \
|
126
|
+
'(1/2, http://localhost:8751)')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|