yax-fauna 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,58 @@
1
+ module Fauna
2
+ # The result of a request. Provided to observers and included within errors.
3
+ class RequestResult
4
+ # The Client.
5
+ attr_reader :client
6
+ # HTTP method. Either +:get+, +:post+, +:put+, +:patch+, or +:delete+
7
+ attr_reader :method
8
+ # Path that was queried. Relative to client's domain.
9
+ attr_reader :path
10
+ # URL query. +nil+ except for +GET+ requests.
11
+ attr_reader :query
12
+ # Request data.
13
+ attr_reader :request_content
14
+ # String value returned by the server.
15
+ attr_reader :response_raw
16
+ ##
17
+ # Parsed value returned by the server.
18
+ # Includes "resource" wrapper hash, or may be an "errors" hash instead.
19
+ # In the case of a JSON parse error, this will be nil.
20
+ attr_reader :response_content
21
+ # HTTP status code.
22
+ attr_reader :status_code
23
+ # A hash of headers.
24
+ attr_reader :response_headers
25
+ # Time the request started.
26
+ attr_reader :start_time
27
+ # Time the response was received.
28
+ attr_reader :end_time
29
+
30
+ def initialize(
31
+ client,
32
+ method, path, query, request_content,
33
+ response_raw, response_content, status_code, response_headers,
34
+ start_time, end_time) # :nodoc:
35
+ @client = client
36
+ @method = method
37
+ @path = path
38
+ @query = query
39
+ @request_content = request_content
40
+ @response_raw = response_raw
41
+ @response_content = response_content
42
+ @status_code = status_code
43
+ @response_headers = response_headers
44
+ @start_time = start_time
45
+ @end_time = end_time
46
+ end
47
+
48
+ # Real time spent performing the request.
49
+ def time_taken
50
+ end_time - start_time
51
+ end
52
+
53
+ # Credentials used by the client.
54
+ def auth
55
+ client.credentials
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,50 @@
1
+ module Fauna
2
+ ##
3
+ # Converts microseconds to a Time object.
4
+ #
5
+ # +microseconds+:: Time in microseconds.
6
+ def self.time_from_usecs(microseconds)
7
+ Time.at(microseconds / 1_000_000, microseconds % 1_000_000)
8
+ end
9
+
10
+ ##
11
+ # Converts a Time object to microseconds.
12
+ #
13
+ # +time+:: A Time object.
14
+ def self.usecs_from_time(time)
15
+ time.to_i * 1_000_000 + time.usec
16
+ end
17
+
18
+ class DSLContext # :nodoc:
19
+ def self.eval_dsl(dsl, *args, &blk)
20
+ ctx = eval('self', blk.binding)
21
+ dsl.instance_variable_set(:@__ctx__, ctx)
22
+
23
+ ctx.instance_variables.each do |iv|
24
+ dsl.instance_variable_set(iv, ctx.instance_variable_get(iv))
25
+ end
26
+
27
+ dsl.instance_exec(*args, &blk)
28
+
29
+ ensure
30
+ dsl.instance_variables.each do |iv|
31
+ if iv.to_sym != :@__ctx__
32
+ ctx.instance_variable_set(iv, dsl.instance_variable_get(iv))
33
+ end
34
+ end
35
+ end
36
+
37
+ NON_PROXIED_METHODS = Set.new %w(__send__ object_id __id__ == equal?
38
+ ! != instance_exec instance_variables
39
+ instance_variable_get instance_variable_set
40
+ ).map(&:to_sym)
41
+
42
+ instance_methods.each do |method|
43
+ undef_method(method) unless NON_PROXIED_METHODS.include?(method.to_sym)
44
+ end
45
+
46
+ def method_missing(method, *args, &block)
47
+ @__ctx__.send(method, *args, &block)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,4 @@
1
+ module Fauna
2
+ # The version of the Fauna gem
3
+ VERSION = '3.0.1'.freeze
4
+ end
@@ -0,0 +1,36 @@
1
+ RSpec.describe Fauna::Bytes do
2
+ describe '#initalize' do
3
+ it 'creates from bytes' do
4
+ raw = random_bytes
5
+ expect(Fauna::Bytes.new(raw).bytes).to eq(raw)
6
+ end
7
+ end
8
+
9
+ describe '#from_base64' do
10
+ it 'creates from base64' do
11
+ raw = random_bytes
12
+ encoded = Base64.urlsafe_encode64(raw)
13
+
14
+ expect(Fauna::Bytes.from_base64(encoded).bytes).to eq(raw)
15
+ end
16
+ end
17
+
18
+ describe '#==' do
19
+ it 'equals same bytes' do
20
+ raw = random_bytes
21
+ bytes = Fauna::Bytes.new(raw)
22
+
23
+ expect(bytes).to eq(Fauna::Bytes.new(raw))
24
+ end
25
+
26
+ it 'does not equal different bytes' do
27
+ expect(Fauna::Bytes.new(random_bytes)).not_to eq(Fauna::Bytes.new(random_bytes))
28
+ end
29
+
30
+ it 'does not equal other type' do
31
+ raw = random_bytes
32
+
33
+ expect(Fauna::Bytes.new(raw)).not_to eq(raw)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,73 @@
1
+ RSpec.describe Fauna::ClientLogger do
2
+ before(:all) do
3
+ create_test_db
4
+ @test_class = client.query { create_class(name: 'logger_test') }[:ref]
5
+ end
6
+
7
+ after(:all) do
8
+ destroy_test_db
9
+ end
10
+
11
+ # Captures logger output from wrapped client and splits it into lines
12
+ def capture_log
13
+ lines = nil
14
+ observer = Fauna::ClientLogger.logger { |log| lines = log.split("\n") }
15
+
16
+ yield get_client(secret: @server_secret, observer: observer)
17
+
18
+ lambda { lines.shift }
19
+ end
20
+
21
+ it 'logs response' do
22
+ reader = capture_log do |client|
23
+ expect(client.ping).to eq('Scope write is OK')
24
+ end
25
+
26
+ expect(reader.call).to eq('Fauna GET /ping')
27
+ expect(reader.call).to match(/^ Credentials:/)
28
+ expect(reader.call).to eq(' Response headers: {')
29
+
30
+ # Skip through headers
31
+ loop do
32
+ line = reader.call
33
+ unless line.start_with? ' '
34
+ expect(line).to eq(' }')
35
+ break
36
+ end
37
+ end
38
+
39
+ expect(reader.call).to eq(' Response JSON: {')
40
+ expect(reader.call).to eq(' "resource": "Scope write is OK"')
41
+ expect(reader.call).to eq(' }')
42
+ expect(reader.call).to match(/^ Response \(200\): Network latency \d+ms$/)
43
+ end
44
+
45
+ it 'logs request content' do
46
+ value = random_number
47
+ reader = capture_log do |client|
48
+ client.query data: { a: value }
49
+ end
50
+
51
+ expect(reader.call).to eq("Fauna POST /")
52
+ expect(reader.call).to match(/^ Credentials:/)
53
+ expect(reader.call).to eq(' Request JSON: {')
54
+ expect(reader.call).to eq(' "object": {')
55
+ expect(reader.call).to eq(' "data": {')
56
+ expect(reader.call).to eq(' "object": {')
57
+ expect(reader.call).to eq(" \"a\": #{value}")
58
+ expect(reader.call).to eq(' }')
59
+ expect(reader.call).to eq(' }')
60
+ expect(reader.call).to eq(' }')
61
+ expect(reader.call).to eq(' }')
62
+ # Ignore the rest
63
+ end
64
+
65
+ it 'logs request query' do
66
+ reader = capture_log do |client|
67
+ client.ping scope: 'global'
68
+ end
69
+
70
+ expect(reader.call).to eq("Fauna GET /ping?scope=global")
71
+ # Ignore the rest
72
+ end
73
+ end
@@ -0,0 +1,127 @@
1
+ RSpec.describe Fauna::Client do
2
+ before(:all) do
3
+ create_test_db
4
+ @test_class = client.query { create_class(name: 'client_test') }[:ref]
5
+ end
6
+
7
+ after(:all) do
8
+ destroy_test_db
9
+ end
10
+
11
+ describe 'connection' do
12
+ # Compress string with gzip
13
+ def gzipped(str)
14
+ out = ''
15
+ StringIO.open out do |io|
16
+ writer = Zlib::GzipWriter.new io
17
+ writer.write str
18
+ writer.flush
19
+ writer.close
20
+ end
21
+ out
22
+ end
23
+
24
+ it 'decodes gzip response' do
25
+ response = gzipped '{"resource": 1}'
26
+ test_client = stub_client(:post, '/', response, 'Content-Encoding' => 'gzip')
27
+ expect(test_client.query('tests/decode')).to be(1)
28
+ end
29
+
30
+ it 'decodes deflate response' do
31
+ response = Zlib::Deflate.deflate '{"resource": 1}'
32
+ test_client = stub_client(:post, '/', response, 'Content-Encoding' => 'deflate')
33
+ expect(test_client.query('tests/decode')).to be(1)
34
+ end
35
+ end
36
+
37
+ describe 'serialization' do
38
+ it 'decodes ref' do
39
+ ref = Fauna::Ref.new(random_number, Fauna::Ref.new(random_string, Fauna::Native.classes))
40
+ test_client = stub_client(:post, '/', to_json(resource: ref))
41
+
42
+ response = test_client.query('tests/ref')
43
+ expect(response).to be_a(Fauna::Ref)
44
+ expect(response).to eq(ref)
45
+ end
46
+
47
+ it 'decodes set' do
48
+ set = Fauna::SetRef.new(match: Fauna::Ref.new(random_string, Fauna::Native.indexes), terms: random_string)
49
+ test_client = stub_client(:post, '/', to_json(resource: set))
50
+
51
+ response = test_client.query('tests/set')
52
+ expect(response).to be_a(Fauna::SetRef)
53
+ expect(response).to eq(set)
54
+ end
55
+
56
+ it 'decodes obj' do
57
+ data = { random_string.to_sym => random_string }
58
+ obj = { :@obj => data }
59
+ test_client = stub_client(:post, '/', to_json(resource: obj))
60
+
61
+ response = test_client.query('tests/obj')
62
+ expect(response).to be_a(Hash)
63
+ expect(response).to eq(data)
64
+ end
65
+
66
+ it 'decodes ts' do
67
+ ts = Time.at(0).utc
68
+ test_client = stub_client(:post, '/', to_json(resource: ts))
69
+
70
+ response = test_client.query('tests/ts')
71
+ expect(response).to be_a(Time)
72
+ expect(response).to eq(ts)
73
+ end
74
+
75
+ it 'decodes date' do
76
+ date = Date.new(1970, 1, 1)
77
+ test_client = stub_client(:post, '/', to_json(resource: date))
78
+
79
+ response = test_client.query('tests/date')
80
+ expect(response).to be_a(Date)
81
+ expect(response).to eq(date)
82
+ end
83
+ end
84
+
85
+ describe '#with_secret' do
86
+ it 'creates client with secret' do
87
+ old_secret = random_string
88
+ new_secret = random_string
89
+
90
+ old_client = get_client(secret: old_secret)
91
+ new_client = old_client.with_secret(new_secret)
92
+
93
+ expect(old_client.credentials).to eq([old_secret])
94
+ expect(new_client.credentials).to eq([new_secret])
95
+ end
96
+ end
97
+
98
+ describe '#query' do
99
+ it 'performs query from expression' do
100
+ value = random_number
101
+
102
+ instance = client.query { create(@test_class, data: { a: value }) }
103
+
104
+ expect(instance[:ref].class_).to eq(@test_class)
105
+ expect(instance[:data][:a]).to eq(value)
106
+ end
107
+
108
+ it 'performs query from block' do
109
+ value = random_number
110
+
111
+ instance = client.query { create @test_class, data: { a: value } }
112
+
113
+ expect(instance[:ref].class_).to eq(@test_class)
114
+ expect(instance[:data][:a]).to eq(value)
115
+ end
116
+ end
117
+
118
+ describe '#ping' do
119
+ it 'performs ping' do
120
+ expect(client.ping).to eq('Scope write is OK')
121
+ end
122
+
123
+ it 'performs ping with scope' do
124
+ expect(client.ping(scope: 'node')).to eq('Scope node is OK')
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,84 @@
1
+ RSpec.describe Fauna::Context do
2
+ before(:all) do
3
+ create_test_db
4
+ @test_class = client.query { create_class(name: 'context_test') }[:ref]
5
+ end
6
+
7
+ after(:all) do
8
+ destroy_test_db
9
+ end
10
+
11
+ around do |ex|
12
+ # Ensure context is not shared between tests
13
+ Fauna::Context.reset
14
+ ex.run
15
+ Fauna::Context.reset
16
+ end
17
+
18
+ describe '#query' do
19
+ it 'performs query' do
20
+ Fauna::Context.block(client) do
21
+ expect(Fauna::Context.query { add 1, 1 }).to eq(2)
22
+ end
23
+ end
24
+ end
25
+
26
+ describe '#paginate' do
27
+ it 'performs paginate' do
28
+ Fauna::Context.block(client) do
29
+ expect(Fauna::Context.paginate(Fauna::Query.expr { ref('classes') }).data).to eq([@test_class])
30
+ end
31
+ end
32
+ end
33
+
34
+ describe '#push' do
35
+ it 'pushes new client' do
36
+ new = Fauna::Client.new
37
+
38
+ Fauna::Context.push(new)
39
+ expect(Fauna::Context.client).to be(new)
40
+ end
41
+ end
42
+
43
+ describe '#pop' do
44
+ it 'pops active client' do
45
+ outer = Fauna::Client.new
46
+ inner = Fauna::Client.new
47
+
48
+ Fauna::Context.push(outer)
49
+ Fauna::Context.push(inner)
50
+
51
+ expect(Fauna::Context.pop).to be(inner)
52
+ expect(Fauna::Context.client).to be(outer)
53
+ end
54
+ end
55
+
56
+ describe '#reset' do
57
+ it 'resets stack' do
58
+ initial = Fauna::Client.new
59
+ outer = Fauna::Client.new
60
+ inner = Fauna::Client.new
61
+
62
+ Fauna::Context.push(initial)
63
+ Fauna::Context.push(outer)
64
+ Fauna::Context.push(inner)
65
+
66
+ Fauna::Context.reset
67
+ expect { Fauna::Context.client }.to raise_error(Fauna::NoContextError)
68
+ end
69
+ end
70
+
71
+ describe '#block' do
72
+ it 'changes client within block' do
73
+ outer = Fauna::Client.new
74
+ inner = Fauna::Client.new
75
+ Fauna::Context.push(outer)
76
+
77
+ expect(Fauna::Context.client).to be(outer)
78
+ Fauna::Context.block(inner) do
79
+ expect(Fauna::Context.client).to be(inner)
80
+ end
81
+ expect(Fauna::Context.client).to be(outer)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,185 @@
1
+ RSpec.describe 'Fauna Errors' do
2
+ RSpec::Matchers.define :raise_fauna_error do |exception, code, position = nil|
3
+ match do |block|
4
+ expect(&block).to raise_error(exception) do |ex|
5
+ expect(ex.errors.length).to be(1)
6
+ error = ex.errors.first
7
+ expect(error.code).to eq(code)
8
+ expect(error.position).to eq(position)
9
+ end
10
+ end
11
+
12
+ def supports_block_expectations?
13
+ true
14
+ end
15
+ end
16
+
17
+ before(:all) do
18
+ create_test_db
19
+ end
20
+
21
+ after(:all) do
22
+ destroy_test_db
23
+ end
24
+
25
+ # Create client with stub adapter responding to / with the given status and response
26
+ def stub_post(status_code, response)
27
+ stubs = Faraday::Adapter::Test::Stubs.new
28
+ stubs.post '/' do
29
+ [status_code, {}, response]
30
+ end
31
+ Fauna::Client.new(adapter: [:test, stubs])
32
+ end
33
+
34
+ # Create client with stub adapter responding to / with the given exception
35
+ def stub_error(exception)
36
+ stubs = Faraday::Adapter::Test::Stubs.new
37
+ stubs.post '/' do
38
+ fail exception
39
+ end
40
+ Fauna::Client.new(adapter: [:test, stubs])
41
+ end
42
+
43
+ it 'sets request result' do
44
+ expect { client.query { add 1, :two } }.to raise_error do |err|
45
+ expect(err).to be_a(Fauna::BadRequest)
46
+ expect(err.request_result.request_content).to eq(Fauna::Query.add [1, :two])
47
+ end
48
+ end
49
+
50
+ it 'parses query errors' do
51
+ expect { client.query { add 1, :two } }.to raise_fauna_error(Fauna::BadRequest, 'invalid argument', [:add, 1])
52
+ end
53
+
54
+ it 'parses invalid data' do
55
+ expect { client.query { create ref('classes'), name: 123 } }.to raise_error(Fauna::BadRequest) do |err|
56
+ expect(err.errors.length).to eq(1)
57
+ error = err.errors.first
58
+
59
+ expect(error.code).to eq('validation failed')
60
+ expect(error.position).to eq([])
61
+ expect(error.failures.length).to eq(1)
62
+ failure = error.failures.first
63
+
64
+ expect(failure.code).to eq('invalid type')
65
+ expect(failure.field).to eq([:name])
66
+ end
67
+ end
68
+
69
+ describe Fauna::ErrorData do
70
+ it 'handles inspect' do
71
+ err = Fauna::ErrorData.new 'code', 'desc', nil, nil
72
+ expect(err.inspect).to eq('ErrorData("code", "desc", nil, nil)')
73
+ end
74
+
75
+ it 'handles inspect with failures' do
76
+ failure = Fauna::Failure.new 'code', 'desc', [:a, :b]
77
+ err = Fauna::ErrorData.new 'code', 'desc', [:pos], [failure]
78
+ expect(err.inspect).to eq('ErrorData("code", "desc", [:pos], [Failure("code", "desc", [:a, :b])])')
79
+ end
80
+ end
81
+
82
+ describe Fauna::BadRequest do
83
+ it 'is handled' do
84
+ expect { client.query { add 1, 'string' } }.to raise_error(Fauna::BadRequest)
85
+ end
86
+ end
87
+
88
+ describe Fauna::Unauthorized do
89
+ it 'is handled' do
90
+ bad_client = get_client secret: 'bad_key'
91
+ expect { bad_client.query { get @db_ref } }.to raise_error(Fauna::Unauthorized)
92
+ end
93
+ end
94
+
95
+ describe Fauna::PermissionDenied do
96
+ it 'is handled' do
97
+ key = root_client.query { create ref('keys'), database: @db_ref, role: :client }
98
+
99
+ expect { get_client(secret: key[:secret]).query { paginate ref('databases') } }.to raise_fauna_error(
100
+ Fauna::PermissionDenied, 'permission denied', [:paginate])
101
+ end
102
+ end
103
+
104
+ describe Fauna::NotFound do
105
+ it 'is handled' do
106
+ expect { client.query { delete Fauna::Ref.new('classes') } }.to raise_fauna_error(Fauna::NotFound, 'instance not found', [])
107
+ end
108
+ end
109
+
110
+ describe Fauna::InternalError do
111
+ it 'is handled' do
112
+ stub_client = stub_post 500,
113
+ '{"errors": [{"code": "internal server error", "description": "sample text", "stacktrace": []}]}'
114
+
115
+ expect { stub_client.query '' }.to raise_fauna_error(Fauna::InternalError, 'internal server error')
116
+ end
117
+ end
118
+
119
+ describe Fauna::UnavailableError do
120
+ it 'handles fauna 503' do
121
+ stub_client = stub_post 503, '{"errors": [{"code": "unavailable", "description": "on vacation"}]}'
122
+ expect { stub_client.query '' }.to raise_fauna_error(Fauna::UnavailableError, 'unavailable')
123
+ end
124
+
125
+ it 'handles upstream 502' do
126
+ stub_client = stub_post 502, 'Bad gateway'
127
+ expect { stub_client.query '' }.to raise_error(Fauna::UnavailableError, 'Bad gateway')
128
+ end
129
+
130
+ it 'handles upstream 503' do
131
+ stub_client = stub_post 503, 'Unable to reach server'
132
+ expect { stub_client.query '' }.to raise_error(Fauna::UnavailableError, 'Unable to reach server')
133
+ end
134
+
135
+ it 'handles upstream 504' do
136
+ stub_client = stub_post 504, 'Server timeout'
137
+ expect { stub_client.query '' }.to raise_error(Fauna::UnavailableError, 'Server timeout')
138
+ end
139
+
140
+ it 'handles upstream json 503' do
141
+ stub_client = stub_post 503, '{ "error" : "service unavailable" }'
142
+ expect { stub_client.query '' }.to raise_error(Fauna::UnavailableError, '{ "error" : "service unavailable" }')
143
+ end
144
+
145
+ it 'handles timeout error' do
146
+ stub_client = stub_error Faraday::TimeoutError.new('timeout')
147
+ expect { stub_client.query '' }.to raise_error(Fauna::UnavailableError, 'Faraday::TimeoutError: timeout')
148
+ end
149
+
150
+ it 'handles connection error' do
151
+ stub_client = stub_error Faraday::ConnectionFailed.new('connection refused')
152
+ expect { stub_client.query '' }.to raise_error(Fauna::UnavailableError, 'Faraday::ConnectionFailed: connection refused')
153
+ end
154
+ end
155
+
156
+ describe Fauna::UnexpectedError do
157
+ it 'raised for json error' do
158
+ expect { stub_post(200, 'I like fine wine').query('') }.to raise_error(Fauna::UnexpectedError, /json/i) do |err|
159
+ rr = err.request_result
160
+ expect(rr.response_content).to be_nil
161
+ expect(rr.response_raw).to eq('I like fine wine')
162
+ end
163
+ end
164
+
165
+ it 'raised for missing resource' do
166
+ expect { stub_post(200, '{"notaresource": 1}').query('') }.to raise_error(Fauna::UnexpectedError, /expected key/)
167
+ end
168
+
169
+ it 'raised for unexpected code' do
170
+ expect { stub_post(1337, '{"errors": []}').query('') }.to raise_error(Fauna::UnexpectedError, /status code/)
171
+ end
172
+
173
+ it 'raised for bad errors format' do
174
+ expect { stub_post(500, '{"errors": true}').query('') }.to raise_error(Fauna::UnexpectedError, /unexpected format/)
175
+ end
176
+
177
+ it 'raised for empty errors' do
178
+ expect { stub_post(500, '{"errors": []}').query('') }.to raise_error(Fauna::UnexpectedError, /blank/)
179
+ end
180
+
181
+ it 'raised for parsing error' do
182
+ expect { stub_error(Faraday::ParsingError.new('parse error')).query('') }.to raise_error(Fauna::UnexpectedError, /parse error/)
183
+ end
184
+ end
185
+ end