This is a generic set of tests for Storages implementing
``IUserRating``.  When a user rates an object their rating is
associated with their user id and they can later change or remove it.
Anonymous users may also rate content (though this can be disabled
globally).  Setting or retrieving a rating yields an IRating object.
The average of all ratings and the number of ratings can be easily
obtained::

  >>> from datetime import datetime
  >>> rating = storage()
  >>> my_rating = rating.rate(6, 'me')
  >>> your_rating = rating.rate(8, 'you')
  >>> my_rating  is rating.userRating('me')
  True
  >>> float(my_rating)
  6.0
  >>> isinstance(my_rating.timestamp, datetime)
  True
  >>> my_rating.userid
  'me'
  >>> your_rating is rating.userRating('you')
  True
  >>> float(your_rating)
  8.0
  >>> my_rating.timestamp < your_rating.timestamp
  True
  >>> your_rating.userid
  'you'

We can get the average rating and number of ratings as well::

  >>> rating.averageRating
  7.0
  >>> rating.numberOfRatings
  2

There's also a most_recent rating property::

  >>> rating.most_recent is your_rating
  True

We can update our user ratings, and the average will change accordingly::

  >>> my_rating = rating.rate(4, 'me')
  >>> float(my_rating)
  4.0
  >>> rating.numberOfRatings
  2
  >>> rating.averageRating
  6.0
  >>> my_rating is rating.userRating('me')
  True
  >>> rating.most_recent is my_rating
  True

Anonymous ratings are a little trickier as they are averaged for all
anonymous entries::

  >>> rating.userRating() is None
  True
  >>> anon_rating = rating.rate(9)
  >>> float(anon_rating)
  9.0
  >>> rating.numberOfRatings
  3
  >>> rating.averageRating
  7.0

The anoymous rating is the average of all anonymous ratings::

  >>> float(rating.userRating())
  9.0

Once we add more anonymous ratings the value becomes the average of the
anonymous ratings::

  >>> anon_rating = rating.rate(7)
  >>> float(anon_rating)
  7.0
  >>> anon_rating = rating.userRating()
  >>> float(anon_rating)
  8.0
  >>> print anon_rating.userid
  None
  >>> rating.averageRating
  7.0
  >>> rating.numberOfRatings
  4

We can also get all the user ratings:

  >>> user_ratings = list(rating.all_user_ratings())
  >>> len(user_ratings)
  2
  >>> float(user_ratings[0])
  4.0
  >>> user_ratings[0].userid
  'me'
  >>> float(user_ratings[1])
  8.0
  >>> user_ratings[1].userid
  'you'

We can include Anonymous ratings as well, the first results
should be identical to the user rating results:

  >>> all_ratings = list(rating.all_user_ratings(True))
  >>> len(all_ratings)
  4
  >>> all_ratings[0] is user_ratings[0]
  True
  >>> all_ratings[1] is user_ratings[1]
  True
  >>> float(all_ratings[2])
  9.0
  >>> print all_ratings[2].userid
  None
  >>> float(all_ratings[3])
  7.0
  >>> print all_ratings[2].userid
  None

There's also a mechanism for quickly obtaining the list of
users who have rated an object::

  >>> list(rating.all_raters())
  ['me', 'you']


We can also remove a rating for a particular user::

  >>> rating.remove_rating('me')
  >>> rating.numberOfRatings
  3
  >>> rating.averageRating
  8.0
  >>> list(rating.all_raters())
  ['you']

It also respects the most_recent property::

  >>> my_rating = rating.rate(5.0, 'me')
  >>> rating.most_recent.userid
  'me'
  >>> rating.remove_rating('me')
  >>> print rating.most_recent.userid
  None
  >>> float(rating.most_recent)
  7.0

Even if there's only one rating, removing the rating will not cause
any trouble::

  >>> new_rating = storage()
  >>> my_rating = new_rating.rate(5.0, 'me')
  >>> new_rating.remove_rating('me')
  >>> print new_rating.most_recent
  None
  >>> new_rating.averageRating
  0.0
  >>> new_rating.numberOfRatings
  0

There's also a mechanism for passing a user/browser specific key when
rating anonymously, which can be checked by a view to restrict
multiple anonymous ratings.  We check here that the timestamp set for
the rating is in the very near past (1 sec)::

  >>> from datetime import datetime, timedelta
  >>> anon_rating = rating.rate(7, session_key='random')
  >>> timestamp = rating.last_anon_rating('random')
  >>> datetime.utcnow() - timestamp < timedelta(0,1)
  True
  >>> datetime.utcnow() > timestamp
  True

This session_key ignored when rating with a userid:

  >>> anon_rating = rating.rate(8, 'them', session_key='random2')
  >>> timestamp2 = rating.last_anon_rating('random2')
  >>> timestamp2 is None
  True
  >>> anon_rating = rating.rate(8, 'them', session_key='random')
  >>> timestamp2 = rating.last_anon_rating('random')
  >>> timestamp2 is timestamp
  True
