/*
 * 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.
 */

'use strict';

const assert = require('assert');
const Client = require('../../../lib/client');
const types = require('../../../lib/types');
const utils = require('../../../lib/utils');
const policies = require('../../../lib/policies');
const ExecutionProfile = require('../../../lib/execution-profile').ExecutionProfile;
const GraphExecutor = require('../../../lib/datastax/graph/graph-executor');
const { GraphExecutionOptions, graphProtocol } = require('../../../lib/datastax/graph/options');
const helper = require('../../test-helper');

const proxyExecuteKey = 'ProxyExecute';

describe('GraphExecutor', function () {
  describe('#send()', () => {
    it('should encode the parameters and build GraphExecutionOptions', async () => {
      const setup = setupGraphExecutor();
      await setup.instance.send('QUERY1', { a: 1 }, null);

      assert.strictEqual(setup.executionArguments.query, 'QUERY1');
      assert.deepStrictEqual(setup.executionArguments.params, ['{"a":1}']);
      helper.assertInstanceOf(setup.executionArguments.options, GraphExecutionOptions);
      assert.ok(setup.executionArguments.options.getCustomPayload());
    });

    it('should not allow a namespace of type different than string', async () => {
      const setup = setupGraphExecutor();

      // Does not throw
      await setup.instance.send('Q1', {}, { graphName: 'abc' });

      await helper.assertThrowsAsync(setup.instance.send('Q1', {}, { graphName: 123 }), TypeError);
    });

    it('should not allow a array query parameters', async () => {
      const setup = setupGraphExecutor();

      await helper.assertThrowsAsync(setup.instance.send('Q1', [], { }), TypeError,
        'Parameters must be a Object instance as an associative array');
    });

    it('should set the same default options when not set', async () => {
      const setup = setupGraphExecutor();

      await setup.instance.send('Q5', { z: 3 }, null);
      const firstOptions = setup.executionArguments.options;
      assert.ok(firstOptions.getRawQueryOptions());

      setup.instance.send('Q6', { z: 4 }, null, helper.noop);
      assert.strictEqual(firstOptions.getRawQueryOptions(), setup.executionArguments.options.getRawQueryOptions());
      assert.ok(firstOptions.getCustomPayload());
    });

    it('should set the default payload for the executions', async () => {
      const clientOptions = {
        contactPoints: ['host1'],
        graphOptions: {
          name: 'name1',
          source: 'a1',
          readConsistency: types.consistencies.localOne
        }
      };

      const setup = setupGraphExecutor(clientOptions);
      const optionsParameter = { anotherOption: { k: 'v'}};

      await setup.instance.send('Q5', { c: 0}, optionsParameter);

      const actualOptions = setup.executionArguments.options;

      assert.strictEqual(optionsParameter.anotherOption, actualOptions.getRawQueryOptions().anotherOption);
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson1);
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-source'], 'a1');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-name'], 'name1');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-read-consistency'], 'LOCAL_ONE');
      assert.strictEqual(actualOptions.getCustomPayload()['graph-write-consistency'], undefined);
    });

    it('should set the default readTimeout in the payload', async () => {
      const clientOptions = {
        contactPoints: ['host1'],
        graphOptions: {
          source: 'x',
          writeConsistency: types.consistencies.two
        }
      };

      const setup = setupGraphExecutor(clientOptions);
      let actualOptions;

      // with options defined
      await setup.instance.send('Q10', { c: 0}, { });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson1);
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-source'], 'x');
      assert.strictEqual(actualOptions.getCustomPayload()['graph-read-consistency'], undefined);
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-write-consistency'], 'TWO');
      assert.strictEqual(typeof actualOptions.getCustomPayload()['request-timeout'], 'undefined');

      // with payload defined
      await setup.instance.send('Q10', { c: 0}, { customPayload: { 'z': utils.allocBufferFromString('zValue')} });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson1);
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-source'], 'x');
      helper.assertBufferString(actualOptions.getCustomPayload()['z'], 'zValue');
      assert.strictEqual(typeof actualOptions.getCustomPayload()['request-timeout'], 'undefined');

      // with timeout defined
      await setup.instance.send('Q10', { c: 0}, { readTimeout: 9999 });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson1);
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-source'], 'x');
      assert.strictEqual(actualOptions.getCustomPayload()['z'], undefined);
      assert.deepEqual(actualOptions.getCustomPayload()['request-timeout'],
        types.Long.toBuffer(types.Long.fromNumber(9999)));
      assert.strictEqual(actualOptions.getReadTimeout(), 9999);

      // without options defined
      await setup.instance.send('Q10', { c: 0});
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson1);
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-source'], 'x');
      assert.strictEqual(typeof actualOptions.getCustomPayload()['request-timeout'], 'undefined');
    });

    it('should set the read and write consistency levels', async () => {
      const clientOptions = {
        contactPoints: ['host1'],
        graphOptions: {
          name: 'name10'
        }
      };

      const setup = setupGraphExecutor(clientOptions);
      let actualOptions;

      await setup.instance.send('Q5', { c: 0});
      actualOptions = setup.executionArguments.options;
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-source'], 'g');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-name'], 'name10');
      assert.strictEqual(actualOptions.getCustomPayload()['graph-read-consistency'], undefined);
      assert.strictEqual(actualOptions.getCustomPayload()['graph-write-consistency'], undefined);

      let optionsParameter = {
        graphReadConsistency: types.consistencies.localQuorum
      };
      await setup.instance.send('Q5', { c: 0}, optionsParameter);
      actualOptions = setup.executionArguments.options;
      assert.notStrictEqual(optionsParameter, actualOptions);
      assert.strictEqual(optionsParameter.anotherOption, actualOptions.getRawQueryOptions().anotherOption);
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson1);
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-read-consistency'], 'LOCAL_QUORUM');
      assert.strictEqual(actualOptions.getCustomPayload()['graph-write-consistency'], undefined);

      optionsParameter = {
        graphWriteConsistency: types.consistencies.quorum
      };
      await setup.instance.send('Q5', { c: 0}, optionsParameter);
      actualOptions = setup.executionArguments.options;
      assert.notStrictEqual(optionsParameter, actualOptions);
      assert.strictEqual(optionsParameter.anotherOption, actualOptions.anotherOption);
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson1);
      assert.strictEqual(actualOptions.getCustomPayload()['graph-read-consistency'], undefined);
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-write-consistency'], 'QUORUM');
    });

    it('should set graph results', async () => {
      const setup = setupGraphExecutor({ contactPoints: ['host1'] });

      await setup.instance.send('Q5', { c: 0});
      let actualOptions = setup.executionArguments.options;
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson1);
      const optionsParameter = {
        graphResults: 'graphson-3.0'
      };
      await setup.instance.send('Q5', { c: 0}, optionsParameter);
      actualOptions = setup.executionArguments.options;
      assert.notStrictEqual(optionsParameter, actualOptions);
      //shallow copy the properties
      assert.strictEqual(optionsParameter.anotherOption, actualOptions.getRawQueryOptions().anotherOption);
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], 'graphson-3.0');
    });

    it('should reuse the values from the default payload for the executions', async () => {
      const clientOptions = { contactPoints: ['host1'], graphOptions: { name: 'name1' }};
      const setup = setupGraphExecutor(clientOptions);
      const optionsParameter = { anotherOption: { k: 'v2'}};

      await setup.instance.send('Q5', { a: 1}, optionsParameter);
      const firstOptions = setup.executionArguments.options;
      await setup.instance.send('Q6', { b: 1}, optionsParameter);

      Object.keys(firstOptions.getCustomPayload())
        .filter(k => k !== 'graph-results')
        .forEach(k => {
          assert.strictEqual(
            firstOptions.getCustomPayload()[k], setup.executionArguments.options.getCustomPayload()[k]);
        });
    });

    it('should set the payload with the options provided', async () => {
      const clientOptions = {
        contactPoints: ['host1'],
        graphOptions: {
          language: 'groovy2',
          source: 'another-source',
          name: 'namespace2'
        }};

      const setup = setupGraphExecutor(clientOptions);
      const optionsParameter = { anotherOption: { k: 'v3'}};
      await setup.instance.send('Q5', { 'x': 1 }, optionsParameter);

      const actualOptions = setup.executionArguments.options;
      assert.strictEqual(optionsParameter.anotherOption, actualOptions.getRawQueryOptions().anotherOption);
      assert.ok(actualOptions.getCustomPayload());
      assert.deepStrictEqual(actualOptions.getCustomPayload()['graph-language'],
        utils.allocBufferFromString('groovy2'));
      assert.deepStrictEqual(actualOptions.getCustomPayload()['graph-source'],
        utils.allocBufferFromString('another-source'));
      assert.deepStrictEqual(actualOptions.getCustomPayload()['graph-name'], utils.allocBufferFromString('namespace2'));
    });

    it('should set the payload with the user/role provided', async () => {
      const clientOptions = { contactPoints: ['host1'], graphOptions: { name: 'name2' }};
      const setup = setupGraphExecutor(clientOptions);
      let actualOptions;

      await setup.instance.send('Q5', { 'x': 1 }, null);
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-name'], 'name2');
      assert.strictEqual(actualOptions.getCustomPayload()[proxyExecuteKey], undefined);
      const previousPayload = actualOptions.getCustomPayload();

      await setup.instance.send('Q5', { 'x': 1 }, { executeAs: 'alice' });
      actualOptions = setup.executionArguments.options;
      assert.notStrictEqual(previousPayload, actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-name'], 'name2');
      helper.assertBufferString(actualOptions.getCustomPayload()[proxyExecuteKey], 'alice');
    });

    it('should set the options according to default profile', async () => {
      const clientOptions = {
        contactPoints: ['host1'],
        graphOptions: {
          language: 'groovy1',
          source: 'source1',
          name: 'name1'
        },
        profiles: [
          new ExecutionProfile('default', {
            graphOptions: {
              name: 'nameZ',
              readConsistency: types.consistencies.two
            }
          })
        ]
      };

      const setup = setupGraphExecutor(clientOptions);

      await setup.instance.send('Q', { 'x': 1 }, null);
      const actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'groovy1');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson2);
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-source'], 'source1');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-name'], 'nameZ');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-read-consistency'], 'TWO');
      helper.assertInstanceOf(actualOptions.getRetryPolicy(), policies.retry.FallthroughRetryPolicy);
    });

    it('should set the options according to specified profile', async () => {
      const clientOptions = {
        contactPoints: ['host1'],
        graphOptions: {
          source: 'source1',
          name: 'name1'
        },
        profiles: [
          new ExecutionProfile('default', {
            graphOptions: {
              name: 'nameZ',
              readConsistency: types.consistencies.two
            }
          }),
          new ExecutionProfile('graph-olap', {
            graphOptions: {
              source: 'aaa',
              readConsistency: types.consistencies.three,
              writeConsistency: types.consistencies.quorum
            },
            readTimeout: 99000
          })
        ]
      };

      const setup = setupGraphExecutor(clientOptions);
      const optionsParameter = { executionProfile: 'graph-olap' };
      let actualOptions;
      await setup.instance.send('Q', { 'x': 1 }, optionsParameter);
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.ok(actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-source'], 'aaa');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-name'], 'nameZ');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-read-consistency'], 'THREE');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-write-consistency'], 'QUORUM');
      assert.deepEqual(actualOptions.getCustomPayload()['request-timeout'],
        types.Long.toBuffer(types.Long.fromNumber(99000)));
      const lastOptions = actualOptions;

      await setup.instance.send('Q2', { 'x': 2 }, optionsParameter);
      actualOptions = setup.executionArguments.options;
      assert.notStrictEqual(actualOptions, lastOptions);
      helper.assertInstanceOf(actualOptions.getRetryPolicy(), policies.retry.FallthroughRetryPolicy);
      optionsParameter.retry = new policies.retry.RetryPolicy();

      await setup.instance.send('Q2', { 'x': 3 }, optionsParameter);
      actualOptions = setup.executionArguments.options;
      assert.strictEqual(actualOptions.getRetryPolicy(), optionsParameter.retry);
    });

    it('should use the retry policy from the default profile when specified', async () => {
      const retryPolicy1 = new policies.retry.RetryPolicy();
      const retryPolicy2 = new policies.retry.FallthroughRetryPolicy();
      const retryPolicy3 = new policies.retry.FallthroughRetryPolicy();
      const clientOptions = {
        contactPoints: ['host1'],
        profiles: [
          new ExecutionProfile('default', {
            retry: retryPolicy1
          }),
          new ExecutionProfile('graph-olap', {
            retry: retryPolicy2
          })
        ]
      };

      const setup = setupGraphExecutor(clientOptions);
      let actualOptions;
      await setup.instance.send('Q', null, null);
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.strictEqual(actualOptions.getRetryPolicy(), retryPolicy1);

      await setup.instance.send('Q', null, { executionProfile: 'graph-olap' });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.strictEqual(actualOptions.getRetryPolicy(), retryPolicy2);

      await setup.instance.send('Q', null, { executionProfile: 'graph-olap', retry: retryPolicy3 });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.strictEqual(actualOptions.getRetryPolicy(), retryPolicy3);

      await setup.instance.send('Q', null, { retry: retryPolicy3 });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.strictEqual(actualOptions.getRetryPolicy(), retryPolicy3);
    });

    it('should use specific retry policy when not specified in the default profile',async () => {
      const retryPolicy1 = new policies.retry.RetryPolicy();
      const retryPolicy2 = new policies.retry.RetryPolicy();
      const clientOptions = {
        contactPoints: ['host1'],
        profiles: [
          new ExecutionProfile('default'),
          new ExecutionProfile('graph-olap', {
            retry: retryPolicy1
          })
        ]
      };
      const setup = setupGraphExecutor(clientOptions);
      let actualOptions;

      await setup.instance.send('Q', null, null);
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      helper.assertInstanceOf(actualOptions.getRetryPolicy(), policies.retry.FallthroughRetryPolicy);

      await setup.instance.send('Q', null, { executionProfile: 'default'});
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      helper.assertInstanceOf(actualOptions.getRetryPolicy(), policies.retry.FallthroughRetryPolicy);

      await setup.instance.send('Q', null, { executionProfile: 'graph-olap' });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.strictEqual(actualOptions.getRetryPolicy(), retryPolicy1);

      await setup.instance.send('Q', null, { executionProfile: 'graph-olap', retry: retryPolicy2 });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.strictEqual(actualOptions.getRetryPolicy(), retryPolicy2);

      await setup.instance.send('Q', null, { retry: retryPolicy2 });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.strictEqual(actualOptions.getRetryPolicy(), retryPolicy2);
    });

    it('should use specific retry policy when no default profile specified', async () => {
      const retryPolicy1 = new policies.retry.RetryPolicy();
      const retryPolicy2 = new policies.retry.RetryPolicy();
      const clientOptions = {
        contactPoints: ['host1'],
        profiles: [
          new ExecutionProfile('graph-olap', {
            retry: retryPolicy1
          })
        ]
      };

      const setup = setupGraphExecutor(clientOptions);
      let actualOptions;

      await setup.instance.send('Q', null, null);
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      helper.assertInstanceOf(actualOptions.getRetryPolicy(), policies.retry.FallthroughRetryPolicy);

      await setup.instance.send('Q', null, { executionProfile: 'default'});
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      helper.assertInstanceOf(actualOptions.getRetryPolicy(), policies.retry.FallthroughRetryPolicy);

      await setup.instance.send('Q', null, { executionProfile: 'graph-olap' });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.strictEqual(actualOptions.getRetryPolicy(), retryPolicy1);

      await setup.instance.send('Q', null, { executionProfile: 'graph-olap', retry: retryPolicy2 });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.strictEqual(actualOptions.getRetryPolicy(), retryPolicy2);

      await setup.instance.send('Q', null, { retry: retryPolicy2 });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions);
      assert.strictEqual(actualOptions.getRetryPolicy(), retryPolicy2);
    });

    it('should use the graph language provided in the profile', async () => {
      const clientOptions = {
        contactPoints: ['host1'],
        profiles: [
          new ExecutionProfile('graph-olap', {
            graphOptions: { language: 'lolcode' }
          })
        ]
      };
      const setup = setupGraphExecutor(clientOptions);

      let actualOptions;
      await setup.instance.send('Q', null, { executionProfile: 'graph-olap' });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions && actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'lolcode');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson2);

      await setup.instance.send('Q');
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions && actualOptions.getCustomPayload());
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-language'], 'gremlin-groovy');
      helper.assertBufferString(actualOptions.getCustomPayload()['graph-results'], graphProtocol.graphson1);
    });

    it('should use the graph results provided in the profile', async () => {
      const clientOptions = {
        contactPoints: ['host1'],
        profiles: [
          new ExecutionProfile('graph-olap', {
            graphOptions: { results: graphProtocol.graphson2 }
          })
        ]
      };
      const setup = setupGraphExecutor(clientOptions);

      let actualOptions;
      await setup.instance.send('Q', null, { executionProfile: 'graph-olap' });
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions && actualOptions.getCustomPayload());
      assert.strictEqual(actualOptions.getCustomPayload()['graph-results'].toString(), graphProtocol.graphson2);
      await setup.instance.send('Q', null, null);
      actualOptions = setup.executionArguments.options;
      assert.ok(actualOptions && actualOptions.getCustomPayload());
      // Default for gremlin groovy
      assert.strictEqual(actualOptions.getCustomPayload()['graph-results'].toString(), graphProtocol.graphson1);
    });

    context('with analytics queries', function () {
      it('should query for analytics master', async () => {
        const clientOptions = {
          contactPoints: ['host1'],
          graphOptions: {
            source: 'a',
            name: 'name1'
          }};

        const setup = setupGraphExecutor(clientOptions);

        await setup.instance.send('g.V()', null, {});
        const actualOptions = setup.executionArguments.options;
        assert.ok(actualOptions);
        assert.ok(actualOptions.getPreferredHost());
        assert.ok(actualOptions.getPreferredHost().address, '10.10.10.10:9042');
      });

      it('should query for analytics master when using execution profile', async () => {
        const clientOptions = {
          contactPoints: ['host1'],
          profiles: [
            new ExecutionProfile('analytics', {
              graphOptions: {
                source: 'a'
              }
            })
          ]
        };

        const setup = setupGraphExecutor(clientOptions);

        await setup.instance.send('g.V()', null, { executionProfile: 'analytics' });
        const actualOptions = setup.executionArguments.options;
        assert.ok(actualOptions);
        assert.ok(actualOptions.getPreferredHost());
        assert.ok(actualOptions.getPreferredHost().address, '10.10.10.10:9042');
      });

      it('should call address translator', async () => {
        let translatorCalled = 0;
        const translator = new policies.addressResolution.AddressTranslator();
        translator.translate = function (ip, port, cb) {
          translatorCalled++;
          cb(ip + ':' + port);
        };

        const clientOptions = {
          contactPoints: ['host1'],
          graphOptions: {
            source: 'a',
            name: 'name1'
          },
          policies: {
            addressResolution: translator
          }
        };

        const setup = setupGraphExecutor(clientOptions);

        await setup.instance.send('g.V()', null, null);
        const actualOptions = setup.executionArguments.options;
        assert.ok(actualOptions);
        assert.ok(actualOptions.getPreferredHost());
        assert.strictEqual(actualOptions.getPreferredHost().address, '10.10.10.10:9042');
        assert.strictEqual(translatorCalled, 1);
      });

      it('should set preferredHost to null when RPC errors', async () => {
        const clientOptions = {
          contactPoints: ['host1'],
          graphOptions: {
            source: 'a',
            name: 'name1'
          }};

        const clientProperties = {
          execute: function (q) {
            if (q === 'CALL DseClientTool.getAnalyticsGraphServer()') {
              return Promise.reject(new Error('Test error'));
            }

            return Promise.resolve({ rows: []});
          }
        };

        const setup = setupGraphExecutor(clientOptions, clientProperties);

        await setup.instance.send('g.V()');
        const actualOptions = setup.executionArguments.options;
        assert.ok(actualOptions);
        assert.strictEqual(actualOptions.getPreferredHost(), null);
      });
    });
  });
});

function setupGraphExecutor(clientOptions, clientProperties) {
  clientOptions = clientOptions || { contactPoints: [ 'h1' ] };
  const client = new Client(clientOptions);

  const result = {
    executionArguments: null,
    instance: null
  };

  result.instance = new GraphExecutor(client, clientOptions, (query, params, options) => {
    result.executionArguments = { query, params, options };
    return Promise.resolve({ rows: [] });
  });

  client.hosts = { get: address => ({ type: 'host', address: address }) };
  client.execute = q => {
    if (q === 'CALL DseClientTool.getAnalyticsGraphServer()') {
      return Promise.resolve({ rows: [ { result: { location: '10.10.10.10:1234' }} ]});
    }

    return Promise.resolve({ rows: []});
  };

  if (clientProperties) {
    Object.keys(clientProperties).forEach(propName => client[propName] = clientProperties[propName]);
  }

  return result;
}