Kotti browser tests
===================

Setup
-----

   >>> from cStringIO import StringIO
   >>> import pytest
   >>> from kotti import testing, DBSession
   >>> from kotti.resources import Node, Document, File, Image

Get a handle for some useful objects:

   >>> tools = testing.setUpFunctional()
   >>> browser = tools['Browser']()
   >>> ctrl = browser.getControl
   >>> root = DBSession.query(Node).get(1)
   >>> mailer = testing.registerDummyMailer()
   >>> save_addable_document = Document.type_info.addable_to
   >>> save_addable_file = File.type_info.addable_to
   >>> save_addable_image = Image.type_info.addable_to

Open the frontpage:

   >>> browser.open(testing.BASE_URL)


Login
-----

Editing is locked down to authenticated users:

  >>> browser.open(testing.BASE_URL + '/edit')
  >>> "Log in" in browser.contents
  True
  >>> ctrl("Username or email", index=0).value = "admin"
  >>> ctrl("Password").value = "secret"
  >>> ctrl(name="submit").click()
  >>> "Welcome, Administrator" in browser.contents
  True
  >>> browser.url == testing.BASE_URL + '/edit'
  True

Logging out redirects us to the URL we came from and presents us with
the login form:

  >>> browser.getLink("Logout").click()
  >>> "You have been logged out" in browser.contents
  True

Log in again, this time force an error:

  >>> ctrl("Username or email", index=0).value = "admin"
  >>> ctrl("Password").value = "funny"
  >>> ctrl(name="submit").click()
  >>> "Welcome, Adminstrator" in browser.contents
  False
  >>> "Login failed" in browser.contents
  True
  >>> ctrl("Username or email", index=0).value = "admin"
  >>> ctrl("Password").value = "secret"
  >>> ctrl(name="submit").click()
  >>> "Welcome, Administrator" in browser.contents
  True
  >>> browser.url == testing.BASE_URL + '/edit'
  True

Add a document
--------------

  >>> browser.getLink("Add").click()
  >>> browser.getLink("Document").click()
  >>> "Add Document to Welcome to Kotti" in browser.contents
  True
  >>> ctrl("Title").value = "Child One"
  >>> ctrl("save").click()
  >>> "Item was added" in browser.contents
  True
  >>> browser.url == testing.BASE_URL + '/child-one/'
  True

Edit the document
-----------------

  >>> browser.getLink("Edit").click()
  >>> "Edit Child One" in browser.contents
  True
  >>> ctrl("Title").value = "First Child"
  >>> ctrl("save").click()
  >>> "Your changes have been saved" in browser.contents
  True
  >>> browser.getLink("Edit").click()
  >>> ctrl("Title").value
  'First Child'
  >>> browser.getLink("View").click()
  >>> "First Child" in browser.contents
  True

And now force a validation error:

  >>> browser.getLink("Edit").click()
  >>> ctrl("Title").value = ""
  >>> ctrl("save").click()
  >>> "There was a problem" in browser.contents
  True
  >>> ctrl("Title").value
  ''
  >>> browser.getLink("View").click()
  >>> "First Child" in browser.contents
  True

And now click the 'Cancel' button:

  >>> browser.getLink("Edit").click()
  >>> ctrl("Title").value = "Firstborn"
  >>> ctrl("cancel").click()
  >>> with pytest.raises(LookupError): ctrl("Title")
  >>> "Firstborn" in browser.contents
  False

Now click the 'Cancel' button for an invalid form entry:

  >>> browser.getLink("Edit").click()
  >>> ctrl("Title").value = ""
  >>> ctrl("cancel").click()
  >>> with pytest.raises(LookupError): ctrl("Title")
  >>> "Firstborn" in browser.contents
  False

Add two more documents, at different levels
-------------------------------------------

  >>> browser.open(testing.BASE_URL + '/')
  >>> browser.getLink("Add").click()
  >>> browser.getLink("Document").click()
  >>> ctrl("Title").value = "Second Child"
  >>> ctrl("save").click()
  >>> "Item was added" in browser.contents
  True
  >>> browser.url == testing.BASE_URL + '/second-child/'
  True

  >>> browser.getLink("Add").click()
  >>> browser.getLink("Document").click()
  >>> ctrl("Title").value = "Grandchild"
  >>> ctrl("save").click()
  >>> browser.url == testing.BASE_URL + '/second-child/grandchild/'
  True
  >>> "Grandchild" in browser.contents
  True

