Testing corner cases
====================

Let's test some corner cases here that would otherwise clutter the
README.txt too much.

First some setup::

  >>> from zope.component import getGlobalSiteManager
  >>> from zope.annotation.attribute import AttributeAnnotations
  >>> from zope.annotation.interfaces import IAnnotations
  >>> from collective.watcherlist.watchers import WatcherList
  >>> class Dummy(object):
  ...     pass
  >>> sm = getGlobalSiteManager()
  >>> sm.registerAdapter(AttributeAnnotations, (Dummy, ), IAnnotations)

With that out of the way, we create a dummy object and a watcherlist
for it::

  >>> dummy = Dummy()
  >>> wl = WatcherList(dummy)
  >>> wl.addresses
  ()

We take care to let the watchers always be a PersistentList::

  >>> wl.watchers
  []
  >>> type(wl.watchers)
  <class 'persistent.list.PersistentList'>
  >>> wl.watchers = ['joe@example.org', 'mary@example.org']
  >>> wl.watchers
  ['joe@example.org', 'mary@example.org']
  >>> type(wl.watchers)
  <class 'persistent.list.PersistentList'>

The extra_addresses should also be persistent::

  >>> wl.extra_addresses
  []
  >>> type(wl.extra_addresses)
  <class 'persistent.list.PersistentList'>
  >>> wl.extra_addresses = ['list@example.org']
  >>> wl.extra_addresses
  ['list@example.org']
  >>> type(wl.extra_addresses)
  <class 'persistent.list.PersistentList'>

The send_emails property should always be a boolean::

  >>> wl.send_emails
  True
  >>> wl.send_emails = 0
  >>> wl.send_emails
  False
  >>> wl.addresses
  ()
  >>> wl.send_emails = 1
  >>> wl.send_emails
  True
  >>> wl.addresses
  ('list@example.org', 'mary@example.org', 'joe@example.org')

We have no Plone Site setup, so no members can watch::

  >>> wl.isWatching()
  False
  >>> wl.toggle_watching()
  >>> wl.isWatching()
  False

Let's see if we can mock a portal_membership tool into these tests,
including mock users::

  >>> class MockMemship(object):
  ...     current_member_name = None
  ...     members = []
  ...     def isAnonymousUser(self):
  ...         return not bool(self.current_member_name)
  ...     def getMemberById(self, member_id):
  ...         for member in self.members:
  ...             if member.username == member_id:
  ...                 return member
  ...     def getAuthenticatedMember(self):
  ...         return self.getMemberById(self.current_member_name)
  ...     def login(self, username):
  ...         self.current_member_name = username
  ...     def logout(self):
  ...         self.current_member_name = None
  >>> memship = MockMemship()
  >>> dummy.portal_membership = memship

We add some members::

  >>> class MockMember(object):
  ...     def __init__(self, username):
  ...         self.username = username
  ...     def getId(self):
  ...         return self.username
  ...     def getProperty(self, propname):
  ...         if propname == 'email':
  ...             return self.username + '@example.org'
  >>> memship.members.append(MockMember('pete'))
  >>> memship.members.append(MockMember('ann'))

We check that anonymous and authenticated users get reported
correctly::

  >>> dummy.portal_membership.isAnonymousUser()
  True
  >>> dummy.portal_membership.getAuthenticatedMember()
  >>> memship.login('pete')
  >>> dummy.portal_membership.isAnonymousUser()
  False
  >>> member = dummy.portal_membership.getAuthenticatedMember()
  >>> member
  <MockMember object at ...>
  >>> member.username
  'pete'
  >>> member.getProperty('email')
  'pete@example.org'

We log out again::

  >>> memship.logout()
  >>> dummy.portal_membership.isAnonymousUser()
  True
  >>> dummy.portal_membership.getAuthenticatedMember()

Let's try toggle watching again; as anonymous this still has no effect::

  >>> wl.isWatching()
  False
  >>> wl.toggle_watching()
  >>> wl.isWatching()
  False

