瀏覽代碼

HUE-3736 [core] Fix flaky jasmine tests

Johan Ahlen 9 年之前
父節點
當前提交
3f5f0ad

+ 751 - 0
desktop/core/src/desktop/static/desktop/ext/js/jasmine-2.3.4/mock-ajax.js

@@ -0,0 +1,751 @@
+/*
+
+ Jasmine-Ajax - v3.2.0: a set of helpers for testing AJAX requests under the Jasmine
+ BDD framework for JavaScript.
+
+ http://github.com/jasmine/jasmine-ajax
+
+ Jasmine Home page: http://jasmine.github.io/
+
+ Copyright (c) 2008-2015 Pivotal Labs
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ */
+
+//Module wrapper to support both browser and CommonJS environment
+(function (root, factory) {
+  if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
+    // CommonJS
+    var jasmineRequire = require('jasmine-core');
+    module.exports = factory(root, function() {
+      return jasmineRequire;
+    });
+  } else {
+    // Browser globals
+    window.MockAjax = factory(root, getJasmineRequireObj);
+  }
+}(typeof window !== 'undefined' ? window : global, function (global, getJasmineRequireObj) {
+
+//
+  getJasmineRequireObj().ajax = function(jRequire) {
+    var $ajax = {};
+
+    $ajax.RequestStub = jRequire.AjaxRequestStub();
+    $ajax.RequestTracker = jRequire.AjaxRequestTracker();
+    $ajax.StubTracker = jRequire.AjaxStubTracker();
+    $ajax.ParamParser = jRequire.AjaxParamParser();
+    $ajax.event = jRequire.AjaxEvent();
+    $ajax.eventBus = jRequire.AjaxEventBus($ajax.event);
+    $ajax.fakeRequest = jRequire.AjaxFakeRequest($ajax.eventBus);
+    $ajax.MockAjax = jRequire.MockAjax($ajax);
+
+    return $ajax.MockAjax;
+  };
+
+  getJasmineRequireObj().AjaxEvent = function() {
+    function now() {
+      return new Date().getTime();
+    }
+
+    function noop() {
+    }
+
+    // Event object
+    // https://dom.spec.whatwg.org/#concept-event
+    function XMLHttpRequestEvent(xhr, type) {
+      this.type = type;
+      this.bubbles = false;
+      this.cancelable = false;
+      this.timeStamp = now();
+
+      this.isTrusted = false;
+      this.defaultPrevented = false;
+
+      // Event phase should be "AT_TARGET"
+      // https://dom.spec.whatwg.org/#dom-event-at_target
+      this.eventPhase = 2;
+
+      this.target = xhr;
+      this.currentTarget = xhr;
+    }
+
+    XMLHttpRequestEvent.prototype.preventDefault = noop;
+    XMLHttpRequestEvent.prototype.stopPropagation = noop;
+    XMLHttpRequestEvent.prototype.stopImmediatePropagation = noop;
+
+    function XMLHttpRequestProgressEvent() {
+      XMLHttpRequestEvent.apply(this, arguments);
+
+      this.lengthComputable = false;
+      this.loaded = 0;
+      this.total = 0;
+    }
+
+    // Extend prototype
+    XMLHttpRequestProgressEvent.prototype = XMLHttpRequestEvent.prototype;
+
+    return {
+      event: function(xhr, type) {
+        return new XMLHttpRequestEvent(xhr, type);
+      },
+
+      progressEvent: function(xhr, type) {
+        return new XMLHttpRequestProgressEvent(xhr, type);
+      }
+    };
+  };
+  getJasmineRequireObj().AjaxEventBus = function(eventFactory) {
+    function EventBus(source) {
+      this.eventList = {};
+      this.source = source;
+    }
+
+    function ensureEvent(eventList, name) {
+      eventList[name] = eventList[name] || [];
+      return eventList[name];
+    }
+
+    function findIndex(list, thing) {
+      if (list.indexOf) {
+        return list.indexOf(thing);
+      }
+
+      for(var i = 0; i < list.length; i++) {
+        if (thing === list[i]) {
+          return i;
+        }
+      }
+
+      return -1;
+    }
+
+    EventBus.prototype.addEventListener = function(event, callback) {
+      ensureEvent(this.eventList, event).push(callback);
+    };
+
+    EventBus.prototype.removeEventListener = function(event, callback) {
+      var index = findIndex(this.eventList[event], callback);
+
+      if (index >= 0) {
+        this.eventList[event].splice(index, 1);
+      }
+    };
+
+    EventBus.prototype.trigger = function(event) {
+      var evt;
+
+      // Event 'readystatechange' is should be a simple event.
+      // Others are progress event.
+      // https://xhr.spec.whatwg.org/#events
+      if (event === 'readystatechange') {
+        evt = eventFactory.event(this.source, event);
+      } else {
+        evt = eventFactory.progressEvent(this.source, event);
+      }
+
+      var eventListeners = this.eventList[event];
+
+      if (eventListeners) {
+        for (var i = 0; i < eventListeners.length; i++) {
+          eventListeners[i].call(this.source, evt);
+        }
+      }
+    };
+
+    return function(source) {
+      return new EventBus(source);
+    };
+  };
+
+  getJasmineRequireObj().AjaxFakeRequest = function(eventBusFactory) {
+    function extend(destination, source, propertiesToSkip) {
+      propertiesToSkip = propertiesToSkip || [];
+      for (var property in source) {
+        if (!arrayContains(propertiesToSkip, property)) {
+          destination[property] = source[property];
+        }
+      }
+      return destination;
+    }
+
+    function arrayContains(arr, item) {
+      for (var i = 0; i < arr.length; i++) {
+        if (arr[i] === item) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    function wrapProgressEvent(xhr, eventName) {
+      return function() {
+        if (xhr[eventName]) {
+          xhr[eventName].apply(xhr, arguments);
+        }
+      };
+    }
+
+    function initializeEvents(xhr) {
+      xhr.eventBus.addEventListener('readystatechange', wrapProgressEvent(xhr, 'onreadystatechange'));
+      xhr.eventBus.addEventListener('loadstart', wrapProgressEvent(xhr, 'onloadstart'));
+      xhr.eventBus.addEventListener('load', wrapProgressEvent(xhr, 'onload'));
+      xhr.eventBus.addEventListener('loadend', wrapProgressEvent(xhr, 'onloadend'));
+      xhr.eventBus.addEventListener('progress', wrapProgressEvent(xhr, 'onprogress'));
+      xhr.eventBus.addEventListener('error', wrapProgressEvent(xhr, 'onerror'));
+      xhr.eventBus.addEventListener('abort', wrapProgressEvent(xhr, 'onabort'));
+      xhr.eventBus.addEventListener('timeout', wrapProgressEvent(xhr, 'ontimeout'));
+    }
+
+    function unconvertibleResponseTypeMessage(type) {
+      var msg = [
+        "Can't build XHR.response for XHR.responseType of '",
+        type,
+        "'.",
+        "XHR.response must be explicitly stubbed"
+      ];
+      return msg.join(' ');
+    }
+
+    function fakeRequest(global, requestTracker, stubTracker, paramParser) {
+      function FakeXMLHttpRequest() {
+        requestTracker.track(this);
+        this.eventBus = eventBusFactory(this);
+        initializeEvents(this);
+        this.requestHeaders = {};
+        this.overriddenMimeType = null;
+      }
+
+      function findHeader(name, headers) {
+        name = name.toLowerCase();
+        for (var header in headers) {
+          if (header.toLowerCase() === name) {
+            return headers[header];
+          }
+        }
+      }
+
+      function normalizeHeaders(rawHeaders, contentType) {
+        var headers = [];
+
+        if (rawHeaders) {
+          if (rawHeaders instanceof Array) {
+            headers = rawHeaders;
+          } else {
+            for (var headerName in rawHeaders) {
+              if (rawHeaders.hasOwnProperty(headerName)) {
+                headers.push({ name: headerName, value: rawHeaders[headerName] });
+              }
+            }
+          }
+        } else {
+          headers.push({ name: "Content-Type", value: contentType || "application/json" });
+        }
+
+        return headers;
+      }
+
+      function parseXml(xmlText, contentType) {
+        if (global.DOMParser) {
+          return (new global.DOMParser()).parseFromString(xmlText, 'text/xml');
+        } else {
+          var xml = new global.ActiveXObject("Microsoft.XMLDOM");
+          xml.async = "false";
+          xml.loadXML(xmlText);
+          return xml;
+        }
+      }
+
+      var xmlParsables = ['text/xml', 'application/xml'];
+
+      function getResponseXml(responseText, contentType) {
+        if (arrayContains(xmlParsables, contentType.toLowerCase())) {
+          return parseXml(responseText, contentType);
+        } else if (contentType.match(/\+xml$/)) {
+          return parseXml(responseText, 'text/xml');
+        }
+        return null;
+      }
+
+      var iePropertiesThatCannotBeCopied = ['responseBody', 'responseText', 'responseXML', 'status', 'statusText', 'responseTimeout', 'responseURL'];
+      extend(FakeXMLHttpRequest.prototype, new global.XMLHttpRequest(), iePropertiesThatCannotBeCopied);
+      extend(FakeXMLHttpRequest.prototype, {
+        open: function() {
+          this.method = arguments[0];
+          this.url = arguments[1];
+          this.username = arguments[3];
+          this.password = arguments[4];
+          this.readyState = 1;
+          this.requestHeaders = {};
+          this.eventBus.trigger('readystatechange');
+        },
+
+        setRequestHeader: function(header, value) {
+          if(this.requestHeaders.hasOwnProperty(header)) {
+            this.requestHeaders[header] = [this.requestHeaders[header], value].join(', ');
+          } else {
+            this.requestHeaders[header] = value;
+          }
+        },
+
+        overrideMimeType: function(mime) {
+          this.overriddenMimeType = mime;
+        },
+
+        abort: function() {
+          this.readyState = 0;
+          this.status = 0;
+          this.statusText = "abort";
+          this.eventBus.trigger('readystatechange');
+          this.eventBus.trigger('progress');
+          this.eventBus.trigger('abort');
+          this.eventBus.trigger('loadend');
+        },
+
+        readyState: 0,
+
+        onloadstart: null,
+        onprogress: null,
+        onabort: null,
+        onerror: null,
+        onload: null,
+        ontimeout: null,
+        onloadend: null,
+        onreadystatechange: null,
+
+        addEventListener: function() {
+          this.eventBus.addEventListener.apply(this.eventBus, arguments);
+        },
+
+        removeEventListener: function(event, callback) {
+          this.eventBus.removeEventListener.apply(this.eventBus, arguments);
+        },
+
+        status: null,
+
+        send: function(data) {
+          this.params = data;
+          this.eventBus.trigger('loadstart');
+
+          var stub = stubTracker.findStub(this.url, data, this.method);
+          if (stub) {
+            if (stub.isReturn()) {
+              this.respondWith(stub);
+            } else if (stub.isError()) {
+              this.responseError();
+            } else if (stub.isTimeout()) {
+              this.responseTimeout();
+            }
+          }
+        },
+
+        contentType: function() {
+          return findHeader('content-type', this.requestHeaders);
+        },
+
+        data: function() {
+          if (!this.params) {
+            return {};
+          }
+
+          return paramParser.findParser(this).parse(this.params);
+        },
+
+        getResponseHeader: function(name) {
+          name = name.toLowerCase();
+          var resultHeader;
+          for(var i = 0; i < this.responseHeaders.length; i++) {
+            var header = this.responseHeaders[i];
+            if (name === header.name.toLowerCase()) {
+              if (resultHeader) {
+                resultHeader = [resultHeader, header.value].join(', ');
+              } else {
+                resultHeader = header.value;
+              }
+            }
+          }
+          return resultHeader;
+        },
+
+        getAllResponseHeaders: function() {
+          var responseHeaders = [];
+          for (var i = 0; i < this.responseHeaders.length; i++) {
+            responseHeaders.push(this.responseHeaders[i].name + ': ' +
+                this.responseHeaders[i].value);
+          }
+          return responseHeaders.join('\r\n') + '\r\n';
+        },
+
+        responseText: null,
+        response: null,
+        responseType: null,
+        responseURL: null,
+
+        responseValue: function() {
+          switch(this.responseType) {
+            case null:
+            case "":
+            case "text":
+              return this.readyState >= 3 ? this.responseText : "";
+            case "json":
+              return JSON.parse(this.responseText);
+            case "arraybuffer":
+              throw unconvertibleResponseTypeMessage('arraybuffer');
+            case "blob":
+              throw unconvertibleResponseTypeMessage('blob');
+            case "document":
+              return this.responseXML;
+          }
+        },
+
+
+        respondWith: function(response) {
+          if (this.readyState === 4) {
+            throw new Error("FakeXMLHttpRequest already completed");
+          }
+
+          this.status = response.status;
+          this.statusText = response.statusText || "";
+          this.responseHeaders = normalizeHeaders(response.responseHeaders, response.contentType);
+          this.readyState = 2;
+          this.eventBus.trigger('readystatechange');
+
+          this.responseText = response.responseText || "";
+          this.responseType = response.responseType || "";
+          this.responseURL = response.responseURL || null;
+          this.readyState = 4;
+          this.responseXML = getResponseXml(response.responseText, this.getResponseHeader('content-type') || '');
+          if (this.responseXML) {
+            this.responseType = 'document';
+          }
+
+          if ('response' in response) {
+            this.response = response.response;
+          } else {
+            this.response = this.responseValue();
+          }
+
+          this.eventBus.trigger('readystatechange');
+          this.eventBus.trigger('progress');
+          this.eventBus.trigger('load');
+          this.eventBus.trigger('loadend');
+        },
+
+        responseTimeout: function() {
+          if (this.readyState === 4) {
+            throw new Error("FakeXMLHttpRequest already completed");
+          }
+          this.readyState = 4;
+          jasmine.clock().tick(30000);
+          this.eventBus.trigger('readystatechange');
+          this.eventBus.trigger('progress');
+          this.eventBus.trigger('timeout');
+          this.eventBus.trigger('loadend');
+        },
+
+        responseError: function() {
+          if (this.readyState === 4) {
+            throw new Error("FakeXMLHttpRequest already completed");
+          }
+          this.readyState = 4;
+          this.eventBus.trigger('readystatechange');
+          this.eventBus.trigger('progress');
+          this.eventBus.trigger('error');
+          this.eventBus.trigger('loadend');
+        }
+      });
+
+      return FakeXMLHttpRequest;
+    }
+
+    return fakeRequest;
+  };
+
+  getJasmineRequireObj().MockAjax = function($ajax) {
+    function MockAjax(global) {
+      var requestTracker = new $ajax.RequestTracker(),
+          stubTracker = new $ajax.StubTracker(),
+          paramParser = new $ajax.ParamParser(),
+          realAjaxFunction = global.XMLHttpRequest,
+          mockAjaxFunction = $ajax.fakeRequest(global, requestTracker, stubTracker, paramParser);
+
+      this.install = function() {
+        if (global.XMLHttpRequest === mockAjaxFunction) {
+          throw "MockAjax is already installed.";
+        }
+
+        global.XMLHttpRequest = mockAjaxFunction;
+      };
+
+      this.uninstall = function() {
+        if (global.XMLHttpRequest !== mockAjaxFunction) {
+          throw "MockAjax not installed.";
+        }
+        global.XMLHttpRequest = realAjaxFunction;
+
+        this.stubs.reset();
+        this.requests.reset();
+        paramParser.reset();
+      };
+
+      this.stubRequest = function(url, data, method) {
+        var stub = new $ajax.RequestStub(url, data, method);
+        stubTracker.addStub(stub);
+        return stub;
+      };
+
+      this.withMock = function(closure) {
+        this.install();
+        try {
+          closure();
+        } finally {
+          this.uninstall();
+        }
+      };
+
+      this.addCustomParamParser = function(parser) {
+        paramParser.add(parser);
+      };
+
+      this.requests = requestTracker;
+      this.stubs = stubTracker;
+    }
+
+    return MockAjax;
+  };
+
+  getJasmineRequireObj().AjaxParamParser = function() {
+    function ParamParser() {
+      var defaults = [
+        {
+          test: function(xhr) {
+            return (/^application\/json/).test(xhr.contentType());
+          },
+          parse: function jsonParser(paramString) {
+            return JSON.parse(paramString);
+          }
+        },
+        {
+          test: function(xhr) {
+            return true;
+          },
+          parse: function naiveParser(paramString) {
+            var data = {};
+            var params = paramString.split('&');
+
+            for (var i = 0; i < params.length; ++i) {
+              var kv = params[i].replace(/\+/g, ' ').split('=');
+              var key = decodeURIComponent(kv[0]);
+              data[key] = data[key] || [];
+              data[key].push(decodeURIComponent(kv[1]));
+            }
+            return data;
+          }
+        }
+      ];
+      var paramParsers = [];
+
+      this.add = function(parser) {
+        paramParsers.unshift(parser);
+      };
+
+      this.findParser = function(xhr) {
+        for(var i in paramParsers) {
+          var parser = paramParsers[i];
+          if (parser.test(xhr)) {
+            return parser;
+          }
+        }
+      };
+
+      this.reset = function() {
+        paramParsers = [];
+        for(var i in defaults) {
+          paramParsers.push(defaults[i]);
+        }
+      };
+
+      this.reset();
+    }
+
+    return ParamParser;
+  };
+
+  getJasmineRequireObj().AjaxRequestStub = function() {
+    var RETURN = 0,
+        ERROR = 1,
+        TIMEOUT = 2;
+
+    function RequestStub(url, stubData, method) {
+      var normalizeQuery = function(query) {
+        return query ? query.split('&').sort().join('&') : undefined;
+      };
+
+      if (url instanceof RegExp) {
+        this.url = url;
+        this.query = undefined;
+      } else {
+        var split = url.split('?');
+        this.url = split[0];
+        this.query = split.length > 1 ? normalizeQuery(split[1]) : undefined;
+      }
+
+      this.data = (stubData instanceof RegExp) ? stubData : normalizeQuery(stubData);
+      this.method = method;
+
+      this.andReturn = function(options) {
+        this.action = RETURN;
+        this.status = options.status || 200;
+
+        this.contentType = options.contentType;
+        this.response = options.response;
+        this.responseText = options.responseText;
+        this.responseHeaders = options.responseHeaders;
+        this.responseURL = options.responseURL;
+      };
+
+      this.isReturn = function() {
+        return this.action === RETURN;
+      };
+
+      this.andError = function() {
+        this.action = ERROR;
+      };
+
+      this.isError = function() {
+        return this.action === ERROR;
+      };
+
+      this.andTimeout = function() {
+        this.action = TIMEOUT;
+      };
+
+      this.isTimeout = function() {
+        return this.action === TIMEOUT;
+      };
+
+      this.matches = function(fullUrl, data, method) {
+        var urlMatches = false;
+        fullUrl = fullUrl.toString();
+        if (this.url instanceof RegExp) {
+          urlMatches = this.url.test(fullUrl);
+        } else {
+          var urlSplit = fullUrl.split('?'),
+              url = urlSplit[0],
+              query = urlSplit[1];
+          urlMatches = this.url === url && this.query === normalizeQuery(query);
+        }
+        var dataMatches = false;
+        if (this.data instanceof RegExp) {
+          dataMatches = this.data.test(data);
+        } else {
+          dataMatches = !this.data || this.data === normalizeQuery(data);
+        }
+        return urlMatches && dataMatches && (!this.method || this.method === method);
+      };
+    }
+
+    return RequestStub;
+  };
+
+  getJasmineRequireObj().AjaxRequestTracker = function() {
+    function RequestTracker() {
+      var requests = [];
+
+      this.track = function(request) {
+        requests.push(request);
+      };
+
+      this.first = function() {
+        return requests[0];
+      };
+
+      this.count = function() {
+        return requests.length;
+      };
+
+      this.reset = function() {
+        requests = [];
+      };
+
+      this.mostRecent = function() {
+        return requests[requests.length - 1];
+      };
+
+      this.at = function(index) {
+        return requests[index];
+      };
+
+      this.filter = function(url_to_match) {
+        var matching_requests = [];
+
+        for (var i = 0; i < requests.length; i++) {
+          if (url_to_match instanceof RegExp &&
+              url_to_match.test(requests[i].url)) {
+            matching_requests.push(requests[i]);
+          } else if (url_to_match instanceof Function &&
+              url_to_match(requests[i])) {
+            matching_requests.push(requests[i]);
+          } else {
+            if (requests[i].url === url_to_match) {
+              matching_requests.push(requests[i]);
+            }
+          }
+        }
+
+        return matching_requests;
+      };
+    }
+
+    return RequestTracker;
+  };
+
+  getJasmineRequireObj().AjaxStubTracker = function() {
+    function StubTracker() {
+      var stubs = [];
+
+      this.addStub = function(stub) {
+        stubs.push(stub);
+      };
+
+      this.reset = function() {
+        stubs = [];
+      };
+
+      this.findStub = function(url, data, method) {
+        for (var i = stubs.length - 1; i >= 0; i--) {
+          var stub = stubs[i];
+          if (stub.matches(url, data, method)) {
+            return stub;
+          }
+        }
+      };
+    }
+
+    return StubTracker;
+  };
+
+
+  var jRequire = getJasmineRequireObj();
+  var MockAjax = jRequire.ajax(jRequire);
+  jasmine.Ajax = new MockAjax(global);
+
+  return MockAjax;
+}));

+ 1070 - 876
desktop/core/src/desktop/static/desktop/spec/sqlAutocompleterSpec.js

@@ -18,94 +18,31 @@ define([
   'desktop/js/sqlAutocompleter',
   'desktop/js/apiHelper',
   'desktop/spec/autocompleterTestUtils'
-], function(ko, SqlAutocompleter, ApiHelper, testUtils) {
-  describe("sqlAutocompleter.js", function() {
-    var subject;
-
-    var ajaxHelper = {
-      responseForUrls: {}
-    };
-
+], function (ko, SqlAutocompleter, ApiHelper, testUtils) {
+  describe('sqlAutocompleter.js', function () {
     var apiHelper = ApiHelper.getInstance({
       i18n: {},
       user: 'testUser'
     });
 
-    var snippet = {
-      type: ko.observable(),
-      database: ko.observable("database_one"),
-      isSqlDialect: function () { return true; },
-      getContext: function () { return ko.mapping.fromJS(null) },
-      getApiHelper: function () { return apiHelper }
-    };
-
-    var changeType = function (newType, callback) {
-      if (snippet.type() === newType) {
-        callback();
-        return;
-      }
-      snippet.type(newType);
-      window.setTimeout(function() {
-        callback();
-      }, 0);
-
-    };
+    var langTools = ace.require('ace/ext/language_tools');
 
-    beforeAll(function() {
+    beforeAll(function () {
       jasmine.addMatchers(testUtils.autocompleteMatcher);
-      $.totalStorage = function(key, value) {
+      $.totalStorage = function (key, value) {
         return null;
       };
-      spyOn($, "ajax").and.callFake(function(options) {
-        var firstUrlPart = options.url.split("?")[0];
-        var response;
-        if (firstUrlPart == "/notebook/api/autocomplete/") {
-          response = {
-            databases: ["database_one", "database_two"]
-          };
-        } else {
-          expect(ajaxHelper.responseForUrls[firstUrlPart]).toBeDefined("fake response for url " + firstUrlPart + " not found");
-          response = ajaxHelper.responseForUrls[firstUrlPart];
-        }
-        response.called = true;
-        response.status = 0;
-        if (typeof options.success === 'function') {
-          options.success(response);
-        }
-
-        var functions = {
-          fail: function() {
-            return functions;
-          },
-          done: function(success) {
-            success(response);
-            return functions;
-          },
-          always: function() {
-            return functions;
-          }
-        };
-
-        return functions;
-      });
-    });
-
-    afterEach(function() {
-      $.each(ajaxHelper.responseForUrls, function(key, value) {
-        expect(value.called).toEqual(true, key + " was never called");
-      })
     });
 
     var getCompleter = function (options) {
-      var langTools = ace.require("ace/ext/language_tools")
-      langTools.textCompleter.setSqlMode(true)
-      sqlAutocompleter = new SqlAutocompleter(options);
+      langTools.textCompleter.setSqlMode(true);
+      var sqlAutocompleter = new SqlAutocompleter(options);
       return {
         autocomplete: function (before, after, callback) {
           var textCompleterCallback = function (values) {
             langTools.textCompleter.getCompletions(null, {
               getValue: function () {
-                return before+after;
+                return before + after;
               },
               getTextRange: function () {
                 return before;
@@ -113,19 +50,13 @@ define([
             }, before.length, null, function (ignore, textCompletions) {
               callback(textCompletions.concat(values))
             });
-          }
+          };
           return sqlAutocompleter.autocomplete(before, after, textCompleterCallback);
         }
       };
-    }
-
-    beforeEach(function(done) {
-      changeType("genericSqlType", done);
-      subject = getCompleter({ snippet: snippet, optEnabled: false });
-      ajaxHelper.responseForUrls = {};
-    });
+    };
 
-    var createCallbackSpyForValues = function(values, includeLocal) {
+    var createCallbackSpyForValues = function (values, includeLocal) {
       return jasmine.createSpy('callback', function (value) {
         if (!includeLocal) {
           expect(value.filter(function (val) {
@@ -137,1289 +68,1552 @@ define([
       }).and.callThrough();
     };
 
-    var assertAutoComplete = function(testDefinition) {
-      ajaxHelper.responseForUrls = testDefinition.serverResponses;
-      var callback = createCallbackSpyForValues(testDefinition.expectedSuggestions, testDefinition.includeLocal);
-      subject.autocomplete(testDefinition.beforeCursor, testDefinition.afterCursor, callback);
-      expect(callback).toHaveBeenCalled();
+    var assertAutoComplete = function (testDefinition) {
+      var snippet = {
+        type: ko.observable(testDefinition.type ? testDefinition.type : 'genericSqlType'),
+        database: ko.observable('database_one'),
+        isSqlDialect: function () {
+          return true;
+        },
+        getContext: function () {
+          return ko.mapping.fromJS(null)
+        },
+        getApiHelper: function () {
+          return apiHelper
+        }
+      };
+
+      jasmine.Ajax.withMock(function() {
+        jasmine.Ajax.stubRequest(/.*\/notebook\/api\/autocomplete\//).andReturn({
+          status: 200,
+          statusText: 'HTTP/1.1 200 OK',
+          contentType: 'application/json;charset=UTF-8',
+          responseText: ko.mapping.toJSON({
+            status: 0,
+            databases: ['database_one', 'database_two']
+          })
+        });
+        testDefinition.serverResponses.forEach(function (responseDef) {
+          jasmine.Ajax.stubRequest(responseDef.url).andReturn({
+            status: 200,
+            statusText: 'HTTP/1.1 200 OK',
+            contentType: 'application/json;charset=UTF-8',
+            responseText: responseDef.response ? ko.mapping.toJSON(responseDef.response) : ''
+          });
+        });
+        var subject = getCompleter({
+          hdfsAutocompleter: {
+            autocomplete: function (before, after, callback) {
+              callback([
+                {
+                  meta: 'file',
+                  score: 1000,
+                  value: 'file_one'
+                },
+                {
+                  meta: 'dir',
+                  score: 999,
+                  value: 'folder_one'
+                }
+              ])
+            }
+          },
+          snippet: snippet,
+          optEnabled: false
+        });
+        var callback = createCallbackSpyForValues(testDefinition.expectedSuggestions, testDefinition.includeLocal);
+        subject.autocomplete(testDefinition.beforeCursor, testDefinition.afterCursor, callback);
+      });
     };
 
-    it("should return empty suggestions for empty statement", function() {
+    it('should return empty suggestions for empty statement', function () {
       assertAutoComplete({
-        serverResponses: { },
-        beforeCursor: "",
-        afterCursor: "",
+        type: 'hive',
+        serverResponses: [],
+        beforeCursor: '',
+        afterCursor: '',
         expectedSuggestions: []
       });
     });
 
-    it("should return empty suggestions for bogus statement", function() {
+    it('should return empty suggestions for bogus statement', function () {
       assertAutoComplete({
-        serverResponses: { },
-        beforeCursor: "foo",
-        afterCursor: "bar",
+        type: 'hive',
+        serverResponses: [],
+        beforeCursor: 'foo',
+        afterCursor: 'bar',
         expectedSuggestions: []
       });
     });
 
-    describe("database awareness", function() {
-      it("should suggest databases after use", function () {
+    describe('database awareness', function () {
+      it('should suggest databases after use', function () {
         assertAutoComplete({
-          serverResponses: {},
-          beforeCursor: "USE ",
-          afterCursor: "",
-          expectedSuggestions: ["database_one", "database_two"]
+          type: 'hive',
+          serverResponses: [],
+          beforeCursor: 'USE ',
+          afterCursor: '',
+          expectedSuggestions: ['database_one', 'database_two']
         });
       });
 
-      it("should use a use statement before the cursor if present", function () {
+      it('should use a use statement before the cursor if present', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_two" : {
-              tables_meta: [{ name: "otherTable1" }, { name: "otherTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_two/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'otherTable1'}, {name: 'otherTable2'}]
             }
-          },
-          beforeCursor: "USE database_two; \n\tSELECT ",
-          afterCursor: "",
-          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2", "? FROM database_one.", "? FROM database_two."]
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/asdf/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'otherTable1'}, {name: 'otherTable2'}]
+            }
+          }],
+          beforeCursor: 'USE database_two; \n\tSELECT ',
+          afterCursor: '',
+          expectedSuggestions: ['? FROM otherTable1', '? FROM otherTable2', '? FROM database_one.', '? FROM database_two.']
         });
       });
 
-      it("should use the last use statement before the cursor if multiple are present", function () {
+      it('should use the last use statement before the cursor if multiple are present', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/closest_db" : {
-              tables_meta: [{ name: "otherTable1" }, { name:  "otherTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/closest_db/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'otherTable1'}, {name: 'otherTable2'}]
             }
-          },
-          beforeCursor: "USE other_db; USE closest_db; \n\tSELECT ",
-          afterCursor: "",
-          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2", "? FROM database_one.", "? FROM database_two."]
+          }],
+          beforeCursor: 'USE other_db; USE closest_db; \n\tSELECT ',
+          afterCursor: '',
+          expectedSuggestions: ['? FROM otherTable1', '? FROM otherTable2', '? FROM database_one.', '? FROM database_two.']
         });
       });
 
-      it("should use the use statement before the cursor if multiple are present after the cursor", function () {
+      it('should use the use statement before the cursor if multiple are present after the cursor', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/closest_db" : {
-              tables_meta: [{ name: "otherTable1" }, { name: "otherTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/closest_db/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'otherTable1'}, {name: 'otherTable2'}]
             }
-          },
-          beforeCursor: "USE other_db; USE closest_db; \n\tSELECT ",
-          afterCursor: "USE some_other_db;",
-          expectedSuggestions: ["? FROM otherTable1", "? FROM otherTable2", "? FROM database_one.", "? FROM database_two."]
+          }],
+          beforeCursor: 'USE other_db; USE closest_db; \n\tSELECT ',
+          afterCursor: 'USE some_other_db;',
+          expectedSuggestions: ['? FROM otherTable1', '? FROM otherTable2', '? FROM database_one.', '? FROM database_two.']
         });
       });
     });
 
-    describe("text completer", function() {
-      it("should ignore line comments for local suggestions", function () {
+    describe('text completer', function () {
+      it('should ignore line comments for local suggestions', function () {
         assertAutoComplete({
-          serverResponses: { },
+          type: 'hive',
+          serverResponses: [],
           includeLocal: true,
-          beforeCursor: "-- line comment'\nSELECT * from testTable1;\n",
-          afterCursor: "\n-- other line comment",
-          expectedSuggestions: ["SELECT", "from", "testTable1"]
+          beforeCursor: '-- line comment\nSELECT * from testTable1;\n',
+          afterCursor: '\n-- other line comment',
+          expectedSuggestions: ['SELECT', 'from', 'testTable1']
         });
       });
 
-      it("should ignore multi-line comments for local suggestions", function () {
+      it('should ignore multi-line comments for local suggestions', function () {
         assertAutoComplete({
-          serverResponses: { },
+          type: 'hive',
+          serverResponses: [],
           includeLocal: true,
-          beforeCursor: "/* line 1\nline 2\n*/\nSELECT * from testTable1;\n",
-          afterCursor: "",
-          expectedSuggestions: ["SELECT", "from", "testTable1"]
+          beforeCursor: '/* line 1\nline 2\n*/\nSELECT * from testTable1;\n',
+          afterCursor: '',
+          expectedSuggestions: ['SELECT', 'from', 'testTable1']
         });
       });
-    })
+    });
 
-    describe("table completion", function() {
-      it("should suggest table names with no columns", function() {
+    describe('table completion', function () {
+      it('should suggest table names with no columns', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one" : {
-              tables_meta: [{ name: "testTable1" }, { name: "testTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
             }
-          },
-          beforeCursor: "SELECT ",
-          afterCursor: "",
-          expectedSuggestions: ["? FROM testTable1", "? FROM testTable2", "? FROM database_one.", "? FROM database_two."]
+          }],
+          beforeCursor: 'SELECT ',
+          afterCursor: '',
+          expectedSuggestions: ['? FROM testTable1', '? FROM testTable2', '? FROM database_one.', '? FROM database_two.']
         });
       });
 
-      it("should follow keyword case for table name completion", function() {
+      it('should follow keyword case for table name completion', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one" : {
-              tables_meta: [{ name: "testTable1" }, { name: "testTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
             }
-          },
-          beforeCursor: "select ",
-          afterCursor: "",
-          expectedSuggestions: ["? from testTable1", "? from testTable2", "? from database_one.", "? from database_two."]
+          }],
+          beforeCursor: 'select ',
+          afterCursor: '',
+          expectedSuggestions: ['? from testTable1', '? from testTable2', '? from database_one.', '? from database_two.']
         });
       });
 
-      it("should suggest table names with *", function() {
+      it('should suggest table names with *', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one" : {
-              tables_meta: [{ name: "testTable1" }, { name: "testTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
             }
-          },
-          beforeCursor: "SELECT *",
-          afterCursor: "",
-          expectedSuggestions: [" FROM testTable1", " FROM testTable2", " FROM database_one.", " FROM database_two."]
+          }],
+          beforeCursor: 'SELECT *',
+          afterCursor: '',
+          expectedSuggestions: [' FROM testTable1', ' FROM testTable2', ' FROM database_one.', ' FROM database_two.']
         });
       });
 
-      it("should suggest table names with started FROM", function() {
+      it('should suggest table names with started FROM', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one" : {
-              tables_meta: [{ name: "testTable1" }, { name: "testTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
             }
-          },
-          beforeCursor: "SELECT * fr",
-          afterCursor: "",
-          expectedSuggestions: ["FROM testTable1", "FROM testTable2", "FROM database_one.", "FROM database_two."]
+          }],
+          beforeCursor: 'SELECT * fr',
+          afterCursor: '',
+          expectedSuggestions: ['FROM testTable1', 'FROM testTable2', 'FROM database_one.', 'FROM database_two.']
         });
       });
 
-      it("should suggest table names after FROM", function() {
+      it('should suggest table names after FROM', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one" : {
-              tables_meta: [{ name: "testTable1" }, { name: "testTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
             }
-          },
-          beforeCursor: "SELECT * FROM ",
-          afterCursor: "",
-          expectedSuggestions: ["testTable1", "testTable2", "database_one.", "database_two."]
+          }],
+          beforeCursor: 'SELECT * FROM ',
+          afterCursor: '',
+          expectedSuggestions: ['testTable1', 'testTable2', 'database_one.', 'database_two.']
         });
       });
 
-      it("should suggest table names after FROM with started name", function() {
+      it('should suggest table names after FROM with started name', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one" : {
-              tables_meta: [{ name: "testTable1" }, { name: "testTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
             }
-          },
-          beforeCursor: "SELECT * FROM tes",
-          afterCursor: "",
-          expectedSuggestions: ["testTable1", "testTable2"]
+          }],
+          beforeCursor: 'SELECT * FROM tes',
+          afterCursor: '',
+          expectedSuggestions: ['testTable1', 'testTable2']
         });
       });
 
-      it("should suggest database names after FROM with started name", function() {
+      it('should suggest database names after FROM with started name', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one" : {
-              tables_meta: [{ name: "testTable1" }, { name: "testTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
             }
-          },
-          beforeCursor: "SELECT * FROM dat",
-          afterCursor: "",
-          expectedSuggestions: ["database_one.", "database_two."]
+          }],
+          beforeCursor: 'SELECT * FROM dat',
+          afterCursor: '',
+          expectedSuggestions: ['database_one.', 'database_two.']
         });
       });
 
-      it("should suggest table names after FROM with database reference", function() {
+      it('should suggest table names after FROM with database reference', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_two" : {
-              tables_meta: [{ name: "testTable3" }, { name: "testTable4" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_two/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'testTable3'}, {name: 'testTable4'}]
             }
-          },
-          beforeCursor: "SELECT * FROM database_two.",
-          afterCursor: "",
-          expectedSuggestions: ["testTable3", "testTable4"]
+          }],
+          beforeCursor: 'SELECT * FROM database_two.',
+          afterCursor: '',
+          expectedSuggestions: ['testTable3', 'testTable4']
         });
       });
 