Add another grandchild with the same name:

  >>> browser.open(testing.BASE_URL + '/second-child')
  >>> browser.getLink("Add").click()
  >>> browser.getLink("Document").click()
  >>> ctrl("Title").value = "Grandchild"
  >>> ctrl("save").click()
  >>> browser.url == testing.BASE_URL + '/second-child/grandchild-1/'
  True

There's no Add link if nothing can be added:

  >>> browser.open(testing.BASE_URL + '/second-child/grandchild-1/')
  >>> browser.getLink("Add").text
  'Add'
  >>> try:
  ...     Document.type_info.addable_to = ()
  ...     File.type_info.addable_to = ()
  ...     Image.type_info.addable_to = ()
  ...     browser.reload()
  ...     browser.getLink("Add")
  ... finally:
  ...     Document.type_info.addable_to = save_addable_document
  ...     File.type_info.addable_to = save_addable_file
  ...     Image.type_info.addable_to = save_addable_image
  Traceback (most recent call last):
  LinkNotFoundError

Add a file
----------

  >>> browser.open(testing.BASE_URL + '/')
  >>> browser.getLink(url="/add_file").click()
  >>> ctrl("Description").value = "A tiny file"
  >>> ctrl("File").add_file('tiny', 'text/plain', 'tiny.txt')
  >>> ctrl("save").click()
  >>> "Item was added" in browser.contents
  True
  >>> print browser.url
  http://localhost:6543/tiny.txt/

Add a file larger than maximum file size
----------------------------------------

  >>> browser.open(testing.BASE_URL + '/')
  >>> browser.getLink(url="/add_file").click()
  >>> ctrl("Title").value = "Huge File"
  >>> ctrl("Description").value = "An huge file"
  >>> huge = '*' * (10 * 1024 * 1024 + 1)
  >>> ctrl("File").add_file(StringIO(huge), 'text/plain', 'huge.txt')
  >>> ctrl("save").click()
  >>> "There was a problem" in browser.contents
  True
  >>> "Maximum file size" in browser.contents
  True

Add tags to a document:
-----------------------

  >>> browser.open(testing.BASE_URL + '/second-child')
  >>> browser.getLink("Add").click()
  >>> browser.getLink("Document").click()
  >>> ctrl("Title").value = "Grandchild"
  >>> ctrl("Tags").value = ''
  >>> ctrl("save").click()

  >>> browser.open(testing.BASE_URL + '/second-child')
  >>> browser.getLink("Add").click()
  >>> browser.getLink("Document").click()
  >>> ctrl("Title").value = "Grandchild"
  >>> ctrl("Tags").value = 'tag 1, tag 2,tag 3'
  >>> ctrl("save").click()
  >>> browser.open(testing.BASE_URL + '/second-child/grandchild-2/@@edit')
  >>> "tag 1" in browser.contents
  True
  >>> "tag 2" in browser.contents
  True
  >>> "tag 3" in browser.contents
  True
  >>> ctrl("Tags").value = 'tag 1, tag 4, tag 5,tag 6, tag 7, übertag'
  >>> ctrl("save").click()
  >>> browser.open(testing.BASE_URL + '/second-child/grandchild-2/@@edit')
  >>> 'value="tag 1,tag 4,tag 5,tag 6,tag 7,übertag"' in browser.contents
  True

Delete a document
-----------------

  >>> browser.open(testing.BASE_URL + '/second-child/grandchild')
  >>> browser.getLink('Delete').click()
  >>> ctrl("delete").click()
  >>> "Grandchild was deleted" in browser.contents
  True
  >>> browser.url == testing.BASE_URL + '/second-child/'
  True

Copy and paste
--------------

  >>> browser.open(testing.BASE_URL + '/second-child')
  >>> browser.getLink('Cut').click()
  >>> "Second Child was cut" in browser.contents
  True

  >>> browser.open(testing.BASE_URL + '/child-one')
  >>> browser.getLink('Paste').click()
  >>> "Second Child was pasted" in browser.contents
  True

  >>> browser.open(testing.BASE_URL + '/child-one/second-child/')
  >>> browser.open(testing.BASE_URL + '/second-child/')
  Traceback (most recent call last):
  HTTPError: HTTP Error 404: Not Found

  >>> browser.open(testing.BASE_URL + '/child-one/second-child')
  >>> browser.getLink('Copy').click()
  >>> "Second Child was copied" in browser.contents
  True
  >>> browser.open(testing.BASE_URL)
  >>> browser.getLink('Paste').click()
  >>> "Second Child was pasted" in browser.contents
  True

