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

Setup
-----

   >>> from kotti import tests, DBSession
   >>> from kotti.resources import Node, Document

Get a handle for some useful objects:

   >>> tools = tests.setUpFunctional()
   >>> browser = tools['Browser']()
   >>> ctrl = browser.getControl
   >>> session = DBSession()
   >>> root = session.query(Node).get(1)
   >>> mailer = tests.registerDummyMailer()

Open the frontpage:

   >>> browser.open(tests.BASE_URL)

Login
-----

Editing is locked down to authenticated users:

  >>> browser.open(tests.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 == tests.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 == tests.BASE_URL + '/@@edit'
  True

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

  >>> browser.open(tests.BASE_URL)
  >>> browser.getLink("Edit").click()
  >>> browser.getLink("Add").click()
  >>> ctrl("Title").value = "Child One"
  >>> ctrl("save").click()
  >>> "Successfully added item" in browser.contents
  True
  >>> browser.url == tests.BASE_URL + '/child-one/@@edit'
  True

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

  >>> browser.getLink("Edit").click()
  >>> ctrl("Title").value = "First Child"
  >>> ctrl("save").click()
  >>> "Your changes have been saved" in browser.contents
  True
  >>> 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

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

  >>> browser.getLink("Edit").click()
  >>> browser.getLink("Add").click()
  >>> ctrl("Add to My Site").selected = True
  >>> ctrl("Continue").click()
  >>> ctrl("Title").value = "Second Child"
  >>> ctrl("save").click()
  >>> "Successfully added item" in browser.contents
  True
  >>> browser.url == tests.BASE_URL + '/second-child/@@edit'
  True

  >>> browser.getLink("Add").click()
  >>> ctrl("Add to Second Child").selected = True
  >>> ctrl("Continue").click()
  >>> ctrl("Title").value = "Grandchild"
  >>> ctrl("save").click()
  >>> browser.url == tests.BASE_URL + '/second-child/grandchild/@@edit'
  True

  >>> browser.getLink("View").click()
  >>> "Grandchild" in browser.contents
  True
  >>> browser.url == tests.BASE_URL + '/second-child/grandchild/'
  True

Add another grandchild with the same name:

  >>> browser.open(tests.BASE_URL + '/second-child/@@edit')
  >>> browser.getLink("Add").click()
  >>> ctrl("Add to Second Child").selected = True
  >>> ctrl("Continue").click()
  >>> ctrl("Title").value = "Grandchild"
  >>> ctrl("save").click()
  >>> browser.url == tests.BASE_URL + '/second-child/grandchild-1/@@edit'
  True

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

  >>> browser.open(tests.BASE_URL)
  >>> index = browser.contents.index
  >>> index("First Child") > index("Second Child")
  False
  >>> browser.getLink("Edit").click()
  >>> browser.getLink("Move").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

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

  >>> browser.open(tests.BASE_URL + '/second-child/grandchild/@@move')
  >>> ctrl(name="delete-confirm").displayValue = ['Yes']
  >>> ctrl("delete").click()
  >>> "Grandchild deleted" in browser.contents
  True
  >>> browser.url == tests.BASE_URL + '/second-child/@@edit'
  True

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

  >>> browser.open(tests.BASE_URL + '/second-child/@@move')
  >>> ctrl(name="cut").click()
  >>> "Second Child cut" in browser.contents
  True

  >>> browser.open(tests.BASE_URL + '/child-one/@@move')
  >>> ctrl(name="paste").click()
  >>> "Second Child pasted" in browser.contents
  True

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

  >>> browser.open(tests.BASE_URL + '/child-one/second-child/@@move')
  >>> ctrl(name="copy").click()
  >>> "Second Child copied" in browser.contents
  True
  >>> browser.open(tests.BASE_URL + '/@@move')
  >>> ctrl(name="paste").click()
  >>> "Second Child pasted" in browser.contents
  True

We can paste twice since we copied:

  >>> ctrl(name="paste").click()
  >>> "Second Child pasted" in browser.contents
  True
  >>> browser.open(tests.BASE_URL + '/second-child/')
  >>> "Second Child" in browser.contents
  True
  >>> browser.open(tests.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(tests.BASE_URL + '/@@move')
  >>> ctrl(name="copy").click()
  >>> "My Site copied" in browser.contents
  True
  >>> browser.open(tests.BASE_URL + '/second-child/@@move')
  >>> ctrl(name="paste").click()
  >>> "My Site pasted" in browser.contents
  True
  >>> browser.open(tests.BASE_URL + '/second-child/my-site/')
  >>> browser.open(tests.BASE_URL + '/second-child/my-site/second-child/')

And finally cut and paste a tree:

  >>> browser.open(tests.BASE_URL + '/second-child/@@move')
  >>> ctrl(name="cut").click()
  >>> browser.open(tests.BASE_URL + '/child-one/second-child/@@move')
  >>> ctrl(name="paste").click()
  >>> "Second Child pasted" in browser.contents
  True
  >>> browser.open(tests.BASE_URL + '/second-child/')
  Traceback (most recent call last):
  HTTPError: HTTP Error 404: Not Found
  >>> browser.open(tests.BASE_URL + '/child-one/second-child/second-child')

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

  >>> browser.open(tests.BASE_URL + '/child-one/@@move')
  >>> ctrl(name="cut").click()
  >>> ctrl(name="paste")
  Traceback (most recent call last):
  LookupError: name 'paste'
  >>> browser.open(tests.BASE_URL + '/child-one/second-child/@@move')
  >>> ctrl(name="paste")
  Traceback (most recent call last):
  LookupError: name 'paste'

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

  >>> browser.open(tests.BASE_URL + '/child-one/@@move')
  >>> ctrl(name="copy").click()
  >>> browser.open(tests.BASE_URL + '/child-one/second-child/@@move')
  >>> ctrl(name="paste")
  <SubmitControl name='paste' type='submitbutton'>
  >>> save_addable = Document.type_info.addable_to
  >>> Document.type_info.addable_to = ()
  >>> browser.reload()
  >>> ctrl(name="paste")
  Traceback (most recent call last):
  LookupError: name 'paste'
  >>> Document.type_info.addable_to = save_addable

We can rename an item:

  >>> browser.open(tests.BASE_URL + '/child-one/second-child/@@move')
  >>> ctrl("New name").value
  'second-child'
  >>> ctrl("New title").value
  'Second Child'
  >>> ctrl("New name").value = "third-child"
  >>> ctrl("New title").value = "My Third Child"
  >>> ctrl(name="rename").click()
  >>> "Item renamed" in browser.contents
  True
  >>> browser.url == tests.BASE_URL + '/child-one/third-child/@@move'
  True
  >>> browser.getLink("View").click()
  >>> "My Third Child" in browser.contents
  True

We cannot rename the root:

  >>> browser.open(tests.BASE_URL + '/@@move')
  >>> ctrl("New name")
  Traceback (most recent call last):
  LookupError: label 'New name'


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("Confirm Password", index=0).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").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("Confirm Password", index=0).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("Confirm Password").value = "newpasswoops" # a silly error
  >>> ctrl(name="submit").click()
  >>> "There was an error" in browser.contents
  True
  >>> ctrl("Confirm Password").value = "newpassword"
  >>> ctrl(name="submit").click()
  >>> "You've reset your password successfully" in browser.contents
  True
  >>> browser.open(tests.BASE_URL + '/@@edit')
  >>> browser.getLink("Logout").click()

We cannot use that link again:

  >>> browser.open(link)
  >>> ctrl("Password", index=0).value = "wontwork"
  >>> ctrl("Confirm Password").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(tests.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(tests.BASE_URL + '/@@edit')
  >>> browser.getLink("Logout").click()

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

  >>> browser.open(tests.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(tests.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 # returns 'None'-the-string currently
  >>> 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(tests.BASE_URL)
  >>> browser.getLink("Edit").click()
  >>> browser.getLink("Share").click()

We can search for users:

  >>> ctrl("Search users and groups").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


  >>> tests.tearDown()