-      it("should suggest aliases", function() {
+      it('should suggest aliases', function () {
         assertAutoComplete({
-          serverResponses: {},
-          beforeCursor: "SELECT ",
-          afterCursor: " FROM testTableA   tta, testTableB",
-          expectedSuggestions: ["tta.", "testTableB."]
+          type: 'hive',
+          serverResponses: [],
+          beforeCursor: 'SELECT ',
+          afterCursor: ' FROM testTableA   tta, testTableB',
+          expectedSuggestions: ['tta.', 'testTableB.']
         });
       });
 
-      it("should suggest aliases in GROUP BY", function() {
+      it('should suggest aliases in GROUP BY', function () {
         assertAutoComplete({
-          serverResponses: {},
-          beforeCursor: "SELECT * FROM testTableA tta, testTableB GROUP BY ",
-          afterCursor: "",
-          expectedSuggestions: ["tta.", "testTableB."]
+          type: 'hive',
+          type: 'hive',
+          serverResponses: [],
+          beforeCursor: 'SELECT * FROM testTableA tta, testTableB GROUP BY ',
+          afterCursor: '',
+          expectedSuggestions: ['tta.', 'testTableB.']
         });
       });
 
-      it("should only suggest table aliases", function() {
+      it('should only suggest table aliases', function () {
         assertAutoComplete({
-          serverResponses: {},
-          beforeCursor: "SELECT ",
-          afterCursor: " FROM testTableA tta, (SELECT SUM(A*B) total FROM tta.array) ttaSum, testTableB ttb",
-          expectedSuggestions: ["tta.", "ttb."]
+          type: 'hive',
+          serverResponses: [],
+          beforeCursor: 'SELECT ',
+          afterCursor: ' FROM testTableA tta, (SELECT SUM(A*B) total FROM tta.array) ttaSum, testTableB ttb',
+          expectedSuggestions: ['tta.', 'ttb.']
         });
       });
 
       // TODO: Fix me...
-      xit("should suggest aliases from nested selects", function() {
+      xit('should suggest aliases from nested selects', function () {
         assertAutoComplete({
-          serverResponses: {},
-          beforeCursor: "SELECT ",
-          afterCursor: " FROM testTableA tta, testTableB ttb, (SELECT SUM(A*B) total FROM tta.array) ttaSum",
-          expectedSuggestions: ["tta.", "ttb.", "ttaSum."]
+          type: 'hive',
+          serverResponses: [],
+          beforeCursor: 'SELECT ',
+          afterCursor: ' FROM testTableA tta, testTableB ttb, (SELECT SUM(A*B) total FROM tta.array) ttaSum',
+          expectedSuggestions: ['tta.', 'ttb.', 'ttaSum.']
         });
       });
     });
 
-    describe("hive-specific stuff", function() {
-      beforeEach(function(done) {
-        changeType("hive", done);
-        ajaxHelper.responseForUrls = {};
-      });
-
-      describe("HDFS autocompletion", function () {
-        beforeEach(function() {
-          subject = getCompleter({
-            hdfsAutocompleter: {
-              autocomplete: function(before, after, callback) {
-                callback([
-                  {
-                    meta: "file",
-                    score: 1000,
-                    value: "file_one"
-                  },
-                  {
-                    meta: "dir",
-                    score: 999,
-                    value: "folder_one"
-                  }
-                ])
-              }
-            },
-            snippet: snippet,
-            optEnabled: false
-          });
-        });
+    describe('hive-specific stuff', function () {
 
-        it("should autocomplete hdfs paths in location references without initial /", function () {
+      describe('HDFS autocompletion', function () {
+        it('should autocomplete hdfs paths in location references without initial /', function () {
           assertAutoComplete({
-            serverResponses: {},
-            beforeCursor: "CREATE EXTERNAL TABLE foo (id int) LOCATION '",
-            afterCursor: "'",
-            expectedSuggestions: ["/file_one", "/folder_one/"]
+            type: 'hive',
+            serverResponses: [],
+            beforeCursor: 'CREATE EXTERNAL TABLE foo (id int) LOCATION \'',
+            afterCursor: '\'',
+            expectedSuggestions: ['/file_one', '/folder_one/']
           });
         });
 
-        it("should autocomplete hdfs paths in location references from root", function () {
+        it('should autocomplete hdfs paths in location references from root', function () {
           assertAutoComplete({
-            serverResponses: {},
-            beforeCursor: "CREATE EXTERNAL TABLE foo (id int) LOCATION '/",
-            afterCursor: "'",
-            expectedSuggestions: ["file_one", "folder_one/"]
+            type: 'hive',
+            serverResponses: [],
+            beforeCursor: 'CREATE EXTERNAL TABLE foo (id int) LOCATION \'/',
+            afterCursor: '\'',
+            expectedSuggestions: ['file_one', 'folder_one/']
           });
         });
 
-        it("should autocomplete hdfs paths and suggest trailing apostrophe if empty after cursor", function () {
+        it('should autocomplete hdfs paths and suggest trailing apostrophe if empty after cursor', function () {
           assertAutoComplete({
-            serverResponses: {},
-            beforeCursor: "CREATE EXTERNAL TABLE foo (id int) LOCATION '/",
-            afterCursor: "",
-            expectedSuggestions: ["file_one'", "folder_one/"]
+            type: 'hive',
+            serverResponses: [],
+            beforeCursor: 'CREATE EXTERNAL TABLE foo (id int) LOCATION \'/',
+            afterCursor: '',
+            expectedSuggestions: ['file_one\'', 'folder_one/']
           });
         });
 
-        it("should autocomplete hdfs paths in location references from inside a path", function () {
+        it('should autocomplete hdfs paths in location references from inside a path', function () {
           assertAutoComplete({
-            serverResponses: {},
-            beforeCursor: "CREATE EXTERNAL TABLE foo (id int) LOCATION '/",
-            afterCursor: "/bar'",
-            expectedSuggestions: ["file_one", "folder_one"]
+            type: 'hive',
+            serverResponses: [],
+            beforeCursor: 'CREATE EXTERNAL TABLE foo (id int) LOCATION \'/',
+            afterCursor: '/bar\'',
+            expectedSuggestions: ['file_one', 'folder_one']
           });
         });
       });
 
-      it("should suggest struct from map values", function() {
+      it('should suggest struct from map values', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
-              type: "map"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
+            response: {
+              status: 0,
+              type: 'map'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldA" },
-                {"type": "string", "name": "fieldB" },
-                {"type": "struct",  "name": "fieldC", "fields": [
-                  {"type": "string", "name": "fieldC_A" },
-                  {"type": "boolean", "name": "fieldC_B"}
-                ]}],
-              type: "struct"
+                {type: 'string', name: 'fieldA'},
+                {type: 'string', name: 'fieldB'},
+                {
+                  type: 'struct', name: 'fieldC', 'fields': [
+                  {type: 'string', name: 'fieldC_A'},
+                  {type: 'boolean', name: 'fieldC_B'}
+                ]
+                }],
+              type: 'struct'
             }
-          },
-          beforeCursor: "SELECT testMap[\"anyKey\"].",
-          afterCursor: " FROM testTable",
-          expectedSuggestions: ["fieldA", "fieldB", "fieldC"]
+          }],
+          beforeCursor: 'SELECT testMap[\'anyKey\'].',
+          afterCursor: ' FROM testTable',
+          expectedSuggestions: ['fieldA', 'fieldB', 'fieldC']
         });
       });
 
-      it("should suggest struct from map values without a given key", function() {
+      it('should suggest struct from map values without a given key', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
-              type: "map"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
+            response: {
+              status: 0,
+              type: 'map'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldA" },
-                {"type": "string", "name": "fieldB" }
+                {type: 'string', name: 'fieldA'},
+                {type: 'string', name: 'fieldB'}
               ],
-              type: "struct"
+              type: 'struct'
             }
-          },
-          beforeCursor: "SELECT testMap[].",
-          afterCursor: " FROM testTable",
-          expectedSuggestions: ["fieldA", "fieldB"]
+          }],
+          beforeCursor: 'SELECT testMap[].',
+          afterCursor: ' FROM testTable',
+          expectedSuggestions: ['fieldA', 'fieldB']
         });
       });
 
-      it("should suggest struct from structs from map values", function() {
+      it('should suggest struct from structs from map values', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
-              type: "map"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testMap/value/fieldC" : {
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
+            response: {
+              status: 0,
+              type: 'map'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value\/fieldC/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldC_A" },
-                {"type": "boolean", "name": "fieldC_B"}
+                {type: 'string', name: 'fieldC_A'},
+                {type: 'boolean', name: 'fieldC_B'}
               ],
-              type: "struct"
+              type: 'struct'
             }
-          },
-          beforeCursor: "SELECT testMap[\"anyKey\"].fieldC.",
-          afterCursor: " FROM testTable",
-          expectedSuggestions: ["fieldC_A", "fieldC_B"]
+          }],
+          beforeCursor: 'SELECT testMap[\'anyKey\'].fieldC.',
+          afterCursor: ' FROM testTable',
+          expectedSuggestions: ['fieldC_A', 'fieldC_B']
         });
       });
 
-      it("should suggest struct from structs from arrays", function() {
+      it('should suggest struct from structs from arrays', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testArray" : {
-              type: "array"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testArray/item/fieldC" : {
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray/,
+            response: {
+              status: 0,
+              type: 'array'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item\/fieldC/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldC_A" },
-                {"type": "boolean", "name": "fieldC_B"}
+                {type: 'string', name: 'fieldC_A'},
+                {type: 'boolean', name: 'fieldC_B'}
               ],
-              type: "struct"
+              type: 'struct'
             }
-          },
-          beforeCursor: "SELECT testArray[1].fieldC.",
-          afterCursor: " FROM testTable",
-          expectedSuggestions: ["fieldC_A", "fieldC_B"]
+          }],
+          beforeCursor: 'SELECT testArray[1].fieldC.',
+          afterCursor: ' FROM testTable',
+          expectedSuggestions: ['fieldC_A', 'fieldC_B']
         });
       });
 
