You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2017/02/20 18:03:19 UTC

[1/3] fauxton commit: updated refs/heads/master to 367d422

Repository: couchdb-fauxton
Updated Branches:
  refs/heads/master 787280b08 -> 367d42268


don't encode username/pass unless necessary


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/a6af114a
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/a6af114a
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/a6af114a

Branch: refs/heads/master
Commit: a6af114a466a1d68c84822b2cb0f9fd7bc42993a
Parents: 787280b
Author: Nolan Lawson <no...@gmail.com>
Authored: Sat Feb 18 08:01:31 2017 -0800
Committer: Garren Smith <ga...@gmail.com>
Committed: Mon Feb 20 20:02:48 2017 +0200

----------------------------------------------------------------------
 app/addons/replication/api.js | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a6af114a/app/addons/replication/api.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js
index e5ccce0..7d5b40b 100644
--- a/app/addons/replication/api.js
+++ b/app/addons/replication/api.js
@@ -40,6 +40,9 @@ export const getUsername = () => {
 };
 
 export const getAuthHeaders = (username, password) => {
+  if (!username || !password) {
+    return {};
+  }
   return {
     'Authorization': 'Basic ' + base64.encode(username + ':' + password)
   };
@@ -61,10 +64,17 @@ export const getTarget = ({replicationTarget, localTarget, remoteTarget, replica
   const encodedLocalTarget = encodeURIComponent(localTarget);
   const headers = getAuthHeaders(username, password);
 
+  const {
+    origin,
+    port,
+    protocol,
+    hostname
+  } = window.location;
+
   if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE) {
     target = {
       headers: headers,
-      url: `${window.location.origin}/${encodedLocalTarget}`
+      url: `${origin}/${encodedLocalTarget}`
     };
   } else if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
 
@@ -72,11 +82,12 @@ export const getTarget = ({replicationTarget, localTarget, remoteTarget, replica
     if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) {
       target = {
         headers: headers,
-        url: `${window.location.origin}/${encodedLocalTarget}`
+        url: `${origin}/${encodedLocalTarget}`
       };
     } else {
-      const port = window.location.port === '' ? '' : ':' + window.location.port;
-      target = `${window.location.protocol}//${username}:${password}@${window.location.hostname}${port}/${encodedLocalTarget}`;
+      target = `${protocol}//` +
+        ((username && password) ? `${username}:${password}@` : '') +
+        `${hostname}${port ? `:${port}` : ''}/${encodedLocalTarget}`;
     }
   }
 


[3/3] fauxton commit: updated refs/heads/master to 367d422

Posted by ga...@apache.org.
Improve URL encoding

This improves the way we encode usernames and passwords. So if no
username and password are required it should still work and if the user
has special characters in the url it should still work.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/367d4226
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/367d4226
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/367d4226

Branch: refs/heads/master
Commit: 367d422683a79d19377a9a6d317aaa56b84b9a96
Parents: 8d28518
Author: Garren Smith <ga...@gmail.com>
Authored: Mon Feb 20 17:21:47 2017 +0200
Committer: Garren Smith <ga...@gmail.com>
Committed: Mon Feb 20 20:02:51 2017 +0200

----------------------------------------------------------------------
 app/addons/replication/__tests__/api.tests.js | 40 +++++++++---
 app/addons/replication/api.js                 | 74 +++++++++-------------
 2 files changed, 62 insertions(+), 52 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/367d4226/app/addons/replication/__tests__/api.tests.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/__tests__/api.tests.js b/app/addons/replication/__tests__/api.tests.js
index 73677cf..29fb17e 100644
--- a/app/addons/replication/__tests__/api.tests.js
+++ b/app/addons/replication/__tests__/api.tests.js
@@ -16,7 +16,9 @@ import {
   continuous,
   createTarget,
   addDocIdAndRev,
-  getDocUrl
+  getDocUrl,
+  encodeFullUrl,
+  decodeFullUrl
 } from '../api';
 import Constants from '../constants';
 
@@ -33,7 +35,7 @@ describe('Replication API', () => {
         remoteSource
       });
 
-      assert.deepEqual(source, 'http://remote-couchdb.com/my%2Fdb%2Fhere');
+      assert.deepEqual(source.url, 'http://remote-couchdb.com/my%2Fdb%2Fhere');
     });
 
     it('returns local source with auth info and encoded', () => {
@@ -44,7 +46,7 @@ describe('Replication API', () => {
         localSource,
         username: 'the-user',
         password: 'password'
-      });
+      }, {origin: 'http://dev:6767'});
 
       assert.deepEqual(source.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
       assert.ok(/my%2Fdb/.test(source.url));
@@ -59,7 +61,7 @@ describe('Replication API', () => {
       assert.deepEqual("http://remote-couchdb.com/my%2Fdb", getTarget({
         replicationTarget: Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE,
         remoteTarget: remoteTarget
-      }));
+      }).url);
     });
 
     it('returns existing local database', () => {
@@ -94,10 +96,10 @@ describe('Replication API', () => {
         localTarget: 'my-new/db',
         username: 'the-user',
         password: 'password'
-      });
+      }, {origin: 'http://dev:5555'});
 
