<commit_before>/*
 * This file is part of EMCOO TEAM PROJECT.
 *
 *  (c) EMCOO TEAM
 *  (c) DevMark <mark@emcoo.com>
 *
 *  For the full copyright and license information, please view http://emcoo.com/
 *
 *
 */

(function () {
    'use strict';

    angular.module('backend', [

        'authModule',
        'userModule',
        'postModule',
        'mediaModule',
        'mediaCategoryModule',
        'postCategoryModule',
        'settingModule',
        'languageModule',

        'theme.directives',
        'theme.services',

        'global.filter',
        'global.service',
        'global.directive',

        'angular-spinkit',
        'smart-table',
        'restangular',
        'ui.router',
        'ui.bootstrap',
        'ui.bootstrap.datetimepicker',
        'ui.tree',
        'ui.select',
        'checklist-model',

        'textAngular',
        'angular.filter',
        'pascalprecht.translate', //Multi Language plugin
        'angularMoment', //moment plugin, date plugin
        'toaster', //notification plugin
        'LocalStorageModule', // local storage module
        'tmh.dynamicLocale',

        'ngAnimate',
        'ngCookies',
        'ngTouch',
        'ngRoute',
        'ngSanitize'

    ])
        //================================================
        // Translate Config
        //================================================
        .config(function ($translateProvider) {
            $translateProvider.useStaticFilesLoader({
                prefix: 'app/languages/locale-',
                suffix: '.json'
            });

            $translateProvider.useCookieStorage();
            $translateProvider.useSanitizeValueStrategy('sanitize');
            $translateProvider.preferredLanguage('en-us');
            $translateProvider.fallbackLanguage(['en-us', 'zh-hk', 'zh-cn']);

        })
        //================================================
        // UI select Config
        //================================================
        .config(function (uiSelectConfig) {
            uiSelectConfig.theme = 'bootstrap';
            uiSelectConfig.resetSearchInput = true;
        })
        //================================================
        // Check isLogin when run system
        //================================================
        .run(function ($rootScope, $location, authenticationService, localStorageService, amMoment, tmhDynamicLocale, settingService, $translate, $timeout) {
            var routesThatRequireAuth = ['/auth/login', '/auth/logout'];

            //================================================
            // page route event
            //================================================
            $rootScope.pageViewLoading = false;
            $rootScope.$on('$stateChangeStart', function (event, next, current) {
                if (!_(routesThatRequireAuth).contains($location.path()) && !authenticationService.check()) {
                    console.log('Oops! please login!');
                    $location.path('auth/login');
                }
                $rootScope.pageViewLoading = true;
            });
            $rootScope.$on('$stateChangeSuccess', function () {
                $timeout(function () {
                    $rootScope.pageViewLoading = false;
                }, 500);
            });

            //================================================
            // page title init
            //================================================
            $translate('panel.name').then(function (translation) {
                $rootScope.siteTitle = translation;
            });
            $rootScope.meta = {
                pageTitle: ''
            };

            //================================================
            // global variable init
            //================================================
            $rootScope.$location = $location;

            //================================================
            // timezone init
            //================================================
            amMoment.changeTimezone(settingService.find('timezone'));

            //================================================
            // currency init
            //================================================
            if (!localStorageService.get('currency')) {
                tmhDynamicLocale.set('zh-hk');
                localStorageService.set('currency', settingService.get('currency'));
            }

        })
        .run(function ($location, authenticationService, localStorageService, $timeout, Restangular, $http, $q) {

            var refreshAccessToken = function () {
                var deferred = $q.defer();
                console.log('start get refresh token');

                $http({
                    method: 'POST', url: 'http://cms.dev/api/admin/auth/refresh-token', headers: {
                        'Authorization': authenticationService.getToken()
                    }
                }).success(function (data, status, headers, config) {
                    console.log('set token');
                    localStorageService.set('token', data.result.token);
                    deferred.resolve(data.result.token);
                }).error(function (data, status, headers, config) {
                    console.log('Oops! Expired Token, please login again! ');
                    $location.path('auth/login');
                    deferred.reject(data);
                });

                return deferred.promise;
            };

            Restangular.setErrorInterceptor(function (response, deferred, responseHandler) {
                if (response.status === 401 || response.message === 'token_expired') {
                    return refreshAccessToken().then(function () {
                        response.config.headers.Authorization = authenticationService.getToken();
                        // Repeat the request and then call the handlers the usual way.
                        $http(response.config).then(responseHandler, deferred.reject);
                        return false;
                        // Be aware that no request interceptors are called this way.
                    });
                    return false; // error handled
                }
                console.log('error not handled');
                return true; // error not handled
            });

            //Restangular.setErrorInterceptor(function (response, deferred, responseHandler) {
            //    if (response.status === 401 || response.message === 'token_expired') {
            //        refreshAccessToken().then(function () {
            //            response.config.headers.Authorization = authenticationService.getToken();
            //            // Repeat the request and then call the handlers the usual way.
            //            $http(response.config).then(responseHandler, deferred.reject)
            //                .then(function (httpResponse) {
            //                    deferred.resolve(httpResponse);
            //                });
            //
            //            // Be aware that no request interceptors are called this way.
            //        });
            //        return false; // error handled
            //    }
            //    return true; // error not handled
            //});

            Restangular.addFullRequestInterceptor(function (element, operation, route, url, headers, params, httpConfig) {
                //================================================
                // Token setup when load each request
                //================================================
                headers.Authorization = authenticationService.getToken();

                return {
                    element: element,
                    headers: headers,
                    params: params,
                    httpConfig: httpConfig
                };

            });

        })

        .config(function ($httpProvider, localStorageServiceProvider) {

            $httpProvider.defaults.useXDomain = true;
            delete $httpProvider.defaults.headers.common['X-Requested-With'];

            //================================================
            // Local storage module init
            //================================================
            localStorageServiceProvider
                .setPrefix('emcoocms')
                .setStorageType('localStorage')
                .setNotify(true, true);
        })

        //================================================
        // Plugin: Smart-table init
        //================================================
        .config(function (stConfig) {
            stConfig.select.mode = 'multiple';
        })

        //================================================
        // Plugin: Restangular init
        //================================================
        .config(function (RestangularProvider) {
            RestangularProvider.setBaseUrl('http://cms.dev/api/admin');
            RestangularProvider.setRestangularFields({
                ids: "_ids"
            });

            RestangularProvider.addResponseInterceptor(function (data, operation, what, url, response, deferred) {
                var extractedData;
                if (operation === 'getList') {
                    // handle the data and meta data
                    extractedData = data.result;
                    if (!_.isUndefined(data.meta)) {
                        extractedData.meta = data.meta;
                    }
                } else {
                    extractedData = data.result;
                }

                return extractedData;
            });

            RestangularProvider.setDefaultHttpFields({
                cache: true
            });

        })
        .config(function ($locationProvider, $stateProvider, $urlRouterProvider) {
            $locationProvider.html5Mode(true);
            $stateProvider
                .state('main', {
                    url: '/',
                    abstract: true,
                    templateUrl: 'app/main/main.html'
                })
                .state('fullscreen', {
                    url: '/',
                    abstract: true,
                    templateUrl: 'app/main/fullscreen.html'
                })
                .state('main.index', {
                    url: '',
                    templateUrl: 'app/main/dashboard.html',
                    resolve: {
                        meta: function ($rootScope, $translate, $q) {
                            var deferred = $q.defer();
                            $translate('panel.dashboard').then(function (translation) {
                                $rootScope.meta.pageTitle = translation;
                                deferred.resolve(true);
                            }, function () {
                                deferred.reject();
                            });
                            return deferred.promise;
                        }
                    }
                })
                .state('main.not-found', {
                    url: '404',
                    templateUrl: 'app/template/404.html',
                    resolve: {
                        meta: function ($rootScope, $translate, $q) {
                            var deferred = $q.defer();
                            $translate('panel.dashboard').then(function (translation) {
                                $rootScope.meta.pageTitle = translation;
                                deferred.resolve(true);
                            }, function () {
                                deferred.reject();
                            });
                            return deferred.promise;
                        }
                    }
                });

            $urlRouterProvider.otherwise('/');
        });

})();
<commit_msg>jet error handle<commit_after>/*
 * This file is part of EMCOO TEAM PROJECT.
 *
 *  (c) EMCOO TEAM
 *  (c) DevMark <mark@emcoo.com>
 *
 *  For the full copyright and license information, please view http://emcoo.com/
 *
 *
 */