-      it("should suggest structs from maps from arrays", function() {
+      it('should suggest structs from maps from arrays', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testArray" : {
-              type: "array"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testArray/item/testMap" : {
-              type: "map"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testArray/item/testMap/value" : {
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray/,
+            response: {
+              status: 0,
+              type: 'array'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item\/testMap/,
+            response: {
+              status: 0,
+              type: 'map'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item\/testMap\/value/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldA" },
-                {"type": "boolean", "name": "fieldB"}
+                {type: 'string', name: 'fieldA'},
+                {type: 'boolean', name: 'fieldB'}
               ],
-              type: "struct"
+              type: 'struct'
             }
-          },
-          beforeCursor: "SELECT testArray[1].testMap[\"key\"].",
-          afterCursor: " FROM testTable",
-          expectedSuggestions: ["fieldA", "fieldB"]
+          }],
+          beforeCursor: 'SELECT testArray[1].testMap[\'key\'].',
+          afterCursor: ' FROM testTable',
+          expectedSuggestions: ['fieldA', 'fieldB']
         });
       });
 
-      describe("lateral views", function() {
-        it("should suggest structs from exploded item references to arrays", function () {
+      describe('lateral views', function () {
+        it('should suggest structs from exploded item references to arrays', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable/testArray/item": {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldA"},
-                  {"type": "string", "name": "fieldB"}
+                  {type: 'string', name: 'fieldA'},
+                  {type: 'string', name: 'fieldB'}
                 ],
-                type: "struct"
+                type: 'struct'
               }
-            },
-            beforeCursor: "SELECT testItem.",
-            afterCursor: " FROM testTable LATERAL VIEW explode(testArray) explodedTable AS testItem",
-            expectedSuggestions: ["fieldA", "fieldB"]
+            }],
+            beforeCursor: 'SELECT testItem.',
+            afterCursor: ' FROM testTable LATERAL VIEW explode(testArray) explodedTable AS testItem',
+            expectedSuggestions: ['fieldA', 'fieldB']
           });
         });
 
