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 2016/09/21 15:19:15 UTC

[24/29] fauxton commit: updated refs/heads/new-replication to b0541e1

fixes and tests


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

Branch: refs/heads/new-replication
Commit: 22f623c81245e99a19c693ea693c4d8be75c9f92
Parents: bd90f75
Author: Garren Smith <ga...@gmail.com>
Authored: Thu Aug 18 16:20:28 2016 +0200
Committer: Garren Smith <ga...@gmail.com>
Committed: Wed Sep 14 17:22:30 2016 +0200

----------------------------------------------------------------------
 app/addons/replication/api.js                   |   1 +
 app/addons/replication/components/controller.js | 202 +++++++++++++++++++
 app/addons/replication/components/target.js     | 202 -------------------
 app/addons/replication/controller.js            | 138 -------------
 app/addons/replication/tests/apiSpec.js         | 162 +++++++++++++++
 app/addons/replication/tests/controller.js      |  46 +++++
 app/addons/replication/tests/replicationSpec.js |   3 +-
 app/addons/replication/tests/storesSpec.js      |   2 +-
 test/mocha/testUtils.js                         |   1 +
 9 files changed, 415 insertions(+), 342 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22f623c8/app/addons/replication/api.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/api.js b/app/addons/replication/api.js
index 037c53e..10dda1f 100644
--- a/app/addons/replication/api.js
+++ b/app/addons/replication/api.js
@@ -13,6 +13,7 @@
 import Constants from './constants';
 import app from '../../app';
 import base64 from 'base-64';