-      assert.ok(/the-user:password@/.test(target));
-      assert.ok(/my-new%2Fdb/.test(target));
+      assert.deepEqual(target.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
+      assert.ok(/my-new%2Fdb/.test(target.url));
     });
 
     it("doesn't encode username and password if it is not supplied", () => {
@@ -117,7 +119,8 @@ describe('Replication API', () => {
         localTarget: 'my-new/db'
       }, location);
 
-      assert.deepEqual("http://dev:8000/my-new%2Fdb", target);
+      assert.deepEqual("http://dev:8000/my-new%2Fdb", target.url);
+      assert.deepEqual({}, target.headers);
     });
   });
 
@@ -185,4 +188,25 @@ describe('Replication API', () => {
     });
   });
 
+  describe("encodeFullUrl", () => {
+    it("encodes db correctly", () => {
+      const url = "http://dev:5984/boom/aaaa";
+      const encodedUrl = encodeFullUrl(url);
+
+      assert.deepEqual("http://dev:5984/boom%2Faaaa", encodedUrl);
+    });
+
+  });
+
+  describe("decodeFullUrl", () => {
+
+    it("encodes db correctly", () => {
+      const url = "http://dev:5984/boom%2Faaaa";
+      const encodedUrl = decodeFullUrl(url);
+
+      assert.deepEqual("http://dev:5984/boom/aaaa", encodedUrl);
+    });
+
+  });
+
 });

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/367d4226/app/addons/replication/api.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js
index 1892956..327d35d 100644
--- a/app/addons/replication/api.js
+++ b/app/addons/replication/api.js
@@ -19,19 +19,12 @@ import _ from 'lodash';
 export const encodeFullUrl = (fullUrl) => {
   if (!fullUrl) {return '';}
   const url = new URL(fullUrl);
-  if (url.username && url.password) {
-    return `${url.protocol}//${url.username}:${url.password}@${url.hostname}/${encodeURIComponent(url.pathname.slice(1))}`;
-  }
   return `${url.origin}/${encodeURIComponent(url.pathname.slice(1))}`;
 };
 
 export const decodeFullUrl = (fullUrl) => {
   if (!fullUrl) {return '';}
   const url = new URL(fullUrl);
-  if (url.username && url.password) {
-    return `${url.protocol}//${url.username}:${url.password}@${url.hostname}/${decodeURIComponent(url.pathname.slice(1))}`;
-  }
-
   return `${url.origin}/${decodeURIComponent(url.pathname.slice(1))}`;
 };
 
@@ -48,15 +41,23 @@ export const getAuthHeaders = (username, password) => {
   };
 };
 