-        it("should suggest structs from multiple exploded item references to arrays", function () {
+        it('should suggest structs from multiple exploded item references to arrays', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable/testArrayA/item": {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArrayA\/item/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldA"},
-                  {"type": "string", "name": "fieldB"}
+                  {type: 'string', name: 'fieldA'},
+                  {type: 'string', name: 'fieldB'}
                 ],
-                type: "struct"
+                type: 'struct'
               }
-            },
-            beforeCursor: "SELECT testItemA.",
-            afterCursor: " FROM testTable" +
-                         " LATERAL VIEW explode(testArrayA) explodedTableA AS testItemA" +
-                         " LATERAL VIEW explode(testArrayB) explodedTableB AS testItemB",
-            expectedSuggestions: ["fieldA", "fieldB"]
+            }],
+            beforeCursor: 'SELECT testItemA.',
+            afterCursor: ' FROM testTable' +
+            ' LATERAL VIEW explode(testArrayA) explodedTableA AS testItemA' +
+            ' LATERAL VIEW explode(testArrayB) explodedTableB AS testItemB',
+            expectedSuggestions: ['fieldA', 'fieldB']
           });
         });
 
-        it("should support table references as arguments of explode function", function() {
+        it('should support table references as arguments of explode function', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable2/testArrayB/item": {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable2\/testArrayB\/item/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldA"},
-                  {"type": "string", "name": "fieldB"}
+                  {type: 'string', name: 'fieldA'},
+                  {type: 'string', name: 'fieldB'}
                 ],
-                type: "struct"
+                type: 'struct'
               }