We can paste twice since we copied:

  >>> browser.open(testing.BASE_URL)
  >>> browser.getLink('Paste').click()
  >>> "Second Child was pasted" in browser.contents
  True
  >>> browser.open(testing.BASE_URL + '/second-child/')
  >>> "Second Child" in browser.contents
  True
  >>> browser.open(testing.BASE_URL + '/second-child-1/')
  >>> "Second Child" in browser.contents
  True

We can also copy and paste items that contain children, like the whole
site:

  >>> browser.open(testing.BASE_URL)
  >>> browser.getLink('Copy').click()
  >>> "Welcome to Kotti was copied" in browser.contents
  True
  >>> browser.open(testing.BASE_URL + '/second-child')
  >>> browser.getLink('Paste').click()
  >>> "Welcome to Kotti was pasted" in browser.contents
  True
  >>> browser.open(
  ...     testing.BASE_URL + '/second-child/welcome-to-kotti/')
  >>> browser.open(
  ...     testing.BASE_URL + '/second-child/welcome-to-kotti/second-child/')

And finally cut and paste a tree:

  >>> browser.open(testing.BASE_URL + '/second-child')
  >>> browser.getLink('Cut').click()
  >>> browser.open(testing.BASE_URL + '/child-one/second-child')
  >>> browser.getLink('Paste').click()
  >>> "Second Child was pasted" in browser.contents
  True
  >>> browser.open(testing.BASE_URL + '/second-child/')
  Traceback (most recent call last):
  HTTPError: HTTP Error 404: Not Found
  >>> browser.open(testing.BASE_URL + '/child-one/second-child/second-child')

Note how we can't cut and paste an item into itself:

  >>> browser.open(testing.BASE_URL + '/child-one')
  >>> browser.getLink('Cut').click()
  >>> browser.getLink('Paste').click()
  Traceback (most recent call last):
  LinkNotFoundError
  >>> browser.open(testing.BASE_URL + '/child-one/second-child')
  >>> browser.getLink('Paste').click()
  Traceback (most recent call last):
  LinkNotFoundError

Whether we can paste or not also depends on the 'type_info.addable'
property:

  >>> browser.open(testing.BASE_URL + '/child-one')
  >>> browser.getLink('Copy').click()
  >>> browser.open(testing.BASE_URL + '/child-one/second-child')
  >>> browser.getLink('Paste').click()
  >>> Document.type_info.addable_to = ()
  >>> browser.open(testing.BASE_URL + '/child-one/second-child')
  >>> try:
  ...     browser.getLink('Paste')
  ... finally:
  ...     Document.type_info.addable_to = save_addable_document
  Traceback (most recent call last):
  LinkNotFoundError


You can't cut the root of a site:

  >>> browser.open(testing.BASE_URL + '/child-one')
  >>> browser.getLink('Cut')
  <Link text='Cut' url='http://localhost:6543/child-one/@@cut'>
  >>> browser.open(testing.BASE_URL)
  >>> browser.getLink('Cut')
  Traceback (most recent call last):
  LinkNotFoundError


We can rename an item. Slashes will be stripped out.:

  >>> browser.open(testing.BASE_URL + '/child-one/second-child')
  >>> browser.getLink('Rename').click()
  >>> ctrl("New name").value
  'second-child'
  >>> ctrl("New title").value
  'Second Child'
  >>> ctrl("New name").value = "thi/rd-ch/ild"
  >>> ctrl("New title").value = "My Third Child"
  >>> ctrl(name="rename").click()
  >>> "Item was renamed" in browser.contents
  True
  >>> browser.url == testing.BASE_URL + '/child-one/third-child/'
  True
  >>> browser.getLink("View").click()
  >>> "My Third Child" in browser.contents
  True

We cannot rename the root:

  >>> browser.open(testing.BASE_URL)
  >>> browser.getLink('Rename').click()
  Traceback (most recent call last):
  LinkNotFoundError


On setup pages we can't use the actions:

  >>> browser.open(testing.BASE_URL)
  >>> browser.getLink('User Management').click()
  >>> browser.getLink('Copy').click()
  Traceback (most recent call last):
  LinkNotFoundError
  >>> browser.getLink('Preferences').click()
  >>> browser.getLink('Copy').click()
  Traceback (most recent call last):
  LinkNotFoundError