(function () {
    'use strict';

    angular.module('backend', [

        'authModule',
        'userModule',
        'postModule',
        'mediaModule',
        'mediaCategoryModule',
        'postCategoryModule',
        'settingModule',
        'languageModule',

        'theme.directives',
        'theme.services',

        'global.filter',
        'global.service',
        'global.directive',

        'angular-spinkit',
        'smart-table',
        'restangular',
        'ui.router',
        'ui.bootstrap',
        'ui.bootstrap.datetimepicker',
        'ui.tree',
        'ui.select',
        'checklist-model',

        'textAngular',
        'angular.filter',
        'pascalprecht.translate', //Multi Language plugin
        'angularMoment', //moment plugin, date plugin
        'toaster', //notification plugin
        'LocalStorageModule', // local storage module
        'tmh.dynamicLocale',

        'ngAnimate',
        'ngCookies',
        'ngTouch',
        'ngRoute',
        'ngSanitize'

    ])
        //================================================
        // Translate Config
        //================================================
        .config(function ($translateProvider) {
            $translateProvider.useStaticFilesLoader({
                prefix: 'app/languages/locale-',
                suffix: '.json'
            });

            $translateProvider.useCookieStorage();
            $translateProvider.useSanitizeValueStrategy('sanitize');
            $translateProvider.preferredLanguage('en-us');
            $translateProvider.fallbackLanguage(['en-us', 'zh-hk', 'zh-cn']);

        })
        //================================================
        // UI select Config
        //================================================
        .config(function (uiSelectConfig) {
            uiSelectConfig.theme = 'bootstrap';
            uiSelectConfig.resetSearchInput = true;
        })
        //================================================
        // Check isLogin when run system
        //================================================
        .run(function ($rootScope, $location, authenticationService, localStorageService, amMoment, tmhDynamicLocale, settingService, $translate, $timeout) {
            var routesThatRequireAuth = ['/auth/login', '/auth/logout'];

            //================================================
            // page route event
            //================================================
            $rootScope.pageViewLoading = false;

            $rootScope.$on('$stateChangeStart', function (event, next, current) {
                if (!_(routesThatRequireAuth).contains($location.path()) && !authenticationService.check()) {
                    console.log('Oops! please login!');
                    $location.path('auth/login');
                }
                $rootScope.pageViewLoading = true;
            });
            $rootScope.$on('$stateChangeSuccess', function () {
                $timeout(function () {
                    $rootScope.pageViewLoading = false;
                }, 500);
            });

            //================================================
            // page title init
            //================================================
            $translate('panel.name').then(function (translation) {
                $rootScope.siteTitle = translation;
            });
            $rootScope.meta = {
                pageTitle: ''
            };

            //================================================
            // global variable init
            //================================================
            $rootScope.$location = $location;

            //================================================
            // timezone init
            //================================================
            amMoment.changeTimezone(settingService.find('timezone'));

            //================================================
            // currency init
            //================================================
            if (!localStorageService.get('currency')) {
                tmhDynamicLocale.set('zh-hk');
                localStorageService.set('currency', settingService.get('currency'));
            }

        })
        .run(function ($location, authenticationService, localStorageService, $timeout, Restangular, $http, $q, $state) {

            var refreshAccessToken = function () {
                var deferred = $q.defer();
                console.log('start get refresh token');

                $http({
                    method: 'POST', url: 'http://cms.dev/api/admin/auth/refresh-token', headers: {
                        'Authorization': authenticationService.getToken()
                    }
                }).success(function (data, status, headers, config) {
                    console.log('set token');
                    localStorageService.set('token', data.result.token);
                    deferred.resolve(data.result.token);
                }).error(function (data, status, headers, config) {
                    console.log('Oops! Expired Token, please login again! ');
                    $location.path('auth/login');
                    deferred.reject(data);
                });

                return deferred.promise;
            };

            Restangular.setErrorInterceptor(function (response, deferred, responseHandler) {
                if (response.data.message === 'token_invalid') {
                    localStorageService.remove('token');
                    $state.go('fullscreen.login');
                    return false;
                }

                if (response.data.message === 'token_expired') {
                    return refreshAccessToken().then(function () {
                        response.config.headers.Authorization = authenticationService.getToken();
                        // Repeat the request and then call the handlers the usual way.
                        $http(response.config).then(responseHandler, deferred.reject);
                        return false;
                        // Be aware that no request interceptors are called this way.
                    });
                    return false;
                }
                console.log('error not handled');
                return true; // error not handled
            });

            //Restangular.setErrorInterceptor(function (response, deferred, responseHandler) {
            //    if (response.status === 401 || response.message === 'token_expired') {
            //        refreshAccessToken().then(function () {
            //            response.config.headers.Authorization = authenticationService.getToken();
            //            // Repeat the request and then call the handlers the usual way.
            //            $http(response.config).then(responseHandler, deferred.reject)
            //                .then(function (httpResponse) {
            //                    deferred.resolve(httpResponse);
            //                });
            //
            //            // Be aware that no request interceptors are called this way.
            //        });
            //        return false; // error handled
            //    }
            //    return true; // error not handled
            //});

            Restangular.addFullRequestInterceptor(function (element, operation, route, url, headers, params, httpConfig) {
                //================================================
                // Token setup when load each request
                //================================================
                headers.Authorization = authenticationService.getToken();

                return {
                    element: element,
                    headers: headers,
                    params: params,
                    httpConfig: httpConfig
                };

            });

        })

        .config(function ($httpProvider, localStorageServiceProvider) {

            $httpProvider.defaults.useXDomain = true;
            delete $httpProvider.defaults.headers.common['X-Requested-With'];

            //================================================
            // Local storage module init
            //================================================
            localStorageServiceProvider
                .setPrefix('emcoocms')
                .setStorageType('localStorage')
                .setNotify(true, true);
        })

        //================================================
        // Plugin: Smart-table init
        //================================================
        .config(function (stConfig) {
            stConfig.select.mode = 'multiple';
        })

        //================================================
        // Plugin: Restangular init
        //================================================
        .config(function (RestangularProvider) {
            RestangularProvider.setBaseUrl('http://cms.dev/api/admin');
            RestangularProvider.setRestangularFields({
                ids: "_ids"
            });

            RestangularProvider.addResponseInterceptor(function (data, operation, what, url, response, deferred) {
                var extractedData;
                if (operation === 'getList') {
                    // handle the data and meta data
                    extractedData = data.result;
                    if (!_.isUndefined(data.meta)) {
                        extractedData.meta = data.meta;
                    }
                } else {
                    extractedData = data.result;
                }

                return extractedData;
            });

            RestangularProvider.setDefaultHttpFields({
                cache: true
            });

        })
        .config(function ($locationProvider, $stateProvider, $urlRouterProvider) {
            $locationProvider.html5Mode(true);
            $stateProvider
                .state('main', {
                    url: '/',
                    abstract: true,
                    templateUrl: 'app/main/main.html'
                })
                .state('fullscreen', {
                    url: '/',
                    abstract: true,
                    templateUrl: 'app/main/fullscreen.html'
                })
                .state('main.index', {
                    url: '',
                    templateUrl: 'app/main/dashboard.html',
                    resolve: {
                        meta: function ($rootScope, $translate, $q) {
                            var deferred = $q.defer();
                            $translate('panel.dashboard').then(function (translation) {
                                $rootScope.meta.pageTitle = translation;
                                deferred.resolve(true);
                            }, function () {
                                deferred.reject();
                            });
                            return deferred.promise;
                        }
                    }
                })
                .state('main.not-found', {
                    url: '404',
                    templateUrl: 'app/template/404.html',
                    resolve: {
                        meta: function ($rootScope, $translate, $q) {
                            var deferred = $q.defer();
                            $translate('panel.dashboard').then(function (translation) {
                                $rootScope.meta.pageTitle = translation;
                                deferred.resolve(true);
                            }, function () {
                                deferred.reject();
                            });
                            return deferred.promise;
                        }
                    }
                });

            $urlRouterProvider.otherwise('/');
        });

})();
<|endoftext|>