-            },
-            beforeCursor: "SELECT\n testItemA,\n testItemB.",
-            afterCursor: "\n\tFROM\n\t testTable2 tt2\n" +
-            "\t LATERAL VIEW EXPLODE(tt2.testArrayA) explodedTableA AS testItemA\n" +
-            "\t LATERAL VIEW EXPLODE(tt2.testArrayB) explodedTableB AS testItemB",
-            expectedSuggestions: ["fieldA", "fieldB"]
+            }],
+            beforeCursor: 'SELECT\n testItemA,\n testItemB.',
+            afterCursor: '\n\tFROM\n\t testTable2 tt2\n' +
+            '\t LATERAL VIEW EXPLODE(tt2.testArrayA) explodedTableA AS testItemA\n' +
+            '\t LATERAL VIEW EXPLODE(tt2.testArrayB) explodedTableB AS testItemB',
+            expectedSuggestions: ['fieldA', 'fieldB']
           });
         });
 
-        it("should suggest structs from exploded item references to exploded item references to arrays ", function () {
+        it('should suggest structs from exploded item references to exploded item references to arrays ', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable/testArray1/item/testArray2/item": {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray1\/item\/testArray2\/item/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldA"},
-                  {"type": "string", "name": "fieldB"}
+                  {type: 'string', name: 'fieldA'},
+                  {type: 'string', name: 'fieldB'}
                 ],
-                type: "struct"
+                type: 'struct'
               }
-            },
-            beforeCursor: "SELECT ta2_exp.",
-            afterCursor: " FROM " +
-            "   testTable tt" +
-            " LATERAL VIEW explode(tt.testArray1) ta1 AS ta1_exp\n" +
-            "   LATERAL VIEW explode(ta1_exp.testArray2)    ta2   AS  ta2_exp",
-            expectedSuggestions: ["fieldA", "fieldB"]
+            }],
+            beforeCursor: 'SELECT ta2_exp.',
+            afterCursor: ' FROM ' +
+            '   testTable tt' +
+            ' LATERAL VIEW explode(tt.testArray1) ta1 AS ta1_exp\n' +
+            '   LATERAL VIEW explode(ta1_exp.testArray2)    ta2   AS  ta2_exp',
+            expectedSuggestions: ['fieldA', 'fieldB']
           });
         });
 
-        it("should suggest structs from references to exploded arrays", function () {
+        it('should suggest structs from references to exploded arrays', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable/testArray/item": {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldA"},
-                  {"type": "string", "name": "fieldB"}
+                  {type: 'string', name: 'fieldA'},
+                  {type: 'string', name: 'fieldB'}
                 ],
-                type: "struct"
+                type: 'struct'
               }
-            },
-            beforeCursor: "SELECT explodedTable.testItem.",
-            afterCursor: " FROM testTable LATERAL VIEW explode(testArray) explodedTable AS testItem",
-            expectedSuggestions: ["fieldA", "fieldB"]
+            }],
+            beforeCursor: 'SELECT explodedTable.testItem.',
+            afterCursor: ' FROM testTable LATERAL VIEW explode(testArray) explodedTable AS testItem',
+            expectedSuggestions: ['fieldA', 'fieldB']
           });
         });
 
-        it("should suggest posexploded references to arrays", function () {
+        it('should suggest posexploded references to arrays', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable/testArray/item": {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldA"},
-                  {"type": "string", "name": "fieldB"}
+                  {type: 'string', name: 'fieldA'},
+                  {type: 'string', name: 'fieldB'}
                 ],
-                type: "struct"
+                type: 'struct'
               }
-            },
-            beforeCursor: "SELECT testValue.",
-            afterCursor: " FROM testTable LATERAL VIEW posexplode(testArray) explodedTable AS (testIndex, testValue)",
-            expectedSuggestions: ["fieldA", "fieldB"]
+            }],
+            beforeCursor: 'SELECT testValue.',
+            afterCursor: ' FROM testTable LATERAL VIEW posexplode(testArray) explodedTable AS (testIndex, testValue)',
+            expectedSuggestions: ['fieldA', 'fieldB']
           });
         });
 
-        it("should suggest exploded references to map values", function () {
+        it('should suggest exploded references to map values', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable/testMap/value": {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldA"},
-                  {"type": "string", "name": "fieldB"}
+                  {type: 'string', name: 'fieldA'},
+                  {type: 'string', name: 'fieldB'}
                 ],
-                type: "struct"
+                type: 'struct'
               }
-            },
-            beforeCursor: "SELECT testMapValue.",
-            afterCursor: " FROM testTable LATERAL VIEW explode(testMap) AS (testMapKey, testMapValue)",
-            expectedSuggestions: ["fieldA", "fieldB"]
+            }],
+            beforeCursor: 'SELECT testMapValue.',
+            afterCursor: ' FROM testTable LATERAL VIEW explode(testMap) AS (testMapKey, testMapValue)',
+            expectedSuggestions: ['fieldA', 'fieldB']
           });
         });
 
-        it("should suggest exploded references to map values from view references", function () {
+        it('should suggest exploded references to map values from view references', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable/testMap/value": {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldA"},
-                  {"type": "string", "name": "fieldB"}
+                  {type: 'string', name: 'fieldA'},
+                  {type: 'string', name: 'fieldB'}
                 ],
-                type: "struct"
+                type: 'struct'
               }
-            },
-            beforeCursor: "SELECT explodedMap.testMapValue.",
-            afterCursor: " FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)",
-            expectedSuggestions: ["fieldA", "fieldB"]
+            }],
+            beforeCursor: 'SELECT explodedMap.testMapValue.',
+            afterCursor: ' FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)',
+            expectedSuggestions: ['fieldA', 'fieldB']
           });
         });
 