Contents view actions
---------------------

  >>> browser.open(testing.BASE_URL + '/child-one')
  >>> browser.getLink('Contents').click()
  >>> browser.url == testing.BASE_URL + '/child-one/@@contents'
  True

  >>> ctrl(name='copy').click()
  >>> u'You have to select items' in browser.contents
  True

  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:1]
  >>> ctrl(name='copy').click()
  >>> 'copied.' in browser.contents
  True

  >>> browser.open(testing.BASE_URL + '/second-child-1/@@contents')
  >>> 'My Third Child' in browser.contents
  False
  >>> ctrl(name='paste').click()
  >>> "My Third Child" in browser.contents
  True

  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:1]
  >>> ctrl(name='cut').click()
  >>> 'cut.' in browser.contents  # TODO: this should be show all names
  True

  >>> browser.open(testing.BASE_URL + '/child-one/@@contents')
  >>> "Grandchild" in browser.contents
  False
  >>> ctrl(name='paste').click()
  >>> "Grandchild" in browser.contents
  True
  >>> with pytest.raises(LookupError): ctrl(name='paste')

  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:2]
  >>> ctrl(name='cut').click()
  >>> browser.open(testing.BASE_URL)
  >>> browser.getLink('Document').click()
  >>> ctrl('Title').value = 'Forth child'
  >>> ctrl(name='save').click()
  >>> browser.open(testing.BASE_URL + '/forth-child/@@contents')

  >>> "Grandchild" in browser.contents
  False
  >>> "My Third Child" in browser.contents
  False
  >>> ctrl(name='paste').click()
  >>> "Grandchild" in browser.contents
  True
  >>> "My Third Child" in browser.contents
  True
  >>> browser.open(testing.BASE_URL + '/child-one/@@contents')
  >>> "Grandchild" in browser.contents
  False

  >>> browser.open(testing.BASE_URL + '/forth-child/@@contents')
  >>> 'third-child' in browser.contents
  True
  >>> 'Grandchild' in browser.contents
  True
  >>> 'child-the-third' in browser.contents
  False
  >>> 'Hello Bob' in browser.contents
  False
  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:2]
  >>> ctrl(name='rename_nodes').click()
  >>> ctrl(name='cancel').click()
  >>> u'No changes were made.' in browser.contents
  True
  >>> browser.url == testing.BASE_URL + '/forth-child/@@contents'
  True

  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:2]
  >>> ctrl(name='rename_nodes').click()
  >>> ctrl(name='%s-name' % childs.options[0]).value = 'child-the-third'
  >>> ctrl(name='%s-title' % childs.options[0]).value = 'child, the third'
  >>> ctrl(name='%s-name' % childs.options[1]).value = 'hello-bob'
  >>> ctrl(name='%s-title' % childs.options[1]).value = 'Hello Bob'
  >>> ctrl(name='rename_nodes').click()

  >>> browser.url == testing.BASE_URL + '/forth-child/@@contents'
  True
  >>> 'third-child' in browser.contents
  False
  >>> 'Grandchild' in browser.contents
  False
  >>> 'child-the-third' in browser.contents
  True
  >>> 'Hello Bob' in browser.contents
  True

  >>> browser.getLink('File').click()
  >>> ctrl('Description').value = 'A file'
  >>> ctrl('File').add_file('something', 'text/plain', 'some.txt')
  >>> ctrl('save').click()
  >>> browser.open(testing.BASE_URL + '/forth-child/@@contents')
  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:3]
  >>> ctrl(name='delete_nodes').click()
  >>> ctrl(name='cancel').click()
  >>> u'No changes were made.' in browser.contents
  True
  >>> browser.url == testing.BASE_URL + '/forth-child/@@contents'
  True

  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:3]
  >>> ctrl(name='delete_nodes').click()
  >>> "Are you sure" in browser.contents
  True

  >>> ctrl(name='children-to-delete').value = childs.options[0:3]
  >>> ctrl(name='delete_nodes').click()
  >>> "child, the third was deleted." in browser.contents
  True
  >>> "Hello Bob was deleted." in browser.contents
  True
  >>> "some.txt was deleted." in browser.contents
  True

  >>> browser.open(testing.BASE_URL + '/forth-child/@@contents')
  >>> "Welcome to Kotti" in browser.contents
  True
  >>> '<i class="glyphicon glyphicon-home"></i>' in browser.contents
  True
  >>> '<i class="glyphicon glyphicon-folder-open"></i>' in browser.contents
  True
  >>> '<i class="glyphicon glyphicon-folder-close"></i>' in browser.contents
  False