<commit_before>/*
 * Copyright DataStax, Inc.
 *
 * Licensed 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 proxyquire = require('proxyquire');

const cloudHelper = require('./cloud-helper');
const helper = require('../../../test-helper');
const policies = require('../../../../lib/policies');
const errors = require('../../../../lib/errors');
const auth = require('../../../../lib/auth');
const utils = require('../../../../lib/utils');
const types = require('../../../../lib/types');
const vdescribe = helper.vdescribe;

const port = 9042;

vdescribe('3.11', 'Cloud support', function () {
  // Only run tests with few versions of DSE as SNI project has a fixed C*/DSE version

  if (helper.isWin()) {
    // Skip altogether for AppVeyor
    return;
  }

  this.timeout(300000);

  context('with a 3 node cluster', () => {

    cloudHelper.setup({
      queries: [
        "CREATE KEYSPACE ks_network_rf1 WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1' : 1}",
        "CREATE TABLE ks_network_rf1.table1 (id int primary key, name text)",
      ]
    });

    it('should resolve dns name of the proxy, connect and set defaults', () => {
      const client = cloudHelper.getClient();

      return client.connect()
        .then(() => {
          assert.strictEqual(client.hosts.length, 3);

          assert.ok(client.options.sni.addressResolver.getIp());
          assert.ok(client.options.sni.port);
          assert.strictEqual(client.metadata.isDbaas(), true);
          assert.strictEqual(client.options.queryOptions.consistency, types.consistencies.localQuorum);

          client.hosts.forEach(h => {
            assert.ok(h.isUp());
            helper.assertContains(h.address, `:${port}`);
            assert.strictEqual(h.pool.connections.length, 1);
            assert.strictEqual(h.pool.connections[0].endpointFriendlyName,
              `${client.options.sni.addressResolver.getIp()}:${client.options.sni.port} (${h.hostId})`);
          });
        })
        .then(() => client.shutdown());
    });

    it('should use all the proxy resolved addresses', () => {
      let resolvedAddress;
      const libPath = '../../../../lib';

      const ControlConnection = proxyquire(`${libPath}/control-connection`, {
        'dns':  {
          resolve4: (name, cb) => {
            resolvedAddress = name;
            // Use different loopback addresses
            cb(null, ['127.0.0.1', '127.0.0.2']);
          }
        }
      });

      const Client = proxyquire(`${libPath}/client`, { './control-connection': ControlConnection });

      const client = new Client(cloudHelper.getOptions({ cloud: { secureConnectBundle: 'certs/bundles/creds-v1.zip' } }));

      after(() => client.shutdown());

      return client.connect()
        .then(() => {
          // Validate custom resolution was used
          assert.ok(resolvedAddress);

          const hosts = client.hosts.values();
          assert.strictEqual(hosts.length, 3);

          hosts.forEach((h, index) => {
            assert.strictEqual(h.pool.connections.length, 1);
            // Validate all resolved addresses are used
            assert.strictEqual(h.pool.connections[0].endpoint.split(':')[0], `127.0.0.${(index % 2) + 1}`);
          });

          return client.shutdown();
        });
    });

    it('should match system.local information of each node', () => {
      const client = cloudHelper.getClient({ policies: new policies.loadBalancing.RoundRobinPolicy()});

      // Use round robin to make sure that the 3 host are targeted in 3 executions
      return client.connect()
        .then(() => Promise.all(new Array(3).fill(0).map(() => client.execute('SELECT * FROM system.local'))))
        .then(results => {
          const queried = new Set();
          results.forEach(rs => {
            queried.add(rs.info.queriedHost);
            const host = client.hosts.get(rs.info.queriedHost);
            const row = rs.first();

            assert.ok(host);
            assert.strictEqual(row['host_id'].toString(), host.hostId.toString());
            assert.strictEqual(host.address, `${row['rpc_address']}:${port}`);
          });

          assert.strictEqual(queried.size, 3);
        })
        .then(() => client.shutdown());
    });

    it('should set the auth provider', () => {
      const client = cloudHelper.getClient({ });

      return client.connect()
        .then(() => {
          helper.assertInstanceOf(client.options.authProvider, auth.PlainTextAuthProvider);
          assert.strictEqual(client.options.authProvider.username, 'cassandra');
        })
        .then(() => client.shutdown());
    });

    it('should support leaving the auth unset', () => {
      const client = cloudHelper.getClient({ cloud: { secureConnectBundle: 'certs/bundles/creds-v1-wo-creds.zip' } });

      return client.connect()
        .catch(() => {})
        .then(() => assert.strictEqual(client.options.authProvider, null))
        .then(() => client.shutdown());
    });

    it('should support overriding the auth provider', () => {
      const authProvider = new auth.PlainTextAuthProvider('user1', '12345678');
      const client = cloudHelper.getClient({ authProvider });

      return client.connect()
        .catch(err => {
          helper.assertInstanceOf(err, errors.NoHostAvailableError);
          helper.assertInstanceOf(utils.objectValues(err.innerErrors)[0], errors.AuthenticationError);
        })
        .then(() => assert.strictEqual(client.options.authProvider, authProvider))
        .then(() => client.shutdown());
    });

    it('should callback in error when bundle file does not exist', () => {
      const client = cloudHelper.getClient({ cloud: { secureConnectBundle: 'certs/bundles/does-not-exist.zip' }});
      let error;

      return client.connect()
        .catch(err => error = err)
        .then(() => {
          helper.assertInstanceOf(error, Error);
          assert.strictEqual(error.code, 'ENOENT');
        })
        .then(() => client.shutdown());
    });

    it('should provide token-aware load balancing by default', () => {
      const replicasByKey = [[0, 2], [1, 2], [2, 2], [3, 1], [4, 3], [5, 2]];

      const client = cloudHelper.getClient();

      return client.connect()
        .then(() => Promise.all(replicasByKey.map(item => {
          const query = 'INSERT INTO ks_network_rf1.table1 (id, name) VALUES (?, ?)';
          const params = [ item[0], `name for id ${item[0]}`];
          const replica = item[1].toString();

          return client.execute(query, params, { prepare: true })
            .then(rs => assert.strictEqual(helper.lastOctetOf(rs.info.queriedHost), replica));
        })))
        .then(() => client.shutdown());
    });

    context('with nodes going down', () => {
      beforeEach(() => cloudHelper.startAllNodes());
      after(() => cloudHelper.startAllNodes());

      it('should reconnect to the same host and refresh dns resolution', () => {
        const client = cloudHelper.getClient({
          pooling: { heartBeatInterval: 50 },
          policies: { reconnection: new policies.reconnection.ConstantReconnectionPolicy(20) }
        });

        let refreshCalled = false;

        return client.connect()
          .then(() => assert.strictEqual(client.hosts.values().find(h => !h.isUp()), undefined))
          .then(() => cloudHelper.stopNode(1))
          .then(() => helper.setIntervalUntilPromise(() => client.hosts.values().find(h => !h.isUp()), 20, 1000))
          .then(() => {
            assert.strictEqual(client.hosts.values().filter(h => h.isUp()).length, 2);

            // Patch refresh() method
            const resolver = client.options.sni.addressResolver;
            const fn = resolver.refresh;
            resolver.refresh = (cb) => {
              refreshCalled = true;
              fn.call(resolver, cb);
            };
          })
          .then(() => cloudHelper.startNode(1))
          .then(() => helper.setIntervalUntilPromise(() => !client.hosts.values().find(h => !h.isUp()), 20, 1000))
          .then(() => {
            assert.strictEqual(client.hosts.values().filter(h => h.isUp()).length, 3);
            assert.ok(refreshCalled);
          });
      });

      it('should continue querying', () => {
        const client = cloudHelper.getClient({
          pooling: { heartBeatInterval: 50 },
          policies: { reconnection: new policies.reconnection.ConstantReconnectionPolicy(40) },
          queryOptions: { isIdempotent: true }
        });

        let restarted = false;

        return client.connect()
          .then(() => assert.strictEqual(client.hosts.values().find(h => !h.isUp()), undefined))
          .then(() => {
            // In the background, stop and restart a node
            Promise.resolve()
              .then(() => cloudHelper.stopNode(1))
              .then(() => promiseDelay(200))
              .then(() => cloudHelper.startNode(1))
              .then(() => promiseDelay(500))
              .then(() => restarted = true);
          })
          .then(() => repeatUntil(
            () => restarted,
            () => promiseRepeat(1000, 32, () => client.execute(helper.queries.basic))
          ))
          .then(() => client.shutdown());
      });
    });
  });

  context('without a metadata service', () => {

    it('should throw a NoHostAvailableError when there address is unreachable', () => {
      let error;

      // Use bundle with unreachable address
      const client = cloudHelper.getClient({ cloud: { secureConnectBundle: 'certs/bundles/creds-v1-unreachable.zip' } });

      return client.connect()
        .catch(err => error = err)
        .then(() => {
          helper.assertInstanceOf(error, errors.NoHostAvailableError);
          assert.strictEqual(error.message, 'There was an error fetching the metadata information');
          assert.deepStrictEqual(Object.keys(error.innerErrors), ['192.0.2.255:30443/metadata']);
        });
    });

    it('should throw a NoHostAvailableError when there is a https connection error', () => {
      let error;

      // Use localhost
      const client = cloudHelper.getClient({ cloud: { secureConnectBundle: 'certs/bundles/creds-v1.zip' } });

      return client.connect()
        .catch(err => error = err)
        .then(() => {
          helper.assertInstanceOf(error, errors.NoHostAvailableError);
          assert.deepStrictEqual(Object.keys(error.innerErrors), ['localhost:30443/metadata']);
        });
    });
  });
});

function repeatUntil(conditionFn, promiseFn) {
  if (conditionFn()) {
    return Promise.resolve();
  }

  return promiseFn().then(() => repeatUntil(conditionFn, promiseFn));
}

function promiseRepeat(times, limit, fn){
  if (times < limit) {
    limit = times;
  }

  let counter = 0;

  const promises = new Array(limit);

  function sendNext() {
    if (counter >= times) {
      return null;
    }
    return fn(counter++).then(sendNext);
  }

  for (let i = 0; i < limit; i++) {
    promises[i] = sendNext();
  }

  return Promise.all(promises).then(() => null);
}