-        it("should suggest references to exploded references from view reference", function () {
+        it('should suggest references to exploded references from view reference', function () {
           assertAutoComplete({
-            serverResponses: {},
-            beforeCursor: "SELECT explodedMap.",
-            afterCursor: " FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)",
-            expectedSuggestions: ["testMapKey", "testMapValue"]
+            type: 'hive',
+            serverResponses: [],
+            beforeCursor: 'SELECT explodedMap.',
+            afterCursor: ' FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)',
+            expectedSuggestions: ['testMapKey', 'testMapValue']
           });
         });
 
-        it("should suggest references to exploded references", function () {
+        it('should suggest references to exploded references', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable" : {
-                columns: ["testTableColumn1", "testTableColumn2"]
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
+              response: {
+                status: 0,
+                columns: ['testTableColumn1', 'testTableColumn2']
               }
-            },
-            beforeCursor: "SELECT ",
-            afterCursor: " FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)",
-            expectedSuggestions: ["*", "explodedMap", "testMapKey", "testMapValue", "testTableColumn1", "testTableColumn2"]
+            }],
+            beforeCursor: 'SELECT ',
+            afterCursor: ' FROM testTable LATERAL VIEW explode(testMap) explodedMap AS (testMapKey, testMapValue)',
+            expectedSuggestions: ['*', 'explodedMap', 'testMapKey', 'testMapValue', 'testTableColumn1', 'testTableColumn2']
           });
         });
       });
     });
 
-    describe("impala-specific stuff", function() {
-      beforeEach(function (done) {
-        changeType("impala", done);
-        ajaxHelper.responseForUrls = {};
-      });
-
-      describe("HDFS autocompletion", function () {
-        beforeEach(function() {
-          subject = getCompleter({
-            hdfsAutocompleter: {
-              autocomplete: function(before, after, callback) {
-                callback([
-                  {
-                    meta: "file",
-                    score: 1000,
-                    value: "file_one"
-                  },
-                  {
-                    meta: "dir",
-                    score: 999,
-                    value: "folder_one"
-                  }
-                ])
-              }
-            },
-            snippet: snippet,
-            optEnabled: false
-          });
-        });
-
-        it("should autocomplete hdfs paths in location references without initial /", function () {
+    describe('impala-specific stuff', function () {
+      describe('HDFS autocompletion', function () {
+        it('should autocomplete hdfs paths in location references without initial /', function () {
           assertAutoComplete({
-            serverResponses: {},
-            beforeCursor: "LOAD DATA INPATH '",
-            afterCursor: "'",
-            expectedSuggestions: ["/file_one", "/folder_one/"]
+            type: 'impala',
+            serverResponses: [],
+            beforeCursor: 'LOAD DATA INPATH \'',
+            afterCursor: '\'',
+            expectedSuggestions: ['/file_one', '/folder_one/']
           });
         });
 
-        it("should autocomplete hdfs paths in location references from root", function () {
+        it('should autocomplete hdfs paths in location references from root', function () {
           assertAutoComplete({
-            serverResponses: {},
-            beforeCursor: "LOAD DATA INPATH '/",
-            afterCursor: "'",
-            expectedSuggestions: ["file_one", "folder_one/"]
+            type: 'impala',
+            serverResponses: [],
+            beforeCursor: 'LOAD DATA INPATH \'/',
+            afterCursor: '\'',
+            expectedSuggestions: ['file_one', 'folder_one/']
           });
         });
 
-        it("should autocomplete hdfs paths and suggest trailing apostrophe if empty after cursor", function () {
+        it('should autocomplete hdfs paths and suggest trailing apostrophe if empty after cursor', function () {
           assertAutoComplete({
-            serverResponses: {},
-            beforeCursor: "LOAD DATA INPATH '/",
-            afterCursor: "",
-            expectedSuggestions: ["file_one'", "folder_one/"]
+            type: 'impala',
+            serverResponses: [],
+            beforeCursor: 'LOAD DATA INPATH \'/',
+            afterCursor: '',
+            expectedSuggestions: ['file_one\'', 'folder_one/']
           });
         });
 
-        it("should autocomplete hdfs paths in location references from inside a path", function () {
+        it('should autocomplete hdfs paths in location references from inside a path', function () {
           assertAutoComplete({
-            serverResponses: {},
-            beforeCursor: "LOAD DATA INPATH '/",
-            afterCursor: "/bar' INTO TABLE foo",
-            expectedSuggestions: ["file_one", "folder_one"]
+            type: 'impala',
+            serverResponses: [],
+            beforeCursor: 'LOAD DATA INPATH \'/',
+            afterCursor: '/bar\' INTO TABLE foo',
+            expectedSuggestions: ['file_one', 'folder_one']
           });
         });
       });
 
-      it("should not suggest struct from map values with hive style syntax", function() {
+      it('should not suggest struct from map values with hive style syntax', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testMap[\"anyKey\"]" : {
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\['anyKey'\]/,
+            response: {
+              status: 0,
               someResponse: true
             }
-          },
-          beforeCursor: "SELECT testMap[\"anyKey\"].",
-          afterCursor: " FROM testTable",
+          }],
+          beforeCursor: 'SELECT testMap[\'anyKey\'].',
+          afterCursor: ' FROM testTable',
           expectedSuggestions: []
         });
       });
 
-      it("should suggest fields from nested structs", function() {
+      it('should suggest fields from nested structs', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/columnA" : {
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/columnA/,
+            response: {
+              status: 0
               // Impala has to query every part for it's type, for hive '[' and ']' is used to indicate map or array.
-            },
-            "/notebook/api/autocomplete/database_one/testTable/columnA/fieldC" : {
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/columnA\/fieldC/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldC_A" },
-                {"type": "boolean", "name": "fieldC_B"}
+                {type: 'string', name: 'fieldC_A'},
+                {type: 'boolean', name: 'fieldC_B'}
               ],
-              "type": "struct",
-              "name": "fieldC"
+              type: 'struct',
+              name: 'fieldC'
             }
-          },
-          beforeCursor: "SELECT columnA.fieldC.",
-          afterCursor: " FROM testTable",
-          expectedSuggestions: ["fieldC_A", "fieldC_B"]
+          }],
+          beforeCursor: 'SELECT columnA.fieldC.',
+          afterCursor: ' FROM testTable',
+          expectedSuggestions: ['fieldC_A', 'fieldC_B']
         });
       });
 
-      it("should suggest fields from map values of type structs", function() {
+      it('should suggest fields from map values of type structs', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
-              type: "map"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
+            response: {
+              status: 0,
+              type: 'map'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldA" },
-                {"type": "string", "name": "fieldB" }
+                {type: 'string', name: 'fieldA'},
+                {type: 'string', name: 'fieldB'}
               ],
-              type: "struct"
+              type: 'struct'
             }
-          },
-          beforeCursor: "SELECT tm.",
-          afterCursor: " FROM testTable t, t.testMap tm;",
-          expectedSuggestions: ["*", "key", "fieldA", "fieldB"]
+          }],
+          beforeCursor: 'SELECT tm.',
+          afterCursor: ' FROM testTable t, t.testMap tm;',
+          expectedSuggestions: ['*', 'key', 'fieldA', 'fieldB']
         });
       });
 
-      it("should suggest map value if type is scalar", function() {
+      it('should suggest map value if type is scalar', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
-              type: "map"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
-              type: "int"
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
+            response: {
+              status: 0,
+              type: 'map'
             }
-          },
-          beforeCursor: "SELECT tm.",
-          afterCursor: " FROM testTable t, t.testMap tm;",
-          expectedSuggestions: ["*", "key", "value"]
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
+            response: {
+              status: 0,
+              type: 'int'
+            }
+          }],
+          beforeCursor: 'SELECT tm.',
+          afterCursor: ' FROM testTable t, t.testMap tm;',
+          expectedSuggestions: ['*', 'key', 'value']
         });
       });
 
-      it("should not suggest items from arrays if complex in select clause", function() {
+      it('should not suggest items from arrays if complex in select clause', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testArray" : {
-              type: "array"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testArray/item" : {
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray/,
+            response: {
+              status: 0,
+              type: 'array'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldA" },
-                {"type": "string", "name": "fieldB" }
+                {type: 'string', name: 'fieldA'},
+                {type: 'string', name: 'fieldB'}
               ],
-              type: "struct"
+              type: 'struct'
             }
-          },
-          beforeCursor: "SELECT ta.",
-          afterCursor: " FROM testTable t, t.testArray ta;",
-          expectedSuggestions: ["*", "fieldA", "fieldB"]
+          }],
+          beforeCursor: 'SELECT ta.',
+          afterCursor: ' FROM testTable t, t.testArray ta;',
+          expectedSuggestions: ['*', 'fieldA', 'fieldB']
         });
       });
 
-      it("should suggest items from arrays if scalar in select clause", function() {
+      it('should suggest items from arrays if scalar in select clause', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testArray" : {
-              type: "array"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testArray/item" : {
-              type: "int"
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray/,
+            response: {
+              status: 0,
+              type: 'array'
             }
-          },
-          beforeCursor: "SELECT ta.",
-          afterCursor: " FROM testTable t, t.testArray ta;",
-          expectedSuggestions: ["*", "items"]
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
+            response: {
+              status: 0,
+              type: 'int'
+            }
+          }],
+          beforeCursor: 'SELECT ta.',
+          afterCursor: ' FROM testTable t, t.testArray ta;',
+          expectedSuggestions: ['*', 'items']
         });
       });
 
-      it("should suggest items from arrays if complex in from clause", function() {
+      it('should suggest items from arrays if complex in from clause', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testArray" : {
-              type: "array"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testArray/item" : {
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray/,
+            response: {
+              status: 0,
+              type: 'array'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testArray\/item/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldA" },
-                {"type": "string", "name": "fieldB" }
+                {type: 'string', name: 'fieldA'},
+                {type: 'string', name: 'fieldB'}
               ],
-              type: "struct"
+              type: 'struct'
             }
-          },
-          beforeCursor: "SELECT ta.* FROM testTable t, t.testArray ta WHERE ta.",
-          afterCursor: "",
-          expectedSuggestions: ["items", "fieldA", "fieldB"]
+          }],
+          beforeCursor: 'SELECT ta.* FROM testTable t, t.testArray ta WHERE ta.',
+          afterCursor: '',
+          expectedSuggestions: ['items', 'fieldA', 'fieldB']
         });
       });
 
 
-      it("should suggest columns from table refs in from clause", function() {
+      it('should suggest columns from table refs in from clause', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT t.*  FROM testTable t, t.",
-          afterCursor: "",
-          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT t.*  FROM testTable t, t.',
+          afterCursor: '',
+          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest map references in select", function() {
+      it('should suggest map references in select', function () {
         assertAutoComplete({
-          serverResponses: {},
-          beforeCursor: "SELECT ",
-          afterCursor: " FROM testTable t, t.testMap tm;",
-          expectedSuggestions: ["t.", "tm."]
+          type: 'impala',
+          serverResponses: [],
+          beforeCursor: 'SELECT ',
+          afterCursor: ' FROM testTable t, t.testMap tm;',
+          expectedSuggestions: ['t.', 'tm.']
         });
       });
 
-      it("should suggest fields with key and value in where clause from map values of type structs", function() {
+      it('should suggest fields with key and value in where clause from map values of type structs', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
-              type: "map"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
+            response: {
+              status: 0,
+              type: 'map'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldA" },
-                {"type": "string", "name": "fieldB" }
+                {type: 'string', name: 'fieldA'},
+                {type: 'string', name: 'fieldB'}
               ],
-              type: "struct"
+              type: 'struct'
             }
-          },
-          beforeCursor: "SELECT tm.* FROM testTable t, t.testMap tm WHERE tm.",
-          afterCursor: "",
-          expectedSuggestions: ["key", "value", "fieldA", "fieldB"]
+          }],
+          beforeCursor: 'SELECT tm.* FROM testTable t, t.testMap tm WHERE tm.',
+          afterCursor: '',
+          expectedSuggestions: ['key', 'value', 'fieldA', 'fieldB']
         });
       });
 