Now toggle watching as authenticated member::

  >>> memship.login('pete')
  >>> wl.isWatching()
  False
  >>> wl.toggle_watching()
  >>> wl.isWatching()
  True
  >>> wl.watchers
  ['joe@example.org', 'mary@example.org', 'pete']
  >>> wl.addresses
  ('list@example.org', 'mary@example.org', 'pete@example.org', 'joe@example.org')
  >>> wl.toggle_watching()
  >>> wl.watchers
  ['joe@example.org', 'mary@example.org']
  >>> wl.addresses
  ('list@example.org', 'mary@example.org', 'joe@example.org')

In Products.Poi the issues have their own 'watchers' field.  This
returns a tuple instead of a list.  We respect that::

  >>> class PoiLikeWatcherList(WatcherList):
  ...     _watchers = tuple()
  ...     def __get_watchers(self):
  ...         return self._watchers
  ...     def __set_watchers(self, v):
  ...         self._watchers = v
  ...     watchers = property(__get_watchers, __set_watchers)
  >>> dummy2 = Dummy()
  >>> poilist = PoiLikeWatcherList(dummy2)
  >>> poilist.watchers
  ()
  >>> dummy2.portal_membership = memship
  >>> poilist.toggle_watching()
  >>> poilist.watchers
  ('pete',)
  >>> poilist.addresses
  ('pete@example.org',)
  >>> poilist.toggle_watching()
  >>> poilist.watchers
  ()
  >>> poilist.addresses
  ()

Let's test some utility functions a bit more.  Get a safe unicode
version of a string or return unchanged; basically a small shim around
safe_unicode from Plone, with the default charset set correctly::

  >>> from collective.watcherlist.utils import su
  >>> su(None)
  >>> su("hi")
  u'hi'
  >>> su(u"hi")
  u'hi'

Get the email of a member, with a few fallbacks::

  >>> from collective.watcherlist.utils import get_member_email
  >>> get_member_email(None, memship)
  'pete@example.org'
  >>> get_member_email('ann', memship)
  'ann@example.org'
  >>> get_member_email('no-one', memship)
  >>> get_member_email('address@example.com', memship)
  'address@example.com'

Allegedly, CMFMember can give some problems, so we check it::

  >>> from AccessControl import Unauthorized
  >>> class MockField(object):
  ...     def getAccessor(self, instance):
  ...         return lambda: instance.username + '@example.org'
  >>> class MockCMFMember(MockMember):
  ...     def getProperty(self, propname):
  ...         if propname == 'email':
  ...             raise Unauthorized
  ...     def getField(self, name):
  ...         if name == 'email':
  ...             return MockField()
  >>> memship.members.append(MockCMFMember('cmf'))
  >>> get_member_email('cmf', memship)
  'cmf@example.org'

Mock another mail host, so we can test some corner cases in mailer.py::

  >>> from smtplib import SMTPException
  >>> class MockSillyMailHost(object):
  ...     def send(self, message, **kwargs):
  ...         if 'error' in message:
  ...             raise AttributeError
  ...         print "Sending mail, SMTPException caught."
  ...         raise SMTPException
  ...     secureSend = send
  >>> silly_host = MockSillyMailHost()
  >>> from collective.watcherlist import utils
  >>> utils.get_mail_host()
  >>> ori_get_mail_host = utils.get_mail_host
  >>> def get_silly_mail_host():
  ...     return silly_host
  >>> utils.get_mail_host = get_silly_mail_host
  >>> utils.get_mail_host()
  <MockSillyMailHost object at ...>

Now some mail tests.  Nothing is sent without addresses::

  >>> from collective.watcherlist.mailer import simple_send_mail
  >>> simple_send_mail('message', [], 'subject')

Empty address are ignored::

  >>> simple_send_mail('message', ['', None, 0, (), []], 'subject')

When an SMTPException is raised, we log it and continue with the next
address::

  >>> simple_send_mail('message', ['a@example.com', 'b@example.com'], 'subject')
  Sending mail, SMTPException caught.
  Sending mail, SMTPException caught.

Other exceptions are reraised::

  >>> simple_send_mail('error message', ['a@example.com'], 'subject')
  Traceback (most recent call last):
  ...
  AttributeError

Restore original method::

  >>> utils.get_mail_host()
  <MockSillyMailHost object at ...>
  >>> utils.get_mail_host = ori_get_mail_host
  >>> utils.get_mail_host() is None
  True