function promiseDelay(ms) {
  return new Promise(r => setTimeout(r, ms));
}<commit_msg>Fix cloud test<commit_after>/*
 * Copyright DataStax, Inc.
 *
 * Licensed 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 proxyquire = require('proxyquire');

const cloudHelper = require('./cloud-helper');
const helper = require('../../../test-helper');
const policies = require('../../../../lib/policies');
const errors = require('../../../../lib/errors');
const auth = require('../../../../lib/auth');
const utils = require('../../../../lib/utils');
const types = require('../../../../lib/types');
const vdescribe = helper.vdescribe;

const port = 9042;

vdescribe('3.11', 'Cloud support', function () {
  // Only run tests with few versions of DSE as SNI project has a fixed C*/DSE version

  if (helper.isWin()) {
    // Skip altogether for AppVeyor
    return;
  }

  this.timeout(300000);

  context('with a 3 node cluster', () => {

    cloudHelper.setup({
      queries: [
        "CREATE KEYSPACE ks_network_rf1 WITH replication = {'class': 'NetworkTopologyStrategy', 'dc1' : 1}",
        "CREATE TABLE ks_network_rf1.table1 (id int primary key, name text)",
      ]
    });

    it('should resolve dns name of the proxy, connect and set defaults', () => {
      const client = cloudHelper.getClient();

      return client.connect()
        .then(() => {
          assert.strictEqual(client.hosts.length, 3);

          assert.ok(client.options.sni.addressResolver.getIp());
          assert.ok(client.options.sni.port);
          assert.strictEqual(client.metadata.isDbaas(), true);
          assert.strictEqual(client.options.queryOptions.consistency, types.consistencies.localQuorum);

          client.hosts.forEach(h => {
            assert.ok(h.isUp());
            helper.assertContains(h.address, `:${port}`);
            assert.strictEqual(h.pool.connections.length, 1);
            assert.strictEqual(h.pool.connections[0].endpointFriendlyName,
              `${client.options.sni.addressResolver.getIp()}:${client.options.sni.port} (${h.hostId})`);
          });
        })
        .then(() => client.shutdown());
    });

    it('should use all the proxy resolved addresses', () => {
      let resolvedAddress;
      const libPath = '../../../../lib';

      const ControlConnection = proxyquire(`${libPath}/control-connection`, {
        'dns':  {
          resolve4: (name, cb) => {
            resolvedAddress = name;
            // Use different loopback addresses
            cb(null, ['127.0.0.1', '127.0.0.2']);
          }
        }
      });

      const Client = proxyquire(`${libPath}/client`, { './control-connection': ControlConnection });

      const client = new Client(cloudHelper.getOptions({ cloud: { secureConnectBundle: 'certs/bundles/creds-v1.zip' } }));

      after(() => client.shutdown());

      return client.connect()
        .then(() => {
          // Validate custom resolution was used
          assert.ok(resolvedAddress);

          const hosts = client.hosts.values();
          assert.strictEqual(hosts.length, 3);

          hosts.forEach((h, index) => {
            assert.strictEqual(h.pool.connections.length, 1);
            // Validate all resolved addresses are used
            assert.strictEqual(h.pool.connections[0].endpoint.split(':')[0], `127.0.0.${(index % 2) + 1}`);
          });

          return client.shutdown();
        });
    });

    it('should match system.local information of each node', () => {
      const client = cloudHelper.getClient({ policies: new policies.loadBalancing.RoundRobinPolicy()});

      // Use round robin to make sure that the 3 host are targeted in 3 executions
      return client.connect()
        .then(() => Promise.all(new Array(3).fill(0).map(() => client.execute('SELECT * FROM system.local'))))
        .then(results => {
          const queried = new Set();
          results.forEach(rs => {
            queried.add(rs.info.queriedHost);
            const host = client.hosts.get(rs.info.queriedHost);
            const row = rs.first();

            assert.ok(host);
            assert.strictEqual(row['host_id'].toString(), host.hostId.toString());
            assert.strictEqual(host.address, `${row['rpc_address']}:${port}`);
          });

          assert.strictEqual(queried.size, 3);
        })
        .then(() => client.shutdown());
    });

    it('should set the auth provider', () => {
      const client = cloudHelper.getClient({ });

      return client.connect()
        .then(() => {
          helper.assertInstanceOf(client.options.authProvider, auth.PlainTextAuthProvider);
          assert.strictEqual(client.options.authProvider.username, 'user1');
        })
        .then(() => client.shutdown());
    });

    it('should support leaving the auth unset', () => {
      const client = cloudHelper.getClient({ cloud: { secureConnectBundle: 'certs/bundles/creds-v1-wo-creds.zip' } });

      return client.connect()
        .catch(() => {})
        .then(() => assert.strictEqual(client.options.authProvider, null))
        .then(() => client.shutdown());
    });

    it('should support overriding the auth provider', () => {
      const authProvider = new auth.PlainTextAuthProvider('user1', '12345678');
      const client = cloudHelper.getClient({ authProvider });

      return client.connect()
        .catch(err => {
          helper.assertInstanceOf(err, errors.NoHostAvailableError);
          helper.assertInstanceOf(utils.objectValues(err.innerErrors)[0], errors.AuthenticationError);
        })
        .then(() => assert.strictEqual(client.options.authProvider, authProvider))
        .then(() => client.shutdown());
    });

    it('should callback in error when bundle file does not exist', () => {
      const client = cloudHelper.getClient({ cloud: { secureConnectBundle: 'certs/bundles/does-not-exist.zip' }});
      let error;

      return client.connect()
        .catch(err => error = err)
        .then(() => {
          helper.assertInstanceOf(error, Error);
          assert.strictEqual(error.code, 'ENOENT');
        })
        .then(() => client.shutdown());
    });

    it('should provide token-aware load balancing by default', () => {
      const replicasByKey = [[0, 2], [1, 2], [2, 2], [3, 1], [4, 3], [5, 2]];

      const client = cloudHelper.getClient();

      return client.connect()
        .then(() => Promise.all(replicasByKey.map(item => {
          const query = 'INSERT INTO ks_network_rf1.table1 (id, name) VALUES (?, ?)';
          const params = [ item[0], `name for id ${item[0]}`];
          const replica = item[1].toString();

          return client.execute(query, params, { prepare: true })
            .then(rs => assert.strictEqual(helper.lastOctetOf(rs.info.queriedHost), replica));
        })))
        .then(() => client.shutdown());
    });

    context('with nodes going down', () => {
      beforeEach(() => cloudHelper.startAllNodes());
      after(() => cloudHelper.startAllNodes());

      it('should reconnect to the same host and refresh dns resolution', () => {
        const client = cloudHelper.getClient({
          pooling: { heartBeatInterval: 50 },
          policies: { reconnection: new policies.reconnection.ConstantReconnectionPolicy(20) }
        });

        let refreshCalled = false;

        return client.connect()
          .then(() => assert.strictEqual(client.hosts.values().find(h => !h.isUp()), undefined))
          .then(() => cloudHelper.stopNode(1))
          .then(() => helper.setIntervalUntilPromise(() => client.hosts.values().find(h => !h.isUp()), 20, 1000))
          .then(() => {
            assert.strictEqual(client.hosts.values().filter(h => h.isUp()).length, 2);

            // Patch refresh() method
            const resolver = client.options.sni.addressResolver;
            const fn = resolver.refresh;
            resolver.refresh = (cb) => {
              refreshCalled = true;
              fn.call(resolver, cb);
            };
          })
          .then(() => cloudHelper.startNode(1))
          .then(() => helper.setIntervalUntilPromise(() => !client.hosts.values().find(h => !h.isUp()), 20, 1000))
          .then(() => {
            assert.strictEqual(client.hosts.values().filter(h => h.isUp()).length, 3);
            assert.ok(refreshCalled);
          });
      });

      it('should continue querying', () => {
        const client = cloudHelper.getClient({
          pooling: { heartBeatInterval: 50 },
          policies: { reconnection: new policies.reconnection.ConstantReconnectionPolicy(40) },
          queryOptions: { isIdempotent: true }
        });

        let restarted = false;

        return client.connect()
          .then(() => assert.strictEqual(client.hosts.values().find(h => !h.isUp()), undefined))
          .then(() => {
            // In the background, stop and restart a node
            Promise.resolve()
              .then(() => cloudHelper.stopNode(1))
              .then(() => promiseDelay(200))
              .then(() => cloudHelper.startNode(1))
              .then(() => promiseDelay(500))
              .then(() => restarted = true);
          })
          .then(() => repeatUntil(
            () => restarted,
            () => promiseRepeat(1000, 32, () => client.execute(helper.queries.basic))
          ))
          .then(() => client.shutdown());
      });
    });
  });

  context('without a metadata service', () => {

    it('should throw a NoHostAvailableError when there address is unreachable', () => {
      let error;

      // Use bundle with unreachable address
      const client = cloudHelper.getClient({ cloud: { secureConnectBundle: 'certs/bundles/creds-v1-unreachable.zip' } });

      return client.connect()
        .catch(err => error = err)
        .then(() => {
          helper.assertInstanceOf(error, errors.NoHostAvailableError);
          assert.strictEqual(error.message, 'There was an error fetching the metadata information');
          assert.deepStrictEqual(Object.keys(error.innerErrors), ['192.0.2.255:30443/metadata']);
        });
    });

    it('should throw a NoHostAvailableError when there is a https connection error', () => {
      let error;

      // Use localhost
      const client = cloudHelper.getClient({ cloud: { secureConnectBundle: 'certs/bundles/creds-v1.zip' } });

      return client.connect()
        .catch(err => error = err)
        .then(() => {
          helper.assertInstanceOf(error, errors.NoHostAvailableError);
          assert.deepStrictEqual(Object.keys(error.innerErrors), ['localhost:30443/metadata']);
        });
    });
  });
});