-      it("should suggest fields in where clause from map values of type structs", function() {
+      it('should suggest fields in where clause from map values of type structs', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
-              type: "map"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testMap/value" : {
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
+            response: {
+              status: 0,
+              type: 'map'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value/,
+            response: {
+              status: 0,
               fields: [
-                {"type": "string", "name": "fieldA" },
-                {"type": "string", "name": "fieldB" }
+                {type: 'string', name: 'fieldA'},
+                {type: 'string', name: 'fieldB'}
               ],
-              type: "struct"
+              type: 'struct'
             }
-          },
-          beforeCursor: "SELECT tm.* FROM testTable t, t.testMap tm WHERE tm.value.",
-          afterCursor: "",
-          expectedSuggestions: ["fieldA", "fieldB"]
+          }],
+          beforeCursor: 'SELECT tm.* FROM testTable t, t.testMap tm WHERE tm.value.',
+          afterCursor: '',
+          expectedSuggestions: ['fieldA', 'fieldB']
         });
       });
 
-      it("should suggest values for map keys", function() {
+      it('should suggest values for map keys', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testMap/key" : {
-              sample: ["value1", "value2"],
-              type: "string"
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/key/,
+            response: {
+              status: 0,
+              sample: ['value1', 'value2'],
+              type: 'string'
             }
-          },
-          beforeCursor: "SELECT * FROM testTable t, t.testMap tm WHERE tm.key =",
-          afterCursor: "",
-          expectedSuggestions: ["t.", "tm.", "'value1'", "'value2'"]
+          }],
+          beforeCursor: 'SELECT * FROM testTable t, t.testMap tm WHERE tm.key =',
+          afterCursor: '',
+          expectedSuggestions: ['t.', 'tm.', '\'value1\'', '\'value2\'']
         });
       });
 
-      it("should suggest values for columns in conditions", function() {
+      it('should suggest values for columns in conditions', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/id" : {
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/id/,
+            response: {
+              status: 0,
               sample: [1, 2, 3],
-              type: "int"
-            },
-            "/notebook/api/sample/database_one/testTable/id": {
+              type: 'int'
+            }
+          }, {
+            url: /.*\/notebook\/api\/sample\/database_one\/testTable\/id/,
+            response: {
               status: 0,
               headers: [],
               rows: []
             }
-          },
-          beforeCursor: "SELECT * FROM testTable WHERE id =",
-          afterCursor: "",
-          expectedSuggestions: ["testTable", "1", "2", "3"]
+          }],
+          beforeCursor: 'SELECT * FROM testTable WHERE id =',
+          afterCursor: '',
+          expectedSuggestions: ['testTable', '1', '2', '3']
         });
       });
 
-      it("should suggest values from fields in map values in conditions", function() {
+      it('should suggest values from fields in map values in conditions', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable/testMap" : {
-              type: "map"
-            },
-            "/notebook/api/autocomplete/database_one/testTable/testMap/value/field" : {
+          type: 'impala',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap/,
+            response: {
+              status: 0,
+              type: 'map'
+            }
+          }, {
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/testMap\/value\/field/,
+            response: {
+              status: 0,
               sample: [1, 2, 3],
-              type: "int"
+              type: 'int'
             }
-          },
-          beforeCursor: "SELECT * FROM testTable t, t.testMap m WHERE m.field = ",
-          afterCursor: "",
-          expectedSuggestions: ["t.", "m.", "1", "2", "3"]
+          }],
+          beforeCursor: 'SELECT * FROM testTable t, t.testMap m WHERE m.field = ',
+          afterCursor: '',
+          expectedSuggestions: ['t.', 'm.', '1', '2', '3']
         });
       })
     });
 
-    describe("value completion", function () {
-      it("should suggest numeric sample values for columns in conditions", function() {
+    describe('value completion', function () {
+      it('should suggest numeric sample values for columns in conditions', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/sample/database_one/testTable/id": {
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/sample\/database_one\/testTable\/id/,
+            response: {
               status: 0,
               headers: ['id'],
               rows: [[1], [2], [3]]
             }
-          },
-          beforeCursor: "SELECT * FROM testTable WHERE id =",
-          afterCursor: "",
+          }],
+          beforeCursor: 'SELECT * FROM testTable WHERE id =',
+          afterCursor: '',
           expectedSuggestions: ['1', '2', '3']
         });
       });
 
-      it("should suggest string sample values for columns in conditions with started value", function() {
+      it('should suggest string sample values for columns in conditions with started value', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/sample/database_one/testTable/string": {
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/sample\/database_one\/testTable\/string/,
+            response: {
               status: 0,
               headers: ['id'],
               rows: [['abc'], ['def'], ['ghi']]
             }
-          },
-          beforeCursor: "SELECT * FROM testTable WHERE string = 'd",
-          afterCursor: "",
-          expectedSuggestions: ["'abc'", "'def'", "'ghi'"]
+          }],
+          beforeCursor: 'SELECT * FROM testTable WHERE string = \'d',
+          afterCursor: '',
+          expectedSuggestions: ['\'abc\'', '\'def\'', '\'ghi\'']
         });
       });
 
-      it("should suggest string sample values for columns in conditions with started value after AND", function() {
+      it('should suggest string sample values for columns in conditions with started value after AND', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/sample/database_one/testTable/string": {
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/sample\/database_one\/testTable\/string/,
+            response: {
               status: 0,
               headers: ['id'],
               rows: [['abc'], ['def'], ['ghi']]
             }
-          },
-          beforeCursor: "SELECT * FROM testTable WHERE id = 1 AND string =",
-          afterCursor: "",
-          expectedSuggestions: ["'abc'", "'def'", "'ghi'"]
+          }],
+          beforeCursor: 'SELECT * FROM testTable WHERE id = 1 AND string =',
+          afterCursor: '',
+          expectedSuggestions: ['\'abc\'', '\'def\'', '\'ghi\'']
         });
       });
 
-      it("should suggest string sample values for columns in conditions with started value after OR", function() {
+      it('should suggest string sample values for columns in conditions with started value after OR', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/sample/database_one/testTable/string": {
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/sample\/database_one\/testTable\/string/,
+            response: {
               status: 0,
               headers: ['id'],
               rows: [['ab'], ['cd'], ['ef']]
             }
-          },
-          beforeCursor: "SELECT * FROM testTable WHERE id = 1 OR string =",
-          afterCursor: "",
-          expectedSuggestions: ["'ab'", "'cd'", "'ef'"]
+          }],
+          beforeCursor: 'SELECT * FROM testTable WHERE id = 1 OR string =',
+          afterCursor: '',
+          expectedSuggestions: ['\'ab\'', '\'cd\'', '\'ef\'']
         });
       });
     });
 
