yax-fauna 3.0.1

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.
@@ -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