Contents view change state actions
----------------------------------

  >>> browser.getLink("Second Child").click()
  >>> browser.getLink("Contents").click()
  >>> 'http://localhost:6543/second-child-1/third-child/@@workflow-change?new_state=public' in browser.contents
  True
  >>> browser.getLink("Make Public", index=2).click()
  >>> 'http://localhost:6543/second-child-1/third-child/@@workflow-change?new_state=private' in browser.contents
  True

  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:3]
  >>> ctrl(name='change_state').click()
  >>> 'Change workflow state' in browser.contents
  True
  >>> ctrl(name='cancel').click()
  >>> u'No changes were made.' in browser.contents
  True
  >>> browser.url == testing.BASE_URL + '/second-child-1/@@contents'
  True

  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:3]
  >>> ctrl(name='change_state').click()
  >>> childs = ctrl(name='children-to-change-state')
  >>> childs.value = []
  >>> ctrl(name='change_state').click()
  >>> u'No changes were made.' in browser.contents
  True

  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:3]
  >>> ctrl(name='change_state').click()
  >>> ctrl(name='to-state').value = ['private']
  >>> ctrl(name='change_state').click()
  >>> 'Your changes have been saved.' in browser.contents
  True
  >>> 'http://localhost:6543/second-child-1/third-child/@@workflow-change?new_state=public' in browser.contents
  True
  >>> 'http://localhost:6543/second-child-1/grandchild-2/@@workflow-change?new_state=public' in browser.contents
  True
  >>> 'http://localhost:6543/second-child-1/grandchild-3/@@workflow-change?new_state=public' in browser.contents
  True

  >>> browser.getLink('My Third Child').click()
  >>> 'http://localhost:6543/second-child-1/third-child/child-one/@@workflow-change?new_state=public' in browser.contents
  True
  >>> browser.open(testing.BASE_URL + '/second-child-1/third-child/child-one/@@workflow-change?new_state=public')
  >>> browser.open(testing.BASE_URL + '/second-child-1/third-child/@@contents')
  >>> 'http://localhost:6543/second-child-1/third-child/child-one/@@workflow-change?new_state=private' in browser.contents
  True

  >>> browser.getLink('First Child', index=1).click()
  >>> browser.getLink('Document').click()
  >>> ctrl('Title').value = 'Sub child'
  >>> ctrl(name='save').click()
  >>> 'http://localhost:6543/second-child-1/third-child/child-one/sub-child/@@workflow-change?new_state=public' in browser.contents
  True

  >>> browser.getLink("Second Child").click()
  >>> browser.getLink("Contents").click()
  >>> childs = ctrl(name='children')
  >>> childs.value = childs.options[0:3]
  >>> ctrl(name='change_state').click()
  >>> ctrl(name='include-children').value = ['include-children']
  >>> ctrl(name='to-state').value = ['public']
  >>> ctrl(name='change_state').click()
  >>> 'Your changes have been saved.' in browser.contents
  True
  >>> 'http://localhost:6543/second-child-1/third-child/@@workflow-change?new_state=private' in browser.contents
  True
  >>> browser.open(testing.BASE_URL + '/second-child-1/third-child/child-one/sub-child/')
  >>> 'http://localhost:6543/second-child-1/third-child/child-one/sub-child/@@workflow-change?new_state=private' in browser.contents
  True


Navigation
----------

  >>> browser.getLink("Navigate").click()
  >>> browser.getLink("Second Child").click()
  >>> browser.url == testing.BASE_URL + '/second-child-1/'
  True

User management
---------------

The user management screen is available through the "Site Setup" submenu:

  >>> browser.getLink("User Management").click()

We add Bob's Group and assign the ``Viewer`` role:

  >>> ctrl("Name", index=1).value = "bobsgroup"
  >>> ctrl("Title", index=1).value = "Bob's Group"
  >>> ctrl("Viewer", index=1).click()
  >>> ctrl(name="add_group").click()
  >>> "Bob's Group was added" in browser.contents
  True
  >>> ctrl(name="role::group:bobsgroup::role:viewer").value
  True