-export const getSource = ({replicationSource, localSource, remoteSource, username, password}) => {
+export const getSource = ({
+  replicationSource,
+  localSource,
+  remoteSource,
+  username,
+  password
+},
+{origin} = window.location) => {
+  let url = remoteSource;
   if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) {
-    return {
-      headers: getAuthHeaders(username, password),
-      url: `${window.location.origin}/${encodeURIComponent(localSource)}`
-    };
-  } else {
-    return encodeFullUrl(remoteSource);
+    url = `${origin}/${localSource}`;
   }
+
+  return {
+    headers: getAuthHeaders(username, password),
+    url: encodeFullUrl(url)
+  };
 };
 
 export const getTarget = ({
@@ -67,40 +68,25 @@ export const getTarget = ({
   username,
   password
 },
-location = window.location //this allows us to mock out window.location for our tests
+{origin} = window.location //this allows us to mock out window.location for our tests
 ) => {
-  let target = encodeFullUrl(remoteTarget);
+
   const encodedLocalTarget = encodeURIComponent(localTarget);
-  const headers = getAuthHeaders(username, password);
-
-  const {
-    origin,
-    port,
-    protocol,
-    hostname
-  } = location;
-
-  if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE) {
-    target = {
-      headers: headers,
-      url: `${origin}/${encodedLocalTarget}`
-    };
-  } else if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
-
-    // check to see if we really need to send headers here or can just do the ELSE clause in all scenarioe
-    if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) {
-      target = {
-        headers: headers,
-        url: `${origin}/${encodedLocalTarget}`
-      };
-    } else {
-      target = `${protocol}//` +
-        ((username && password) ? `${username}:${password}@` : '') +
-        `${hostname}${port ? `:${port}` : ''}/${encodedLocalTarget}`;
-    }
+  let headers = getAuthHeaders(username, password);
+  let target = `${origin}/${encodedLocalTarget}`;
+
+  if (replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
+        replicationTarget === Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE) {
+
+    const targetUrl = new URL(remoteTarget);
+    target = encodeFullUrl(remoteTarget);
+    headers = getAuthHeaders(targetUrl.username, targetUrl.password);
   }
 
-  return target;
+  return {
+    headers: headers,
+    url: target
+  };
 };
 
 export const createTarget = (replicationTarget) => {


[2/3] fauxton commit: updated refs/heads/master to 367d422

Posted by ga...@apache.org.
Add test for replication without password

This extends Nolan's work and adds a test to prove its working


Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/8d28518a
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/8d28518a
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/8d28518a

Branch: refs/heads/master
Commit: 8d28518a4c166ceb597239890c2fcc2d3d81b628
Parents: a6af114
Author: Garren Smith <ga...@gmail.com>
Authored: Mon Feb 20 13:52:35 2017 +0200
Committer: Garren Smith <ga...@gmail.com>
Committed: Mon Feb 20 20:02:49 2017 +0200

----------------------------------------------------------------------
 app/addons/replication/__tests__/api.tests.js | 188 +++++++++++++++++++++
 app/addons/replication/actiontypes.js         |  40 +++--
 app/addons/replication/api.js                 |  13 +-
 app/addons/replication/constants.js           |  35 ++--
 app/addons/replication/tests/apiSpec.js       | 168 ------------------
 5 files changed, 234 insertions(+), 210 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/8d28518a/app/addons/replication/__tests__/api.tests.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/__tests__/api.tests.js b/app/addons/replication/__tests__/api.tests.js
new file mode 100644
index 0000000..73677cf
--- /dev/null
+++ b/app/addons/replication/__tests__/api.tests.js
@@ -0,0 +1,188 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+import utils from '../../../../test/mocha/testUtils';
+import {
+  getSource,
+  getTarget,
+  continuous,
+  createTarget,
+  addDocIdAndRev,
+  getDocUrl
+} from '../api';
+import Constants from '../constants';
+
+const assert = utils.assert;
+
+describe('Replication API', () => {
+
+  describe('getSource', () => {
+
+    it('encodes remote db', () => {
+      const remoteSource = 'http://remote-couchdb.com/my/db/here';
+      const source = getSource({
+        replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
+        remoteSource
+      });
+
+      assert.deepEqual(source, 'http://remote-couchdb.com/my%2Fdb%2Fhere');
+    });
+
+    it('returns local source with auth info and encoded', () => {
+      const localSource = 'my/db';
+
+      const source = getSource({
+        replicationSource: Constants.REPLICATION_SOURCE.LOCAL,
+        localSource,
+        username: 'the-user',
+        password: 'password'
+      });
+
+      assert.deepEqual(source.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
+      assert.ok(/my%2Fdb/.test(source.url));
+    });
+  });
+
+  describe('getTarget', () => {
+
+    it('returns remote encoded target', () => {
+      const remoteTarget = 'http://remote-couchdb.com/my/db';
+
+      assert.deepEqual("http://remote-couchdb.com/my%2Fdb", getTarget({
+        replicationTarget: Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE,
+        remoteTarget: remoteTarget
+      }));
+    });
+
+    it('returns existing local database', () => {
+      const target = getTarget({
+        replicationTarget: Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE,
+        localTarget: 'my-existing/db',
+        username: 'the-user',
+        password: 'password'
+      });
+
+      assert.deepEqual(target.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
+      assert.ok(/my-existing%2Fdb/.test(target.url));
+    });
+
+    it('returns new local database', () => {
+      const target = getTarget({
+        replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
+        replicationSource: Constants.REPLICATION_SOURCE.LOCAL,
+        localTarget: 'my-new/db',
+        username: 'the-user',
+        password: 'password'
+      });
+
+      assert.deepEqual(target.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
+      assert.ok(/my-new%2Fdb/.test(target.url));
+    });
+
+    it('returns new local for remote source', () => {
+      const target = getTarget({
+        replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
+        replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
+        localTarget: 'my-new/db',
+        username: 'the-user',
+        password: 'password'
+      });
+
+      assert.ok(/the-user:password@/.test(target));
+      assert.ok(/my-new%2Fdb/.test(target));
+    });
+
+    it("doesn't encode username and password if it is not supplied", () => {
+      const location = {
+        host: "dev:8000",
+        hostname: "dev",
+        href: "http://dev:8000/#database/animaldb/_all_docs",
+        origin: "http://dev:8000",
+        pathname: "/",
+        port: "8000",
+        protocol: "http:",
+      };
+
+      const target = getTarget({
+        replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
+        replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
+        localTarget: 'my-new/db'
+      }, location);
+
+      assert.deepEqual("http://dev:8000/my-new%2Fdb", target);
+    });
+  });
+
+  describe('continuous', () => {
+
+    it('returns true for continuous', () => {
+      assert.ok(continuous(Constants.REPLICATION_TYPE.CONTINUOUS));
+    });
+
+    it('returns false for once', () => {
+      assert.notOk(continuous(Constants.REPLICATION_TYPE.ONE_TIME));
+    });
+  });
+
+  describe('create target', () => {
+
+    it('returns true for new local', () => {
+      assert.ok(createTarget(Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE));
+    });
+
+    it('returns true for new remote', () => {
+      assert.ok(createTarget(Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE));
+    });
+
+    it('returns false for existing', () => {
+      assert.notOk(createTarget(Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE));
+    });
+
+  });
+
+  describe('addDocId', () => {
+
+    it('adds doc id if it exists', () => {
+      const docId = 'docId';
+
+      assert.deepEqual(
+        addDocIdAndRev(docId, null,  {}), {
+          _id: docId
+        });
+    });
+
+    it('adds doc and Rev if it exists', () => {
+      const docId = 'docId';
+      const _rev = "1-rev123";
+
+      assert.deepEqual(
+        addDocIdAndRev(docId, _rev, {}), {
+          _id: docId,
+          _rev: _rev
+        });
+    });
+
+    it('does not add doc id if it does not exists', () => {
+      assert.deepEqual(
+        addDocIdAndRev(null, null, {}), {});
+    });
+  });
+
+  describe("getDocUrl", () => {
+    it("scrubs passwords and decodes", () => {
+      const url = "http://userone:theirpassword@couchdb-host.com/my%2Fdb%2fhere";
+      const cleanedUrl = "http://couchdb-host.com/my/db/here";
+
+      assert.deepEqual(getDocUrl(url), cleanedUrl);
+    });
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/8d28518a/app/addons/replication/actiontypes.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/actiontypes.js b/app/addons/replication/actiontypes.js
index fbe389d..f2bb478 100644
--- a/app/addons/replication/actiontypes.js
+++ b/app/addons/replication/actiontypes.js
@@ -10,24 +10,22 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([], function () {
-  return {
-    INIT_REPLICATION: 'INIT_REPLICATION',
-    CHANGE_REPLICATION_SOURCE: 'CHANGE_REPLICATION_SOURCE',
-    REPLICATION_DATABASES_LOADED: 'REPLICATION_DATABASES_LOADED',
-    REPLICATION_UPDATE_FORM_FIELD: 'REPLICATION_UPDATE_FORM_FIELD',
-    REPLICATION_CLEAR_FORM: 'REPLICATION_CLEAR_FORM',
-    REPLICATION_STARTING: 'REPLICATION_STARTING',
-    REPLICATION_STATUS: 'REPLICATION_STATUS',
-    REPLICATION_FETCHING_STATUS: 'REPLICATION_FETCHING_STATUS',
-    REPLICATION_FILTER_DOCS: 'REPLICATION_FILTER_DOCS',
-    REPLICATION_TOGGLE_ALL_DOCS: 'REPLICATION_TOGGLE_ALL_DOCS',
-    REPLICATION_TOGGLE_DOC: 'REPLICATION_TOGGLE_DOC',
-    REPLICATION_DELETE_DOCS: 'REPLICATION_DELETE_DOCS',
-    REPLICATION_SET_STATE_FROM_DOC: 'REPLICATION_SET_STATE_FROM_DOC',
-    REPLICATION_SHOW_CONFLICT_MODAL: 'REPLICATION_SHOW_CONFLICT_MODAL',
-    REPLICATION_HIDE_CONFLICT_MODAL: 'REPLICATION_HIDE_CONFLICT_MODAL',
-    REPLICATION_CHANGE_ACTIVITY_SORT: 'REPLICATION_CHANGE_ACTIVITY_SORT',
-    REPLICATION_CLEAR_SELECTED_DOCS: 'REPLICATION_CLEAR_SELECTED_DOCS'
-  };
-});
+export default {
+  INIT_REPLICATION: 'INIT_REPLICATION',
+  CHANGE_REPLICATION_SOURCE: 'CHANGE_REPLICATION_SOURCE',
+  REPLICATION_DATABASES_LOADED: 'REPLICATION_DATABASES_LOADED',
+  REPLICATION_UPDATE_FORM_FIELD: 'REPLICATION_UPDATE_FORM_FIELD',
+  REPLICATION_CLEAR_FORM: 'REPLICATION_CLEAR_FORM',
+  REPLICATION_STARTING: 'REPLICATION_STARTING',
+  REPLICATION_STATUS: 'REPLICATION_STATUS',
+  REPLICATION_FETCHING_STATUS: 'REPLICATION_FETCHING_STATUS',
+  REPLICATION_FILTER_DOCS: 'REPLICATION_FILTER_DOCS',
+  REPLICATION_TOGGLE_ALL_DOCS: 'REPLICATION_TOGGLE_ALL_DOCS',
+  REPLICATION_TOGGLE_DOC: 'REPLICATION_TOGGLE_DOC',
+  REPLICATION_DELETE_DOCS: 'REPLICATION_DELETE_DOCS',
+  REPLICATION_SET_STATE_FROM_DOC: 'REPLICATION_SET_STATE_FROM_DOC',
+  REPLICATION_SHOW_CONFLICT_MODAL: 'REPLICATION_SHOW_CONFLICT_MODAL',
+  REPLICATION_HIDE_CONFLICT_MODAL: 'REPLICATION_HIDE_CONFLICT_MODAL',
+  REPLICATION_CHANGE_ACTIVITY_SORT: 'REPLICATION_CHANGE_ACTIVITY_SORT',
+  REPLICATION_CLEAR_SELECTED_DOCS: 'REPLICATION_CLEAR_SELECTED_DOCS'
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/8d28518a/app/addons/replication/api.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js
index 7d5b40b..1892956 100644
--- a/app/addons/replication/api.js
+++ b/app/addons/replication/api.js
@@ -59,7 +59,16 @@ export const getSource = ({replicationSource, localSource, remoteSource, usernam
   }
 };
 
-export const getTarget = ({replicationTarget, localTarget, remoteTarget, replicationSource, username, password}) => {
+export const getTarget = ({
+  replicationTarget,
+  localTarget,
+  remoteTarget,
+  replicationSource,
+  username,
+  password
+},
+location = window.location //this allows us to mock out window.location for our tests
+) => {
   let target = encodeFullUrl(remoteTarget);
   const encodedLocalTarget = encodeURIComponent(localTarget);
   const headers = getAuthHeaders(username, password);
@@ -69,7 +78,7 @@ export const getTarget = ({replicationTarget, localTarget, remoteTarget, replica
     port,
     protocol,
     hostname
-  } = window.location;
+  } = location;
 
   if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE) {
     target = {

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/8d28518a/app/addons/replication/constants.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/constants.js b/app/addons/replication/constants.js
index eb5459f..7538f20 100644
--- a/app/addons/replication/constants.js
+++ b/app/addons/replication/constants.js
@@ -10,25 +10,22 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-define([], function () {
 
-  return {
-    REPLICATION_SOURCE: {
-      LOCAL: 'REPLICATION_SOURCE_LOCAL',
-      REMOTE: 'REPLICATION_SOURCE_REMOTE'
-    },
+export default {
+  REPLICATION_SOURCE: {
+    LOCAL: 'REPLICATION_SOURCE_LOCAL',
+    REMOTE: 'REPLICATION_SOURCE_REMOTE'
+  },
 
-    REPLICATION_TARGET: {
-      EXISTING_LOCAL_DATABASE: 'REPLICATION_TARGET_EXISTING_LOCAL_DATABASE',
-      EXISTING_REMOTE_DATABASE: 'REPLICATION_TARGET_EXISTING_REMOTE_DATABASE',
-      NEW_LOCAL_DATABASE: 'REPLICATION_TARGET_NEW_LOCAL_DATABASE',
-      NEW_REMOTE_DATABASE: 'REPLICATION_TARGET_NEW_REMOTE_DATABASE'
-    },
+  REPLICATION_TARGET: {
+    EXISTING_LOCAL_DATABASE: 'REPLICATION_TARGET_EXISTING_LOCAL_DATABASE',
+    EXISTING_REMOTE_DATABASE: 'REPLICATION_TARGET_EXISTING_REMOTE_DATABASE',
+    NEW_LOCAL_DATABASE: 'REPLICATION_TARGET_NEW_LOCAL_DATABASE',
+    NEW_REMOTE_DATABASE: 'REPLICATION_TARGET_NEW_REMOTE_DATABASE'
+  },
 
-    REPLICATION_TYPE: {
-      ONE_TIME: 'REPLICATION_TYPE_ONE_TIME',
-      CONTINUOUS: 'REPLICATION_TYPE_CONTINUOUS'
-    }
-  };
-
-});
+  REPLICATION_TYPE: {
+    ONE_TIME: 'REPLICATION_TYPE_ONE_TIME',
+    CONTINUOUS: 'REPLICATION_TYPE_CONTINUOUS'
+  }
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/8d28518a/app/addons/replication/tests/apiSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/tests/apiSpec.js b/app/addons/replication/tests/apiSpec.js
deleted file mode 100644
index eb13c57..0000000
--- a/app/addons/replication/tests/apiSpec.js
+++ /dev/null
@@ -1,168 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-import utils from '../../../../test/mocha/testUtils';
-import {
-  getSource,
-  getTarget,
-  continuous,
-  createTarget,
-  addDocIdAndRev,
-  getDocUrl
-} from '../api';
-import Constants from '../constants';
-
-const assert = utils.assert;
-
-describe('Replication API', () => {
-
-  describe('getSource', () => {
-
-    it('encodes remote db', () => {
-      const remoteSource = 'http://remote-couchdb.com/my/db/here';
-      const source = getSource({
-        replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
-        remoteSource
-      });
-
-      assert.deepEqual(source, 'http://remote-couchdb.com/my%2Fdb%2Fhere');
-    });
-
-    it('returns local source with auth info and encoded', () => {
-      const localSource = 'my/db';
-
-      const source = getSource({
-        replicationSource: Constants.REPLICATION_SOURCE.LOCAL,
-        localSource,
-        username: 'the-user',
-        password: 'password'
-      });
-
-      assert.deepEqual(source.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
-      assert.ok(/my%2Fdb/.test(source.url));
-    });
-  });
-
-  describe('getTarget', () => {
-
-    it('returns remote encoded target', () => {
-      const remoteTarget = 'http://remote-couchdb.com/my/db';
-
-      assert.deepEqual("http://remote-couchdb.com/my%2Fdb", getTarget({
-        replicationTarget: Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE,
-        remoteTarget: remoteTarget
-      }));
-    });
-
-    it('returns existing local database', () => {
-      const target = getTarget({
-        replicationTarget: Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE,
-        localTarget: 'my-existing/db',
-        username: 'the-user',
-        password: 'password'
-      });
-
-      assert.deepEqual(target.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
-      assert.ok(/my-existing%2Fdb/.test(target.url));
-    });
-
-    it('returns new local database', () => {
-      const target = getTarget({
-        replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
-        replicationSource: Constants.REPLICATION_SOURCE.LOCAL,
-        localTarget: 'my-new/db',
-        username: 'the-user',
-        password: 'password'
-      });
-
-      assert.deepEqual(target.headers, {Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="});
-      assert.ok(/my-new%2Fdb/.test(target.url));
-    });
-
-    it('returns new local for remote source', () => {
-      const target = getTarget({
-        replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
-        replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
-        localTarget: 'my-new/db',
-        username: 'the-user',
-        password: 'password'
-      });
-
-      assert.ok(/the-user:password@/.test(target));
-      assert.ok(/my-new%2Fdb/.test(target));
-    });
-  });
-
-  describe('continuous', () => {
-
-    it('returns true for continuous', () => {
-      assert.ok(continuous(Constants.REPLICATION_TYPE.CONTINUOUS));
-    });
-
-    it('returns false for once', () => {
-      assert.notOk(continuous(Constants.REPLICATION_TYPE.ONE_TIME));
-    });
-  });
-
-  describe('create target', () => {
-
-    it('returns true for new local', () => {
-      assert.ok(createTarget(Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE));
-    });
-
-    it('returns true for new remote', () => {
-      assert.ok(createTarget(Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE));
-    });
-
-    it('returns false for existing', () => {
-      assert.notOk(createTarget(Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE));
-    });
-
-  });
-
-  describe('addDocId', () => {
-
-    it('adds doc id if it exists', () => {
-      const docId = 'docId';
-
-      assert.deepEqual(
-        addDocIdAndRev(docId, null,  {}), {
-          _id: docId
-        });
-    });
-
-    it('adds doc and Rev if it exists', () => {
-      const docId = 'docId';
-      const _rev = "1-rev123";
-
-      assert.deepEqual(
-        addDocIdAndRev(docId, _rev, {}), {
-          _id: docId,
-          _rev: _rev
-        });
-    });
-
-    it('does not add doc id if it does not exists', () => {
-      assert.deepEqual(
-        addDocIdAndRev(null, null, {}), {});
-    });
-  });
-
-  describe("getDocUrl", () => {
-    it("scrubs passwords and decodes", () => {
-      const url = "http://userone:theirpassword@couchdb-host.com/my%2Fdb%2fhere";
-      const cleanedUrl = "http://couchdb-host.com/my/db/here";
-
-      assert.deepEqual(getDocUrl(url), cleanedUrl);
-    });
-  });
-
-});