-
Notifications
You must be signed in to change notification settings - Fork 135
/
http._coffee
346 lines (271 loc) · 11 KB
/
http._coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
#
# Tests for the GraphDatabase `http` method, e.g. the ability to make custom,
# arbitrary HTTP requests, and have responses parsed for nodes, rels, & errors.
#
{expect} = require 'chai'
fixtures = require './fixtures'
fs = require 'fs'
helpers = require './util/helpers'
http = require 'http'
neo4j = require '../'
Request = require 'request'
## CONSTANTS
FAKE_JSON_PATH = "#{__dirname}/fixtures/fake.json"
FAKE_JSON = require FAKE_JSON_PATH
## SHARED STATE
{DB} = fixtures
[TEST_NODE_A, TEST_NODE_B, TEST_REL] = []
## HELPERS
#
# Asserts that the given object is a proper HTTP client request,
# set to the given method and path, and optionally including the given headers.
#
expectRequest = (req, method, path, headers={}) ->
expect(req).to.be.an.instanceOf Request.Request
expect(req.method).to.equal method
expect(req.path).to.equal path
# Special-case for headers since they're stored differently:
for name, val of headers
expect(req.getHeader name).to.equal val
#
# Asserts that the given object is a proper HTTP client response,
# with the given status code, and by default, a JSON Content-Type.
#
expectResponse = (resp, statusCode, json=true) ->
expect(resp).to.be.an.instanceOf http.IncomingMessage
expect(resp.statusCode).to.equal statusCode
expect(resp.headers).to.be.an 'object'
if json
expect(resp.headers['content-type']).to.match /^application\/json\b/
#
# Asserts that the given object is the root Neo4j object.
#
expectNeo4jRoot = (body) ->
expect(body).to.be.an 'object'
expect(body).to.have.keys 'data', 'management'
#
# Streams the given Request response, and calls either the error callback
# (for failing tests) or the success callback (with the parsed JSON body).
# Also asserts that the response has the given status code.
#
streamRequestResponse = (req, statusCode, cbErr, cbBody) ->
body = null
req.on 'error', cbErr
req.on 'close', -> cbErr new Error 'Response closed!'
req.on 'response', (resp) ->
expectResponse resp, statusCode
req.on 'data', (str) ->
body ?= ''
body += str
req.on 'end', ->
try
body = JSON.parse body
catch err
return cbErr err
cbBody body
## TESTS
describe 'GraphDatabase::http', ->
it 'should support simple GET requests by default', (_) ->
body = DB.http '/', _
expectNeo4jRoot body
it 'should support complex requests with options', (_) ->
body = DB.http
method: 'GET'
path: '/'
headers:
'X-Foo': 'bar'
, _
expectNeo4jRoot body
it 'should throw errors for 4xx responses by default', (done) ->
DB.http
method: 'POST'
path: '/'
, (err, body) ->
expect(err).to.exist()
expect(body).to.not.exist()
helpers.expectRawError err, 'ClientError',
'405 Method Not Allowed response for POST /'
done()
it 'should properly parse Neo4j exceptions', (done) ->
DB.http
method: 'GET'
path: '/db/data/node/-1'
, (err, body) ->
expect(err).to.exist()
expect(body).to.not.exist()
# Neo4j 2.2 returns a proper new-style error object for this case,
# but previous versions return an old-style error.
try
helpers.expectError err,
'ClientError', 'Statement', 'EntityNotFound',
'Cannot find node with id [-1] in database.'
catch assertionErr
# Check for the older case, but if this fails,
# throw the original assertion error, not this one.
try
helpers.expectOldError err, 404, 'NodeNotFoundException',
'org.neo4j.server.rest.web.NodeNotFoundException',
'Cannot find node with id [-1] in database.'
catch doubleErr
throw assertionErr
done()
it 'should support returning raw responses', (_) ->
resp = DB.http
method: 'GET'
path: '/'
raw: true
, _
expectResponse resp, 200
expectNeo4jRoot resp.body
it 'should not throw 4xx errors for raw responses', (_) ->
resp = DB.http
method: 'POST'
path: '/'
raw: true
, _
expectResponse resp, 405, false # Method Not Allowed, no JSON body
it 'should throw native errors always', (done) ->
db = new neo4j.GraphDatabase 'http://idontexist.foobarbaz.nodeneo4j'
db.http
path: '/'
raw: true
, (err, resp) ->
expect(err).to.exist()
expect(resp).to.not.exist()
# NOTE: *Not* using our `expectError` helpers here, because we
# explicitly don't wrap native (non-Neo4j) errors.
expect(err).to.be.an.instanceOf Error
expect(err.name).to.equal 'Error'
expect(err.code).to.equal 'ENOTFOUND'
expect(err.syscall).to.equal 'getaddrinfo'
expect(err.message).to.contain "#{err.syscall} #{err.code}"
# NOTE: Node 0.12 adds the hostname to the message.
expect(err.stack).to.contain '\n'
expect(err.stack.split('\n')[0]).to.equal \
"#{err.name}: #{err.message}"
done()
it 'should support streaming responses', (done) ->
opts =
method: 'GET'
path: '/db/data/node/-1'
req = DB.http opts
expectRequest req, opts.method, opts.path
streamRequestResponse req, 404, done, (body) ->
# Simplified error parsing; just verifying stream:
expect(body).to.be.an 'object'
expect(body.exception).to.equal 'NodeNotFoundException'
expect(body.message).to.equal '
Cannot find node with id [-1] in database.'
# Neo4j 2.2 changed `stacktrace` to `stackTrace`:
expect(body.stackTrace or body.stacktrace).to.be.an 'array'
done()
it 'should support streaming responses, even if not requests', (done) ->
opts =
method: 'POST'
path: '/db/data/node'
body: FAKE_JSON
req = DB.http opts
# TODO: Should we also assert that the request has automatically added
# Content-Type and Content-Length headers? Not technically required?
expectRequest req, opts.method, opts.path
streamRequestResponse req, 400, done, (body) ->
# Simplified error parsing; just verifying stream:
expect(body).to.be.an 'object'
expect(body.exception).to.equal 'PropertyValueException'
expect(body.message).to.equal 'Could not set property "object",
unsupported type: {foo={bar=baz}}'
# Neo4j 2.2 changed `stacktrace` to `stackTrace`:
expect(body.stackTrace or body.stacktrace).to.be.an 'array'
done()
it 'should support streaming both requests and responses', (done) ->
opts =
method: 'POST'
path: '/db/data/node'
headers:
'Content-Type': 'application/json'
# NOTE: It seems that Neo4j needs an explicit Content-Length,
# at least for requests to this `POST /db/data/node` endpoint.
'Content-Length': (fs.statSync FAKE_JSON_PATH).size
# Ideally, we would instead send this header for streaming:
# http://nodejs.org/api/http.html#http_request_write_chunk_encoding_callback
# 'Transfer-Encoding': 'chunked'
req = DB.http opts
expectRequest req, opts.method, opts.path, opts.headers
# Now stream some fake JSON to the request:
fs.createReadStream(FAKE_JSON_PATH).pipe req
streamRequestResponse req, 400, done, (body) ->
# Simplified error parsing; just verifying stream:
expect(body).to.be.an 'object'
expect(body.exception).to.equal 'PropertyValueException'
expect(body.message).to.equal 'Could not set property "object",
unsupported type: {foo={bar=baz}}'
# Neo4j 2.2 changed `stacktrace` to `stackTrace`:
expect(body.stackTrace or body.stacktrace).to.be.an 'array'
done()
## Object parsing:
it '(create test graph)', (_) ->
[TEST_NODE_A, TEST_REL, TEST_NODE_B] =
fixtures.createTestGraph module, 2, _
it 'should parse nodes by default', (_) ->
node = DB.http
method: 'GET'
path: "/db/data/node/#{TEST_NODE_A._id}"
, _
expect(node).to.be.an.instanceOf neo4j.Node
expect(node).to.eql TEST_NODE_A
it 'should parse relationships by default', (_) ->
rel = DB.http
method: 'GET'
path: "/db/data/relationship/#{TEST_REL._id}"
, _
expect(rel).to.be.an.instanceOf neo4j.Relationship
expect(rel).to.eql TEST_REL
it 'should parse nested nodes & relationships by default', (_) ->
{data} = DB.http
method: 'POST'
path: '/db/data/cypher'
body:
query: '''
START a = node({idA})
MATCH (a) -[r]-> (b)
RETURN a, r, b
'''
params:
idA: TEST_NODE_A._id
, _
[row] = data
[nodeA, rel, nodeB] = row
expect(nodeA).to.be.an.instanceOf neo4j.Node
expect(nodeA).to.eql TEST_NODE_A
expect(nodeB).to.be.an.instanceOf neo4j.Node
expect(nodeB).to.eql TEST_NODE_B
expect(rel).to.be.an.instanceOf neo4j.Relationship
expect(rel).to.eql TEST_REL
it 'should not parse nodes for raw responses', (_) ->
{body} = DB.http
method: 'GET'
path: "/db/data/node/#{TEST_NODE_A._id}"
raw: true
, _
expect(body).to.not.be.an.instanceOf neo4j.Node
# NOTE: Neo4j <2.1.5 didn't return `metadata`, so can't rely on it:
if fixtures.DB_VERSION_STR >= '2.1.5'
expect(body.metadata).to.be.an 'object'
expect(body.metadata.id).to.equal TEST_NODE_A._id
expect(body.metadata.labels).to.eql TEST_NODE_A.labels
expect(body.data).to.eql TEST_NODE_A.properties
it 'should not parse relationships for raw responses', (_) ->
{body} = DB.http
method: 'GET'
path: "/db/data/relationship/#{TEST_REL._id}"
raw: true
, _
expect(body).to.not.be.an.instanceOf neo4j.Relationship
# NOTE: Neo4j <2.1.5 didn't return `metadata`, so can't rely on it:
if fixtures.DB_VERSION_STR >= '2.1.5'
expect(body.metadata).to.be.an 'object'
expect(body.metadata.id).to.equal TEST_REL._id
expect(body.type).to.equal TEST_REL.type
expect(body.data).to.eql TEST_REL.properties
it '(delete test graph)', (_) ->
fixtures.deleteTestGraph module, _