+import _ from 'lodash';
 
 export const getUsername = () => {
   return app.session.get('userCtx').name;

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22f623c8/app/addons/replication/components/controller.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/components/controller.js b/app/addons/replication/components/controller.js
new file mode 100644
index 0000000..86ed5fd
--- /dev/null
+++ b/app/addons/replication/components/controller.js
@@ -0,0 +1,202 @@
+// 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 React from 'react';
+import Constants from '../constants';
+import Components from '../../components/react-components.react';
+import ReactSelect from 'react-select';
+
+const { StyledSelect } = Components;
+
+const replicationTargetSourceOptions = () => {
+  return [
+    { value: '', label: 'Select target' },
+    { value: Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE, label: 'Existing local database' },
+    { value: Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE, label: 'Existing remote database' },
+    { value: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE, label: 'New local database' },
+    { value: Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE, label: 'New remote database' }
+  ].map((option) => {
+    return (
+      <option value={option.value} key={option.value}>{option.label}</option>
+    );
+  });
+};
+
+const ReplicationTargetSelect = ({value, onChange}) => {
+  return (
+    <div className="replication-section">
+      <div className="replication-input-label">
+        Replication Target:
+      </div>
+      <div className="replication-input-select">
+        <StyledSelect
+          selectContent={replicationTargetSourceOptions()}
+          selectChange={(e) => onChange(e.target.value)}
+          selectId="replication-target"
+          selectValue={value} />
+      </div>
+    </div>
+  );
+};
+
+ReplicationTargetSelect.propTypes = {
+  value: React.PropTypes.string.isRequired,
+  onChange: React.PropTypes.func.isRequired
+};
+
+const RemoteTargetReplicationRow = ({onChange, value}) => {
+  return (
+    <div>
+      <input type="text" className="replication-remote-connection-url" placeholder="https://" value={value}
+        onChange={(e) => onChange(e.target.value)} />
+      <div className="replication-remote-connection-url-text">e.g. https://$REMOTE_USERNAME:$REMOTE_PASSWORD@$REMOTE_SERVER/$DATABASE</div>
+    </div>
+  );
+};
+
+RemoteTargetReplicationRow.propTypes = {
+  value: React.PropTypes.string.isRequired,
+  onChange: React.PropTypes.func.isRequired
+};
+
+const ExistingLocalTargetReplicationRow = ({onChange, value, databases}) => {
+  const options = databases.map(db => ({value: db, label: db}));
+  return (
+    <div className="replication-input-react-select">
+      <ReactSelect
+        value={value}
+        options={options}
+        placeholder="Database name"
+        clearable={false}
+        onChange={({value}) => onChange(value)}
+      />
+    </div>
+  );
+};
+
+ExistingLocalTargetReplicationRow.propTypes = {
+  value: React.PropTypes.string.isRequired,
+  databases: React.PropTypes.array.isRequired,
+  onChange: React.PropTypes.func.isRequired
+};
+
+const NewLocalTargetReplicationRow = ({onChange, value}) =>
+  <input
+    type="text"
+    className="replication-new-input"
+    placeholder="Database name"
+    value={value}
+    onChange={(e) => onChange(e.target.value)}
+  />;
+
+NewLocalTargetReplicationRow.propTypes = {
+  value: React.PropTypes.string.isRequired,
+  onChange: React.PropTypes.func.isRequired
+};
+
+const ReplicationTargetRow = ({
+  replicationTarget,
+  onLocalTargetChange,
+  onRemoteTargetChange,
+  localTarget,
+  remoteTarget,
+  databases
+}) => {
+  if (!replicationTarget) {
+    return null;
+  }
+  let input;
+
+  if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
+    targetLabel = 'New Database:';
+    input = <NewLocalTargetReplicationRow
+      value={localTarget}
+      onChange={onLocalTargetChange}
+            />;
+  } else if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE) {
+    input = <ExistingLocalTargetReplicationRow
+      onChange={onLocalTargetChange}
+      databases={databases}
+      value={localTarget}
+            />;
+  } else {
+    input = <RemoteTargetReplicationRow
+      onChange={onRemoteTargetChange}
+      value={remoteTarget}
+            />;
+  }
+
+  let targetLabel = 'Target Name:';
+
+  if (replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
+      replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
+    targetLabel = 'New Database:';
+  }
+
+  return (
+    <div className="replication-section">
+      <div className="replication-input-label">{targetLabel}</div>
+      <div>
+        {input}
+      </div>
+    </div>
+  );
+};
+
+ReplicationTargetRow.propTypes = {
+  databases: React.PropTypes.array.isRequired,
+  onLocalTargetChange: React.PropTypes.func.isRequired,
+  onRemoteTargetChange: React.PropTypes.func.isRequired,
+  remoteTarget: React.PropTypes.string.isRequired,
+  localTarget: React.PropTypes.string.isRequired,
+  replicationTarget: React.PropTypes.string.isRequired
+};
+
+export class ReplicationTarget extends React.Component {
+
+  render () {
+    const {
+      replicationTarget,
+      onLocalTargetChange,
+      onTargetChange,
+      databases,
+      localTarget,
+      onRemoteTargetChange,
+      remoteTarget
+    } = this.props;
+    return (
+      <div>
+        <ReplicationTargetSelect
+          value={replicationTarget}
+          onChange={onTargetChange}
+        />
+        <ReplicationTargetRow
+          remoteTarget={remoteTarget}
+          replicationTarget={replicationTarget}
+          databases={databases}
+          localTarget={localTarget}
+          onRemoteTargetChange={onRemoteTargetChange}
+          onLocalTargetChange={onLocalTargetChange}
+        />
+      </div>
+    );
+  }
+}
+
+ReplicationTarget.propTypes = {
+  databases: React.PropTypes.array.isRequired,
+  onTargetChange: React.PropTypes.func.isRequired,
+  onLocalTargetChange: React.PropTypes.func.isRequired,
+  onRemoteTargetChange: React.PropTypes.func.isRequired,
+  remoteTarget: React.PropTypes.string.isRequired,
+  localTarget: React.PropTypes.string.isRequired,
+  replicationTarget: React.PropTypes.string.isRequired
+};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22f623c8/app/addons/replication/components/target.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/components/target.js b/app/addons/replication/components/target.js
deleted file mode 100644
index 86ed5fd..0000000
--- a/app/addons/replication/components/target.js
+++ /dev/null
@@ -1,202 +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 React from 'react';
-import Constants from '../constants';
-import Components from '../../components/react-components.react';
-import ReactSelect from 'react-select';
-
-const { StyledSelect } = Components;
-
-const replicationTargetSourceOptions = () => {
-  return [
-    { value: '', label: 'Select target' },
-    { value: Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE, label: 'Existing local database' },
-    { value: Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE, label: 'Existing remote database' },
-    { value: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE, label: 'New local database' },
-    { value: Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE, label: 'New remote database' }
-  ].map((option) => {
-    return (
-      <option value={option.value} key={option.value}>{option.label}</option>
-    );
-  });
-};
-
-const ReplicationTargetSelect = ({value, onChange}) => {
-  return (
-    <div className="replication-section">
-      <div className="replication-input-label">
-        Replication Target:
-      </div>
-      <div className="replication-input-select">
-        <StyledSelect
-          selectContent={replicationTargetSourceOptions()}
-          selectChange={(e) => onChange(e.target.value)}
-          selectId="replication-target"
-          selectValue={value} />
-      </div>
-    </div>
-  );
-};
-
-ReplicationTargetSelect.propTypes = {
-  value: React.PropTypes.string.isRequired,
-  onChange: React.PropTypes.func.isRequired
-};
-
-const RemoteTargetReplicationRow = ({onChange, value}) => {
-  return (
-    <div>
-      <input type="text" className="replication-remote-connection-url" placeholder="https://" value={value}
-        onChange={(e) => onChange(e.target.value)} />
-      <div className="replication-remote-connection-url-text">e.g. https://$REMOTE_USERNAME:$REMOTE_PASSWORD@$REMOTE_SERVER/$DATABASE</div>
-    </div>
-  );
-};
-
-RemoteTargetReplicationRow.propTypes = {
-  value: React.PropTypes.string.isRequired,
-  onChange: React.PropTypes.func.isRequired
-};
-
-const ExistingLocalTargetReplicationRow = ({onChange, value, databases}) => {
-  const options = databases.map(db => ({value: db, label: db}));
-  return (
-    <div className="replication-input-react-select">
-      <ReactSelect
-        value={value}
-        options={options}
-        placeholder="Database name"
-        clearable={false}
-        onChange={({value}) => onChange(value)}
-      />
-    </div>
-  );
-};
-
-ExistingLocalTargetReplicationRow.propTypes = {
-  value: React.PropTypes.string.isRequired,
-  databases: React.PropTypes.array.isRequired,
-  onChange: React.PropTypes.func.isRequired
-};
-
-const NewLocalTargetReplicationRow = ({onChange, value}) =>
-  <input
-    type="text"
-    className="replication-new-input"
-    placeholder="Database name"
-    value={value}
-    onChange={(e) => onChange(e.target.value)}
-  />;
-
-NewLocalTargetReplicationRow.propTypes = {
-  value: React.PropTypes.string.isRequired,
-  onChange: React.PropTypes.func.isRequired
-};
-
-const ReplicationTargetRow = ({
-  replicationTarget,
-  onLocalTargetChange,
-  onRemoteTargetChange,
-  localTarget,
-  remoteTarget,
-  databases
-}) => {
-  if (!replicationTarget) {
-    return null;
-  }
-  let input;
-
-  if (replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
-    targetLabel = 'New Database:';
-    input = <NewLocalTargetReplicationRow
-      value={localTarget}
-      onChange={onLocalTargetChange}
-            />;
-  } else if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE) {
-    input = <ExistingLocalTargetReplicationRow
-      onChange={onLocalTargetChange}
-      databases={databases}
-      value={localTarget}
-            />;
-  } else {
-    input = <RemoteTargetReplicationRow
-      onChange={onRemoteTargetChange}
-      value={remoteTarget}
-            />;
-  }
-
-  let targetLabel = 'Target Name:';
-
-  if (replicationTarget === Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE ||
-      replicationTarget === Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE) {
-    targetLabel = 'New Database:';
-  }
-
-  return (
-    <div className="replication-section">
-      <div className="replication-input-label">{targetLabel}</div>
-      <div>
-        {input}
-      </div>
-    </div>
-  );
-};
-
-ReplicationTargetRow.propTypes = {
-  databases: React.PropTypes.array.isRequired,
-  onLocalTargetChange: React.PropTypes.func.isRequired,
-  onRemoteTargetChange: React.PropTypes.func.isRequired,
-  remoteTarget: React.PropTypes.string.isRequired,
-  localTarget: React.PropTypes.string.isRequired,
-  replicationTarget: React.PropTypes.string.isRequired
-};
-
-export class ReplicationTarget extends React.Component {
-
-  render () {
-    const {
-      replicationTarget,
-      onLocalTargetChange,
-      onTargetChange,
-      databases,
-      localTarget,
-      onRemoteTargetChange,
-      remoteTarget
-    } = this.props;
-    return (
-      <div>
-        <ReplicationTargetSelect
-          value={replicationTarget}
-          onChange={onTargetChange}
-        />
-        <ReplicationTargetRow
-          remoteTarget={remoteTarget}
-          replicationTarget={replicationTarget}
-          databases={databases}
-          localTarget={localTarget}
-          onRemoteTargetChange={onRemoteTargetChange}
-          onLocalTargetChange={onLocalTargetChange}
-        />
-      </div>
-    );
-  }
-}
-
-ReplicationTarget.propTypes = {
-  databases: React.PropTypes.array.isRequired,
-  onTargetChange: React.PropTypes.func.isRequired,
-  onLocalTargetChange: React.PropTypes.func.isRequired,
-  onRemoteTargetChange: React.PropTypes.func.isRequired,
-  remoteTarget: React.PropTypes.string.isRequired,
-  localTarget: React.PropTypes.string.isRequired,
-  replicationTarget: React.PropTypes.string.isRequired
-};

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22f623c8/app/addons/replication/controller.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/controller.js b/app/addons/replication/controller.js
index 7c92ee2..2420085 100644
--- a/app/addons/replication/controller.js
+++ b/app/addons/replication/controller.js
@@ -100,17 +100,6 @@ export default class ReplicationController extends React.Component {
     AuthActions.showPasswordModal();
   }
 
