Error Handling
--------------

These tests try to make sure the error handling for searches via Solr is more
or less sane, i.e. misconfigurations, connection errors and other things are
handled gracefully. We'll use a testbrowser to verify these issues:

  >>> from plone.testing.z2 import Browser
  >>> browser = Browser(layer['app'])
  >>> maintenance = layer['portal'].unrestrictedTraverse('@@solr-maintenance')
  >>> from collective.solr.testing import activateAndReindex

First let's make sure an appropriate exceptions is raised if Solr hasn't even
been configured in our Plone site yet. This basically tests the same as
`testInactiveException` (in `test_integration.py`), but another aspect of
having two separate tests trying to open a real connection is to make sure
the `ConnectionStateErrors` that occurred for the connection manager don't
bite anymore:

  >>> from zope.component import getUtility
  >>> from collective.solr.interfaces import ISearch
  >>> search = getUtility(ISearch)
  >>> search('foo')
  Traceback (most recent call last):
  ...
  collective.solr.exceptions.SolrInactiveException

Next we test error handling when Solr support has been activated, i.e. when
searches should in fact be possible, but things like network failures get in
the way. First we need to activate Solr support and reindex the site's
content:

  >>> activateAndReindex(layer['portal'])
  >>> import transaction
  >>> transaction.commit()

Then we start searching: the first time around things are fine:

  >>> browser.open('http://nohost/plone')
  >>> form = browser.getForm(name='searchform', index=0)
  >>> browser.getControl(name='SearchableText', index=0).value = 'Welcome'
  >>> form.submit()
  >>> browser.contents
  '...Search results...1...items matching your search terms...
   ...http://nohost/plone/front-page...Welcome to Plone...'

However, the second time a network failure occurs. It is faked by changing
the configuration to use a different port and manually triggering a reconnect
to avoid re-using the already open connection. In reality a network outage
would essentially come down to the same, of course:

  >>> from collective.solr.utils import getConfig
  >>> config = getConfig()
  >>> port = config.port
  >>> config.port = 55555

  >>> from zope.component import getUtility
  >>> from collective.solr.interfaces import ISolrConnectionManager
  >>> getUtility(ISolrConnectionManager).closeConnection()

  >>> transaction.commit()

Also, before we try to continue, we need to prevent the test framework from
choking on the expected exception. Normally the zope publisher would handle
the exception by returning the exception value which is nothing else but a
html page responding to the error. However, the doctest framework doesn't
support this kind of error handling but catches and reports the exception
instead. So we "trick" the framework by letting it know about the exception
and telling it that it's okay (by means of status code 200). This way the
intended error message is returned and we can continue the test:

  >>> from ZPublisher import HTTPResponse
  >>> HTTPResponse.status_codes['error'] = 200
  >>> HTTPResponse.status_codes['connectionrefusederror'] = 200
  >>> import transaction
  >>> transaction.commit()
  >>> browser = Browser(layer['app'])
  >>> browser.open('http://nohost/plone/')
  >>> form = browser.getForm(name='searchform', index=0)
  >>> browser.getControl(name='SearchableText', index=0).value = 'Welcome'
  >>> form.submit()

As a last thing we need to activate a monkey patch, which will make the
publisher check for and render browser views registered for specific
exceptions. A normal plone instance would apply the patch automatically as
its part of the `plone.app.linkintegrity` package. However, in a test
environment that package isn't loaded by default, so we patch manually:

  >>> try:
  ...     from plone.app.linkintegrity.monkey import installExceptionHook
  ...     installExceptionHook()   # Plone 4.3
  ... except ImportError:
  ...     pass   # no longer needed for Plone 5.x

Now we can finally try to search again:

  >>> browser.reload()
  >>> browser.contents
  '...Solr Error...
   ...The Solr server is currently unreachable, so no results could be found...
   ...Please try again later or contact your system administration...'

Next let's set up a fake server which will respond rather slowly, so that a
timeout error is provoked:

  >>> from collective.solr.tests.utils import fakeServer
  >>> from time import sleep
  >>> def action(handler):
  ...   sleep(1.0)
  >>> thread = fakeServer([action, action], port=55555)
  >>> config.search_timeout = 0.7
  >>> transaction.commit()

And try the search again:

  >>> form = browser.getForm(name='searchform', index=0)
  >>> try:
  ...     browser.getControl(name='SearchableText', index=0).value = 'Welcome'
  ...     form.submit()
  ... except Exception:
  ...     pass
  >>> browser.contents
  '...Solr Error...
   ...The request to the Solr server has timed out.  No search results could be found...
   ...Please try again later or contact your system administration...'

In the end we need to clean up our changes to avoid having ill effects on
other tests:

  >>> getUtility(ISolrConnectionManager).setHost(active=False, port=port)

# FIXME: How is this supposed to clean anything up?
# It does work on python 2 when importing directly from urllib but breaks with
# python 3 and/or six.
#  >>> from six.moves.urllib.request import urlopen
#  >>> urlopen('http://127.0.0.1:55555')
#  Traceback (most recent call last):
#  ...[Errno 104]...
#  >>> urlopen('http://127.0.0.1:55555')
#  Traceback (most recent call last):
#  ...[Errno 104]...

  >>> thread.join()