And a Bob.  Only alphanumeric characters are allowed for the name:

  >>> ctrl("Name", index=0).value = "bob:"
  >>> ctrl("Full name", index=0).value = "Bob Dabolina"
  >>> ctrl("Password", index=0).value = "secret"
  >>> ctrl(name='password-confirm').value = "secret"
  >>> ctrl("Email", index=0).value = "bob@DABOLINA.com"
  >>> ctrl(name="add_user").click()
  >>> "There was a problem" in browser.contents
  True

Use a valid username now.  Note how the checkbox to send a password
registration link is ticked:

  >>> ctrl("Name", index=0).value = u"Bob"
  >>> ctrl("Email", index=0).value = "bob@dabolina.com"
  >>> ctrl("Send password registration link", index=0).selected
  True
  >>> ctrl(name="add_user").click()
  >>> "Bob Dabolina was added" in browser.contents
  True

We cannot add Bob twice:

  >>> ctrl("Name", index=0).value = "bob"
  >>> ctrl("Full name", index=0).value = "Bobby Brown"
  >>> ctrl("Password", index=0).value = "secret"
  >>> ctrl(name='password-confirm').value = "secret"
  >>> ctrl(name="add_user").click()
  >>> "A user with that name already exists" in browser.contents
  True

We cannot add Bob Dabolina's email twice:

  >>> ctrl("Name", index=0).value = "bob2"
  >>> ctrl("Full name", index=0).value = "Bobby Brown"
  >>> ctrl("Email", index=0).value = "bob@dabolina.com"
  >>> ctrl("Password", index=0).value = "secret"
  >>> ctrl(name='password-confirm').value = "secret"
  >>> ctrl(name="add_user").click()
  >>> "A user with that email already exists" in browser.contents
  True
  >>> ctrl("Email", index=0).value = "bob@gmail.com"
  >>> ctrl(name="add_user").click()
  >>> "Bobby Brown was added" in browser.contents
  True

Searching for Bob will return both Bob and Bob's Group:

  >>> ctrl(name="query").value = "Bob"
  >>> ctrl(name="search").click()
  >>> "Bob Dabolina" in browser.contents
  True
  >>> "Bob's Group" in browser.contents
  True
  >>> ctrl(name="role::group:bobsgroup::role:viewer").value
  True
  >>> ctrl(name="role::group:bobsgroup::role:editor").value
  False
  >>> ctrl(name="role::bob::role:editor").value
  False

We can click on the Bob's entry to edit the list of groups he belongs
to:

  >>> browser.getLink("Bob Dabolina").click()
  >>> ctrl(name="group").value = "bobsgroup"
  >>> ctrl(name="save").click()
  >>> "Your changes have been saved" in browser.contents
  True

We cannot update on the Bobby Brown's entry with duplicated email:

  >>> browser.getLink("Back to User Management").click()
  >>> ctrl(name="query").value = "Bobby"
  >>> ctrl(name="search").click()
  >>> browser.getLink("Bobby Brown").click()
  >>> ctrl("Email", index=0).value = "bob@dabolina.com"
  >>> ctrl(name="save").click()
  >>> "A user with that email already exists" in browser.contents
  True

If we cancel the edit of the user, we are redirected to the user management
screen:

  >>> ctrl(name="cancel").click()
  >>> "No changes were made." in browser.contents
  True
  >>> browser.url
  'http://localhost:6543/@@setup-users'


Back in user management we can see how Bob's inherited the Viewer role
from Bob's Group:

  >>> ctrl(name="query").value = "Bob"
  >>> ctrl(name="search").click()
  >>> ctrl(name="role::group:bobsgroup::role:viewer").value
  True
  >>> ctrl(name="role::group:bobsgroup::role:viewer").disabled
  False
  >>> ctrl(name="role::bob::role:viewer").value
  True
  >>> ctrl(name="role::bob::role:viewer").disabled
  True

We can click on the Bob's Group entry to edit an email address:

  >>> browser.getLink("Bob's Group").click()
  >>> ctrl(name="email").value = 'bogsgroup@gmail.com'
  >>> ctrl(name="save").click()
  >>> "Your changes have been saved" in browser.contents
  True

Set password
------------

Remember that we sent Bob an email for registration.  He can use it to
set his own password:

  >>> [email, email2] = mailer.outbox
  >>> print email.recipients
  [u'"Bob Dabolina" <bob@dabolina.com>']
  >>> print email.subject
  Your registration for Website des Kottbusser Tors
  >>> print email.body # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  Hello, Bob Dabolina!
  You are joining Website des Kottbusser Tors.
  Click here to set your password and log in:
  http://localhost:6543/@@set-password?token=...

