boltless 1.0.0
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 +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
|