Skip to content

Commit e109fc3

Browse files
bigmontzfbiville
andauthored
Fix RxSession in Testkit Backend (#980)
Testkit environment variables without `TEST_` prefix are not forward by the Testkit to the scripts in the driver. So we need change the session type variable expected by the `backend.py` to be `TEST_SESSION_TYPE` and then remove the prefix before redirect to the backend. This PR also fix the `SessionRun` and `TransactionRun` to not initiate the stream. **Warn**: `RxSession.beginTransaction` and `RxSession.execute*` were tweaked. The `Observable<RxTransaction>` only emits the `RxTransaction` when the begin is executed with success. The begin still defer the execution to the point observer is subscribed. Co-authored-by: Florent Biville <445792+fbiville@users.noreply.github.com>
1 parent a6ac115 commit e109fc3

File tree

12 files changed

+228
-29
lines changed

12 files changed

+228
-29
lines changed

packages/core/src/internal/util.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import { stringify } from '../json'
2424

2525
const ENCRYPTION_ON: EncryptionLevel = 'ENCRYPTION_ON'
2626
const ENCRYPTION_OFF: EncryptionLevel = 'ENCRYPTION_OFF'
27-
27+
// eslint-disable-next-line @typescript-eslint/naming-convention
28+
const __isBrokenObject__ = '__isBrokenObject__'
29+
// eslint-disable-next-line @typescript-eslint/naming-convention
30+
const __reason__ = '__reason__'
2831
/**
2932
* Verifies if the object is null or empty
3033
* @param obj The subject object
@@ -237,7 +240,16 @@ function createBrokenObject<T extends object> (error: Error, object: any = {}):
237240
}
238241

239242
return new Proxy(object, {
240-
get: fail,
243+
get: (_: T, p: string | Symbol): any => {
244+
if (p === __isBrokenObject__) {
245+
return true
246+
} else if (p === __reason__) {
247+
return error
248+
} else if (p === 'toJSON') {
249+
return undefined
250+
}
251+
fail()
252+
},
241253
set: fail,
242254
apply: fail,
243255
construct: fail,
@@ -253,6 +265,27 @@ function createBrokenObject<T extends object> (error: Error, object: any = {}):
253265
})
254266
}
255267

268+
/**
269+
* Verifies if it is a Broken Object
270+
* @param {any} object The object
271+
* @returns {boolean} If it was created with createBrokenObject
272+
*/
273+
function isBrokenObject (object: any): boolean {
274+
return object !== null && typeof object === 'object' && object[__isBrokenObject__] === true
275+
}
276+
277+
/**
278+
* Returns if the reason the object is broken.
279+
*
280+
* This method should only be called with instances create with {@link createBrokenObject}
281+
*
282+
* @param {any} object The object
283+
* @returns {Error} The reason the object is broken
284+
*/
285+
function getBrokenObjectReason (object: any): Error {
286+
return object[__reason__]
287+
}
288+
256289
export {
257290
isEmptyObjectOrNull,
258291
isObject,
@@ -265,5 +298,7 @@ export {
265298
validateQueryAndParameters,
266299
ENCRYPTION_ON,
267300
ENCRYPTION_OFF,
268-
createBrokenObject
301+
createBrokenObject,
302+
isBrokenObject,
303+
getBrokenObjectReason
269304
}

packages/core/src/json.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,25 @@
1717
* limitations under the License.
1818
*/
1919

20+
import { isBrokenObject, getBrokenObjectReason } from './internal/util'
21+
2022
/**
2123
* Custom version on JSON.stringify that can handle values that normally don't support serialization, such as BigInt.
2224
* @private
2325
* @param val A JavaScript value, usually an object or array, to be converted.
2426
* @returns A JSON string representing the given value.
2527
*/
2628
export function stringify (val: any): string {
27-
return JSON.stringify(val, (_, value) =>
28-
typeof value === 'bigint' ? `${value}n` : value
29-
)
29+
return JSON.stringify(val, (_, value) => {
30+
if (isBrokenObject(value)) {
31+
return {
32+
__isBrokenObject__: true,
33+
__reason__: getBrokenObjectReason(value)
34+
}
35+
}
36+
if (typeof value === 'bigint') {
37+
return `${value}n`
38+
}
39+
return value
40+
})
3041
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`json .stringify should handle objects created with createBrokenObject 1`] = `"{\\"__isBrokenObject__\\":true,\\"__reason__\\":{\\"code\\":\\"N/A\\",\\"name\\":\\"Neo4jError\\",\\"retriable\\":false}}"`;
4+
5+
exports[`json .stringify should handle objects created with createBrokenObject in list 1`] = `"[{\\"__isBrokenObject__\\":true,\\"__reason__\\":{\\"code\\":\\"N/A\\",\\"name\\":\\"Neo4jError\\",\\"retriable\\":false}}]"`;
6+
7+
exports[`json .stringify should handle objects created with createBrokenObject inside other object 1`] = `"{\\"number\\":1,\\"broken\\":{\\"__isBrokenObject__\\":true,\\"__reason__\\":{\\"code\\":\\"N/A\\",\\"name\\":\\"Neo4jError\\",\\"retriable\\":false}}}"`;

packages/core/test/internal/util.test.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* See the License for the specific language governing permissions and
1717
* limitations under the License.
1818
*/
19+
import { newError } from '../../src'
1920
import Integer, { int } from '../../src/integer'
2021
import {
2122
isEmptyObjectOrNull,
@@ -28,7 +29,10 @@ import {
2829
assertValidDate,
2930
validateQueryAndParameters,
3031
ENCRYPTION_ON,
31-
ENCRYPTION_OFF
32+
ENCRYPTION_OFF,
33+
createBrokenObject,
34+
isBrokenObject,
35+
getBrokenObjectReason
3236
} from '../../src/internal/util'
3337

3438
/* eslint-disable no-new-wrappers */
@@ -250,4 +254,47 @@ describe('Util', () => {
250254
expect(ENCRYPTION_ON).toBe('ENCRYPTION_ON'))
251255
test('should ENCRYPTION_OFF toBe "ENCRYPTION_OFF"', () =>
252256
expect(ENCRYPTION_OFF).toBe('ENCRYPTION_OFF'))
257+
258+
describe('isBrokenObject', () => {
259+
it('should return true when object created with createBrokenObject', () => {
260+
const object = createBrokenObject(newError('error'), {})
261+
262+
expect(isBrokenObject(object)).toBe(true)
263+
})
264+
265+
it('should return false for regular objects', () => {
266+
const object = {}
267+
268+
expect(isBrokenObject(object)).toBe(false)
269+
})
270+
271+
it('should return false for non-objects', () => {
272+
expect(isBrokenObject(null)).toBe(false)
273+
expect(isBrokenObject(undefined)).toBe(false)
274+
expect(isBrokenObject(1)).toBe(false)
275+
expect(isBrokenObject(() => {})).toBe(false)
276+
expect(isBrokenObject('string')).toBe(false)
277+
})
278+
})
279+
280+
describe('getBrokenObjectReason', () => {
281+
it('should return the reason the object is broken', () => {
282+
const reason = newError('error')
283+
const object = createBrokenObject(reason, {})
284+
285+
expect(getBrokenObjectReason(object)).toBe(reason)
286+
})
287+
})
288+
289+
describe('createBrokenObject', () => {
290+
describe('toJSON', () => {
291+
it('should return undefined', () => {
292+
const reason = newError('error')
293+
const object = createBrokenObject(reason, {})
294+
295+
// @ts-expect-error
296+
expect(object.toJSON).toBeUndefined()
297+
})
298+
})
299+
})
253300
})

packages/core/test/json.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
import { json, newError } from '../src'
21+
import { createBrokenObject } from '../src/internal/util'
22+
23+
describe('json', () => {
24+
describe('.stringify', () => {
25+
it('should handle objects created with createBrokenObject', () => {
26+
const reason = newError('some error')
27+
const broken = createBrokenObject(reason, { })
28+
29+
expect(json.stringify(broken)).toMatchSnapshot()
30+
})
31+
32+
it('should handle objects created with createBrokenObject in list', () => {
33+
const reason = newError('some error')
34+
const broken = createBrokenObject(reason, { })
35+
36+
expect(json.stringify([broken])).toMatchSnapshot()
37+
})
38+
39+
it('should handle objects created with createBrokenObject inside other object', () => {
40+
const reason = newError('some error')
41+
const broken = createBrokenObject(reason, { })
42+
43+
expect(json.stringify({
44+
number: 1,
45+
broken
46+
})).toMatchSnapshot()
47+
})
48+
})
49+
})

packages/neo4j-driver/src/result-rx.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ export default class RxResult {
3535
* @constructor
3636
* @protected
3737
* @param {Observable<Result>} result - An observable of single Result instance to relay requests.
38+
* @param {number} state - The streaming state
3839
*/
39-
constructor (result) {
40+
constructor (result, state) {
4041
const replayedResult = result.pipe(publishReplay(1), refCount())
4142

4243
this._result = replayedResult
@@ -48,7 +49,7 @@ export default class RxResult {
4849
this._records = undefined
4950
this._controls = new StreamControl()
5051
this._summary = new ReplaySubject()
51-
this._state = States.READY
52+
this._state = state || States.READY
5253
}
5354

5455
/**
@@ -184,6 +185,31 @@ export default class RxResult {
184185
}
185186
}
186187

188+
/**
189+
* Create a {@link Observable} for the current {@link RxResult}
190+
*
191+
*
192+
* @package
193+
* @experimental
194+
* @since 5.0
195+
* @return {Observable<RxResult>}
196+
*/
197+
_toObservable () {
198+
function wrap (result) {
199+
return new Observable(observer => {
200+
observer.next(result)
201+
observer.complete()
202+
})
203+
}
204+
return new Observable(observer => {
205+
this._result.subscribe({
206+
complete: () => observer.complete(),
207+
next: result => observer.next(new RxResult(wrap(result)), this._state),
208+
error: e => observer.error(e)
209+
})
210+
})
211+
}
212+
187213
_setupRecordsStream (result) {
188214
if (this._records) {
189215
return this._records

packages/neo4j-driver/src/session-rx.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,14 @@ export default class RxSession {
205205

206206
return new Observable(observer => {
207207
try {
208-
observer.next(
209-
new RxTransaction(
210-
this._session._beginTransaction(accessMode, txConfig)
211-
)
212-
)
213-
observer.complete()
208+
this._session._beginTransaction(accessMode, txConfig)
209+
.then(tx => {
210+
observer.next(
211+
new RxTransaction(tx)
212+
)
213+
observer.complete()
214+
})
215+
.catch(err => observer.error(err))
214216
} catch (err) {
215217
observer.error(err)
216218
}

packages/neo4j-driver/test/index.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ describe('#unit index', () => {
152152

153153
function subject () {
154154
const driver = neo4j.driver('bolt://localhost')
155+
driver._createSession = () => ({
156+
_mode: 'READ',
157+
_beginTransaction: async () => new Transaction({})
158+
})
155159
return driver.rxSession().beginTransaction().toPromise()
156160
}
157161
})
@@ -202,6 +206,10 @@ describe('#unit index', () => {
202206

203207
async function subject () {
204208
const driver = neo4j.driver('bolt://localhost')
209+
driver._createSession = () => ({
210+
_mode: 'READ',
211+
_beginTransaction: async () => new Transaction({})
212+
})
205213
const tx = await driver.rxSession().beginTransaction().toPromise()
206214
return InternalRxManagedTransaction.fromTransaction(tx)
207215
}

packages/testkit-backend/src/feature/async.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
const features = [
22
'Feature:API:Result.List',
33
'Feature:API:Result.Peek',
4-
'Optimization:EagerTransactionBegin',
54
'Optimization:PullPipelining'
65
]
76

packages/testkit-backend/src/feature/common.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const features = [
3333
'Feature:API:ConnectionAcquisitionTimeout',
3434
'Feature:API:Driver:GetServerInfo',
3535
'Feature:API:Driver.VerifyConnectivity',
36-
'Feature:API:Result.Peek',
36+
'Optimization:EagerTransactionBegin',
3737
'Optimization:ImplicitDefaultArguments',
3838
'Optimization:MinimalResets',
3939
...SUPPORTED_TLS

0 commit comments

Comments
 (0)