function repeatUntil(conditionFn, promiseFn) {
  if (conditionFn()) {
    return Promise.resolve();
  }

  return promiseFn().then(() => repeatUntil(conditionFn, promiseFn));
}

function promiseRepeat(times, limit, fn){
  if (times < limit) {
    limit = times;
  }

  let counter = 0;

  const promises = new Array(limit);

  function sendNext() {
    if (counter >= times) {
      return null;
    }
    return fn(counter++).then(sendNext);
  }

  for (let i = 0; i < limit; i++) {
    promises[i] = sendNext();
  }

  return Promise.all(promises).then(() => null);
}

function promiseDelay(ms) {
  return new Promise(r => setTimeout(r, ms));
}<|endoftext|>


<commit_before>/*!
 * Module dependencies.
 */

var _ = require('underscore'),
	keystone = require('../../'),
	util = require('util'),
	knox = require('knox'),
	// s3 = require('s3'),
	utils = require('keystone-utils'),
	super_ = require('../field');

/**
 * S3File FieldType Constructor
 * @extends Field
 * @api public
 */

function s3file(list, path, options) {
	this._underscoreMethods = ['format'];
	// TODO: implement filtering, hard-coded as disabled for now
	options.nofilter = true;
	s3file.super_.call(this, list, path, options);
};

/*!
 * Inherit from Field
 */

util.inherits(s3file, super_);

/**
 * Registers the field on the List's Mongoose Schema.
 *
 * @api public
 */

s3file.prototype.addToSchema = function() {

	var field = this,
		schema = this.list.schema;

	var paths = this.paths = {
		// fields
		filename:		this._path.append('.filename'),
		path:			this._path.append('.path'),
		size:			this._path.append('.size'),
		filetype:		this._path.append('.filetype'),
		url:			this._path.append('.url'),
		// virtuals
		exists:			this._path.append('.exists'),
		upload:			this._path.append('_upload'),
		action:			this._path.append('_action')
	};

	var schemaPaths = this._path.addTo({}, {
		filename:		String,
		path:			String,
		size:			Number,
		filetype:		String,
		url:			String
	});

	schema.add(schemaPaths);

	var exists = function(item) {
		return (item.get(paths.url) ? true : false);
	}

	// The .exists virtual indicates whether a file is stored
	schema.virtual(paths.exists).get(function() {
		return schemaMethods.exists.apply(this);
	});

	var reset = function(item) {
		item.set(field.path, {
			filename: '',
			path: '',
			size: 0,
			filetype: '',
			url: ''
		});
	}

	var schemaMethods = {
		exists: function() {
			return exists(this);
		},
		/**
		 * Resets the value of the field
		 *
		 * @api public
		 */
		reset: function() {
			reset(this);
		},
		/**
		 * Deletes the file from S3File and resets the field
		 *
		 * @api public
		 */
		delete: function() {
			try {
				var client = knox.createClient(keystone.get('s3file config'));
				client.deleteFile(this.get(paths.path) + this.get(paths.filename), function(err, res){ res ? res.resume() : false });
			} catch(e) {}
			reset(this);
		}
	}

	_.each(schemaMethods, function(fn, key) {
		field.underscoreMethod(key, fn);
	});

	// expose a method on the field to call schema methods
	this.apply = function(item, method) {
		return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
	};

	this.bindUnderscoreMethods();
}

/**
 * Formats the field value
 *
 * @api public
 */

s3file.prototype.format = function(item) {
	return item.get(this.paths.url);
}

/**
 * Detects whether the field has been modified
 *
 * @api public
 */

s3file.prototype.isModified = function(item) {
	return item.isModified(this.paths.url);
}

/**
 * Validates that a value for this field has been provided in a data object
 *
 * @api public
 */

s3file.prototype.validateInput = function(data) {
	// TODO - how should file field input be validated?
	return true;
}

/**
 * Updates the value for this field in the item from a data object
 *
 * @api public
 */

s3file.prototype.updateItem = function(item, data) {
	// TODO - direct updating of data (not via upload)
}

/**
 * Returns a callback that handles a standard form submission for the field
 *
 * Expected form parts are
 * - `field.paths.action` in `req.body` (`clear` or `delete`)
 * - `field.paths.upload` in `req.files` (uploads the file to s3file)
 *
 * @api public
 */

s3file.prototype.getRequestHandler = function(item, req, paths, callback) {

	var field = this;

	if (utils.isFunction(paths)) {
		callback = paths;
		paths = field.paths;
	} else if (!paths) {
		paths = field.paths;
	}

	callback = callback || function() {};

	return function() {

		if (req.body) {
			var action = req.body[paths.action];

			if (/^(delete|reset)$/.test(action))
				field.apply(item, action);
		}

		if (req.files && req.files[paths.upload] && req.files[paths.upload].size) {

			var file = req.files[paths.upload],
				path = field.options.s3path ? field.options.s3path + '/' : '',
				prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '';

			var name = prefix + file.name;

			var client = knox.createClient(keystone.get('s3file config'));

			client.putFile(file.path, path + name, {
				'Content-Type': file.type,
				'x-amz-acl': 'public-read'
			}, function(err, res) {
				if (res) {
					res.resume();
				}
				if (err) {
					console.log('Error uploading Amazon S3 file.');
					callback(true);
				} else {
					item.set(field.path, {
						filename: name,
						path: path,
						size: file.size,
						filetype: file.type,
						url: res.req.url
					});
					callback();
				}
			});

			// Alternative method via S3 module, which provides helpful events for uploading files, leaving for future reference
			/*
			var client = s3.createClient(keystone.get('s3file config'));

			var headers = {
				'Content-Type': file.type,
				'x-amz-acl': 'public-read'
			};

			var uploader = client.upload(file.path, file.name, headers);

			uploader.on('error', function(res) {
				console.log('Error uploading Amazon S3 file:', res.stack);
				callback(res.stack);
			});

			uploader.on('progress', function(amountDone, amountTotal) {
				console.log('Amazon S3 file progress: ' + amountDone + ' of ' + amountTotal);
			});

			uploader.on('end', function(url) {
				item.set(field.path, {
					filename: file.name,
					size: file.size,
					filetype: file.type,
					url: url
				});
				callback();
			});
			*/

		} else {
			callback();
		}

	}

}

/**
 * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
 *
 * @api public
 */

s3file.prototype.handleRequest = function(item, req, paths, callback) {
	this.getRequestHandler(item, req, paths, callback)();
}

/*!
 * Export class
 */

exports = module.exports = s3file;
<commit_msg>Exposing S3 File upload logic as it's own function to be used as an underscore method with other misc optimisations. Renaming 's3file config' to 's3 config'.<commit_after>/*!
 * Module dependencies.
 */

var _ = require('underscore'),
	keystone = require('../../'),
	util = require('util'),
	knox = require('knox'),
	// s3 = require('s3'),
	utils = require('keystone-utils'),
	super_ = require('../field');

/**
 * S3File FieldType Constructor
 * @extends Field
 * @api public
 */

function s3file(list, path, options) {
	this._underscoreMethods = ['format', 'uploadFile'];
	// TODO: implement filtering, hard-coded as disabled for now
	options.nofilter = true;
	s3file.super_.call(this, list, path, options);
};

/*!
 * Inherit from Field
 */

util.inherits(s3file, super_);

/**
 * Exposes the custom or keystone s3 config settings
 */

Object.defineProperty(s3file.prototype, 's3config', { get: function() {
	return this.options.s3config || keystone.get('s3 config');
}});

/**
 * Registers the field on the List's Mongoose Schema.
 *
 * @api public
 */

