| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| /** |
| * @author Jorge Bay Gondra |
| */ |
| 'use strict'; |
| |
| const Mocha = require('mocha'); |
| const assert = require('assert'); |
| const { AssertionError } = require('assert'); |
| const DriverRemoteConnection = require('../../lib/driver/driver-remote-connection'); |
| const { Vertex, Edge, VertexProperty} = require('../../lib/structure/graph'); |
| const { traversal } = require('../../lib/process/anonymous-traversal'); |
| const { P, n } = require('../../lib/process/traversal'); |
| const { GraphTraversalSource, GraphTraversal, statics } = require('../../lib/process/graph-traversal'); |
| const { SubgraphStrategy, ReadOnlyStrategy, SeedStrategy, HaltedTraverserStrategy, FilterRankingStrategy, |
| OptionsStrategy, ReservedKeysVerificationStrategy, EdgeLabelVerificationStrategy } = require('../../lib/process/traversal-strategy'); |
| const Bytecode = require('../../lib/process/bytecode'); |
| const helper = require('../helper'); |
| const {getDriverRemoteConnectionGraphSON} = require("../helper"); |
| const __ = statics; |
| |
| let connection; |
| let txConnection; |
| |
| class SocialTraversal extends GraphTraversal { |
| constructor(graph, traversalStrategies, bytecode) { |
| super(graph, traversalStrategies, bytecode); |
| } |
| |
| aged(age) { |
| return this.has('person', 'age', age); |
| } |
| } |
| |
| class SocialTraversalSource extends GraphTraversalSource { |
| constructor(graph, traversalStrategies, bytecode) { |
| super(graph, traversalStrategies, bytecode, SocialTraversalSource, SocialTraversal); |
| } |
| |
| person(name) { |
| return this.V().has('person', 'name', name); |
| } |
| } |
| |
| function anonymous() { |
| return new SocialTraversal(null, null, new Bytecode()); |
| } |
| |
| function aged(age) { |
| return anonymous().aged(age); |
| } |
| |
| describe('Traversal', function () { |
| before(function () { |
| connection = helper.getConnection('gmodern'); |
| return connection.open(); |
| }); |
| after(function () { |
| return connection.close(); |
| }); |
| describe("#construct", function () { |
| it('should not hang if server not present', function() { |
| const g = traversal().with_(helper.getDriverRemoteConnection('ws://localhost:9998/gremlin', {traversalSource: 'g'})); |
| return g.V().toList().then(function() { |
| assert.fail("there is no server so an error should have occurred"); |
| }).catch(function(err) { |
| if (err instanceof AssertionError) throw err; |
| assert.strictEqual(err.code, "ECONNREFUSED"); |
| }); |
| }); |
| }); |
| describe('#toList()', function () { |
| it('should submit the traversal and return a list', function () { |
| var g = traversal().with_(connection); |
| return g.V().toList().then(function (list) { |
| assert.ok(list); |
| assert.strictEqual(list.length, 6); |
| list.forEach(v => assert.ok(v instanceof Vertex)); |
| }); |
| }); |
| }); |
| describe('#clone()', function () { |
| it('should reset a traversal when cloned', function () { |
| var g = traversal().with_(connection); |
| var t = g.V().count(); |
| return t.next().then(function (item1) { |
| assert.ok(item1); |
| assert.strictEqual(item1.value, 6); |
| t.clone().next().then(function (item2) { |
| assert.ok(item2); |
| assert.strictEqual(item2.value, 6); |
| }); |
| }); |
| }); |
| }); |
| describe('#next()', function () { |
| it('should submit the traversal and return an iterator', function () { |
| var g = traversal().with_(connection); |
| var t = g.V().count(); |
| return t.hasNext() |
| .then(function (more) { |
| assert.ok(more); |
| assert.strictEqual(more, true); |
| return t.next(); |
| }).then(function (item) { |
| assert.strictEqual(item.done, false); |
| assert.strictEqual(typeof item.value, 'number'); |
| return t.next(); |
| }).then(function (item) { |
| assert.ok(item); |
| assert.strictEqual(item.done, true); |
| assert.strictEqual(item.value, null); |
| }); |
| }); |
| }); |
| describe('#materializeProperties()', function () { |
| it('should skip vertex properties when tokens is set', function () { |
| var g = traversal().withRemote(connection); |
| return g.with_("materializeProperties", "tokens").V().toList().then(function (list) { |
| assert.ok(list); |
| assert.strictEqual(list.length, 6); |
| list.forEach(v => assert.ok(v instanceof Vertex)); |
| list.forEach(v => assert.strictEqual(v.properties.length, 0)); |
| }); |
| }); |
| it('should skip edge properties when tokens is set', function () { |
| var g = traversal().withRemote(connection); |
| return g.with_("materializeProperties", "tokens").E().toList().then(function (list) { |
| assert.ok(list); |
| assert.strictEqual(list.length, 6); |
| list.forEach(e => assert.ok(e instanceof Edge)); |
| // due to the way edge is constructed, edge properties will be {} regardless if it's null or [] |
| list.forEach(e => assert.strictEqual(Object.keys(e.properties).length, 0)); |
| }); |
| }); |
| it('should skip vertex property properties when tokens is set', function () { |
| var g = traversal().withRemote(connection); |
| return g.with_("materializeProperties", "tokens").V().properties().toList().then(function (list) { |
| assert.ok(list); |
| assert.strictEqual(list.length, 12); |
| list.forEach(vp => assert.ok(vp instanceof VertexProperty)); |
| list.forEach(vp => assert.strictEqual(vp.properties.length, 0)); |
| }); |
| }); |
| it('should skip path element properties when tokens is set', function () { |
| var g = traversal().withRemote(connection); |
| return g.with_("materializeProperties", "tokens").V().has('name','marko').outE().inV().hasLabel('software').path().next().then(function (item) { |
| const p = item.value; |
| assert.ok(p); |
| assert.strictEqual(p.objects.length, 3); |
| const a = p.objects[0]; |
| const b = p.objects[1]; |
| const c = p.objects[2]; |
| assert.ok(a instanceof Vertex); |
| assert.ok(b instanceof Edge); |
| assert.ok(c instanceof Vertex); |
| assert.strictEqual(a.properties.length, 0); |
| assert.strictEqual(Object.keys(b.properties).length, 0); |
| assert.strictEqual(c.properties.length, 0); |
| }); |
| }); |
| it('should materialize path element properties when all is set', function () { |
| var g = traversal().withRemote(connection); |
| return g.with_("materializeProperties", "all").V().has('name','marko').outE().inV().hasLabel('software').path().next().then(function (item) { |
| const p = item.value; |
| assert.ok(p); |
| assert.strictEqual(p.objects.length, 3); |
| const a = p.objects[0]; |
| const b = p.objects[1]; |
| const c = p.objects[2]; |
| assert.ok(a instanceof Vertex); |
| assert.ok(b instanceof Edge); |
| assert.ok(c instanceof Vertex); |
| assert.ok(a.properties); |
| // these assertions are if/then because of how mixed up javascript is right now on serialization |
| // standards as discussed on TINKERPOP-3186 - can fix that on a breaking change...until then we |
| // just retain existing weirdness. |
| let aNameProps, aAgeProps; |
| if (a.properties instanceof Array) { |
| aNameProps = a.properties.filter(p => p.key === 'name'); |
| aAgeProps = a.properties.filter(p => p.key === 'age'); |
| } else { |
| aNameProps = a.properties['name']; |
| aAgeProps = a.properties['age']; |
| } |
| assert.ok(aNameProps); |
| assert.strictEqual(aNameProps.length, 1); |
| assert.strictEqual(aNameProps[0].value, 'marko'); |
| assert.ok(aAgeProps); |
| assert.strictEqual(aAgeProps.length, 1); |
| assert.strictEqual(aAgeProps[0].value, 29); |
| assert.ok(b.properties); |
| const bWeight = b.properties[0].value |
| assert.ok(bWeight !== undefined); |
| assert.strictEqual(bWeight, 0.4); |
| assert.ok(c.properties); |
| let cNameProps, cLangProps; |
| if (c.properties instanceof Array) { |
| cNameProps = c.properties.filter(p => p.key === 'name'); |
| cLangProps = c.properties.filter(p => p.key === 'lang'); |
| } else { |
| cNameProps = c.properties['name']; |
| cLangProps = c.properties['lang']; |
| } |
| assert.ok(cNameProps); |
| assert.strictEqual(cNameProps.length, 1); |
| assert.strictEqual(cNameProps[0].value, 'lop'); |
| assert.ok(cLangProps); |
| assert.strictEqual(cLangProps.length, 1); |
| assert.strictEqual(cLangProps[0].value, 'java'); |
| }); |
| }); |
| }); |
| describe('lambdas', function() { |
| it('should handle 1-arg lambdas', function() { |
| const g = traversal().with_(connection); |
| return g.V().has('person','name','marko').values('name').map(() => "it.get()[1]").toList().then(function (s) { |
| assert.ok(s); |
| assert.strictEqual(s[0], 'a'); |
| }) |
| }); |
| }); |
| describe('dsl', function() { |
| it('should expose DSL methods', function() { |
| const g = traversal(SocialTraversalSource).with_(connection); |
| return g.person('marko').aged(29).values('name').toList().then(function (list) { |
| assert.ok(list); |
| assert.strictEqual(list.length, 1); |
| assert.strictEqual(list[0], 'marko'); |
| }); |
| }); |
| |
| it('should expose anonymous DSL methods', function() { |
| const g = traversal(SocialTraversalSource).with_(connection); |
| return g.person('marko').filter(aged(29)).values('name').toList().then(function (list) { |
| assert.ok(list); |
| assert.strictEqual(list.length, 1); |
| assert.strictEqual(list[0], 'marko'); |
| }); |
| }); |
| }); |
| describe("more complex traversals", function() { |
| it('should return paths of value maps', function() { |
| const g = traversal().with_(connection); |
| return g.V(1).out().order().in_().order().limit(1).path().by(__.valueMap('name')).toList().then(function (list) { |
| assert.ok(list); |
| assert.strictEqual(list.length, 1); |
| assert.strictEqual(list[0].objects[0].get('name')[0], "marko"); |
| assert.strictEqual(list[0].objects[1].get('name')[0], "vadas"); |
| assert.strictEqual(list[0].objects[2].get('name')[0], "marko"); |
| }); |
| }); |
| }); |
| describe("should allow TraversalStrategy definition", function() { |
| it('should allow SubgraphStrategy', function() { |
| const g = traversal().with_(connection).withStrategies( |
| new SubgraphStrategy({vertices:__.hasLabel("person"), edges:__.hasLabel("created")})); |
| g.V().count().next().then(function (item1) { |
| assert.ok(item1); |
| assert.strictEqual(item1.value, 4); |
| }, (err) => assert.fail("tanked: " + err)); |
| g.E().count().next().then(function (item1) { |
| assert.ok(item1); |
| assert.strictEqual(item1.value, 0); |
| }, (err) => assert.fail("tanked: " + err)); |
| g.V().label().dedup().count().next().then(function (item1) { |
| assert.ok(item1); |
| assert.strictEqual(item1.value, 1); |
| }, (err) => assert.fail("tanked: " + err)); |
| g.V().label().dedup().next().then(function (item1) { |
| assert.ok(item1); |
| assert.strictEqual(item1.value, "person"); |
| }, (err) => assert.fail("tanked: " + err)); |
| }); |
| it('should allow ReadOnlyStrategy', function() { |
| const g = traversal().with_(connection).withStrategies(new ReadOnlyStrategy()); |
| return g.addV().iterate().then(() => assert.fail("should have tanked"), (err) => assert.ok(err)); |
| }); |
| it('should allow OptionsStrategy', function() { |
| const g = traversal().with_(connection).withStrategies(new OptionsStrategy()); |
| return g.V().count().next().then(function (item1) { |
| assert.ok(item1); |
| assert.strictEqual(item1.value, 6); |
| }); |
| }); |
| it('should allow ReservedKeysVerificationStrategy', function() { |
| const g = traversal().with_(connection).withStrategies(new ReservedKeysVerificationStrategy({logWarnings: false, throwException: true})); |
| return g.addV().property("id", "please-don't-use-id").iterate().then(() => assert.fail("should have tanked"), (err) => assert.ok(err)); |
| }); |
| it('should allow EdgeLabelVerificationStrategy', function() { |
| const g = traversal().with_(connection).withStrategies(new EdgeLabelVerificationStrategy({logWarnings: false, throwException: true})); |
| g.V().outE("created", "knows").count().next().then(function (item1) { |
| assert.ok(item1); |
| assert.strictEqual(item1.value, 6); |
| }); |
| return g.V().out().iterate().then(() => assert.fail("should have tanked"), (err) => assert.strictEqual(err.statusCode, 500)); |
| }); |
| it('should allow with_(evaluationTimeout,10)', function() { |
| const g = traversal().with_(connection).with_('x').with_('evaluationTimeout', 10); |
| return g.V().repeat(__.both()).iterate().then(() => assert.fail("should have tanked"), (err) => assert.strictEqual(err.statusCode, 598)); |
| }); |
| it('should allow SeedStrategy', function () { |
| const g = traversal().with_(connection).withStrategies(new SeedStrategy({seed: 999999})); |
| return g.V().coin(0.4).count().next().then(function (item1) { |
| assert.ok(item1); |
| assert.strictEqual(item1.value, 1); |
| }, (err) => assert.fail("tanked: " + err)); |
| }); |
| it('should allow without HaltedTraverserStrategy', function() { |
| const c = helper.getDriverRemoteConnectionGraphSON( 'gmodern'); |
| const g = traversal().with_(c).withoutStrategies(HaltedTraverserStrategy); |
| return g.V().count().next().then(function (item1) { |
| assert.ok(item1); |
| assert.strictEqual(item1.value, 6); |
| c.close(); |
| }); |
| }); |
| it('should allow with FilterRankingStrategy', function() { |
| const c = helper.getDriverRemoteConnectionGraphSON( 'gmodern'); |
| const g = traversal().with_(c).withStrategies(new FilterRankingStrategy()); |
| return g.V().out().order().dedup().count().next().then(function (item1) { |
| assert.ok(item1); |
| assert.strictEqual(item1.value, 4); |
| c.close(); |
| }); |
| }); |
| }); |
| describe("should handle tx errors if graph not support tx", function() { |
| it('should throw exception on commit if graph not support tx', async function() { |
| const g = traversal().withRemote(connection); |
| const tx = g.tx(); |
| const gtx = tx.begin(); |
| const result = await g.V().count().next(); |
| assert.strictEqual(6, result.value); |
| try { |
| await tx.commit(); |
| assert.fail("should throw error"); |
| } catch (err) { |
| assert.strictEqual("Server error: Graph does not support transactions (500)", err.message); |
| } |
| }); |
| it('should throw exception on rollback if graph not support tx', async function() { |
| const g = traversal().withRemote(connection); |
| const tx = g.tx(); |
| tx.begin(); |
| try { |
| await tx.rollback(); |
| assert.fail("should throw error"); |
| } catch (err) { |
| assert.strictEqual("Server error: Graph does not support transactions (500)", err.message); |
| } |
| }); |
| }); |
| describe('support remote transactions - commit', function() { |
| before(function () { |
| txConnection = helper.getConnection('gtx'); |
| return txConnection.open(); |
| }); |
| after(function () { |
| const g = traversal().with_(txConnection); |
| return g.V().drop().iterate().then(() => { |
| return txConnection.close() |
| }); |
| }); |
| it('should commit a simple transaction', async function () { |
| const g = traversal().with_(txConnection); |
| const tx = g.tx(); |
| const gtx = tx.begin(); |
| await Promise.all([ |
| gtx.addV("person").property("name", "jorge").iterate(), |
| gtx.addV("person").property("name", "josh").iterate() |
| ]); |
| |
| let r = await gtx.V().count().next(); |
| // assert within the transaction.... |
| assert.ok(r); |
| assert.strictEqual(r.value, 2); |
| |
| // now commit changes to test outside of the transaction |
| await tx.commit(); |
| |
| r = await g.V().count().next(); |
| assert.ok(r); |
| assert.strictEqual(r.value, 2); |
| // connection closing async, so need to wait |
| while (tx._sessionBasedConnection.isOpen) { |
| await new Promise(resolve => setTimeout(resolve, 10)); |
| } |
| assert.ok(!tx._sessionBasedConnection.isOpen); |
| }); |
| }); |
| describe('support remote transactions - rollback', function() { |
| before(function () { |
| |
| txConnection = helper.getConnection('gtx'); |
| return txConnection.open(); |
| }); |
| after(function () { |
| const g = traversal().with_(txConnection); |
| return g.V().drop().iterate().then(() => { |
| return txConnection.close() |
| }); |
| }); |
| it('should rollback a simple transaction', async function() { |
| const g = traversal().with_(txConnection); |
| const tx = g.tx(); |
| const gtx = tx.begin(); |
| await Promise.all([ |
| gtx.addV("person").property("name", "jorge").iterate(), |
| gtx.addV("person").property("name", "josh").iterate() |
| ]); |
| |
| let r = await gtx.V().count().next(); |
| // assert within the transaction.... |
| assert.ok(r); |
| assert.strictEqual(r.value, 2); |
| |
| // now rollback changes to test outside of the transaction |
| await tx.rollback(); |
| |
| r = await g.V().count().next(); |
| assert.ok(r); |
| assert.strictEqual(r.value, 0); |
| // connection closing async, so need to wait |
| while (tx._sessionBasedConnection.isOpen) { |
| await new Promise(resolve => setTimeout(resolve, 10)); |
| } |
| assert.ok(!tx._sessionBasedConnection.isOpen); |
| }); |
| }); |
| }); |