====================
Functional doc tests
====================

Doctests are a way to write tests while documenting the thing that is
tested at the same time.  As an example, this file both documents
functional doc tests *and* tests them.

Doctests look like regular interactive interpreter sessions.  That
makes them very easy to create.  Doctests can either occur in an
object's or method's docstring or in a separate file.  Use either
``DocTestSuite`` or ``DocFileSuite`` in these cases.


Creating functional doctests
----------------------------

Creating functional doctests is just as easy.  Obviously, you cannot
simply use an interpreter shell for the initial test creation.
Instead, you can use the `tcpwatch` program to record browser sessions
and turn them into tests:

1. Start out with a clean ZODB database.

   - Create a folder named `test_folder_1_` in the root folder.

   - Create a user in the root user folder called `test_user_1_` with
     the password `secret`.

    - Create a role `test_role_1_` and grant the role to the test
      user.  Grant the permissions 'Access contents information' and
      'View' to the role.

2. Install tcpwatch.  You can get a recent version from Zope CVS:
   http://cvs.zope.org/Packages/tcpwatch/

3. Create a temporary directory to record tcpwatch output.

4. Run tcpwatch using:
   tcpwatch.py -L 8081:8080 -s -r tmpdir
   (the ports are the listening port and forwarded-to port; the
   second port must match the Zope configuration)

5. In a browser, connect to the listening port and do whatever needs
   to be recorded.

6. Shut down tcpwatch.

7. Run the script at Zope/lib/python/Testing/ZopeTestCase/doctest/dochttp.py
   python2.3 dochttp.py tmpdir > .../mytest.txt

8. Edit the generated text file to add explanations and elide
   uninteresting portions of the output.

9. In a functional test module (usually ftests.py), import
   ``FunctionalDocFileSuite`` and instantiate it, passing the name of the
   text file containing the test.  For example:

   from unittest import TestSuite
   from Testing.ZopeTestCase import FunctionalDocFileSuite

   def test_suite():
       return TestSuite((
           FunctionalDocFileSuite('FunctionalDocTest.txt'),
       ))


Examples
--------

Test Publish Document

  >>> print http(r"""
  ... GET /test_folder_1_/index_html HTTP/1.1
  ... """, handle_errors=False)
  HTTP/1.1 200 OK
  Content-Length: 5
  Content-Type: text/plain; charset=...
  <BLANKLINE>
  index

Test parameter containing an additional '?'

  >>> print http(r"""
  ... GET /test_folder_1_?foo=bla%3Fbaz HTTP/1.1
  ... """)
  HTTP/1.1 200 OK
  Content-Length: 5
  Content-Type: text/plain; charset=utf-8
  <BLANKLINE>
  index
              
Test Unauthorized

  >>> self.folder.index_html.manage_permission('View', ['Owner'])
  >>> print http(r"""
  ... GET /test_folder_1_/index_html HTTP/1.1
  ... """, handle_errors=True)
  HTTP/1.1 401 Unauthorized
  ...
  Www-Authenticate: basic realm=...

Test Basic Authentication

  >>> from AccessControl.Permissions import manage_properties
  >>> self.setPermissions([manage_properties])

  >>> print http(r"""
  ... GET /test_folder_1_/index_html/change_title?title=Foo HTTP/1.1
  ... Authorization: Basic %s
  ... """ % user_auth, handle_errors=False)
  HTTP/1.1 200 OK
  Content-Length: 0
  ...

  >>> self.folder.index_html.title_or_id()
  'Foo'

Test passing in non-base64-encoded login/pass

  >>> from Testing.ZopeTestCase import user_name, user_password
  >>> print http(r"""
  ... GET /test_folder_1_/index_html/change_title?title=Baz HTTP/1.1
  ... Authorization: Basic %s:%s
  ... """ % (user_name, user_password), handle_errors=False)
  HTTP/1.1 200 OK
  Content-Length: 0
  ...

  >>> self.folder.index_html.title_or_id()
  'Baz'

Test setting cookies

  >>> print http(r"""
  ... GET /test_folder_1_/index_html/set_cookie HTTP/1.1
  ... """, handle_errors=False)
  HTTP/1.1 200 OK
  Content-Length: 0
  ...
  Set-Cookie: foo="Bar"; Path=/
  <BLANKLINE>

Test reading cookies

  >>> print http(r"""
  ... GET /test_folder_1_/index_html/show_cookies HTTP/1.1
  ... Cookie: foo=bar; baz="oki doki"
  ... """, handle_errors=False)
  HTTP/1.1 200 OK
  Content-Length: 23
  Content-Type: text/plain; charset=...
  <BLANKLINE>
  foo: bar
  baz: oki doki
  <BLANKLINE>