s3file.prototype.addToSchema = function() {

	var field = this,
		schema = this.list.schema;

	var paths = this.paths = {
		// fields
		filename:		this._path.append('.filename'),
		path:			this._path.append('.path'),
		size:			this._path.append('.size'),
		filetype:		this._path.append('.filetype'),
		url:			this._path.append('.url'),
		// virtuals
		exists:			this._path.append('.exists'),
		upload:			this._path.append('_upload'),
		action:			this._path.append('_action')
	};

	var schemaPaths = this._path.addTo({}, {
		filename:		String,
		path:			String,
		size:			Number,
		filetype:		String,
		url:			String
	});

	schema.add(schemaPaths);

	var exists = function(item) {
		return (item.get(paths.url) ? true : false);
	}

	// The .exists virtual indicates whether a file is stored
	schema.virtual(paths.exists).get(function() {
		return schemaMethods.exists.apply(this);
	});

	var reset = function(item) {
		item.set(field.path, {
			filename: '',
			path: '',
			size: 0,
			filetype: '',
			url: ''
		});
	}

	var schemaMethods = {
		exists: function() {
			return exists(this);
		},
		/**
		 * Resets the value of the field
		 *
		 * @api public
		 */
		reset: function() {
			reset(this);
		},
		/**
		 * Deletes the file from S3File and resets the field
		 *
		 * @api public
		 */
		delete: function() {
			try {
				var client = knox.createClient(field.s3config);
				client.deleteFile(this.get(paths.path) + this.get(paths.filename), function(err, res){ res ? res.resume() : false });
			} catch(e) {}
			reset(this);
		}
	}

	_.each(schemaMethods, function(fn, key) {
		field.underscoreMethod(key, fn);
	});

	// expose a method on the field to call schema methods
	this.apply = function(item, method) {
		return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
	};

	this.bindUnderscoreMethods();
}

/**
 * Formats the field value
 *
 * @api public
 */

s3file.prototype.format = function(item) {
	return item.get(this.paths.url);
}

/**
 * Detects whether the field has been modified
 *
 * @api public
 */

s3file.prototype.isModified = function(item) {
	return item.isModified(this.paths.url);
}

/**
 * Validates that a value for this field has been provided in a data object
 *
 * @api public
 */

s3file.prototype.validateInput = function(data) {
	// TODO - how should file field input be validated?
	return true;
}

/**
 * Updates the value for this field in the item from a data object
 *
 * @api public
 */

s3file.prototype.updateItem = function(item, data) {
	// TODO - direct updating of data (not via upload)
}

/**
 * Uploads the file for this field
 *
 * @api public
 */

s3file.prototype.uploadFile = function(item, file, update, callback) {

	var field = this,
		path = field.options.s3path ? field.options.s3path + '/' : '',
		prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '',
		name = prefix + file.name,
		client = knox.createClient(field.s3config);

	if ('function' == typeof update) {
		callback = update;
		update = false;
	}

	client.putFile(file.path, path + name, {
		'Content-Type': file.type,
		'x-amz-acl': 'public-read'
	}, function(err, res) {

		if (res) res.resume();
		if (err) return callback(err);

		var fileData = {
			filename: name,
			path: path,
			size: file.size,
			filetype: file.type,
			url: res.req.url
		};

		if (update) {
			item.set(field.path, fileData);
		}

		callback(null, fileData);

	});

	// Alternative method via S3 module, which provides helpful events for uploading files, leaving for future reference
	/*
	var client = s3.createClient(keystone.get('s3 config'));

	var headers = {
		'Content-Type': file.type,
		'x-amz-acl': 'public-read'
	};

	var uploader = client.upload(file.path, file.name, headers);

	uploader.on('error', function(res) {
		console.log('Error uploading Amazon S3 file:', res.stack);
		callback(res.stack);
	});

	uploader.on('progress', function(amountDone, amountTotal) {
		console.log('Amazon S3 file progress: ' + amountDone + ' of ' + amountTotal);
	});

	uploader.on('end', function(url) {
		item.set(field.path, {
			filename: file.name,
			size: file.size,
			filetype: file.type,
			url: url
		});
		callback();
	});
	*/

}

/**
 * Returns a callback that handles a standard form submission for the field
 *
 * Expected form parts are
 * - `field.paths.action` in `req.body` (`clear` or `delete`)
 * - `field.paths.upload` in `req.files` (uploads the file to s3file)
 *
 * @api public
 */

s3file.prototype.getRequestHandler = function(item, req, paths, callback) {

	var field = this;

	if (utils.isFunction(paths)) {
		callback = paths;
		paths = field.paths;
	} else if (!paths) {
		paths = field.paths;
	}

	callback = callback || function() {};

	return function() {

		if (req.body) {
			var action = req.body[paths.action];

			if (/^(delete|reset)$/.test(action))
				field.apply(item, action);
		}

		if (req.files && req.files[paths.upload] && req.files[paths.upload].size) {
			return field.uploadFile(item, file, true, callback);
		}

		return callback();

	}

}

/**
 * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
 *
 * @api public
 */

s3file.prototype.handleRequest = function(item, req, paths, callback) {
	this.getRequestHandler(item, req, paths, callback)();
}

/*!
 * Export class
 */

exports = module.exports = s3file;
<|endoftext|>


<commit_before>/*
* Copyright (c) 2013 BlackBerry Limited
*
* Licensed 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.
*/