-    describe("field completion", function() {
-
-      it("should suggest columns for table", function() {
+    describe('field completion', function () {
+      it('should suggest columns for table', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT ",
-          afterCursor: " FROM testTable",
-          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT ',
+          afterCursor: ' FROM testTable',
+          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns for tables with where keyword in name", function() {
+      it('should suggest columns for tables with where keyword in name', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testwhere" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testwhere/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT ",
-          afterCursor: " FROM testwhere",
-          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT ',
+          afterCursor: ' FROM testwhere',
+          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns for tables with on keyword in name", function() {
+      it('should suggest columns for tables with on keyword in name', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/teston" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/teston/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT ",
-          afterCursor: " FROM teston",
-          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT ',
+          afterCursor: ' FROM teston',
+          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns for table with database prefix", function() {
+      it('should suggest columns for table with database prefix', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_two/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_two\/testTable/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT ",
-          afterCursor: " FROM database_two.testTable",
-          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT ',
+          afterCursor: ' FROM database_two.testTable',
+          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns for table after WHERE", function() {
+      it('should suggest columns for table after WHERE', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT * FROM testTable WHERE ",
-          afterCursor: "",
-          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT * FROM testTable WHERE ',
+          afterCursor: '',
+          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns for table after ORDER BY ", function() {
+      it('should suggest columns for table after ORDER BY ', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT * FROM testTable ORDER BY ",
-          afterCursor: "",
-          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT * FROM testTable ORDER BY ',
+          afterCursor: '',
+          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns for table after ORDER BY with db reference", function() {
+      it('should suggest columns for table after ORDER BY with db reference', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_two/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_two\/testTable/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT * FROM database_two.testTable ORDER BY ",
-          afterCursor: "",
-          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT * FROM database_two.testTable ORDER BY ',
+          afterCursor: '',
+          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns for table after GROUP BY ", function() {
+      it('should suggest columns for table after GROUP BY ', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT * FROM testTable GROUP BY ",
-          afterCursor: "",
-          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT * FROM testTable GROUP BY ',
+          afterCursor: '',
+          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns for table after ON ", function() {
+      it('should suggest columns for table after ON ', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable1" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable1/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT t1.testTableColumn1, t2.testTableColumn3 FROM testTable1 t1 JOIN testTable2 t2 ON t1.",
-          afterCursor: "",
-          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT t1.testTableColumn1, t2.testTableColumn3 FROM testTable1 t1 JOIN testTable2 t2 ON t1.',
+          afterCursor: '',
+          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns for table after ON with database reference", function() {
+      it('should suggest columns for table after ON with database reference', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_two/testTable1" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_two\/testTable1/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT t1.testTableColumn1, t2.testTableColumn3 FROM database_two.testTable1 t1 JOIN testTable2 t2 ON t1.",
-          afterCursor: "",
-          expectedSuggestions: ["testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT t1.testTableColumn1, t2.testTableColumn3 FROM database_two.testTable1 t1 JOIN testTable2 t2 ON t1.',
+          afterCursor: '',
+          expectedSuggestions: ['testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns for table with table ref", function() {
+      it('should suggest columns for table with table ref', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT testTable.",
-          afterCursor: " FROM testTable",
-          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT testTable.',
+          afterCursor: ' FROM testTable',
+          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns with table alias", function() {
+      it('should suggest columns with table alias', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT tt.",
-          afterCursor: " FROM testTable tt",
-          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT tt.',
+          afterCursor: ' FROM testTable tt',
+          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns with table alias from database reference", function() {
+      it('should suggest columns with table alias from database reference', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_two/testTable" : {
-              columns: ["testTableColumn1", "testTableColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_two\/testTable/,
+            response: {
+              status: 0,
+              columns: ['testTableColumn1', 'testTableColumn2']
             }
-          },
-          beforeCursor: "SELECT tt.",
-          afterCursor: " FROM database_two.testTable tt",
-          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+          }],
+          beforeCursor: 'SELECT tt.',
+          afterCursor: ' FROM database_two.testTable tt',
+          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
         });
       });
 
-      it("should suggest columns with multiple table aliases", function() {
-        var serverResponses = {
-          "/notebook/api/autocomplete/database_one/testTableA": {
-            columns: ["testTableColumn1", "testTableColumn2"]
-          },
-          "/notebook/api/autocomplete/database_one/testTableB": {
-            columns: ["testTableColumn3", "testTableColumn4"]
+      it('should suggest columns with multiple table aliases', function () {
+        var serverResponses = [{
+          url: /.*\/notebook\/api\/autocomplete\/database_one\/testTableA/,
+          response: {
+            status: 0,
+            columns: ['testTableColumn1', 'testTableColumn2']
+          }
+        }, {
+          url: /.*\/notebook\/api\/autocomplete\/database_one\/testTableB/,
+          response: {
+            status: 0,
+            columns: ['testTableColumn3', 'testTableColumn4']
           }
-        };
+        }];
         assertAutoComplete({
+          type: 'hive',
           serverResponses: serverResponses,
-          beforeCursor: "SELECT tta.",
-          afterCursor: " FROM testTableA tta, testTableB ttb",
-          expectedSuggestions: ["*", "testTableColumn1", "testTableColumn2"]
+          beforeCursor: 'SELECT tta.',
+          afterCursor: ' FROM testTableA tta, testTableB ttb',
+          expectedSuggestions: ['*', 'testTableColumn1', 'testTableColumn2']
         });
         assertAutoComplete({
+          type: 'hive',
           serverResponses: serverResponses,
-          beforeCursor: "SELECT ttb.",
-          afterCursor: " FROM testTableA tta, testTableB ttb",
-          expectedSuggestions: ["*", "testTableColumn3", "testTableColumn4"]
+          beforeCursor: 'SELECT ttb.',
+          afterCursor: ' FROM testTableA tta, testTableB ttb',
+          expectedSuggestions: ['*', 'testTableColumn3', 'testTableColumn4']
         });
       });
 
-      describe("struct completion", function() {
-        it("should suggest fields from columns that are structs", function() {
+      describe('struct completion', function () {
+        it('should suggest fields from columns that are structs', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable/columnA" : {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/columnA/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldA" },
-                  {"type": "boolean", "name": "fieldB" },
-                  {"type": "struct",  "name": "fieldC", "fields": [
-                    {"type": "string", "name": "fieldC_A" },
-                    {"type": "boolean", "name": "fieldC_B"}
-                  ]}
+                  {type: 'string', name: 'fieldA'},
+                  {type: 'boolean', name: 'fieldB'},
+                  {
+                    type: 'struct', name: 'fieldC', 'fields': [
+                    {type: 'string', name: 'fieldC_A'},
+                    {type: 'boolean', name: 'fieldC_B'}
+                  ]
+                  }
                 ],
-                "type": "struct",
-                "name": "columnB"
+                type: 'struct',
+                name: 'columnB'
               }
-            },
-            beforeCursor: "SELECT columnA.",
-            afterCursor: " FROM testTable",
-            expectedSuggestions: ["fieldA", "fieldB", "fieldC"]
+            }],
+            beforeCursor: 'SELECT columnA.',
+            afterCursor: ' FROM testTable',
+            expectedSuggestions: ['fieldA', 'fieldB', 'fieldC']
           });
         });
 
-        it("should suggest fields from nested structs", function() {
+        it('should suggest fields from nested structs', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_one/testTable/columnA/fieldC" : {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable\/columnA\/fieldC/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldC_A" },
-                  {"type": "boolean", "name": "fieldC_B"}
+                  {type: 'string', name: 'fieldC_A'},
+                  {type: 'boolean', name: 'fieldC_B'}
                 ],
-                "type": "struct",
-                "name": "fieldC"
+                type: 'struct',
+                name: 'fieldC'
               }
-            },
-            beforeCursor: "SELECT columnA.fieldC.",
-            afterCursor: " FROM testTable",
-            expectedSuggestions: ["fieldC_A", "fieldC_B"]
+            }],
+            beforeCursor: 'SELECT columnA.fieldC.',
+            afterCursor: ' FROM testTable',
+            expectedSuggestions: ['fieldC_A', 'fieldC_B']
           });
         });
 
-        it("should suggest fields from nested structs with database reference", function() {
+        it('should suggest fields from nested structs with database reference', function () {
           assertAutoComplete({
-            serverResponses: {
-              "/notebook/api/autocomplete/database_two/testTable/columnA/fieldC" : {
+            type: 'hive',
+            serverResponses: [{
+              url: /.*\/notebook\/api\/autocomplete\/database_two\/testTable\/columnA\/fieldC/,
+              response: {
+                status: 0,
                 fields: [
-                  {"type": "string", "name": "fieldC_A" },
-                  {"type": "boolean", "name": "fieldC_B"}
+                  {'type': 'string', 'name': 'fieldC_A'},
+                  {'type': 'boolean', 'name': 'fieldC_B'}
                 ],
-                "type": "struct",
-                "name": "fieldC"
+                type: 'struct',
+                name: 'fieldC'
               }
-            },
-            beforeCursor: "SELECT columnA.fieldC.",
-            afterCursor: " FROM database_two.testTable",
-            expectedSuggestions: ["fieldC_A", "fieldC_B"]
+            }],
+            beforeCursor: 'SELECT columnA.fieldC.',
+            afterCursor: ' FROM database_two.testTable',
+            expectedSuggestions: ['fieldC_A', 'fieldC_B']
           });
         });
       });
     });
 
-    describe("joins", function() {
-
-      it("should suggest tables to join with", function() {
+    describe('joins', function () {
+      it('should suggest tables to join with', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one" : {
-              tables_meta: [{ name: "testTable1" }, { name: "testTable2" }]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one/,
+            response: {
+              status: 0,
+              tables_meta: [{name: 'testTable1'}, {name: 'testTable2'}]
             }
-          },
-          beforeCursor: "SELECT * FROM testTable1 JOIN ",
-          afterCursor: "",
-          expectedSuggestions: ["testTable1", "testTable2", "database_one.", "database_two."]
+          }],
+          beforeCursor: 'SELECT * FROM testTable1 JOIN ',
+          afterCursor: '',
+          expectedSuggestions: ['testTable1', 'testTable2', 'database_one.', 'database_two.']
         });
       });
 
-      it("should suggest table references in join condition if not already there", function() {
+      it('should suggest table references in join condition if not already there', function () {
         assertAutoComplete({
-          serverResponses: {},
-          beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (",
-          afterCursor: "",
-          expectedSuggestions: ["testTable1.", "testTable2."]
+          type: 'hive',
+          serverResponses: [],
+          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (',
+          afterCursor: '',
+          expectedSuggestions: ['testTable1.', 'testTable2.']
         });
       });
 
-      it("should suggest table references in join condition if not already there for multiple conditions", function() {
+      it('should suggest table references in join condition if not already there for multiple conditions', function () {
         assertAutoComplete({
-          serverResponses: {},
-          beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND ",
-          afterCursor: "",
-          expectedSuggestions: ["testTable1.", "testTable2."]
+          type: 'hive',
+          serverResponses: [],
+          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND ',
+          afterCursor: '',
+          expectedSuggestions: ['testTable1.', 'testTable2.']
         });
       });
 
-      it("should suggest table references in join condition if not already there for multiple conditions", function() {
+      it('should suggest table references in join condition if not already there for multiple conditions', function () {
         assertAutoComplete({
-          serverResponses: {},
-          beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (",
-          afterCursor: " AND testTable1.testColumn1 = testTable2.testColumn3",
-          expectedSuggestions: ["testTable1.", "testTable2."]
+          type: 'hive',
+          serverResponses: [],
+          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (',
+          afterCursor: ' AND testTable1.testColumn1 = testTable2.testColumn3',
+          expectedSuggestions: ['testTable1.', 'testTable2.']
         });
       });
 
-      it("should suggest field references in join condition if table reference is present", function() {
+      it('should suggest field references in join condition if table reference is present', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable2" : {
-              columns: ["testColumn3", "testColumn4"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable2/,
+            response: {
+              status: 0,
+              columns: ['testColumn3', 'testColumn4']
             }
-          },
-          beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable2.",
-          afterCursor: "",
-          expectedSuggestions: ["testColumn3", "testColumn4"]
+          }],
+          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable2.',
+          afterCursor: '',
+          expectedSuggestions: ['testColumn3', 'testColumn4']
         });
       });
 
-      it("should suggest field references in join condition if table reference is present from multiple tables", function() {
+      it('should suggest field references in join condition if table reference is present from multiple tables', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable2" : {
-              columns: ["testColumn3", "testColumn4"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable2/,
+            response: {
+              status: 0,
+              columns: ['testColumn3', 'testColumn4']
             }
-          },
-          beforeCursor: "select * from testTable1 JOIN testTable2 on (testTable1.testColumn1 = testTable2.",
-          afterCursor: "",
-          expectedSuggestions: ["testColumn3", "testColumn4"]
+          }],
+          beforeCursor: 'select * from testTable1 JOIN testTable2 on (testTable1.testColumn1 = testTable2.',
+          afterCursor: '',
+          expectedSuggestions: ['testColumn3', 'testColumn4']
         });
       });
 
-      it("should suggest field references in join condition if table reference is present from multiple tables for multiple conditions", function() {
+      it('should suggest field references in join condition if table reference is present from multiple tables for multiple conditions', function () {
         assertAutoComplete({
-          serverResponses: {
-            "/notebook/api/autocomplete/database_one/testTable1" : {
-              columns: ["testColumn1", "testColumn2"]
+          type: 'hive',
+          serverResponses: [{
+            url: /.*\/notebook\/api\/autocomplete\/database_one\/testTable1/,
+            response: {
+              status: 0,
+              columns: ['testColumn1', 'testColumn2']
             }
-          },
-          beforeCursor: "SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND testTable1.",
-          afterCursor: "",
-          expectedSuggestions: ["testColumn1", "testColumn2"]
+          }],
+          beforeCursor: 'SELECT testTable1.* FROM testTable1 JOIN testTable2 ON (testTable1.testColumn1 = testTable2.testColumn3 AND testTable1.',
+          afterCursor: '',
+          expectedSuggestions: ['testColumn1', 'testColumn2']
         });
       });
     })

+ 7 - 3
desktop/core/src/desktop/templates/common_jasmine.mako

@@ -44,14 +44,18 @@
       paths: {
         'jasmine': 'desktop/ext/js/jasmine-2.3.4/jasmine',
         'jasmine-html': 'desktop/ext/js/jasmine-2.3.4/jasmine-html',
-        'jasmine-boot': 'desktop/ext/js/jasmine-2.3.4/boot'
+        'jasmine-boot': 'desktop/ext/js/jasmine-2.3.4/boot',
+        'mock-ajax':  'desktop/ext/js/jasmine-2.3.4/mock-ajax'
       },
       shim: {
         'jasmine-html': {
-          deps : ['jasmine']
+          deps: ['jasmine']
         },
         'jasmine-boot': {
-          deps : ['jasmine', 'jasmine-html']
+          deps: ['jasmine', 'jasmine-html']
+        },
+        'mock-ajax': {
+          deps: ['jasmine-boot']
         }
       }
     })

+ 1 - 1
desktop/core/src/desktop/templates/jasmine.mako

@@ -19,7 +19,7 @@
 
 <%block name="specs">
   <script type="text/javascript" charset="utf-8">
-  require(['jasmine-boot', 'jasmine'], function () {
+  require(['jasmine-boot', 'jasmine', 'mock-ajax'], function () {
     // Add specs below
     require([
       'desktop/spec/sqlAutocompleterSpec',