We'll use that link to set our password:

  >>> browser.getLink("Logout").click()
  >>> link = email.body[email.body.index('http://localhost'):].split()[0]
  >>> browser.open(link)
  >>> ctrl("Password", index=0).value = "newpassword"
  >>> ctrl(name='password-confirm').value = "newpasswoops" # a silly error
  >>> ctrl(name="submit").click()
  >>> "There was an error" in browser.contents
  True
  >>> ctrl("Password", index=0).value = "newpassword"
  >>> ctrl(name='password-confirm').value = "newpassword"
  >>> ctrl(name="submit").click()
  >>> "You have reset your password" in browser.contents
  True
  >>> browser.getLink("Logout").click()

We cannot use that link again:

  >>> browser.open(link)
  >>> ctrl("Password", index=0).value = "wontwork"
  >>> ctrl(name='password-confirm').value = "wontwork"
  >>> ctrl(name="submit").click()
  >>> "Your password reset token may have expired" in browser.contents
  True

Log in as Bob with the new password:

  >>> browser.open(testing.BASE_URL + '/@@edit')
  >>> ctrl("Username or email", index=0).value = "bOB"
  >>> ctrl("Password").value = "newpassword"
  >>> ctrl(name="submit").click()
  >>> "Welcome, Bob Dabolina" in browser.contents
  True
  >>> browser.open(testing.BASE_URL + '/@@edit')
  >>> browser.getLink("Logout").click()

The login form has a "Reset password" button.  Let's try it:

  >>> browser.open(testing.BASE_URL + '/@@edit')
  >>> ctrl("Username or email", index=1).value = "bobby" # silly error
  >>> ctrl("Password").value = ""
  >>> ctrl(name="reset-password").click()
  >>> "That username or email is not known by this system" in browser.contents
  True
  >>> ctrl("Username or email", index=1).value = "bob"
  >>> ctrl(name="reset-password").click()
  >>> "You should be receiving an email" in browser.contents
  True

  >>> [email1, email2, email3] = mailer.outbox
  >>> print email3.body # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
  Hello, Bob Dabolina!
  <BLANKLINE>
  Click this link to reset your password at Website des Kottbusser Tors:...

User preferences
----------------

The "Preferences" link leads us to a form where the user can change
their preferences so the user need to be authenticated:

  >>> browser.open(testing.BASE_URL + '/@@prefs')
  >>> ctrl("Username or email", index=0).value = "admin"
  >>> ctrl("Password").value = "secret"
  >>> ctrl(name="submit").click()
  >>> "Welcome, Administrator" in browser.contents
  True

  >>> ctrl("Full name").value = "Mr. Administrator"
  >>> ctrl("Email").value = 'admin@minad.com'
  >>> ctrl(name="save").click()
  >>> "Your changes have been saved" in browser.contents
  True

The email could not be used if already a users with that email exists:

  >>> ctrl("Email").value = 'bob@dabolina.com'
  >>> ctrl(name="save").click()
  >>> "A user with that email already exists" in browser.contents
  True


If the user cancel the process he will be redirected to the site root:

  >>> ctrl(name="cancel").click()
  >>> 'Welcome to Kotti' in browser.contents
  True


Share
-----

The Share tab allows us to assign users and groups to roles:

  >>> browser.open(testing.BASE_URL)
  >>> browser.getLink("Edit").click()
  >>> browser.getLink("Share").click()

We can search for users:

  >>> ctrl(name='query').value = "Bob"
  >>> ctrl(name="search").click()

Bob and Bob's Group are listed now:

  >>> "Bob Dabolina" in browser.contents
  True
  >>> "Bob's Group" in browser.contents
  True

We add Bob's Group to Owners and Editors before taking away Owners
again:

  >>> ctrl(name="role::group:bobsgroup::role:owner").value = True
  >>> ctrl(name="role::group:bobsgroup::role:editor").value = True
  >>> ctrl(name="apply").click()
  >>> "Your changes have been saved" in browser.contents
  True
  >>> browser.reload()
  >>> ctrl(name="role::group:bobsgroup::role:owner").value
  True
  >>> ctrl(name="role::group:bobsgroup::role:editor").value
  True
  >>> ctrl(name="role::group:bobsgroup::role:owner").value = False
  >>> ctrl(name="apply").click()
  >>> "Your changes have been saved" in browser.contents
  True
  >>> ctrl(name="role::group:bobsgroup::role:owner").value
  False

