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

Setup
-----

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

Get a handle for some useful objects:

   >>> tools = testing.setUpFunctional()
   >>> browser = tools['Browser']()
   >>> ctrl = browser.getControl
   >>> session = DBSession()
   >>> root = session.query(Node).get(1)
   >>> mailer = testing.registerDummyMailer()
   >>> save_addable_document = Document.type_info.addable_to
   >>> save_addable_file = File.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").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").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").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 <em>Welcome to Kotti</em>" in browser.contents
  True
  >>> ctrl("Title").value = "Child One"
  >>> ctrl("save").click()
  >>> "Successfully added item" in browser.contents
  True
  >>> browser.url == testing.BASE_URL + '/child-one/'
  True

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

  >>> browser.getLink("Edit").click()
  >>> "Edit <em>Child One</em>" 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()
  >>> "Successfully added item" 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 = ()
  ...     browser.reload()
  ...     browser.getLink("Add")
  ... finally:
  ...     Document.type_info.addable_to = save_addable_document
  ...     File.type_info.addable_to = save_addable_file
  Traceback (most recent call last):
  LinkNotFoundError

Reorder documents
-----------------

  >>> browser.open(testing.BASE_URL)
  >>> index = browser.contents.index
  >>> index("First Child") > index("Second Child")
  False
  >>> browser.open(testing.BASE_URL)
  >>> "/@@order" in browser.contents
  True
  >>> browser.getLink('Order').click()
  >>> ctrl(name="order-up", index=1).click()
  >>> "Second Child moved" in browser.contents
  True
  >>> browser.getLink("View").click()
  >>> index = browser.contents.index
  >>> index("First Child") > index("Second Child")
  True

Hide and show documents
-----------------------

  >>> browser.open(testing.BASE_URL)
  >>> "First Child" in browser.contents
  True
  >>> browser.open(testing.BASE_URL)
  >>> "/@@order" in browser.contents
  True
  >>> browser.getLink('Order').click()
  >>> ctrl(name="toggle-visibility", index=1).click()
  >>> "First Child is no longer visible in the navigation" in browser.contents
  True
  >>> browser.getLink("View").click()
  >>> "First Child" in browser.contents
  False
  >>> browser.getLink('Order').click()
  >>> ctrl(name="toggle-visibility", index=1).click()
  >>> "First Child is now visible in the navigation" in browser.contents
  True
  >>> browser.getLink("View").click()
  >>> "First Child" in browser.contents
  True


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

  >>> browser.open(testing.BASE_URL + '/second-child/grandchild')
  >>> browser.getLink('Delete').click()
  >>> ctrl(name="delete-confirm").displayValue = ['Yes']
  >>> ctrl("delete").click()
  >>> "Grandchild 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 cut" in browser.contents
  True

  >>> browser.open(testing.BASE_URL + '/child-one')
  >>> browser.getLink('Paste').click()  
  >>> "Second Child 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 copied" in browser.contents
  True
  >>> browser.open(testing.BASE_URL)
  >>> browser.getLink('Paste').click()  
  >>> "Second Child pasted" in browser.contents
  True

We can paste twice since we copied:

  >>> browser.getLink('Paste').click()  
  >>> "Second Child 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 copied" in browser.contents
  True
  >>> browser.open(testing.BASE_URL + '/second-child')
  >>> browser.getLink('Paste').click()  
  >>> "Welcome to Kotti 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 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 = ()
  >>> try:
  ...     browser.reload()
  ...     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 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('Site Setup').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


Navigation
----------

  >>> browser.getLink("Navigate").click()
  >>> browser.getLink("My Third Child").click()
  >>> browser.url == testing.BASE_URL + '/child-one/third-child/'
  True

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

The user management screen is available through Site Setup:

  >>> browser.getLink("Site Setup").click()
  >>> 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 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("Send password registration link", index=0).selected
  True
  >>> ctrl(name="add_user").click()
  >>> "Bob Dabolina 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("Email", index=0).value = "bob@gmail.com"
  >>> 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

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

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

  >>> browser.getLink("User Management").click()
  >>> 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

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

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

  >>> [email] = 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've just been invited to join 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(name='password-confirm').value = "newpassword"
  >>> ctrl(name="submit").click()
  >>> "You've reset your password successfully" 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").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").value = "bobby" # silly error
  >>> ctrl("Password").value = ""
  >>> ctrl(name="reset-password").click()
  >>> "That username or email is not known to us" in browser.contents
  True
  >>> ctrl("Username or email").value = "bob"
  >>> ctrl(name="reset-password").click()
  >>> "You should receive an email" in browser.contents
  True

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

Log in as admin again:

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

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

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

  >>> browser.getLink("Preferences").click()
  >>> 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

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


  >>> testing.tearDown()