-  /*getUsername () {
-    return app.session.get('userCtx').name;
-  }*/
-
-  /*getAuthHeaders () {
-    const username = this.getUsername();
-    return {
-      'Authorization': 'Basic ' + base64.encode(username + ':' + this.state.password)
-    };
-  }*/
-
   submit () {
     const {
       replicationTarget,
@@ -128,30 +117,6 @@ export default class ReplicationController extends React.Component {
       return;
     }
 
-    /*const params = {
-      source: this.getSource(),
-      target: this.getTarget()
-    };
-
-    if (_.contains([Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE, Constants.REPLICATION_TARGET.NEW_REMOTE_DATABASE], replicationTarget)) {
-      params.create_target = true;
-    }
-    if (replicationType === Constants.REPLICATION_TYPE.CONTINUOUS) {
-      params.continuous = true;
-    }
-
-    if (replicationDocName) {
-      params._id = this.state.replicationDocName;
-    }*/
-
-    // POSTing to the _replicator DB requires auth
-    /*const user = FauxtonAPI.session.user();
-    const userName = _.isNull(user) ? '' : FauxtonAPI.session.user().name;
-    params.user_ctx = {
-      name: userName,
-      roles: ['_admin', '_reader', '_writer']
-    };*/
-
     Actions.replicate({
       replicationTarget,
       replicationSource,
@@ -165,45 +130,6 @@ export default class ReplicationController extends React.Component {
     });
   }
 
-  /*getSource () {
-    const { replicationSource, sourceDatabase, remoteSource } = this.state;
-    if (replicationSource === Constants.REPLICATION_SOURCE.LOCAL) {
-      return {
-        headers: this.getAuthHeaders(),
-        url: window.location.origin + '/' + sourceDatabase
-      };
-    } else {
-      return remoteSource;
-    }
-  }
-
-  getTarget () {
-    const { replicationTarget, targetDatabase, remoteTarget, replicationSource, password } = this.state;
-
-    let target = remoteTarget;
-    if (replicationTarget === Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE) {
-      target = {
-        headers: this.getAuthHeaders(),
-        url: window.location.origin + '/' + targetDatabase
-      };
-    } 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: this.getAuthHeaders(),
-          url: window.location.origin + '/' + targetDatabase
-        };
-      } else {
-        const port = window.location.port === '' ? '' : ':' + window.location.port;
-        target = window.location.protocol + '//' + this.getUsername() + ':' + password + '@'
-          + window.location.hostname + port + '/' + targetDatabase;
-      }
-    }
-
-    return target;
-  }*/
-
   validate () {
     const { replicationTarget, targetDatabase, databases } = this.state;
 
@@ -310,67 +236,3 @@ export default class ReplicationController extends React.Component {
     );
   }
 }
-
-
-
-/*
-<div className="row">
-  <div className="span3">
-    Replication Target:
-  </div>
-  <div className="span7">
-    <ReplicationTarget
-      value={replicationTarget}
-      onChange={(repTarget) => Actions.updateFormField('replicationTarget', repTarget)}/>
-  </div>
-</div>
-{replicationTarget ?
-  <ReplicationTargetRow
-    remoteTarget={remoteTarget}
-    replicationTarget={replicationTarget}
-    databases={databases}
-    targetDatabase={targetDatabase}
-  /> : null}
-
-<hr className="replication-seperator" className="replication-seperator" size="1"/>
-
-<div className="row">
-  <div className="span3">
-    Replication Type:
-  </div>
-  <div className="span7">
-    <ReplicationType
-      value={replicationType}
-      onChange={(repType) => Actions.updateFormField('replicationType', repType)}/>
-  </div>
-</div>
-
-<div className="row">
-  <div className="span3">
-    Replication Document:
-  </div>
-  <div className="span7">
-    <div className="custom-id-field">
-      <span className="fonticon fonticon-cancel" title="Clear field"
-        onClick={(e) => Actions.updateFormField('replicationDocName', '')} />
-      <input type="text" placeholder="Custom, new ID (optional)" value={replicationDocName}
-        onChange={(e) => Actions.updateFormField('replicationDocName', e.target.value)}/>
-    </div>
-  </div>
-</div>
-
-<div className="row buttons-row">
-  <div className="span3">
-  </div>
-  <div className="span7">
-    <ConfirmButton id="replicate" text="Start Replication" onClick={this.showPasswordModal} disabled={!this.confirmButtonEnabled()}/>
-    <a href="#" data-bypass="true" onClick={this.clear}>Clear</a>
-  </div>
-</div>
-
-<PasswordModal
-  visible={passwordModalVisible}
-  modalMessage={<p>Replication requires authentication.</p>}
-  submitBtnLabel="Continue Replication"
-  onSuccess={this.submit} />
-*/

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22f623c8/app/addons/replication/tests/apiSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/tests/apiSpec.js b/app/addons/replication/tests/apiSpec.js
new file mode 100644
index 0000000..11a8279
--- /dev/null
+++ b/app/addons/replication/tests/apiSpec.js
@@ -0,0 +1,162 @@
+// 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,
+  addDocId
+} from '../api';
+import Constants from '../constants';
+
+const assert = utils.assert;
+
+describe('Replication API', () => {
+
+  describe('getSource', () => {
+
+    it('returns remote source as is', () => {
+      const remoteSource = 'remote-source';
+      const source = getSource({
+        replicationSource: Constants.REPLICATION_SOURCE.REMOTE,
+        remoteSource
+      });
+
+      assert.deepEqual(source, remoteSource);
+    });
+
+    it('returns local source with auth info', () => {
+      const localSource = 'mydb';
+
+      const source = getSource({
+        replicationSource: Constants.REPLICATION_SOURCE.LOCAL,
+        localSource,
+        username: 'the-user',
+        password: 'password'
+      });
+
+      // the file url is fine, because its just the window.location.origin in phantomjs
+      assert.deepEqual({
+        headers:{
+          Authorization:"Basic dGhlLXVzZXI6cGFzc3dvcmQ="
+        },
+        url:"file:///mydb"
+      }
+      , source);
+    });
+  });
+
+  describe('getTarget', () => {
+
+    it('returns remote target', () => {
+      const remoteTarget = 'a-remote-db';
+
+      assert.deepEqual(remoteTarget, 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="
+        },
+        url:"file:///my-existing-db"
+      });
+    });
+
+    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="
+        },
+        url:"file:///my-new-db"
+      });
+    });
+
+    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.deepEqual(target, 'file://the-user:password@/my-new-db');
+    });
+  });
+
+  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 it if it exists', () => {
+      const docId = 'docId';
+
+      assert.deepEqual(
+        addDocId(docId, {}), {
+          _id: docId
+        });
+    });
+
+    it('does not add doc it if it does not exists', () => {
+      const docId = 'docId';
+
+      assert.deepEqual(
+        addDocId(null, {}), {});
+    });
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22f623c8/app/addons/replication/tests/controller.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/tests/controller.js b/app/addons/replication/tests/controller.js
new file mode 100644
index 0000000..51ded6c
--- /dev/null
+++ b/app/addons/replication/tests/controller.js
@@ -0,0 +1,46 @@
+// 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 React from "react";
+import TestUtils from "react-addons-test-utils";
+import utils from "../../../../test/mocha/testUtils";
+import { mount } from 'enzyme';
+import sinon from "sinon";
+import Stores from '../stores';
+import Controller from '../controller';
+import Constants from '../constants';
+
+const store = Stores.replicationStore;
+
+var assert = utils.assert;
+
+describe('Replication Controller', () => {
+
+  describe('validation', () => {
+
+    beforeEach(() => {
+      store.reset();
+    });
+
+    it('returns true for source and target selected', () => {
+      const controller = mount(<Controller />);
+      controller.setState({
+        replicationTarget: Constants.REPLICATION_TARGET.NEW_LOCAL_DATABASE,
+        targetDatabase: 'new-database',
+
+      });
+
+      assert.ok(controller.validate());
+    });
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22f623c8/app/addons/replication/tests/replicationSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/tests/replicationSpec.js b/app/addons/replication/tests/replicationSpec.js
index 4664c4e..9788aa0 100644
--- a/app/addons/replication/tests/replicationSpec.js
+++ b/app/addons/replication/tests/replicationSpec.js
@@ -9,7 +9,7 @@
 // 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 React from 'react';
+/*import React from 'react';
 import ReactDOM from 'react-dom';
 import FauxtonAPI from '../../../core/api';
 import TestUtils from 'react-addons-test-utils';
@@ -210,3 +210,4 @@ describe('Replication', () => {
   });
 
 });
+*/

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22f623c8/app/addons/replication/tests/storesSpec.js
----------------------------------------------------------------------
diff --git a/app/addons/replication/tests/storesSpec.js b/app/addons/replication/tests/storesSpec.js
index 04be3df..e225805 100644
--- a/app/addons/replication/tests/storesSpec.js
+++ b/app/addons/replication/tests/storesSpec.js
@@ -16,7 +16,7 @@ import Constants from '../constants';
 const assert = utils.assert;
 const store = Stores.replicationStore;
 
-describe('Databases Store', function () {
+describe('Replication Store', function () {
 
   afterEach(function () {
     store.reset();

http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/22f623c8/test/mocha/testUtils.js
----------------------------------------------------------------------
diff --git a/test/mocha/testUtils.js b/test/mocha/testUtils.js
index fa0fd0f..5ef39ca 100644
--- a/test/mocha/testUtils.js
+++ b/test/mocha/testUtils.js
@@ -11,6 +11,7 @@
 // the License.
 
 import chai from "chai";
+import _ from 'lodash';
 
 var ViewSandbox = function () {
   this.initialize();