var _self = {},
    _ID = "com.blackberry.community.nowplaying",
    exec = cordova.require("cordova/exec"),
    playbackRequested = false,
    stopped = true;

    /**
     * NowPlayingRequestPlayback
     * Sets up the app for playing music and binds callbacks to the app.
     *
     * @param input: a json object with methods that callback to the app.
     * The callback methods that must be specified are:
     * - play: Fired when the track is played.
     * - pause: Fired when the track is paused.
     * - stop: Fired when the track is stopped.
     * - next: Fired when the next track is invoked. Callback must invoke com.blackberry.community.nowplaying.play()
     *         with arguments for the next track.
     * - previous: Fired when the previous track is invoked. Callback must invoke com.blackberry.community.nowplaying.play()
     *             with arguments for the previous track.
     * - error: Fired when an internal error occurs.
     * @returns String: whether playback was requested (set up) successfully.
     */
	_self.NowPlayingRequestPlayback = function(input) {

        // TODO: verify json input.

        /* Bind callbacks */
        if (!playbackRequested) {

            var valid = _self.NowPlayingValidateCallback(input);

            if(valid){
                _self.NowPlayingBindPlayCallback(input.play);
                _self.NowPlayingBindPauseCallback(input.pause);
                _self.NowPlayingBindStopCallback(input.stop);
                _self.NowPlayingBindNextCallback(input.next);
                _self.NowPlayingBindPreviousCallback(input.previous);
                //_self.NowPlayingBindErrorCallback(input.error);

                var result,
                success = function (data, response) {
                    result = data;
                    playbackRequested = true;
                },
                fail = function (data, response) {
                    console.log("Error: " + data);
                };
                exec(success, fail, _ID, "NowPlayingRequestPlayback");
                return result;
            } else {
                // passes response into error callback
                return "Playback input was not valid";
            }

        } else {
          return "Playback already requested.";
        }
	};

    _self.NowPlayingBindPlayCallback = function (callback) {
        var success = function (data, response) {
                var json = JSON.parse(data);
                callback(json);
                stopped = false;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindPlayCallback", null);
    };

    _self.NowPlayingBindPauseCallback = function (callback) {
        var success = function (data, response) {
                var json = JSON.parse(data);
                callback(json);
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindPauseCallback", null);
    };

    _self.NowPlayingBindStopCallback = function (callback) {
        var success = function (data, response) {
                var json = JSON.parse(data);
                callback(json);
                stopped = true;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindStopCallback", null);
    };

    _self.NowPlayingBindNextCallback = function (callback) {
        var success = function (data, response) {
                var json = JSON.parse(data);
                callback(json);
                stopped = false;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindNextCallback", null);
    };

    _self.NowPlayingBindPreviousCallback = function (callback) {
        var success = function (data, response) {
                var json = JSON.parse(data);
                callback(json);
                stopped = false;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindPreviousCallback", null);
    };

    _self.NowPlayingBindErrorCallback = function (callback) {
        var success = function (data, response) {
                console.log(data);
                var json = JSON.parse(data);
                callback(json);
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindErrorCallback", null);
    };

    /**
     * NowPlayingPlay
     * Plays a specified track in the background.
     * Executes the callback method given to NowPlayingRequestPlayback.
     * Use this method to play the track if it isn't automatically restarted
     * after the app playback is no longer preempted.
     *
     * @param input: a json object with track details.
     * The details that must be specified are:
     * - trackURL: URL of the track to play.
     * - iconURL: URL of the icon to display on the volume overlay.
     * - metadata: a json object with metadata details to display on the volume overlay.
     *             Can include title, artist,
     * - nextEnabled: boolean used to set whether or not the Next button should be
     *                enabled on the volume overlay.
     * - previousEnabled: boolean used to set whether or not the Previous button should be
     *                    enabled on the volume overlay.
     * @returns String: whether the track played successfully.
     */
	_self.NowPlayingPlay = function (input) {

        // TODO: verify json input.
        var validInput = _self.NowPlayingValidatePlayInput(input);
        if(validInput) {
            var result,
    			success = function (data, response) {
    				result = data;
    			},
    			fail = function (data, response) {
    				console.log("Error: " + data);
    			};
    		exec(success, fail, _ID, "NowPlayingPlay", { input: input });
    		return result;
        } else {
            return "Play input was not valid";
        }

	};

    /**
     * NowPlayingPause
     * Pauses the track if there is any in the background.
     * Executes the callback method given to NowPlayingRequestPlayback.
     *
     * @returns String: whether the track paused successfully.
     */
	_self.NowPlayingPause = function () {
			var result;
            success = function (data, response) {
				result = data;
			},
			fail = function (data, response) {
				console.log("Error: " + data);
			};
		exec(success, fail, _ID, "NowPlayingPause", null);
        return result;
	};

    /**
     * NowPlayingResume
     * Resumes the paused track if there is any in the background.
     * Executes the callback method given to NowPlayingRequestPlayback.
     *
     * @returns String: whether the track resumed successfully.
     */
    _self.NowPlayingResume = function () {
        var result,
            success = function (data, response) {
                result = data;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };

        if (stopped) {
            result = "Track cannot be resumed because it is stopped. " +
                "Call the play method to set up a new track to play instead.";
        } else {
            exec(success, fail, _ID, "NowPlayingResume", null);
        }

        return result;
    };

    /**
     * NowPlayingStop
     * Stops the track if there is any in the background.
     * Executes the callback method given to NowPlayingRequestPlayback.
     *
     * @returns String: whether the track stopped successfully.
     */
    _self.NowPlayingStop = function () {
		var result,
			success = function (data, response) {
				result = data;
			},
			fail = function (data, response) {
				console.log("Error: " + data);
			};
        exec(success, fail, _ID, "NowPlayingStop", null);
		return result;
	};

    /**
     * NowPlayingNext
     * Plays the next track according to the callback method given to
     * NowPlayingRequestPlayback.
     *
     * @returns String: whether the track was changed to next one successfully.
     */
    _self.NowPlayingNext = function () {
        var result,
            success = function (data, response) {
                result = data;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingNext", null);
        return result;
    };

    /**
     * NowPlayingPrevious
     * Plays the previous track according to the callback method given to
     * NowPlayingRequestPlayback.
     *
     * @returns String: whether the track was changed to previous one successfully.
     */
    _self.NowPlayingPrevious = function () {
        var result,
            success = function (data, response) {
                result = data;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingPrevious", null);
        return result;
    };

    /**
     * NowPlayingError
     * Fires the error callback method given to NowPlayingRequestPlayback.
     * For debugging purposes only.
     *
     * @returns String: error information
     */
    _self.NowPlayingError = function (error) {
        var result,
            success = function (data, response) {
                result = data;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingError", { input: error });
        return result;
    };

    // Validate play input

    /* We expect the following callbacks to be present:
    play, pause, stop, next, previous, error
    */
    _self.NowPlayingValidateCallback = function(input) {
        var result = "";
        var valid = true;
        if(!('play' in input)) result = "Play callback was not provided";
        if(!('pause' in input)) result = "Pause callback was not provided";
        if(!('stop' in input)) result = "Stop callback was not provided";
        if(!('next' in input)) result = "Next callback was not provided";
        if(!('previous' in input)) result = "Previous callback was not provided";
        if(!('error' in input)) result = "Error callback was not provided";

        if(result !== "") {
            valid = false;
            // log error to console since no error callback is defined
            console.log(result);
        }
        if(valid) {
            valid = _self.NowPlayingValidateCallbackAttributes(input);
        }
        return valid;
    };

    _self.NowPlayingValidateCallbackAttributes = function(input) {
        var result = true;
        if(!(isFunction(input.play))) result = "Play attribute was not a function";
        if(!(isFunction(input.pause))) result = "Pause attribute was not a function";
        if(!(isFunction(input.stop))) result = "Stop attribute was not a function";
        if(!(isFunction(input.next))) result = "Next attribute was not a function";
        if(!(isFunction(input.previous))) result = "Previous attribute was not a function";
        if(!(isFunction(input.error))) result = "Error attribute was not a function";

        if(typeof(result) === "boolean" && result === true) {
            return true;
        } else {
            // no error callback set up, log to console
            console.log(result);
            return false;
        }
    };

    function isFunction(functionToCheck) {
        var getType = {};
        return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
    };

    /*
     trackURL: "http://www.pch.gc.ca/DAMAssetPub/DAM-hymChs-antSgn/STAGING/audio-audio/o-canada_1359474460106_eng.MP3",
            iconURL: "img/canadian-flag-small.jpg",
            metadata: {
                Title: "O Canada",
                Artist: "Canada",
                Album: "Canada's Favorites"
            }
    */

    _self.NowPlayingValidatePlayInput = function(input) {
        var response = "";
        if(!('trackURL' in input)) response =  "trackURL attribute was not provided";

        /* Only need trackURL for it to be considered a valid play input

        if(!('iconURL' in input)) response = "iconURL attribute was not provided";
        if(('metadata' in input)) {
            // is this necessary or should we only check for trackURL
            if(!('Title' in input.metadata)) response = "Title field in metadata was not provided";
            if(!('Artist') in input.metadata) response = "Artist field in metadata was not provided";
            if(!('Album' in input.metadata)) response = "Album field in metadata was not provided";
        } else {
            response = "metadata attribute was not provided";
        }
        */
        var valid = true;
        if(response !== "") {
            valid = false;
            // throw error
        }

        return valid
    };

    /**
     * NowPlayingGetState
     * Returns the state of the plugin.
     * Used for debugging purposes.
     *
     * @returns String: containing:
     * - the state of the MediaPlayer,
     * - whether the NowPlayingConnection is acquired,
     * - whether the NowPlayingConnection is preempted.
     */
	_self.NowPlayingGetState = function () {
		var result,
			success = function (data, response) {
				result = data;
			},
			fail = function (data, response) {
				console.log("Error: " + data);
			};
		exec(success, fail, _ID, "NowPlayingGetState", null);
		return result;
	};

module.exports = _self;
<commit_msg>Error check for relevant functions to see if playback has been requested<commit_after>/*
* Copyright (c) 2013 BlackBerry Limited
*
* Licensed 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.
*/

var _self = {},
    _ID = "com.blackberry.community.nowplaying",
    exec = cordova.require("cordova/exec"),
    playbackRequested = false,
    stopped = true;

    /**
     * NowPlayingRequestPlayback
     * Sets up the app for playing music and binds callbacks to the app.
     *
     * @param input: a json object with methods that callback to the app.
     * The callback methods that must be specified are:
     * - play: Fired when the track is played.
     * - pause: Fired when the track is paused.
     * - stop: Fired when the track is stopped.
     * - next: Fired when the next track is invoked. Callback must invoke com.blackberry.community.nowplaying.play()
     *         with arguments for the next track.
     * - previous: Fired when the previous track is invoked. Callback must invoke com.blackberry.community.nowplaying.play()
     *             with arguments for the previous track.
     * - error: Fired when an internal error occurs.
     * @returns String: whether playback was requested (set up) successfully.
     */
	_self.NowPlayingRequestPlayback = function(input) {

        // TODO: verify json input.

        /* Bind callbacks */
        if (!playbackRequested) {

            var valid = _self.NowPlayingValidateCallback(input);

            if(valid){
                _self.NowPlayingBindPlayCallback(input.play);
                _self.NowPlayingBindPauseCallback(input.pause);
                _self.NowPlayingBindStopCallback(input.stop);
                _self.NowPlayingBindNextCallback(input.next);
                _self.NowPlayingBindPreviousCallback(input.previous);
                _self.NowPlayingBindErrorCallback(input.error);

                var result,
                success = function (data, response) {
                    result = data;
                    playbackRequested = true;
                },
                fail = function (data, response) {
                    console.log("Error: " + data);
                };
                exec(success, fail, _ID, "NowPlayingRequestPlayback");
                return result;
            } else {
                // passes response into error callback
                return "Playback input was not valid";
            }

        } else {
          return "Playback already requested.";
        }
	};

    _self.NowPlayingBindPlayCallback = function (callback) {
        var success = function (data, response) {
                var json = JSON.parse(data);
                callback(json);
                stopped = false;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindPlayCallback", null);
    };

    _self.NowPlayingBindPauseCallback = function (callback) {
        var success = function (data, response) {
                var json = JSON.parse(data);
                callback(json);
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindPauseCallback", null);
    };

    _self.NowPlayingBindStopCallback = function (callback) {
        var success = function (data, response) {
                var json = JSON.parse(data);
                callback(json);
                stopped = true;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindStopCallback", null);
    };

    _self.NowPlayingBindNextCallback = function (callback) {
        var success = function (data, response) {
                var json = JSON.parse(data);
                callback(json);
                stopped = false;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindNextCallback", null);
    };

    _self.NowPlayingBindPreviousCallback = function (callback) {
        var success = function (data, response) {
                var json = JSON.parse(data);
                callback(json);
                stopped = false;
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindPreviousCallback", null);
    };

    _self.NowPlayingBindErrorCallback = function (callback) {
        var success = function (data, response) {
                console.log(data);
                var json = JSON.parse(data);
                callback(json);
            },
            fail = function (data, response) {
                console.log("Error: " + data);
            };
        exec(success, fail, _ID, "NowPlayingBindErrorCallback", null);
    };

    /**
     * NowPlayingPlay
     * Plays a specified track in the background.
     * Executes the callback method given to NowPlayingRequestPlayback.
     * Use this method to play the track if it isn't automatically restarted
     * after the app playback is no longer preempted.
     *
     * @param input: a json object with track details.
     * The details that must be specified are:
     * - trackURL: URL of the track to play.
     * - iconURL: URL of the icon to display on the volume overlay.
     * - metadata: a json object with metadata details to display on the volume overlay.
     *             Can include title, artist,
     * - nextEnabled: boolean used to set whether or not the Next button should be
     *                enabled on the volume overlay.
     * - previousEnabled: boolean used to set whether or not the Previous button should be
     *                    enabled on the volume overlay.
     * @returns String: whether the track played successfully.
     */
	_self.NowPlayingPlay = function (input) {

        // TODO: verify json input.

        if(!playbackRequested) {
            console.log("Need to request playback first.");
            return;
        } else {

            var validInput = _self.NowPlayingValidatePlayInput(input);
            if(validInput) {
                var result,
        			success = function (data, response) {
        				result = data;
        			},
        			fail = function (data, response) {
        				console.log("Error: " + data);
        			};
        		exec(success, fail, _ID, "NowPlayingPlay", { input: input });
        		return result;
            } else {
                return "Play input was not valid";
            }
        }

	};

    /**
     * NowPlayingPause
     * Pauses the track if there is any in the background.
     * Executes the callback method given to NowPlayingRequestPlayback.
     *
     * @returns String: whether the track paused successfully.
     */
	_self.NowPlayingPause = function () {
        if(!playbackRequested) {
            console.log("Need to request playback first.");
            return;
        } else {
			var result;
            success = function (data, response) {
				result = data;
			},
			fail = function (data, response) {
				console.log("Error: " + data);
			};
		exec(success, fail, _ID, "NowPlayingPause", null);
        return result;
        }
	};

    /**
     * NowPlayingResume
     * Resumes the paused track if there is any in the background.
     * Executes the callback method given to NowPlayingRequestPlayback.
     *
     * @returns String: whether the track resumed successfully.
     */
    _self.NowPlayingResume = function () {
        if(!playbackRequested) {
            console.log("Need to request playback first.");
            return;
        } else {
            var result,
                success = function (data, response) {
                    result = data;
                },
                fail = function (data, response) {
                    console.log("Error: " + data);
                };

            if (stopped) {
                result = "Track cannot be resumed because it is stopped. " +
                    "Call the play method to set up a new track to play instead.";
            } else {
                exec(success, fail, _ID, "NowPlayingResume", null);
            }

            return result;
        }
    };

    /**
     * NowPlayingStop
     * Stops the track if there is any in the background.
     * Executes the callback method given to NowPlayingRequestPlayback.
     *
     * @returns String: whether the track stopped successfully.
     */
    _self.NowPlayingStop = function () {
		if(!playbackRequested) {
            console.log("Need to request playback first.");
            return;
        } else {
            var result,
    			success = function (data, response) {
    				result = data;
    			},
    			fail = function (data, response) {
    				console.log("Error: " + data);
    			};
            exec(success, fail, _ID, "NowPlayingStop", null);
    		return result;
        }
	};

    /**
     * NowPlayingNext
     * Plays the next track according to the callback method given to
     * NowPlayingRequestPlayback.
     *
     * @returns String: whether the track was changed to next one successfully.
     */
    _self.NowPlayingNext = function () {
        if(!playbackRequested) {
            console.log("Need to request playback first.");
            return;
        } else {
            var result,
                success = function (data, response) {
                    result = data;
                },
                fail = function (data, response) {
                    console.log("Error: " + data);
                };
            exec(success, fail, _ID, "NowPlayingNext", null);
            return result;
        }
    };

    /**
     * NowPlayingPrevious
     * Plays the previous track according to the callback method given to
     * NowPlayingRequestPlayback.
     *
     * @returns String: whether the track was changed to previous one successfully.
     */
    _self.NowPlayingPrevious = function () {
        if(!playbackRequested) {
            console.log("Need to request playback first.");
            return;
        } else {
            var result,
                success = function (data, response) {
                    result = data;
                },
                fail = function (data, response) {
                    console.log("Error: " + data);
                };
            exec(success, fail, _ID, "NowPlayingPrevious", null);
            return result;
        }
    };

    /**
     * NowPlayingError
     * Fires the error callback method given to NowPlayingRequestPlayback.
     * For debugging purposes only.
     *
     * @returns String: error information
     */
    _self.NowPlayingError = function (error) {
        if(!playbackRequested) {
            console.log("Need to request playback first.");
            return;
        } else {
            var result,
                success = function (data, response) {
                    result = data;
                },
                fail = function (data, response) {
                    console.log("Error: " + data);
                };
            exec(success, fail, _ID, "NowPlayingError", { input: error });
            return result;
        }
    };

    // Validate play input

    /* We expect the following callbacks to be present:
    play, pause, stop, next, previous, error
    */
    _self.NowPlayingValidateCallback = function(input) {
        var result = "";
        var valid = true;
        if(!('play' in input)) result = "Play callback was not provided";
        if(!('pause' in input)) result = "Pause callback was not provided";
        if(!('stop' in input)) result = "Stop callback was not provided";
        if(!('next' in input)) result = "Next callback was not provided";
        if(!('previous' in input)) result = "Previous callback was not provided";
        if(!('error' in input)) result = "Error callback was not provided";

        if(result !== "") {
            valid = false;
            // log error to console since no error callback is defined
            console.log(result);
        }
        if(valid) {
            valid = _self.NowPlayingValidateCallbackAttributes(input);
        }
        return valid;
    };

    _self.NowPlayingValidateCallbackAttributes = function(input) {
        var result = true;
        if(!(isFunction(input.play))) result = "Play attribute was not a function";
        if(!(isFunction(input.pause))) result = "Pause attribute was not a function";
        if(!(isFunction(input.stop))) result = "Stop attribute was not a function";
        if(!(isFunction(input.next))) result = "Next attribute was not a function";
        if(!(isFunction(input.previous))) result = "Previous attribute was not a function";
        if(!(isFunction(input.error))) result = "Error attribute was not a function";

        if(typeof(result) === "boolean" && result === true) {
            return true;
        } else {
            // no error callback set up, log to console
            console.log(result);
            return false;
        }
    };

    function isFunction(functionToCheck) {
        var getType = {};
        return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
    };

    /*
     trackURL: "http://www.pch.gc.ca/DAMAssetPub/DAM-hymChs-antSgn/STAGING/audio-audio/o-canada_1359474460106_eng.MP3",
            iconURL: "img/canadian-flag-small.jpg",
            metadata: {
                Title: "O Canada",
                Artist: "Canada",
                Album: "Canada's Favorites"
            }
    */

    _self.NowPlayingValidatePlayInput = function(input) {
        var response = "";
        if(!('trackURL' in input)) response =  "trackURL attribute was not provided";

        /* Only need trackURL for it to be considered a valid play input

        if(!('iconURL' in input)) response = "iconURL attribute was not provided";
        if(('metadata' in input)) {
            // is this necessary or should we only check for trackURL
            if(!('Title' in input.metadata)) response = "Title field in metadata was not provided";
            if(!('Artist') in input.metadata) response = "Artist field in metadata was not provided";
            if(!('Album' in input.metadata)) response = "Album field in metadata was not provided";
        } else {
            response = "metadata attribute was not provided";
        }
        */
        var valid = true;
        if(response !== "") {
            valid = false;
            // throw error
        }

        return valid
    };

    /**
     * NowPlayingGetState
     * Returns the state of the plugin.
     * Used for debugging purposes.
     *
     * @returns String: containing:
     * - the state of the MediaPlayer,
     * - whether the NowPlayingConnection is acquired,
     * - whether the NowPlayingConnection is preempted.
     */
	_self.NowPlayingGetState = function () {
		var result,
			success = function (data, response) {
				result = data;
			},
			fail = function (data, response) {
				console.log("Error: " + data);
			};
		exec(success, fail, _ID, "NowPlayingGetState", null);
		return result;
	};

module.exports = _self;
<|endoftext|>