Not making any changes will give us a different feedback message:

  >>> ctrl(name="apply").click()
  >>> "Your changes have been saved" in browser.contents
  False
  >>> ctrl(name="role::group:bobsgroup::role:owner").value
  False
  >>> ctrl(name="role::group:bobsgroup::role:editor").value
  True

Bob should now have an inherited Editor role, because he's part of
Bob's Group:

  >>> ctrl(name="query").value = "Bob Dabolina"
  >>> ctrl(name="search").click()
  >>> ctrl(name="role::bob::role:editor").value
  True
  >>> ctrl(name="role::bob::role:owner").value
  False
  >>> ctrl(name="role::bob::role:editor").disabled
  True

Lastly, let's take away the remaining Editor role from Bob's Group
again:

  >>> "Bob's Group" in browser.contents
  True
  >>> ctrl(name="role::group:bobsgroup::role:editor").value = False
  >>> ctrl(name="apply").click()
  >>> "Your changes have been saved" in browser.contents
  True
  >>> "Bob's Group" in browser.contents
  False


Delete users
------------

  >>> browser.getLink("User Management").click()

We add a group and assign the ``Manager`` role to one:

  >>> ctrl("Name", index=1).value = "pelayogroup"
  >>> ctrl("Title", index=1).value = "Pelayo Group"
  >>> ctrl("Admin", index=1).click()
  >>> ctrl(name="add_group").click()
  >>> "Pelayo Group was added" in browser.contents
  True
  >>> ctrl(name="role::group:pelayogroup::role:admin").value
  True

And add some users to this group.

  >>> ctrl("Name", index=0).value = "Bruce"
  >>> ctrl("Full name", index=0).value = "Bruce Pelayo"
  >>> ctrl("Password", index=0).value = "secret"
  >>> ctrl(name='password-confirm').value = "secret"
  >>> ctrl("Email", index=0).value = "bruce@pelayno.com"
  >>> ctrl(name="group", index=0).value = "pelayogroup"
  >>> ctrl(name="add_user").click()
  >>> "Bruce Pelayo was added" in browser.contents
  True

  >>> ctrl("Name", index=0).value = "Brian"
  >>> ctrl("Full name", index=0).value = "Brian Pelayo"
  >>> ctrl("Password", index=0).value = "secret"
  >>> ctrl(name='password-confirm').value = "secret"
  >>> ctrl("Email", index=0).value = "brian@pelayno.com"
  >>> ctrl(name="group", index=0).value = "pelayogroup"
  >>> ctrl(name="add_user").click()
  >>> "Brian Pelayo was added" in browser.contents
  True

Lets login as Bruce and add some content:

  >>> browser.getLink("Logout").click()
  >>> browser.open(testing.BASE_URL + '/login')
  >>> ctrl("Username or email", index=0).value = "bruce"
  >>> ctrl("Password").value = "secret"
  >>> ctrl(name="submit").click()
  >>> "Welcome, Bruce Pelayo!" in browser.contents
  True

  >>> browser.getLink("Document").click()
  >>> ctrl("Title").value = "Bruce one"
  >>> ctrl("save").click()
  >>> browser.open(testing.BASE_URL)
  >>> browser.getLink("Document").click()
  >>> ctrl("Title").value = "Bruce two"
  >>> ctrl("save").click()

Now let's delete Bruce::

  >>> browser.getLink("Logout").click()
  >>> browser.open(testing.BASE_URL + '/login')
  >>> ctrl("Username or email", index=0).value = "admin"
  >>> ctrl("Password").value = "secret"
  >>> ctrl(name="submit").click()

  >>> browser.getLink("User Management").click()
  >>> ctrl(name="query").value = "Bruce"
  >>> ctrl(name="search").click()
  >>> browser.getLink("Bruce Pelayo").click()
  >>> ctrl(name='delete').click()

  >>> "Delete <em>Bruce Pelayo</em>" in browser.contents
  True
  >>> "Are you sure you want to delete <span>User</span> <em>Bruce Pelayo</em>?" in browser.contents
  True

  >>> ctrl(name='delete').click()
  >>> "User Bruce Pelayo was deleted." in browser.contents
  True

  >>> ctrl(name="query").value = "Bruce"
  >>> ctrl(name="search").click()
  >>> "No users or groups were found." in browser.contents
  True

TearDown
--------

  >>> testing.tearDown()
