diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2010-07-31 21:10:59 -0400 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2010-07-31 21:10:59 -0400 |
commit | bdbbe34c030aad6aa72a925a7fae3037b55bb7d3 (patch) | |
tree | c333425f7315070cb165263bbfe1a6b12f8b29c1 | |
parent | abb6ed03bac2464e0ca0e7473b1351d8c3c73db6 (diff) | |
download | askbot-bdbbe34c030aad6aa72a925a7fae3037b55bb7d3.tar.gz askbot-bdbbe34c030aad6aa72a925a7fae3037b55bb7d3.tar.bz2 askbot-bdbbe34c030aad6aa72a925a7fae3037b55bb7d3.zip |
added permission tests for voting, split tests.py into several files
-rw-r--r-- | askbot/auth.py | 47 | ||||
-rw-r--r-- | askbot/conf/minimum_reputation.py | 9 | ||||
-rw-r--r-- | askbot/models/__init__.py | 473 | ||||
-rw-r--r-- | askbot/models/content.py | 3 | ||||
-rw-r--r-- | askbot/models/meta.py | 3 | ||||
-rw-r--r-- | askbot/models/signals.py | 2 | ||||
-rw-r--r-- | askbot/templatetags/extra_filters.py | 36 | ||||
-rw-r--r-- | askbot/tests.py | 1839 | ||||
-rw-r--r-- | askbot/tests/__init__.py | 4 | ||||
-rw-r--r-- | askbot/tests/email_alert_tests.py | 643 | ||||
-rw-r--r-- | askbot/tests/on_screen_notification_tests.py | 709 | ||||
-rw-r--r-- | askbot/tests/page_load_tests.py | 273 | ||||
-rw-r--r-- | askbot/tests/permission_assertion_tests.py | 426 | ||||
-rw-r--r-- | askbot/tests/utils.py | 30 | ||||
-rw-r--r-- | askbot/views/commands.py | 13 |
15 files changed, 2511 insertions, 1999 deletions
diff --git a/askbot/auth.py b/askbot/auth.py index 809fc708..6a92b005 100644 --- a/askbot/auth.py +++ b/askbot/auth.py @@ -1,8 +1,11 @@ """ Authorisation related functions. -The actions a User is authorised to perform are dependent on their reputation -and superuser status. +This entire module will be removed some time in +the future + +Many of these functions are being replaced with assertions: +User.assert_can... """ import datetime from django.utils.translation import ugettext as _ @@ -15,18 +18,6 @@ import logging from askbot.conf import settings as askbot_settings -def can_vote_up(user): - """Determines if a User can vote Questions and Answers up.""" - if user.is_authenticated(): - if user.reputation >= askbot_settings.MIN_REP_TO_VOTE_UP: - if user.is_blocked(): - return False - else: - return True - if user.is_administrator() or user.is_moderator(): - return True - return False - def can_flag_offensive(user): """Determines if a User can flag Questions and Answers as offensive.""" return user.is_authenticated() and ( @@ -47,15 +38,6 @@ def can_add_comments(user, subject): return True return False -def can_vote_down(user): - """Determines if a User can vote Questions and Answers down.""" - if user.is_authenticated(): - if user.reputation >= askbot_settings.MIN_REP_TO_VOTE_DOWN: - return True - if user.is_superuser: - return True - return False - def can_retag_questions(user): """Determines if a User can retag Questions.""" if user.is_authenticated(): @@ -131,23 +113,6 @@ def can_reopen_question(user, question): return True return False -def can_delete_post(user, post): - if user.is_superuser: - return True - elif user.is_authenticated() and user == post.author: - if isinstance(post, Answer): - return True - elif isinstance(post, Question): - answers = post.answers.all() - for answer in answers: - if user != answer.author and answer.deleted == False: - return False - return True - else: - return False - else: - return False - def can_view_deleted_post(user, post): return user.is_superuser @@ -541,7 +506,7 @@ def onDeleted(post, user, timestamp=None): elif isinstance(post, Answer): Question.objects.update_answer_count(post.question) logging.debug('updated answer count to %d' % post.question.answer_count) - signals.delete_post_or_answer.send( + signals.delete_question_or_answer.send( sender=post.__class__, instance=post, delete_by=user diff --git a/askbot/conf/minimum_reputation.py b/askbot/conf/minimum_reputation.py index d7044911..59726981 100644 --- a/askbot/conf/minimum_reputation.py +++ b/askbot/conf/minimum_reputation.py @@ -60,6 +60,15 @@ settings.register( settings.register( IntegerValue( MIN_REP, + 'MIN_REP_TO_DELETE_OTHERS_POSTS', + default=5000, + description=_('Delete questions and answers posted by others') + ) + ) + +settings.register( + IntegerValue( + MIN_REP, 'MIN_REP_TO_UPLOAD_FILES', default=60, description=_('Upload files') diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 9d4d6db7..e75593b5 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -33,6 +33,12 @@ from askbot.startup_tests import run_startup_tests run_startup_tests() +class InsufficientReputation(exceptions.PermissionDenied): + """exception class to indicate that permission + was denied due to insufficient reputation + """ + pass + User.add_to_class( 'status', models.CharField( @@ -72,52 +78,6 @@ User.add_to_class('tag_filter_setting', ) User.add_to_class('response_count', models.IntegerField(default=0)) - -def user_assert_can_vote_for_post( - self, - post = None, - direction = None, - ): - """raises exceptions.PermissionDenied exception - if user can't in fact upvote - - :param:direction can be 'up' or 'down' - :param:post can be instance of question or answer - """ - if self.is_blocked(): - raise exceptions.PermissionDenied( - _( - 'Sorry your account appears to be blocked ' + - 'and you cannot vote - please contact the ' + - 'site administrator to resolve the issue' - ) - ) - if self.is_suspended(): - raise exceptions.PermissionDenied( - _( - 'Sorry your account appears to be suspended ' + - 'and you cannot vote - please contact the ' + - 'site administrator to resolve the issue' - ) - ) - if self.is_moderator() or self.is_administrator(): - return - - if direction == 'up': - if self.reputation < askbot_settings.MIN_REP_TO_VOTE_UP: - msg = _(">%(points)s points required to upvote") % \ - {'points': askbot_settings.MIN_REP_TO_VOTE_UP} - raise exceptions.PermissionDenied(msg) - else: - if self.reputation < askbot_settings.MIN_REP_TO_VOTE_DOWN: - msg = _(">%(points)s points required to downvote") % \ - {'points': askbot_settings.MIN_REP_TO_VOTE_DOWN} - raise exceptions.PermissionDenied(msg) - - if self == post.author: - raise exceptions.PermissionDenied('cannot vote for own posts') - - def user_get_old_vote_for_post(self, post): """returns previous vote for this post by the user or None, if does not exist @@ -139,29 +99,104 @@ def user_get_old_vote_for_post(self, post): return old_votes[0] -def user_assert_can_upload_file(request_user): +def _assert_user_can( + user = None, + post = None, #related post (may be parent) + owner_can = False, + blocked_error_message = None, + suspended_error_message = None, + min_rep_setting = None, + low_rep_error_message = None, + ): + """generic helper assert for use in several + User.assert_can_XYZ() calls regarding changing content - if request_user.is_authenticated(): - if request_user.is_suspended(): - raise exceptions.PermissionDenied( - _('Sorry, suspended users cannot upload files') - ) - elif request_user.is_blocked(): - raise exceptions.PermissionDenied( - _('Sorry, blocked users cannot upload files') - ) - elif request_user.is_moderator(): - return - elif request_user.is_administrator(): - return - if request_user.reputation < askbot_settings.MIN_REP_TO_UPLOAD_FILES: - msg = _( - 'uploading images is limited to users ' + - 'with >%(min_rep)s reputation points' - ) % {'min_rep': askbot_settings.MIN_REP_TO_UPLOAD_FILES } - raise exceptions.PermissionDenied(msg) + user is required and at least one error message + + if assertion fails, method raises exception.PermissionDenied + with appropriate text as a payload + """ + if blocked_error_message and user.is_blocked(): + error_message = blocked_error_message + elif post and owner_can and user == post.get_owner(): + return + elif suspended_error_message and user.is_suspended(): + error_message = suspended_error_message + elif user.is_administrator() or user.is_moderator(): + return + elif low_rep_error_message and user.reputation < min_rep_setting: + error_message = low_rep_error_message % {'min_rep': min_rep_setting} + raise InsufficientReputation(error_message) else: - raise exceptions.PermissionDenied(_('Sorry, anonymous users cannot upload files')) + return + raise exceptions.PermissionDenied(error_message) + + +def user_assert_can_vote_for_post( + self, + post = None, + direction = None, + ): + """raises exceptions.PermissionDenied exception + if user can't in fact upvote + + :param:direction can be 'up' or 'down' + :param:post can be instance of question or answer + """ + + if self == post.author: + raise exceptions.PermissionDenied('cannot vote for own posts') + + blocked_error_message = _( + 'Sorry your account appears to be blocked ' + + 'and you cannot vote - please contact the ' + + 'site administrator to resolve the issue' + ), + suspended_error_message = _( + 'Sorry your account appears to be suspended ' + + 'and you cannot vote - please contact the ' + + 'site administrator to resolve the issue' + ) + + assert(direction in ('up', 'down')) + + if direction == 'up': + min_rep_setting = askbot_settings.MIN_REP_TO_VOTE_UP + low_rep_error_message = _( + ">%(points)s points required to upvote" + ) % \ + {'points': askbot_settings.MIN_REP_TO_VOTE_UP} + else: + min_rep_setting = askbot_settings.MIN_REP_TO_VOTE_DOWN + low_rep_error_message = _( + ">%(points)s points required to downvote" + ) % \ + {'points': askbot_settings.MIN_REP_TO_VOTE_DOWN} + + _assert_user_can( + user = self, + blocked_error_message = blocked_error_message, + suspended_error_message = suspended_error_message, + min_rep_setting = min_rep_setting, + low_rep_error_message = low_rep_error_message + ) + + +def user_assert_can_upload_file(request_user): + + blocked_error_message = _('Sorry, blocked users cannot upload files') + suspended_error_message = _('Sorry, suspended users cannot upload files') + low_rep_error_message = _( + 'uploading images is limited to users ' + \ + 'with >%(min_rep)s reputation points' + ) % {'min_rep': askbot_settings.MIN_REP_TO_UPLOAD_FILES } + + _assert_user_can( + user = request_user, + suspended_error_message = suspended_error_message, + min_rep_setting = askbot_settings.MIN_REP_TO_UPLOAD_FILES, + low_rep_error_message = low_rep_error_message + ) def user_assert_can_post_question(self): @@ -169,45 +204,226 @@ def user_assert_can_post_question(self): text that has the reason for the denial """ - if self.is_suspended(): - raise exceptions.PermissionDenied(_('suspended users cannot post')) - if self.is_blocked(): - raise exceptions.PermissionDenied(_('blocked users cannot post')) + _assert_user_can( + user = self, + blocked_error_message = _('blocked users cannot post'), + suspended_error_message = _('suspended users cannot post'), + ) + def user_assert_can_post_answer(self): """same as user_can_post_question """ self.assert_can_post_question() -def user_assert_can_post_comment(self, parent_post): + +def user_assert_can_post_comment(self, parent_post = None): """raises exceptions.PermissionDenied if user cannot post comment the reason will be in text of exception """ - if self.is_authenticated(): - if self.is_blocked(): - error_message = _('blocked users cannot post') - elif self == parent_post.author: - return - elif self.is_suspended(): - error_message = _( - 'Sorry, since your account is suspended ' + \ - 'you can comment only you own posts' - ) - elif self.is_administrator() or self.is_moderator(): - return - elif self.reputation < askbot_settings.MIN_REP_TO_LEAVE_COMMENTS: - error_message = _( - 'Sorry, to comment any post a minimum reputation of ' + \ - '%(min_rep)s points is required.' - ) % {'min_rep': askbot_settings.MIN_REP_TO_LEAVE_COMMENTS} - else: - return - else: - error_message = _('anonymous users cannot comment %(sign_in_url)s') % \ - {'sign_in_url': reverse('user_signin')} - raise exceptions.PermissionDenied(error_message) + + suspended_error_message = _( + 'Sorry, since your account is suspended ' + \ + 'you can comment only your own posts' + ) + low_rep_error_message = _( + 'Sorry, to comment any post a minimum reputation of ' + \ + '%(min_rep)s points is required.' + ) % {'min_rep': askbot_settings.MIN_REP_TO_LEAVE_COMMENTS} + + try: + _assert_user_can( + user = self, + post = parent_post, + owner_can = True, + blocked_error_message = _('blocked users cannot post'), + suspended_error_message = suspended_error_message, + min_rep_setting = askbot_settings.MIN_REP_TO_LEAVE_COMMENTS, + low_rep_error_message = low_rep_error_message, + ) + except InsufficientReputation, e: + if isinstance(parent_post, Answer): + if self == parent_post.question.author: + return + raise e + + +def user_assert_can_edit_post(self, post = None): + """assertion that raises exceptions.PermissionDenied + when user is not authorised to edit this post + """ + + blocked_error_message = _( + 'Sorry, since your account is blocked ' + \ + 'you cannot edit posts' + ) + suspended_error_message = _( + 'Sorry, since your account is suspended ' + \ + 'you can edit only your own posts' + ) + low_rep_error_message = _( + 'Sorry, to edit other people\' posts, a minimum ' + \ + 'reputation of %(min_rep)s is required' + ) % \ + {'min_rep': askbot_settings.MIN_REP_TO_EDIT_OTHERS_POSTS} + min_rep_setting = askbot_settings.MIN_REP_TO_EDIT_OTHERS_POSTS + + _assert_user_can( + user = self, + post = post, + owner_can = True, + blocked_error_message = blocked_error_message, + suspended_error_message = suspended_error_message, + low_rep_error_message = low_rep_error_message, + min_rep_setting = min_rep_setting + ) + + +def user_assert_can_delete_post(self, post = None): + #todo: move here advanced logic to authorize deletion + blocked_error_message = _( + 'Sorry, since your account is blocked ' + \ + 'you cannot delete posts' + ) + suspended_error_message = _( + 'Sorry, since your account is suspended ' + \ + 'you can delete only your own posts' + ) + low_rep_error_message = _( + 'Sorry, to deleted other people\' posts, a minimum ' + \ + 'reputation of %(min_rep)s is required' + ) % \ + {'min_rep': askbot_settings.MIN_REP_TO_DELETE_OTHERS_POSTS} + min_rep_setting = askbot_settings.MIN_REP_TO_DELETE_OTHERS_POSTS + + _assert_user_can( + user = self, + post = post, + owner_can = True, + blocked_error_message = blocked_error_message, + suspended_error_message = suspended_error_message, + low_rep_error_message = low_rep_error_message, + min_rep_setting = min_rep_setting + ) + + +def user_assert_can_delete_question(self, question = None): + self.assert_can_delete_post(post = question) + + +def user_assert_can_delete_answer(self, answer = None): + self.assert_can_delete_post(post = answer) + + +def user_assert_can_close_question(self, question = None): + assert(isinstance(question, Question) == True) + blocked_error_message = _( + 'Sorry, since your account is blocked ' + \ + 'you cannot close questions' + ) + suspended_error_message = _( + 'Sorry, since your account is suspended ' + \ + 'you cannot close questions' + ) + low_rep_error_message = _( + 'Sorry, to deleted other people\' posts, a minimum ' + \ + 'reputation of %(min_rep)s is required' + ) % \ + {'min_rep': askbot_settings.MIN_REP_TO_CLOSE_OTHERS_QUESTIONS} + min_rep_setting = askbot_settings.MIN_REP_TO_CLOSE_OTHERS_QUESTIONS + + _assert_user_can( + user = self, + post = post, + blocked_error_message = blocked_error_message, + suspended_error_message = suspended_error_message, + low_rep_error_message = low_rep_error_message, + min_rep_setting = min_rep_setting + ) + + +def user_assert_can_flag_offensive(self): + + blocked_error_message = _( + 'Sorry, since your account is blocked ' + \ + 'you cannot flag posts as offensive' + ) + suspended_error_message = _( + 'Sorry, since your account is suspended ' + \ + 'you cannot flag posts as offensive' + ) + low_rep_error_message = _( + 'Sorry, to flag posts as offensive a minimum ' + \ + 'reputation of %(min_rep)s is required' + ) % \ + {'min_rep': askbot_settings.MIN_REP_TO_FLAG_OFFENSIVE} + min_rep_setting = askbot_settings.MIN_REP_TO_FLAG_OFFENSIVE + + _assert_user_can( + user = self, + post = post, + blocked_error_message = blocked_error_message, + suspended_error_message = suspended_error_message, + low_rep_error_message = low_rep_error_message, + min_rep_setting = min_rep_setting + ) + + +def user_assert_can_retag_questions(self): + + blocked_error_message = _( + 'Sorry, since your account is blocked ' + \ + 'you cannot retag questions' + ) + suspended_error_message = _( + 'Sorry, since your account is suspended ' + \ + 'you can retag only your own questions' + ) + low_rep_error_message = _( + 'Sorry, to retag questions a minimum ' + \ + 'reputation of %(min_rep)s is required' + ) % \ + {'min_rep': askbot_settings.MIN_REP_TO_RETAG_QUESTIONS} + min_rep_setting = askbot_settings.MIN_REP_TO_RETAG_QUESTIONS + + _assert_user_can( + user = self, + post = post, + owner_can = True, + blocked_error_message = blocked_error_message, + suspended_error_message = suspended_error_message, + low_rep_error_message = low_rep_error_message, + min_rep_setting = min_rep_setting + ) + + +def user_assert_can_delete_comment(self, comment = None): + blocked_error_message = _( + 'Sorry, since your account is blocked ' + \ + 'you cannot delete comment' + ) + suspended_error_message = _( + 'Sorry, since your account is suspended ' + \ + 'you can delete only your own comments' + ) + low_rep_error_message = _( + 'Sorry, to delete comments ' + \ + 'reputation of %(min_rep)s is required' + ) % \ + {'min_rep': askbot_settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS} + min_rep_setting = askbot_settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS + + _assert_user_can( + user = self, + post = comment, + owner_can = True, + blocked_error_message = blocked_error_message, + suspended_error_message = suspended_error_message, + low_rep_error_message = low_rep_error_message, + min_rep_setting = min_rep_setting + ) def user_assert_can_revoke_old_vote(self, vote): @@ -262,15 +478,49 @@ def user_post_comment( #print len(Comment.objects.all()) return comment +def user_delete_comment( + self, + comment = None, + timestamp = None + ): + self.assert_can_delete_comment(comment = comment) + comment.delete() + +def user_delete_answer( + self, + answer = None, + timestamp = None + ): + self.assert_can_delete_answer(answer = answer) + #todo - move onDeleted method where appropriate + auth.onDeleted(answer, self, timestamp = timestamp) + +def user_delete_question( + self, + question = None, + timestamp = None + ): + self.assert_can_delete_question(question = question) + #todo - move onDeleted method to a fitting class + auth.onDeleted(question, self, timestamp = timestamp) + def user_delete_post( self, post = None, timestamp = None ): + """generic delete method for all kinds of posts + + if there is no use cases for it, the method will be removed + """ if isinstance(post, Comment): - post.delete() + self.delete_comment(comment = post, timestamp = timestamp) + elif isinstance(post, Answer): + self.delete_answer(answer = post, timestamp = timestamp) + elif isinstance(post, Question): + self.delete_question(question = post, timestamp = timestamp) else: - auth.onDeleted(post, self, timestamp = timestamp) + raise TypeError('either Comment, Question or Answer expected') def user_post_question( self, @@ -433,7 +683,12 @@ def user_set_status(self, new_status): if new status is applied to user, then the record is committed to the database """ - assert(new_status in ('m','s','b','w','a')) + #m - moderator + #s - suspended + #b - blocked + #w - watched + #a - approved (regular user) + assert(new_status in ('m', 's', 'b', 'w', 'a')) if new_status == self.status: return @@ -747,14 +1002,28 @@ User.add_to_class('can_moderate_user', user_can_moderate_user) User.add_to_class('moderate_user_reputation', user_moderate_user_reputation) User.add_to_class('set_status', user_set_status) User.add_to_class('get_status_display', user_get_status_display) -User.add_to_class('assert_can_vote_for_post', user_assert_can_vote_for_post) User.add_to_class('get_old_vote_for_post', user_get_old_vote_for_post) +User.add_to_class('get_unused_votes_today', user_get_unused_votes_today) +User.add_to_class('delete_comment', user_delete_comment) +User.add_to_class('delete_question', user_delete_question) +User.add_to_class('delete_answer', user_delete_answer) + +#assertions +User.add_to_class('assert_can_vote_for_post', user_assert_can_vote_for_post) User.add_to_class('assert_can_revoke_old_vote', user_assert_can_revoke_old_vote) User.add_to_class('assert_can_upload_file', user_assert_can_upload_file) User.add_to_class('assert_can_post_question', user_assert_can_post_question) User.add_to_class('assert_can_post_answer', user_assert_can_post_answer) User.add_to_class('assert_can_post_comment', user_assert_can_post_comment) -User.add_to_class('get_unused_votes_today', user_get_unused_votes_today) +User.add_to_class('assert_can_edit_post', user_assert_can_edit_post) +User.add_to_class('assert_can_close_question', user_assert_can_close_question) +User.add_to_class('assert_can_flag_offensive', user_assert_can_flag_offensive) +User.add_to_class('assert_can_retag_questions', user_assert_can_retag_questions) +#todo: do we need assert_can_delete_post +User.add_to_class('assert_can_delete_post', user_assert_can_delete_post) +User.add_to_class('assert_can_delete_comment', user_assert_can_delete_comment) +User.add_to_class('assert_can_delete_answer', user_assert_can_delete_answer) +User.add_to_class('assert_can_delete_question', user_assert_can_delete_question) #todo: move this to askbot/utils ?? def format_instant_notification_body( @@ -1144,8 +1413,8 @@ django_signals.post_save.connect( django_signals.post_delete.connect(record_cancel_vote, sender=Vote) #change this to real m2m_changed with Django1.2 -signals.delete_post_or_answer.connect(record_delete_question, sender=Question) -signals.delete_post_or_answer.connect(record_delete_question, sender=Answer) +signals.delete_question_or_answer.connect(record_delete_question, sender=Question) +signals.delete_question_or_answer.connect(record_delete_question, sender=Answer) signals.mark_offensive.connect(record_mark_offensive, sender=Question) signals.mark_offensive.connect(record_mark_offensive, sender=Answer) signals.tags_updated.connect(record_update_tags, sender=Question) diff --git a/askbot/models/content.py b/askbot/models/content.py index 57f9ee97..c439b4dc 100644 --- a/askbot/models/content.py +++ b/askbot/models/content.py @@ -195,6 +195,9 @@ class Content(models.Model): else: return self.added_at + def get_owner(self): + return self.author + def get_author_list( self, include_comments = False, diff --git a/askbot/models/meta.py b/askbot/models/meta.py index c10bb8fe..83e82933 100644 --- a/askbot/models/meta.py +++ b/askbot/models/meta.py @@ -125,6 +125,9 @@ class Comment(base.MetaContent, base.UserContent): def set_text(self, text): self.comment = text + def get_owner(self): + return self.user + def get_updated_activity_data(self, created = False): if self.content_object.__class__.__name__ == 'Question': return const.TYPE_ACTIVITY_COMMENT_QUESTION, self diff --git a/askbot/models/signals.py b/askbot/models/signals.py index b4ed0d1b..1c5324c3 100644 --- a/askbot/models/signals.py +++ b/askbot/models/signals.py @@ -6,7 +6,7 @@ tags_updated = django.dispatch.Signal(providing_args=['question']) edit_question_or_answer = django.dispatch.Signal( providing_args=['instance', 'modified_by'] ) -delete_post_or_answer = django.dispatch.Signal( +delete_question_or_answer = django.dispatch.Signal( providing_args=['instance', 'deleted_by'] ) mark_offensive = django.dispatch.Signal(providing_args=['instance', 'mark_by']) diff --git a/askbot/templatetags/extra_filters.py b/askbot/templatetags/extra_filters.py index 1418f535..a8884895 100644 --- a/askbot/templatetags/extra_filters.py +++ b/askbot/templatetags/extra_filters.py @@ -1,5 +1,7 @@ from django import template +from django.core import exceptions from askbot import auth +from askbot import models from askbot.deps.grapefruit import Color from django.utils.translation import ugettext as _ import logging @@ -18,20 +20,12 @@ def can_moderate_user(user, other_user): return False @register.filter -def can_vote_up(user): - return auth.can_vote_up(user) - -@register.filter def can_flag_offensive(user): return auth.can_flag_offensive(user) @register.filter -def can_add_comments(user,subject): - return auth.can_add_comments(user,subject) - -@register.filter -def can_vote_down(user): - return auth.can_vote_down(user) +def can_add_comments(user, subject): + return auth.can_add_comments(user, subject) @register.filter def can_retag_questions(user): @@ -43,7 +37,13 @@ def can_edit_post(user, post): @register.filter def can_delete_comment(user, comment): - return auth.can_delete_comment(user, comment) + if user.is_anonymous(): + return False + try: + user.assert_can_delete_comment(comment) + return True + except exceptions.PermissionDenied: + return False @register.filter def can_view_offensive_flags(user): @@ -67,7 +67,19 @@ def can_reopen_question(user, question): @register.filter def can_delete_post(user, post): - return auth.can_delete_post(user, post) + if user.is_anonymous(): + return False + try: + if isinstance(post, models.Question): + user.assert_can_delete_question(question = post) + return True + elif isinstance(post, models.Answer): + user.assert_can_delete_answer(answer = post) + return True + else: + return False + except exceptions.PermissionDenied: + return False @register.filter def can_view_user_edit(request_user, target_user): diff --git a/askbot/tests.py b/askbot/tests.py deleted file mode 100644 index 0dbdafcb..00000000 --- a/askbot/tests.py +++ /dev/null @@ -1,1839 +0,0 @@ -""" -.. _tests: - -:mod:`tests` -- Module for testing Askbot -========================================== - -.. automodule:: tests - .. moduleauthor:: Evgeny Fadeev <evgeny.fadeev@gmail.com> -""" -import copy -import datetime -import time -import functools -import django.core.mail -from django.core import exceptions -from django.conf import settings as django_settings -from django.test import TestCase -from django.template import defaultfilters -from django.core import management -from django.core.urlresolvers import reverse -from django.contrib.auth.models import AnonymousUser -from askbot.models import User, Question, Answer, Activity, Comment -from askbot.models import EmailFeedSetting -from askbot import const -from askbot.conf import settings as askbot_settings - -def email_alert_test(test_func): - """decorator for test methods in EmailAlertTests - wraps tests with a generic sequence of testing - email alerts on updates to anything relating to - given question - """ - @functools.wraps(test_func) - def wrapped_test(test_object, *args, **kwargs): - func_name = test_func.__name__ - if func_name.startswith('test_'): - test_name = func_name.replace('test_', '', 1) - test_func(test_object) - test_object.maybe_visit_question() - test_object.send_alerts() - test_object.check_results(test_name) - else: - raise ValueError('test method names must have prefix "test_"') - return wrapped_test - -def setup_email_alert_tests(setup_func): - @functools.wraps(setup_func) - def wrapped_setup(test_object, *args, **kwargs): - #empty email subscription schedule - #no email is sent - test_object.notification_schedule = copy.deepcopy(EmailFeedSetting.NO_EMAIL_SCHEDULE) - #timestamp to use for the setup - #functions - test_object.setup_timestamp = datetime.datetime.now() - - #timestamp to use for the question visit - #by the target user - #if this timestamp is None then there will be no visit - #otherwise question will be visited by the target user - #at that time - test_object.visit_timestamp = None - - #dictionary to hols expected results for each test - #actual data@is initialized in the code just before the function - #or in the body of the subclass - test_object.expected_results = dict() - - #do not follow by default (do not use q_sel type subscription) - test_object.follow_question = False - - #fill out expected result for each test - test_object.expected_results['q_ask'] = {'message_count': 0, } - test_object.expected_results['q_ask_delete_answer'] = {'message_count': 0, } - test_object.expected_results['question_comment'] = {'message_count': 0, } - test_object.expected_results['question_comment_delete'] = {'message_count': 0, } - test_object.expected_results['answer_comment'] = {'message_count': 0, } - test_object.expected_results['answer_delete_comment'] = {'message_count': 0, } - test_object.expected_results['mention_in_question'] = {'message_count': 0, } - test_object.expected_results['mention_in_answer'] = {'message_count': 0, } - test_object.expected_results['question_edit'] = {'message_count': 0, } - test_object.expected_results['answer_edit'] = {'message_count': 0, } - test_object.expected_results['question_and_answer_by_target'] = {'message_count': 0, } - test_object.expected_results['q_ans'] = {'message_count': 0, } - test_object.expected_results['q_ans_new_answer'] = {'message_count': 0, } - - #this function is expected to contain a difference between this - #one and the desired setup within the concrete test - setup_func(test_object) - #must call this after setting up the notification schedule - #because it is needed in setUpUsers() function - test_object.setUpUsers() - return wrapped_setup - -def create_user( - username = None, - email = None, - notification_schedule = None, - date_joined = None - ): - """Creates a user and sets default update subscription - settings""" - user = User.objects.create_user(username, email) - if date_joined is not None: - user.date_joined = date_joined - user.save() - if notification_schedule == None: - notification_schedule = EmailFeedSetting.NO_EMAIL_SCHEDULE - - for feed_type, frequency in notification_schedule.items(): - feed = EmailFeedSetting( - feed_type = feed_type, - frequency = frequency, - subscriber = user - ) - feed.save() - return user - -def get_re_notif_after(timestamp): - notifications = Activity.objects.filter( - activity_type__in = const.RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY, - active_at__gte = timestamp - ) - return notifications - -class CommentPermissionAssertionTests(TestCase): - - def setUp(self): - self.user = create_user( - username = 'test', - email = 'test@test.com' - ) - self.min_rep = askbot_settings.MIN_REP_TO_LEAVE_COMMENTS - - def create_other_user(self): - return create_user( - username = 'other', - email = 'other@test.com' - ) - - def post_question(self, author = None): - if author is None: - author = self.user - return author.post_question( - title = 'test question title', - body_text = 'test question body', - tags = 'test' - ) - - def post_answer(self, question = None, author = None): - if author is None: - author = self.user - return author.post_answer( - question = question, - body_text = 'test answer' - ) - - def test_blocked_user_cannot_comment_own_question(self): - question = self.post_question() - - self.user.set_status('b') - self.assertRaises( - exceptions.PermissionDenied, - self.user.post_comment, - parent_post = question, - body_text = 'test comment' - ) - - def test_blocked_user_cannot_comment_own_answer(self): - question = self.post_question() - answer = self.post_answer(question) - - self.user.set_status('b') - - self.assertRaises( - exceptions.PermissionDenied, - self.user.post_comment, - parent_post = answer, - body_text = 'test comment' - ) - - def test_low_rep_user_cannot_comment_others(self): - other_user = create_user( - username = 'other', - email = 'other@test.com' - ) - question = self.post_question( - author = other_user - ) - assert(self.user.reputation < self.min_rep) - self.assertRaises( - exceptions.PermissionDenied, - self.user.post_comment, - parent_post = question, - body_text = 'test comment' - ) - - def test_low_rep_user_can_comment_others_answer_to_own_question(self): - question = self.post_question() - assert(self.user.reputation < self.min_rep) - other_user = self.create_other_user() - answer = other_user.post_answer( - question = question, - body_text = 'test answer' - ) - comment = other_user.post_comment( - parent_post = answer, - body_text = 'test comment' - ) - self.assertTrue(isinstance(comment, Comment)) - - def test_high_rep_user_can_comment(self): - other_user = self.create_other_user() - question = self.post_question( - author = other_user - ) - self.user.reputation = self.min_rep - comment = self.user.post_comment( - parent_post = question, - body_text = 'test comment' - ) - self.assertTrue(isinstance(comment, Comment)) - - def test_suspended_user_cannot_comment_others_question(self): - other_user = self.create_other_user() - question = self.post_question(author = other_user) - self.user.set_status('s') - self.assertRaises( - exceptions.PermissionDenied, - self.user.post_comment, - parent_post = question, - body_text = 'test comment' - ) - - def test_suspended_user_can_comment_own_question(self): - question = self.post_question() - self.user.set_status('s') - comment = self.user.post_comment( - parent_post = question, - body_text = 'test comment' - ) - self.assertTrue(isinstance(comment, Comment)) - - def test_low_rep_admin_can_comment_others_question(self): - question = self.post_question() - other_user = self.create_other_user() - other_user.is_superuser = True - assert(other_user.is_administrator()) - assert(other_user.reputation < self.min_rep) - comment = other_user.post_comment( - parent_post = question, - body_text = 'test comment' - ) - self.assertTrue(isinstance(comment, Comment)) - - def test_low_rep_moderator_can_comment_others_question(self): - question = self.post_question() - other_user = self.create_other_user() - other_user.set_status('m') - assert(other_user.is_moderator()) - assert(other_user.reputation < self.min_rep) - comment = other_user.post_comment( - parent_post = question, - body_text = 'test comment' - ) - self.assertTrue(isinstance(comment, Comment)) - -class UploadPermissionAssertionTests(TestCase): - """Tests permissions for file uploads - """ - - def setUp(self): - self.user = create_user( - username = 'test', - email = 'test@test.com' - ) - self.min_rep = askbot_settings.MIN_REP_TO_UPLOAD_FILES - - def test_suspended_user_cannot_upload(self): - self.user.set_status('s') - self.assertRaises( - exceptions.PermissionDenied, - self.user.assert_can_upload_file - ) - - def test_blocked_user_cannot_upload(self): - self.user.set_status('b') - self.assertRaises( - exceptions.PermissionDenied, - self.user.assert_can_upload_file - ) - def test_low_rep_user_cannot_upload(self): - self.user.reputation = self.min_rep - 1 - self.assertRaises( - exceptions.PermissionDenied, - self.user.assert_can_upload_file - ) - - def test_high_rep_user_can_upload(self): - self.user.reputation = self.min_rep - try: - self.user.assert_can_upload_file() - except exceptions.PermissionDenied: - fail('high rep user must be able to upload') - - def test_low_rep_moderator_can_upload(self): - assert(self.user.reputation < self.min_rep) - self.user.set_status('m') - try: - self.user.assert_can_upload_file() - except exceptions.PermissionDenied: - fail('high rep user must be able to upload') - - def test_low_rep_administrator_can_upload(self): - assert(self.user.reputation < self.min_rep) - self.user.is_superuser = True - try: - self.user.assert_can_upload_file() - except exceptions.PermissionDenied: - fail('high rep user must be able to upload') - - -class EmailAlertTests(TestCase): - """Base class for testing delayed Email notifications - that are triggered by the send_email_alerts - command - - this class tests cases where target user has no subscriptions - that is all subscriptions off - - subclasses should redefine initial data via the static - class member this class tests cases where target user has no subscriptions - that is all subscriptions off - - this class also defines a few utility methods that do - not run any tests themselves - - class variables: - - * notification_schedule - * setup_timestamp - * visit_timestamp - * expected_results - - should be set in subclasses to reuse testing code - """ - - def send_alerts(self): - """runs the send_email_alerts management command - and makes a shortcut access to the outbox - """ - #make sure tha we are not sending email for real - #this setting must be present in settings.py - assert( - django_settings.EMAIL_BACKEND == 'django.core.mail.backends.locmem.EmailBackend' - ) - management.call_command('send_email_alerts') - - @setup_email_alert_tests - def setUp(self): - """generic pre-test setup method: - - this function is empty - because it's intendend content is - entirely defined by the decorator - - the ``setUp()`` function in any subclass must only enter differences - between the default version (defined in the decorator) and the - desired version in the "real" test - """ - pass - - def setUpUsers(self): - self.other_user = create_user( - username = 'other', - email = 'other@domain.com', - date_joined = self.setup_timestamp - ) - self.target_user = create_user( - username = 'target', - email = 'target@domain.com', - notification_schedule = self.notification_schedule, - date_joined = self.setup_timestamp - ) - #moderators to avoid permission issues - self.other_user.set_status('m') - self.other_user.save() - self.target_user.set_status('m') - self.target_user.save() - - def post_comment( - self, - author = None, - parent_post = None, - body_text = 'dummy test comment', - timestamp = None - ): - """posts and returns a comment to parent post, uses - now timestamp if not given, dummy body_text - author is required - """ - if timestamp is None: - timestamp = self.setup_timestamp - comment = author.post_comment( - parent_post = parent_post, - body_text = body_text, - timestamp = timestamp, - ) - return comment - - def edit_post( - self, - author = None, - post = None, - timestamp = None, - body_text = 'edited body text', - ): - """todo: this method may also edit other stuff - like post title and tags - in the case when post is - of type question - """ - if timestamp is None: - timestamp = self.setup_timestamp - post.apply_edit( - edited_by = author, - edited_at = timestamp, - text = body_text, - comment = 'nothing serious' - ) - - def post_question( - self, - author = None, - timestamp = None, - title = 'test question title', - body_text = 'test question body', - tags = 'test', - ): - """post a question with dummy content - and return it - """ - if timestamp is None: - timestamp = self.setup_timestamp - self.question = author.post_question( - title = title, - body_text = body_text, - tags = tags, - timestamp = timestamp - ) - if self.follow_question: - self.target_user.follow_question(self.question) - return self.question - - def maybe_visit_question(self, user = None): - """visits question on behalf of a given user and applies - a timestamp set in the class attribute ``visit_timestamp`` - - if ``visit_timestamp`` is None, then visit is skipped - - parameter ``user`` is optional if not given, the visit will occur - on behalf of the user stored in the class attribute ``target_user`` - """ - if self.visit_timestamp: - if user is None: - user = self.target_user - user.visit_post( - question = self.question, - timestamp = self.visit_timestamp - ) - - def post_answer( - self, - question = None, - author = None, - body_text = 'test answer body', - timestamp = None, - follow = None,#None - do nothing, True/False - follow/unfollow - ): - """post answer with dummy content and return it - """ - if timestamp is None: - timestamp = self.setup_timestamp - - if follow is None: - if author.is_following(question): - follow = True - else: - follow = False - elif follow not in (True, False): - raise ValueError('"follow" may be only None, True or False') - - return author.post_answer( - question = question, - body_text = body_text, - timestamp = timestamp, - follow = follow, - ) - - def check_results(self, test_key = None): - if test_key is None: - raise ValueError('test_key parameter is required') - expected = self.expected_results[test_key] - outbox = django.core.mail.outbox - error_message = 'emails_sent=%d, expected=%d, function=%s.test_%s' % ( - len(outbox), - expected['message_count'], - self.__class__.__name__, - test_key, - ) - self.assertEqual(len(outbox), expected['message_count'], error_message) - if expected['message_count'] > 0: - if len(outbox) > 0: - self.assertEqual( - outbox[0].recipients()[0], - self.target_user.email, - error_message - ) - - def proto_post_answer_comment(self): - """base method for use in some tests - """ - question = self.post_question( - author = self.other_user - ) - answer = self.post_answer( - question = question, - author = self.target_user - ) - comment = self.post_comment( - parent_post = answer, - author = self.other_user, - ) - return comment - - @email_alert_test - def test_answer_comment(self): - """target user posts answer and other user posts a comment - to the answer - """ - self.proto_post_answer_comment() - - @email_alert_test - def test_answer_delete_comment(self): - comment = self.proto_post_answer_comment() - comment.user.delete_post(comment) - - @email_alert_test - def test_question_edit(self): - question = self.post_question( - author = self.target_user - ) - self.edit_post( - post = question, - author = self.other_user - ) - self.question = question - - @email_alert_test - def test_answer_edit(self): - question = self.post_question( - author = self.target_user - ) - answer = self.post_answer( - question = question, - author = self.target_user - ) - self.edit_post( - post = answer, - author = self.other_user - ) - self.question = question - - @email_alert_test - def test_question_and_answer_by_target(self): - question = self.post_question( - author = self.target_user - ) - answer = self.post_answer( - question = question, - author = self.target_user - ) - self.question = question - - def proto_question_comment(self): - question = self.post_question( - author = self.target_user, - ) - comment = self.post_comment( - author = self.other_user, - parent_post = question, - ) - return comment - - @email_alert_test - def test_question_comment(self): - """target user posts question other user posts a comment - target user does or does not receive email notification - depending on the setup parameters - - in the base class user does not receive a notification - """ - self.proto_question_comment() - - @email_alert_test - def test_question_comment_delete(self): - """target user posts question other user posts a comment - target user does or does not receive email notification - depending on the setup parameters - - in the base class user does not receive a notification - """ - comment = self.proto_question_comment() - comment.user.delete_post(comment) - - def proto_test_q_ask(self): - """base method for tests that - have name containing q_ask - i.e. target asks other answers - answer is returned - """ - question = self.post_question( - author = self.target_user, - ) - answer = self.post_answer( - question = question, - author = self.other_user, - ) - return answer - - @email_alert_test - def test_q_ask(self): - """target user posts question - other user answer the question - """ - self.proto_test_q_ask() - - @email_alert_test - def test_q_ask_delete_answer(self): - answer = self.proto_test_q_ask() - self.other_user.delete_post(answer) - - @email_alert_test - def test_q_ans(self): - """other user posts question - target user post answer - """ - question = self.post_question( - author = self.other_user, - ) - self.post_answer( - question = question, - author = self.target_user - ) - self.question = question - - @email_alert_test - def test_q_ans_new_answer(self): - """other user posts question - target user post answer and other user - posts another answer - """ - question = self.post_question( - author = self.other_user, - ) - self.post_answer( - question = question, - author = self.target_user - ) - self.post_answer( - question = question, - author = self.other_user - ) - self.question = question - - @email_alert_test - def test_mention_in_question(self): - question = self.post_question( - author = self.other_user, - body_text = 'hey @target get this' - ) - self.question = question - - @email_alert_test - def test_mention_in_answer(self): - question = self.post_question( - author = self.other_user, - ) - self.post_answer( - question = question, - author = self.other_user, - body_text = 'hey @target check this out' - ) - self.question = question - -class WeeklyQAskEmailAlertTests(EmailAlertTests): - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['q_ask'] = 'w' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(14) - self.expected_results['q_ask'] = {'message_count': 1} - self.expected_results['q_ask_delete_answer'] = {'message_count': 0} - self.expected_results['question_edit'] = {'message_count': 1, } - self.expected_results['answer_edit'] = {'message_count': 1, } - - #local tests - self.expected_results['question_edit_reedited_recently'] = \ - {'message_count': 1} - self.expected_results['answer_edit_reedited_recently'] = \ - {'message_count': 1} - - @email_alert_test - def test_question_edit_reedited_recently(self): - question = self.post_question( - author = self.target_user - ) - self.edit_post( - post = question, - author = self.other_user, - ) - self.edit_post( - post = question, - author = self.other_user, - timestamp = datetime.datetime.now() - datetime.timedelta(1) - ) - - @email_alert_test - def test_answer_edit_reedited_recently(self): - question = self.post_question( - author = self.target_user - ) - answer = self.post_answer( - question = question, - author = self.other_user, - ) - self.edit_post( - post = answer, - author = self.other_user, - timestamp = datetime.datetime.now() - datetime.timedelta(1) - ) - -class WeeklyMentionsAndCommentsEmailAlertTests(EmailAlertTests): - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['m_and_c'] = 'w' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(14) - self.expected_results['question_comment'] = {'message_count': 1, } - self.expected_results['question_comment_delete'] = {'message_count': 0, } - self.expected_results['answer_comment'] = {'message_count': 1, } - self.expected_results['answer_delete_comment'] = {'message_count': 0, } - self.expected_results['mention_in_question'] = {'message_count': 1, } - self.expected_results['mention_in_answer'] = {'message_count': 1, } - -class WeeklyQAnsEmailAlertTests(EmailAlertTests): - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['q_ans'] = 'w' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(14) - self.expected_results['answer_edit'] = {'message_count': 1, } - self.expected_results['q_ans_new_answer'] = {'message_count': 1, } - -class InstantQAskEmailAlertTests(EmailAlertTests): - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['q_ask'] = 'i' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) - self.expected_results['q_ask'] = {'message_count': 1} - self.expected_results['q_ask_delete_answer'] = {'message_count': 1} - self.expected_results['question_edit'] = {'message_count': 1, } - self.expected_results['answer_edit'] = {'message_count': 1, } - -class InstantWholeForumEmailAlertTests(EmailAlertTests): - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['q_all'] = 'i' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) - - self.expected_results['q_ask'] = {'message_count': 1, } - self.expected_results['q_ask_delete_answer'] = {'message_count': 1} - self.expected_results['question_comment'] = {'message_count': 1, } - self.expected_results['question_comment_delete'] = {'message_count': 1, } - self.expected_results['answer_comment'] = {'message_count': 2, } - self.expected_results['answer_delete_comment'] = {'message_count': 2, } - self.expected_results['mention_in_question'] = {'message_count': 1, } - self.expected_results['mention_in_answer'] = {'message_count': 2, } - self.expected_results['question_edit'] = {'message_count': 1, } - self.expected_results['answer_edit'] = {'message_count': 1, } - self.expected_results['question_and_answer_by_target'] = {'message_count': 0, } - self.expected_results['q_ans'] = {'message_count': 1, } - self.expected_results['q_ans_new_answer'] = {'message_count': 2, } - -class BlankWeeklySelectedQuestionsEmailAlertTests(EmailAlertTests): - """blank means that this is testing for the absence of email - because questions are not followed as set by default in the - parent class - """ - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['q_sel'] = 'w' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(14) - -class BlankInstantSelectedQuestionsEmailAlertTests(EmailAlertTests): - """blank means that this is testing for the absence of email - because questions are not followed as set by default in the - parent class - """ - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['q_sel'] = 'i' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) - -class LiveWeeklySelectedQuestionsEmailAlertTests(EmailAlertTests): - """live means that this is testing for the presence of email - as all questions are automatically followed by user here - """ - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['q_sel'] = 'w' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(14) - self.follow_question = True - - self.expected_results['q_ask'] = {'message_count': 1, } - self.expected_results['q_ask_delete_answer'] = {'message_count': 0} - self.expected_results['question_comment'] = {'message_count': 0, } - self.expected_results['question_comment_delete'] = {'message_count': 0, } - self.expected_results['answer_comment'] = {'message_count': 0, } - self.expected_results['answer_delete_comment'] = {'message_count': 0, } - self.expected_results['mention_in_question'] = {'message_count': 1, } - self.expected_results['mention_in_answer'] = {'message_count': 1, } - self.expected_results['question_edit'] = {'message_count': 1, } - self.expected_results['answer_edit'] = {'message_count': 1, } - self.expected_results['question_and_answer_by_target'] = {'message_count': 0, } - self.expected_results['q_ans'] = {'message_count': 0, } - self.expected_results['q_ans_new_answer'] = {'message_count': 1, } - -class LiveInstantSelectedQuestionsEmailAlertTests(EmailAlertTests): - """live means that this is testing for the presence of email - as all questions are automatically followed by user here - """ - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['q_sel'] = 'i' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) - self.follow_question = True - - self.expected_results['q_ask'] = {'message_count': 1, } - self.expected_results['q_ask_delete_answer'] = {'message_count': 1} - self.expected_results['question_comment'] = {'message_count': 1, } - self.expected_results['question_comment_delete'] = {'message_count': 1, } - self.expected_results['answer_comment'] = {'message_count': 1, } - self.expected_results['answer_delete_comment'] = {'message_count': 1, } - self.expected_results['mention_in_question'] = {'message_count': 0, } - self.expected_results['mention_in_answer'] = {'message_count': 1, } - self.expected_results['question_edit'] = {'message_count': 1, } - self.expected_results['answer_edit'] = {'message_count': 1, } - self.expected_results['question_and_answer_by_target'] = {'message_count': 0, } - self.expected_results['q_ans'] = {'message_count': 0, } - self.expected_results['q_ans_new_answer'] = {'message_count': 1, } - -class InstantMentionsAndCommentsEmailAlertTests(EmailAlertTests): - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['m_and_c'] = 'i' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) - self.expected_results['question_comment'] = {'message_count': 1, } - self.expected_results['question_comment_delete'] = {'message_count': 1, } - self.expected_results['answer_comment'] = {'message_count': 1, } - self.expected_results['answer_delete_comment'] = {'message_count': 1, } - self.expected_results['mention_in_question'] = {'message_count': 1, } - self.expected_results['mention_in_answer'] = {'message_count': 1, } - - #specialized local test case - self.expected_results['question_edited_mention_stays'] = {'message_count': 1} - - @email_alert_test - def test_question_edited_mention_stays(self): - question = self.post_question( - author = self.other_user, - body_text = 'hey @target check this one', - ) - self.edit_post( - post = question, - author = self.other_user, - body_text = 'yoyo @target do look here' - ) - -class InstantQAnsEmailAlertTests(EmailAlertTests): - @setup_email_alert_tests - def setUp(self): - self.notification_schedule['q_ans'] = 'i' - self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) - self.expected_results['answer_edit'] = {'message_count': 1, } - self.expected_results['q_ans_new_answer'] = {'message_count': 1, } - -class OnScreenUpdateNotificationTests(TestCase): - """Test update notifications that are displayed on - screen in the user profile responses view - and "the red envelope" - """ - - def reset_response_counts(self): - self.reload_users() - for user in self.users: - user.response_count = 0 - user.save() - - def reload_users(self): - self.u11 = User.objects.get(id=self.u11.id) - self.u12 = User.objects.get(id=self.u12.id) - self.u13 = User.objects.get(id=self.u13.id) - self.u14 = User.objects.get(id=self.u14.id) - self.u21 = User.objects.get(id=self.u21.id) - self.u22 = User.objects.get(id=self.u22.id) - self.u23 = User.objects.get(id=self.u23.id) - self.u24 = User.objects.get(id=self.u24.id) - self.u31 = User.objects.get(id=self.u31.id) - self.u32 = User.objects.get(id=self.u32.id) - self.u33 = User.objects.get(id=self.u33.id) - self.u34 = User.objects.get(id=self.u34.id) - self.users = [ - self.u11, - self.u12, - self.u13, - self.u14, - self.u21, - self.u22, - self.u23, - self.u24, - self.u31, - self.u32, - self.u33, - self.u34, - ] - - def setUp(self): - #users for the question - self.u11 = create_user('user11', 'user11@example.com') - self.u12 = create_user('user12', 'user12@example.com') - self.u13 = create_user('user13', 'user13@example.com') - self.u14 = create_user('user14', 'user14@example.com') - - #users for first answer - self.u21 = create_user('user21', 'user21@example.com')#post answer - self.u22 = create_user('user22', 'user22@example.com')#edit answer - self.u23 = create_user('user23', 'user23@example.com') - self.u24 = create_user('user24', 'user24@example.com') - - #users for second answer - self.u31 = create_user('user31', 'user31@example.com')#post answer - self.u32 = create_user('user32', 'user32@example.com')#edit answer - self.u33 = create_user('user33', 'user33@example.com') - self.u34 = create_user('user34', 'user34@example.com') - - #a hack to initialize .users list - self.reload_users() - - #pre-populate askbot with some content - self.question = Question.objects.create_new( - title = 'test question', - author = self.u11, - added_at = datetime.datetime.now(), - wiki = False, - tagnames = 'test', - text = 'hey listen up', - ) - self.comment12 = self.question.add_comment( - user = self.u12, - comment = 'comment12' - ) - self.comment13 = self.question.add_comment( - user = self.u13, - comment = 'comment13' - ) - self.answer1 = Answer.objects.create_new( - question = self.question, - author = self.u21, - added_at = datetime.datetime.now(), - text = 'answer1' - ) - self.comment22 = self.answer1.add_comment( - user = self.u22, - comment = 'comment22' - ) - self.comment23 = self.answer1.add_comment( - user = self.u23, - comment = 'comment23' - ) - self.answer2 = Answer.objects.create_new( - question = self.question, - author = self.u31, - added_at = datetime.datetime.now(), - text = 'answer2' - ) - self.comment32 = self.answer2.add_comment( - user = self.u32, - comment = 'comment32' - ) - self.comment33 = self.answer2.add_comment( - user = self.u33, - comment = 'comment33' - ) - - def post_then_delete_answer_comment(self): - pass - - def post_then_delete_answer(self): - pass - - def post_then_delete_question_comment(self): - pass - - def post_mention_in_question_then_delete(self): - pass - - def post_mention_in_answer_then_delete(self): - pass - - def post_mention_in_question_then_edit_out(self): - pass - - def post_mention_in_answer_then_edit_out(self): - pass - - def test_post_mention_in_question_comment_then_delete(self): - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - comment = self.question.add_comment( - user = self.u11, - comment = '@user12 howyou doin?', - added_at = timestamp - ) - comment.delete() - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 0) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ] - ) - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - comment = self.answer1.add_comment( - user = self.u21, - comment = 'hey @user22 blah', - added_at = timestamp - ) - comment.delete() - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 0) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ] - ) - - def test_self_comments(self): - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.question.add_comment( - user = self.u11, - comment = 'self-comment', - added_at = timestamp - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set([self.u12, self.u13]), - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 0, 1, 1, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ] - ) - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.answer1.add_comment( - user = self.u21, - comment = 'self-comment 2', - added_at = timestamp - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set([self.u22, self.u23]), - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 0, 0, 0, 0, - 0, 1, 1, 0, - 0, 0, 0, 0, - ] - ) - - def test_self_mention_not_posting_in_comment_to_question1(self): - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.question.add_comment( - user = self.u11, - comment = 'self-comment @user11', - added_at = timestamp - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set([self.u12, self.u13]), - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 0, 1, 1, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ] - ) - - def test_self_mention_not_posting_in_comment_to_question2(self): - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.question.add_comment( - user = self.u11, - comment = 'self-comment @user11 blah', - added_at = timestamp - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set([self.u12, self.u13]), - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 0, 1, 1, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ] - ) - - def test_self_mention_not_posting_in_comment_to_answer(self): - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.answer1.add_comment( - user = self.u21, - comment = 'self-comment 1 @user21', - added_at = timestamp - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set([self.u22, self.u23]), - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 0, 0, 0, 0, - 0, 1, 1, 0, - 0, 0, 0, 0, - ] - ) - - def test_comments_to_post_authors(self): - self.question.apply_edit( - edited_by = self.u14, - text = 'now much better', - comment = 'improved text' - ) - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.question.add_comment( - user = self.u12, - comment = 'self-comment 1', - added_at = timestamp - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set([self.u11, self.u13, self.u14]), - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 1, 0, 1, 1, - 0, 0, 0, 0, - 0, 0, 0, 0, - ] - ) - self.answer1.apply_edit( - edited_by = self.u24, - text = 'now much better', - comment = 'improved text' - ) - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.answer1.add_comment( - user = self.u22, - comment = 'self-comment 1', - added_at = timestamp - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set([self.u21, self.u23, self.u24]), - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 0, 0, 0, 0, - 1, 0, 1, 1, - 0, 0, 0, 0, - ] - ) - - def test_question_edit(self): - """when question is edited - response receivers are question authors, commenters - and answer authors, but not answer commenters - """ - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.question.apply_edit( - edited_by = self.u14, - text = 'waaay better question!', - comment = 'improved question', - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set([self.u11, self.u12, self.u13, self.u21, self.u31]) - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 1, 1, 1, 0, - 1, 0, 0, 0, - 1, 0, 0, 0, - ] - ) - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.question.apply_edit( - edited_by = self.u31, - text = 'waaay even better question!', - comment = 'improved question', - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set([self.u11, self.u12, self.u13, self.u14, self.u21]) - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 1, 1, 1, 1, - 1, 0, 0, 0, - 0, 0, 0, 0, - ] - ) - - def test_answer_edit(self): - """ - """ - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.answer1.apply_edit( - edited_by = self.u24, - text = 'waaay better answer!', - comment = 'improved answer1', - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set( - [ - self.u11, self.u12, self.u13, - self.u21, self.u22, self.u23, - self.u31 - ] - ) - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 1, 1, 1, 0, - 1, 1, 1, 0, - 1, 0, 0, 0, - ] - ) - - def test_new_answer(self): - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.answer3 = Answer.objects.create_new( - question = self.question, - author = self.u11, - added_at = timestamp, - text = 'answer3' - ) - time_end = datetime.datetime.now() - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set( - [ - self.u12, self.u13, - self.u21, self.u31 - ] - ) - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 0, 1, 1, 0, - 1, 0, 0, 0, - 1, 0, 0, 0, - ] - ) - self.reset_response_counts() - time.sleep(1) - timestamp = datetime.datetime.now() - self.answer3 = Answer.objects.create_new( - question = self.question, - author = self.u31, - added_at = timestamp, - text = 'answer4' - ) - notifications = get_re_notif_after(timestamp) - self.assertEqual(len(notifications), 1) - self.assertEqual( - set(notifications[0].receiving_users.all()), - set( - [ - self.u11, self.u12, self.u13, - self.u21 - ] - ) - ) - self.reload_users() - self.assertEqual( - [ - self.u11.response_count, - self.u12.response_count, - self.u13.response_count, - self.u14.response_count, - self.u21.response_count, - self.u22.response_count, - self.u23.response_count, - self.u24.response_count, - self.u31.response_count, - self.u32.response_count, - self.u33.response_count, - self.u34.response_count, - ], - [ - 1, 1, 1, 0, - 1, 0, 0, 0, - 0, 0, 0, 0, - ] - ) - - -class PageLoadTestCase(TestCase): - def try_url( - self, - url_name, status_code=200, template=None, - kwargs={}, redirect_url=None, follow=False, - data = {}, - ): - url = reverse(url_name, kwargs = kwargs) - url_info = 'getting url %s' % url - if data: - url_info += '?' + '&'.join(['%s=%s' % (k, v) for k, v in data.iteritems()]) - print url_info - - r = self.client.get(url, data=data, follow=follow) - if hasattr(self.client, 'redirect_chain'): - print 'redirect chain: %s' % ','.join(self.client.redirect_chain) - - self.assertEqual(r.status_code, status_code) - - if template: - #asuming that there is more than one template - print 'templates are %s' % ','.join([t.name for t in r.template]) - self.assertEqual(r.template[0].name, template) - -class PageLoadTests(PageLoadTestCase): - fixtures = ['tmp/fixture1.json', ] - - def test_index(self): - #todo: merge this with all reader url tests - print 'trying to reverse index' - response = self.client.get(reverse('index'), follow=True) - self.assertEqual(response.status_code, 200) - self.failUnless(len(response.redirect_chain) == 1) - self.failUnless(response.redirect_chain[0][0].endswith('/questions/')) - c = response.context[0] - t = response.template[0] - self.assertEqual(t.name, 'questions.html') - print 'index works' - - def proto_test_non_user_urls(self): - """test all reader views thoroughly - on non-crashiness (no correcteness tests here) - """ - - self.try_url('sitemap') - self.try_url('feeds', kwargs={'url':'rss'}) - self.try_url('about', template='about.html') - self.try_url('privacy', template='privacy.html') - self.try_url('logout', template='logout.html') - self.try_url('user_signin', template='authopenid/signin.html') - #todo: test different tabs - self.try_url('tags', template='tags.html') - self.try_url('tags', data={'sort':'name'}, template='tags.html') - self.try_url('tags', data={'sort':'used'}, template='tags.html') - self.try_url('badges', template='badges.html') - self.try_url( - 'answer_revisions', - template='revisions_answer.html', - kwargs={'id':38} - ) - #todo: test different sort methods and scopes - self.try_url( - 'questions', - template='questions.html' - ) - self.try_url( - 'questions', - data={'start_over':'true'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'scope':'unanswered'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'scope':'all'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'scope':'favorite'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'scope':'unanswered', 'sort':'latest'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'scope':'unanswered', 'sort':'oldest'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'scope':'unanswered', 'sort':'active'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'scope':'unanswered', 'sort':'inactive'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'sort':'hottest'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'sort':'coldest'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'sort':'mostvoted'}, - template='questions.html' - ) - self.try_url( - 'questions', - data={'sort':'leastvoted'}, - template='questions.html' - ) - self.try_url( - 'question', - kwargs={'id':1}, - ) - self.try_url( - 'question', - kwargs={'id':2}, - ) - self.try_url( - 'question', - kwargs={'id':3}, - ) - self.try_url( - 'question_revisions', - kwargs={'id':17}, - template='revisions_question.html' - ) - self.try_url('users', template='users.html') - #todo: really odd naming conventions for sort methods - self.try_url( - 'users', - template='users.html', - data={'sort':'reputation'}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'newest'}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'last'}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'user'}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'reputation', 'page':2}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'newest', 'page':2}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'last', 'page':2}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'user', 'page':2}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'reputation', 'page':1}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'newest', 'page':1}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'last', 'page':1}, - ) - self.try_url( - 'users', - template='users.html', - data={'sort':'user', 'page':1}, - ) - self.try_url( - 'edit_user', - template='authopenid/signin.html', - kwargs={'id':4}, - status_code=200, - follow=True, - ) - - def test_non_user_urls(self): - self.proto_test_non_user_urls() - - #def test_non_user_urls_logged_in(self): - #user = User.objects.get(id=1) - #somehow login this user - #self.proto_test_non_user_urls() - - def test_user_urls(self): - user = User.objects.get(id=2) - name_slug = defaultfilters.slugify(user.username) - self.try_url( - 'user_profile', - kwargs={'id': 2, 'slug': name_slug}, - data={'sort':'stats'}, - template='user_stats.html' - ) - self.try_url( - 'user_profile', - kwargs={'id': 2, 'slug': name_slug}, - data={'sort':'recent'}, - template='user_recent.html' - ) - self.try_url( - 'user_profile', - kwargs={'id': 2, 'slug': name_slug}, - data={'sort':'responses'}, - status_code=404, - template='404.html' - ) - self.try_url( - 'user_profile', - kwargs={'id': 2, 'slug': name_slug}, - data={'sort':'reputation'}, - template='user_reputation.html' - ) - self.try_url( - 'user_profile', - kwargs={'id': 2, 'slug': name_slug}, - data={'sort':'votes'}, - status_code=404, - template='404.html' - ) - self.try_url( - 'user_profile', - kwargs={'id': 2, 'slug': name_slug}, - data={'sort':'favorites'}, - template='user_favorites.html' - ) - self.try_url( - 'user_profile', - kwargs={'id': 2, 'slug': name_slug}, - data={'sort':'email_subscriptions'}, - status_code=404, - template='404.html' - ) diff --git a/askbot/tests/__init__.py b/askbot/tests/__init__.py new file mode 100644 index 00000000..bc1b9431 --- /dev/null +++ b/askbot/tests/__init__.py @@ -0,0 +1,4 @@ +from askbot.tests.email_alert_tests import * +from askbot.tests.on_screen_notification_tests import * +from askbot.tests.page_load_tests import * +from askbot.tests.permission_assertion_tests import * diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py new file mode 100644 index 00000000..48e32240 --- /dev/null +++ b/askbot/tests/email_alert_tests.py @@ -0,0 +1,643 @@ +import datetime +import functools +import copy +from django.conf import settings as django_settings +from django.core import management +import django.core.mail +from django.test import TestCase +from askbot.tests import utils +from askbot import models + +def email_alert_test(test_func): + """decorator for test methods in EmailAlertTests + wraps tests with a generic sequence of testing + email alerts on updates to anything relating to + given question + """ + @functools.wraps(test_func) + def wrapped_test(test_object, *args, **kwargs): + func_name = test_func.__name__ + if func_name.startswith('test_'): + test_name = func_name.replace('test_', '', 1) + test_func(test_object) + test_object.maybe_visit_question() + test_object.send_alerts() + test_object.check_results(test_name) + else: + raise ValueError('test method names must have prefix "test_"') + return wrapped_test + +def setup_email_alert_tests(setup_func): + @functools.wraps(setup_func) + def wrapped_setup(test_object, *args, **kwargs): + #empty email subscription schedule + #no email is sent + test_object.notification_schedule = \ + copy.deepcopy(models.EmailFeedSetting.NO_EMAIL_SCHEDULE) + #timestamp to use for the setup + #functions + test_object.setup_timestamp = datetime.datetime.now() + + #timestamp to use for the question visit + #by the target user + #if this timestamp is None then there will be no visit + #otherwise question will be visited by the target user + #at that time + test_object.visit_timestamp = None + + #dictionary to hols expected results for each test + #actual data@is initialized in the code just before the function + #or in the body of the subclass + test_object.expected_results = dict() + + #do not follow by default (do not use q_sel type subscription) + test_object.follow_question = False + + #fill out expected result for each test + test_object.expected_results['q_ask'] = {'message_count': 0, } + test_object.expected_results['q_ask_delete_answer'] = {'message_count': 0, } + test_object.expected_results['question_comment'] = {'message_count': 0, } + test_object.expected_results['question_comment_delete'] = {'message_count': 0, } + test_object.expected_results['answer_comment'] = {'message_count': 0, } + test_object.expected_results['answer_delete_comment'] = {'message_count': 0, } + test_object.expected_results['mention_in_question'] = {'message_count': 0, } + test_object.expected_results['mention_in_answer'] = {'message_count': 0, } + test_object.expected_results['question_edit'] = {'message_count': 0, } + test_object.expected_results['answer_edit'] = {'message_count': 0, } + test_object.expected_results['question_and_answer_by_target'] = {'message_count': 0, } + test_object.expected_results['q_ans'] = {'message_count': 0, } + test_object.expected_results['q_ans_new_answer'] = {'message_count': 0, } + + #this function is expected to contain a difference between this + #one and the desired setup within the concrete test + setup_func(test_object) + #must call this after setting up the notification schedule + #because it is needed in setUpUsers() function + test_object.setUpUsers() + return wrapped_setup + +class EmailAlertTests(TestCase): + """Base class for testing delayed Email notifications + that are triggered by the send_email_alerts + command + + this class tests cases where target user has no subscriptions + that is all subscriptions off + + subclasses should redefine initial data via the static + class member this class tests cases where target user has no subscriptions + that is all subscriptions off + + this class also defines a few utility methods that do + not run any tests themselves + + class variables: + + * notification_schedule + * setup_timestamp + * visit_timestamp + * expected_results + + should be set in subclasses to reuse testing code + """ + + def send_alerts(self): + """runs the send_email_alerts management command + and makes a shortcut access to the outbox + """ + #make sure tha we are not sending email for real + #this setting must be present in settings.py + assert( + django_settings.EMAIL_BACKEND == 'django.core.mail.backends.locmem.EmailBackend' + ) + management.call_command('send_email_alerts') + + @setup_email_alert_tests + def setUp(self): + """generic pre-test setup method: + + this function is empty - because it's intendend content is + entirely defined by the decorator + + the ``setUp()`` function in any subclass must only enter differences + between the default version (defined in the decorator) and the + desired version in the "real" test + """ + pass + + def setUpUsers(self): + self.other_user = utils.create_user( + username = 'other', + email = 'other@domain.com', + date_joined = self.setup_timestamp, + status = 'm' + ) + self.target_user = utils.create_user( + username = 'target', + email = 'target@domain.com', + notification_schedule = self.notification_schedule, + date_joined = self.setup_timestamp, + status = 'm' + ) + + def post_comment( + self, + author = None, + parent_post = None, + body_text = 'dummy test comment', + timestamp = None + ): + """posts and returns a comment to parent post, uses + now timestamp if not given, dummy body_text + author is required + """ + if timestamp is None: + timestamp = self.setup_timestamp + comment = author.post_comment( + parent_post = parent_post, + body_text = body_text, + timestamp = timestamp, + ) + return comment + + def edit_post( + self, + author = None, + post = None, + timestamp = None, + body_text = 'edited body text', + ): + """todo: this method may also edit other stuff + like post title and tags - in the case when post is + of type question + """ + if timestamp is None: + timestamp = self.setup_timestamp + post.apply_edit( + edited_by = author, + edited_at = timestamp, + text = body_text, + comment = 'nothing serious' + ) + + def post_question( + self, + author = None, + timestamp = None, + title = 'test question title', + body_text = 'test question body', + tags = 'test', + ): + """post a question with dummy content + and return it + """ + if timestamp is None: + timestamp = self.setup_timestamp + self.question = author.post_question( + title = title, + body_text = body_text, + tags = tags, + timestamp = timestamp + ) + if self.follow_question: + self.target_user.follow_question(self.question) + return self.question + + def maybe_visit_question(self, user = None): + """visits question on behalf of a given user and applies + a timestamp set in the class attribute ``visit_timestamp`` + + if ``visit_timestamp`` is None, then visit is skipped + + parameter ``user`` is optional if not given, the visit will occur + on behalf of the user stored in the class attribute ``target_user`` + """ + if self.visit_timestamp: + if user is None: + user = self.target_user + user.visit_post( + question = self.question, + timestamp = self.visit_timestamp + ) + + def post_answer( + self, + question = None, + author = None, + body_text = 'test answer body', + timestamp = None, + follow = None,#None - do nothing, True/False - follow/unfollow + ): + """post answer with dummy content and return it + """ + if timestamp is None: + timestamp = self.setup_timestamp + + if follow is None: + if author.is_following(question): + follow = True + else: + follow = False + elif follow not in (True, False): + raise ValueError('"follow" may be only None, True or False') + + return author.post_answer( + question = question, + body_text = body_text, + timestamp = timestamp, + follow = follow, + ) + + def check_results(self, test_key = None): + if test_key is None: + raise ValueError('test_key parameter is required') + expected = self.expected_results[test_key] + outbox = django.core.mail.outbox + error_message = 'emails_sent=%d, expected=%d, function=%s.test_%s' % ( + len(outbox), + expected['message_count'], + self.__class__.__name__, + test_key, + ) + self.assertEqual(len(outbox), expected['message_count'], error_message) + if expected['message_count'] > 0: + if len(outbox) > 0: + self.assertEqual( + outbox[0].recipients()[0], + self.target_user.email, + error_message + ) + + def proto_post_answer_comment(self): + """base method for use in some tests + """ + question = self.post_question( + author = self.other_user + ) + answer = self.post_answer( + question = question, + author = self.target_user + ) + comment = self.post_comment( + parent_post = answer, + author = self.other_user, + ) + return comment + + @email_alert_test + def test_answer_comment(self): + """target user posts answer and other user posts a comment + to the answer + """ + self.proto_post_answer_comment() + + @email_alert_test + def test_answer_delete_comment(self): + comment = self.proto_post_answer_comment() + comment.get_owner().delete_comment(comment = comment) + + @email_alert_test + def test_question_edit(self): + question = self.post_question( + author = self.target_user + ) + self.edit_post( + post = question, + author = self.other_user + ) + self.question = question + + @email_alert_test + def test_answer_edit(self): + question = self.post_question( + author = self.target_user + ) + answer = self.post_answer( + question = question, + author = self.target_user + ) + self.edit_post( + post = answer, + author = self.other_user + ) + self.question = question + + @email_alert_test + def test_question_and_answer_by_target(self): + question = self.post_question( + author = self.target_user + ) + answer = self.post_answer( + question = question, + author = self.target_user + ) + self.question = question + + def proto_question_comment(self): + question = self.post_question( + author = self.target_user, + ) + comment = self.post_comment( + author = self.other_user, + parent_post = question, + ) + return comment + + @email_alert_test + def test_question_comment(self): + """target user posts question other user posts a comment + target user does or does not receive email notification + depending on the setup parameters + + in the base class user does not receive a notification + """ + self.proto_question_comment() + + @email_alert_test + def test_question_comment_delete(self): + """target user posts question other user posts a comment + target user does or does not receive email notification + depending on the setup parameters + + in the base class user does not receive a notification + """ + comment = self.proto_question_comment() + comment.get_owner().delete_comment(comment) + + def proto_test_q_ask(self): + """base method for tests that + have name containing q_ask - i.e. target asks other answers + answer is returned + """ + question = self.post_question( + author = self.target_user, + ) + answer = self.post_answer( + question = question, + author = self.other_user, + ) + return answer + + @email_alert_test + def test_q_ask(self): + """target user posts question + other user answer the question + """ + self.proto_test_q_ask() + + @email_alert_test + def test_q_ask_delete_answer(self): + answer = self.proto_test_q_ask() + self.other_user.delete_post(answer) + + @email_alert_test + def test_q_ans(self): + """other user posts question + target user post answer + """ + question = self.post_question( + author = self.other_user, + ) + self.post_answer( + question = question, + author = self.target_user + ) + self.question = question + + @email_alert_test + def test_q_ans_new_answer(self): + """other user posts question + target user post answer and other user + posts another answer + """ + question = self.post_question( + author = self.other_user, + ) + self.post_answer( + question = question, + author = self.target_user + ) + self.post_answer( + question = question, + author = self.other_user + ) + self.question = question + + @email_alert_test + def test_mention_in_question(self): + question = self.post_question( + author = self.other_user, + body_text = 'hey @target get this' + ) + self.question = question + + @email_alert_test + def test_mention_in_answer(self): + question = self.post_question( + author = self.other_user, + ) + self.post_answer( + question = question, + author = self.other_user, + body_text = 'hey @target check this out' + ) + self.question = question + +class WeeklyQAskEmailAlertTests(EmailAlertTests): + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['q_ask'] = 'w' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(14) + self.expected_results['q_ask'] = {'message_count': 1} + self.expected_results['q_ask_delete_answer'] = {'message_count': 0} + self.expected_results['question_edit'] = {'message_count': 1, } + self.expected_results['answer_edit'] = {'message_count': 1, } + + #local tests + self.expected_results['question_edit_reedited_recently'] = \ + {'message_count': 1} + self.expected_results['answer_edit_reedited_recently'] = \ + {'message_count': 1} + + @email_alert_test + def test_question_edit_reedited_recently(self): + question = self.post_question( + author = self.target_user + ) + self.edit_post( + post = question, + author = self.other_user, + ) + self.edit_post( + post = question, + author = self.other_user, + timestamp = datetime.datetime.now() - datetime.timedelta(1) + ) + + @email_alert_test + def test_answer_edit_reedited_recently(self): + question = self.post_question( + author = self.target_user + ) + answer = self.post_answer( + question = question, + author = self.other_user, + ) + self.edit_post( + post = answer, + author = self.other_user, + timestamp = datetime.datetime.now() - datetime.timedelta(1) + ) + +class WeeklyMentionsAndCommentsEmailAlertTests(EmailAlertTests): + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['m_and_c'] = 'w' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(14) + self.expected_results['question_comment'] = {'message_count': 1, } + self.expected_results['question_comment_delete'] = {'message_count': 0, } + self.expected_results['answer_comment'] = {'message_count': 1, } + self.expected_results['answer_delete_comment'] = {'message_count': 0, } + self.expected_results['mention_in_question'] = {'message_count': 1, } + self.expected_results['mention_in_answer'] = {'message_count': 1, } + +class WeeklyQAnsEmailAlertTests(EmailAlertTests): + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['q_ans'] = 'w' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(14) + self.expected_results['answer_edit'] = {'message_count': 1, } + self.expected_results['q_ans_new_answer'] = {'message_count': 1, } + +class InstantQAskEmailAlertTests(EmailAlertTests): + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['q_ask'] = 'i' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) + self.expected_results['q_ask'] = {'message_count': 1} + self.expected_results['q_ask_delete_answer'] = {'message_count': 1} + self.expected_results['question_edit'] = {'message_count': 1, } + self.expected_results['answer_edit'] = {'message_count': 1, } + +class InstantWholeForumEmailAlertTests(EmailAlertTests): + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['q_all'] = 'i' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) + + self.expected_results['q_ask'] = {'message_count': 1, } + self.expected_results['q_ask_delete_answer'] = {'message_count': 1} + self.expected_results['question_comment'] = {'message_count': 1, } + self.expected_results['question_comment_delete'] = {'message_count': 1, } + self.expected_results['answer_comment'] = {'message_count': 2, } + self.expected_results['answer_delete_comment'] = {'message_count': 2, } + self.expected_results['mention_in_question'] = {'message_count': 1, } + self.expected_results['mention_in_answer'] = {'message_count': 2, } + self.expected_results['question_edit'] = {'message_count': 1, } + self.expected_results['answer_edit'] = {'message_count': 1, } + self.expected_results['question_and_answer_by_target'] = {'message_count': 0, } + self.expected_results['q_ans'] = {'message_count': 1, } + self.expected_results['q_ans_new_answer'] = {'message_count': 2, } + +class BlankWeeklySelectedQuestionsEmailAlertTests(EmailAlertTests): + """blank means that this is testing for the absence of email + because questions are not followed as set by default in the + parent class + """ + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['q_sel'] = 'w' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(14) + +class BlankInstantSelectedQuestionsEmailAlertTests(EmailAlertTests): + """blank means that this is testing for the absence of email + because questions are not followed as set by default in the + parent class + """ + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['q_sel'] = 'i' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) + +class LiveWeeklySelectedQuestionsEmailAlertTests(EmailAlertTests): + """live means that this is testing for the presence of email + as all questions are automatically followed by user here + """ + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['q_sel'] = 'w' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(14) + self.follow_question = True + + self.expected_results['q_ask'] = {'message_count': 1, } + self.expected_results['q_ask_delete_answer'] = {'message_count': 0} + self.expected_results['question_comment'] = {'message_count': 0, } + self.expected_results['question_comment_delete'] = {'message_count': 0, } + self.expected_results['answer_comment'] = {'message_count': 0, } + self.expected_results['answer_delete_comment'] = {'message_count': 0, } + self.expected_results['mention_in_question'] = {'message_count': 1, } + self.expected_results['mention_in_answer'] = {'message_count': 1, } + self.expected_results['question_edit'] = {'message_count': 1, } + self.expected_results['answer_edit'] = {'message_count': 1, } + self.expected_results['question_and_answer_by_target'] = {'message_count': 0, } + self.expected_results['q_ans'] = {'message_count': 0, } + self.expected_results['q_ans_new_answer'] = {'message_count': 1, } + +class LiveInstantSelectedQuestionsEmailAlertTests(EmailAlertTests): + """live means that this is testing for the presence of email + as all questions are automatically followed by user here + """ + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['q_sel'] = 'i' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) + self.follow_question = True + + self.expected_results['q_ask'] = {'message_count': 1, } + self.expected_results['q_ask_delete_answer'] = {'message_count': 1} + self.expected_results['question_comment'] = {'message_count': 1, } + self.expected_results['question_comment_delete'] = {'message_count': 1, } + self.expected_results['answer_comment'] = {'message_count': 1, } + self.expected_results['answer_delete_comment'] = {'message_count': 1, } + self.expected_results['mention_in_question'] = {'message_count': 0, } + self.expected_results['mention_in_answer'] = {'message_count': 1, } + self.expected_results['question_edit'] = {'message_count': 1, } + self.expected_results['answer_edit'] = {'message_count': 1, } + self.expected_results['question_and_answer_by_target'] = {'message_count': 0, } + self.expected_results['q_ans'] = {'message_count': 0, } + self.expected_results['q_ans_new_answer'] = {'message_count': 1, } + +class InstantMentionsAndCommentsEmailAlertTests(EmailAlertTests): + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['m_and_c'] = 'i' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) + self.expected_results['question_comment'] = {'message_count': 1, } + self.expected_results['question_comment_delete'] = {'message_count': 1, } + self.expected_results['answer_comment'] = {'message_count': 1, } + self.expected_results['answer_delete_comment'] = {'message_count': 1, } + self.expected_results['mention_in_question'] = {'message_count': 1, } + self.expected_results['mention_in_answer'] = {'message_count': 1, } + + #specialized local test case + self.expected_results['question_edited_mention_stays'] = {'message_count': 1} + + @email_alert_test + def test_question_edited_mention_stays(self): + question = self.post_question( + author = self.other_user, + body_text = 'hey @target check this one', + ) + self.edit_post( + post = question, + author = self.other_user, + body_text = 'yoyo @target do look here' + ) + +class InstantQAnsEmailAlertTests(EmailAlertTests): + @setup_email_alert_tests + def setUp(self): + self.notification_schedule['q_ans'] = 'i' + self.setup_timestamp = datetime.datetime.now() - datetime.timedelta(1) + self.expected_results['answer_edit'] = {'message_count': 1, } + self.expected_results['q_ans_new_answer'] = {'message_count': 1, } diff --git a/askbot/tests/on_screen_notification_tests.py b/askbot/tests/on_screen_notification_tests.py new file mode 100644 index 00000000..ea607810 --- /dev/null +++ b/askbot/tests/on_screen_notification_tests.py @@ -0,0 +1,709 @@ +""" +.. _on_screen_notification_tests: + +:mod:`on_screen_notification_tests` -- Module for testing on-screen notifications +================================================================================= + +.. automodule:: on_screen_notification_tests + .. moduleauthor:: Evgeny Fadeev <evgeny.fadeev@gmail.com> +""" +import datetime +import time +from django.test import TestCase +from askbot import models +from askbot import const +from askbot.tests.utils import create_user + + +def get_re_notif_after(timestamp): + """returns query set with response notifications + posted after the ``timestamp`` - a ``datetime.datetime`` instance + """ + notifications = models.Activity.objects.filter( + activity_type__in = const.RESPONSE_ACTIVITY_TYPES_FOR_DISPLAY, + active_at__gte = timestamp + ) + return notifications + + +class OnScreenUpdateNotificationTests(TestCase): + """Test update notifications that are displayed on + screen in the user profile responses view + and "the red envelope" + """ + + def reset_response_counts(self): + self.reload_users() + for user in self.users: + user.response_count = 0 + user.save() + + def reload_users(self): + self.u11 = models.User.objects.get(id=self.u11.id) + self.u12 = models.User.objects.get(id=self.u12.id) + self.u13 = models.User.objects.get(id=self.u13.id) + self.u14 = models.User.objects.get(id=self.u14.id) + self.u21 = models.User.objects.get(id=self.u21.id) + self.u22 = models.User.objects.get(id=self.u22.id) + self.u23 = models.User.objects.get(id=self.u23.id) + self.u24 = models.User.objects.get(id=self.u24.id) + self.u31 = models.User.objects.get(id=self.u31.id) + self.u32 = models.User.objects.get(id=self.u32.id) + self.u33 = models.User.objects.get(id=self.u33.id) + self.u34 = models.User.objects.get(id=self.u34.id) + self.users = [ + self.u11, + self.u12, + self.u13, + self.u14, + self.u21, + self.u22, + self.u23, + self.u24, + self.u31, + self.u32, + self.u33, + self.u34, + ] + + def setUp(self): + #users for the question + self.u11 = create_user('user11', 'user11@example.com', status='m') + self.u12 = create_user('user12', 'user12@example.com', status='m') + self.u13 = create_user('user13', 'user13@example.com', status='m') + self.u14 = create_user('user14', 'user14@example.com', status='m') + + #users for first answer + self.u21 = create_user('user21', 'user21@example.com', status='m')#post answer + self.u22 = create_user('user22', 'user22@example.com', status='m')#edit answer + self.u23 = create_user('user23', 'user23@example.com', status='m') + self.u24 = create_user('user24', 'user24@example.com', status='m') + + #users for second answer + self.u31 = create_user('user31', 'user31@example.com', status='m')#post answer + self.u32 = create_user('user32', 'user32@example.com', status='m')#edit answer + self.u33 = create_user('user33', 'user33@example.com', status='m') + self.u34 = create_user('user34', 'user34@example.com', status='m') + + #a hack to initialize .users list + self.reload_users() + + #pre-populate askbot with some content + self.question = models.Question.objects.create_new( + title = 'test question', + author = self.u11, + added_at = datetime.datetime.now(), + wiki = False, + tagnames = 'test', + text = 'hey listen up', + ) + self.comment12 = self.question.add_comment( + user = self.u12, + comment = 'comment12' + ) + self.comment13 = self.question.add_comment( + user = self.u13, + comment = 'comment13' + ) + self.answer1 = models.Answer.objects.create_new( + question = self.question, + author = self.u21, + added_at = datetime.datetime.now(), + text = 'answer1' + ) + self.comment22 = self.answer1.add_comment( + user = self.u22, + comment = 'comment22' + ) + self.comment23 = self.answer1.add_comment( + user = self.u23, + comment = 'comment23' + ) + self.answer2 = models.Answer.objects.create_new( + question = self.question, + author = self.u31, + added_at = datetime.datetime.now(), + text = 'answer2' + ) + self.comment32 = self.answer2.add_comment( + user = self.u32, + comment = 'comment32' + ) + self.comment33 = self.answer2.add_comment( + user = self.u33, + comment = 'comment33' + ) + + def post_then_delete_answer_comment(self): + pass + + def post_then_delete_answer(self): + pass + + def post_then_delete_question_comment(self): + pass + + def post_mention_in_question_then_delete(self): + pass + + def post_mention_in_answer_then_delete(self): + pass + + def post_mention_in_question_then_edit_out(self): + pass + + def post_mention_in_answer_then_edit_out(self): + pass + + def test_post_mention_in_question_comment_then_delete(self): + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + comment = self.question.add_comment( + user = self.u11, + comment = '@user12 howyou doin?', + added_at = timestamp + ) + comment.delete() + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 0) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + comment = self.answer1.add_comment( + user = self.u21, + comment = 'hey @user22 blah', + added_at = timestamp + ) + comment.delete() + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 0) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + + def test_self_comments(self): + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.question.add_comment( + user = self.u11, + comment = 'self-comment', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u12, self.u13]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 1, 1, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer1.add_comment( + user = self.u21, + comment = 'self-comment 2', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u22, self.u23]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 0, 0, 0, + 0, 1, 1, 0, + 0, 0, 0, 0, + ] + ) + + def test_self_mention_not_posting_in_comment_to_question1(self): + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.question.add_comment( + user = self.u11, + comment = 'self-comment @user11', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u12, self.u13]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 1, 1, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + + def test_self_mention_not_posting_in_comment_to_question2(self): + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.question.add_comment( + user = self.u11, + comment = 'self-comment @user11 blah', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u12, self.u13]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 1, 1, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + + def test_self_mention_not_posting_in_comment_to_answer(self): + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer1.add_comment( + user = self.u21, + comment = 'self-comment 1 @user21', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u22, self.u23]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 0, 0, 0, + 0, 1, 1, 0, + 0, 0, 0, 0, + ] + ) + + def test_comments_to_post_authors(self): + self.question.apply_edit( + edited_by = self.u14, + text = 'now much better', + comment = 'improved text' + ) + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.question.add_comment( + user = self.u12, + comment = 'self-comment 1', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u11, self.u13, self.u14]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 1, 0, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + self.answer1.apply_edit( + edited_by = self.u24, + text = 'now much better', + comment = 'improved text' + ) + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer1.add_comment( + user = self.u22, + comment = 'self-comment 1', + added_at = timestamp + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u21, self.u23, self.u24]), + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 0, 0, 0, + 1, 0, 1, 1, + 0, 0, 0, 0, + ] + ) + + def test_question_edit(self): + """when question is edited + response receivers are question authors, commenters + and answer authors, but not answer commenters + """ + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.question.apply_edit( + edited_by = self.u14, + text = 'waaay better question!', + comment = 'improved question', + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u11, self.u12, self.u13, self.u21, self.u31]) + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 1, 1, 1, 0, + 1, 0, 0, 0, + 1, 0, 0, 0, + ] + ) + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.question.apply_edit( + edited_by = self.u31, + text = 'waaay even better question!', + comment = 'improved question', + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set([self.u11, self.u12, self.u13, self.u14, self.u21]) + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 1, 1, 1, 1, + 1, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + + def test_answer_edit(self): + """ + """ + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer1.apply_edit( + edited_by = self.u24, + text = 'waaay better answer!', + comment = 'improved answer1', + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set( + [ + self.u11, self.u12, self.u13, + self.u21, self.u22, self.u23, + self.u31 + ] + ) + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 1, 1, 1, 0, + 1, 1, 1, 0, + 1, 0, 0, 0, + ] + ) + + def test_new_answer(self): + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer3 = models.Answer.objects.create_new( + question = self.question, + author = self.u11, + added_at = timestamp, + text = 'answer3' + ) + time_end = datetime.datetime.now() + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set( + [ + self.u12, self.u13, + self.u21, self.u31 + ] + ) + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 0, 1, 1, 0, + 1, 0, 0, 0, + 1, 0, 0, 0, + ] + ) + self.reset_response_counts() + time.sleep(1) + timestamp = datetime.datetime.now() + self.answer3 = models.Answer.objects.create_new( + question = self.question, + author = self.u31, + added_at = timestamp, + text = 'answer4' + ) + notifications = get_re_notif_after(timestamp) + self.assertEqual(len(notifications), 1) + self.assertEqual( + set(notifications[0].receiving_users.all()), + set( + [ + self.u11, self.u12, self.u13, + self.u21 + ] + ) + ) + self.reload_users() + self.assertEqual( + [ + self.u11.response_count, + self.u12.response_count, + self.u13.response_count, + self.u14.response_count, + self.u21.response_count, + self.u22.response_count, + self.u23.response_count, + self.u24.response_count, + self.u31.response_count, + self.u32.response_count, + self.u33.response_count, + self.u34.response_count, + ], + [ + 1, 1, 1, 0, + 1, 0, 0, 0, + 0, 0, 0, 0, + ] + ) + + diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py new file mode 100644 index 00000000..7a999a22 --- /dev/null +++ b/askbot/tests/page_load_tests.py @@ -0,0 +1,273 @@ +from django.test import TestCase +from django.template import defaultfilters +from django.core.urlresolvers import reverse +from askbot import models + +class PageLoadTestCase(TestCase): + def try_url( + self, + url_name, status_code=200, template=None, + kwargs={}, redirect_url=None, follow=False, + data = {}, + ): + url = reverse(url_name, kwargs = kwargs) + url_info = 'getting url %s' % url + if data: + url_info += '?' + '&'.join(['%s=%s' % (k, v) for k, v in data.iteritems()]) + print url_info + + r = self.client.get(url, data=data, follow=follow) + if hasattr(self.client, 'redirect_chain'): + print 'redirect chain: %s' % ','.join(self.client.redirect_chain) + + self.assertEqual(r.status_code, status_code) + + if template: + #asuming that there is more than one template + print 'templates are %s' % ','.join([t.name for t in r.template]) + self.assertEqual(r.template[0].name, template) + +class PageLoadTests(PageLoadTestCase): + fixtures = ['tmp/fixture1.json', ] + + def test_index(self): + #todo: merge this with all reader url tests + print 'trying to reverse index' + response = self.client.get(reverse('index'), follow=True) + self.assertEqual(response.status_code, 200) + self.failUnless(len(response.redirect_chain) == 1) + self.failUnless(response.redirect_chain[0][0].endswith('/questions/')) + c = response.context[0] + t = response.template[0] + self.assertEqual(t.name, 'questions.html') + print 'index works' + + def proto_test_non_user_urls(self): + """test all reader views thoroughly + on non-crashiness (no correcteness tests here) + """ + + self.try_url('sitemap') + self.try_url('feeds', kwargs={'url':'rss'}) + self.try_url('about', template='about.html') + self.try_url('privacy', template='privacy.html') + self.try_url('logout', template='logout.html') + self.try_url('user_signin', template='authopenid/signin.html') + #todo: test different tabs + self.try_url('tags', template='tags.html') + self.try_url('tags', data={'sort':'name'}, template='tags.html') + self.try_url('tags', data={'sort':'used'}, template='tags.html') + self.try_url('badges', template='badges.html') + self.try_url( + 'answer_revisions', + template='revisions_answer.html', + kwargs={'id':38} + ) + #todo: test different sort methods and scopes + self.try_url( + 'questions', + template='questions.html' + ) + self.try_url( + 'questions', + data={'start_over':'true'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'scope':'unanswered'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'scope':'all'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'scope':'favorite'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'scope':'unanswered', 'sort':'latest'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'scope':'unanswered', 'sort':'oldest'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'scope':'unanswered', 'sort':'active'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'scope':'unanswered', 'sort':'inactive'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'sort':'hottest'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'sort':'coldest'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'sort':'mostvoted'}, + template='questions.html' + ) + self.try_url( + 'questions', + data={'sort':'leastvoted'}, + template='questions.html' + ) + self.try_url( + 'question', + kwargs={'id':1}, + ) + self.try_url( + 'question', + kwargs={'id':2}, + ) + self.try_url( + 'question', + kwargs={'id':3}, + ) + self.try_url( + 'question_revisions', + kwargs={'id':17}, + template='revisions_question.html' + ) + self.try_url('users', template='users.html') + #todo: really odd naming conventions for sort methods + self.try_url( + 'users', + template='users.html', + data={'sort':'reputation'}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'newest'}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'last'}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'user'}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'reputation', 'page':2}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'newest', 'page':2}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'last', 'page':2}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'user', 'page':2}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'reputation', 'page':1}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'newest', 'page':1}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'last', 'page':1}, + ) + self.try_url( + 'users', + template='users.html', + data={'sort':'user', 'page':1}, + ) + self.try_url( + 'edit_user', + template='authopenid/signin.html', + kwargs={'id':4}, + status_code=200, + follow=True, + ) + + def test_non_user_urls(self): + self.proto_test_non_user_urls() + + #def test_non_user_urls_logged_in(self): + #user = User.objects.get(id=1) + #somehow login this user + #self.proto_test_non_user_urls() + + def test_user_urls(self): + user = models.User.objects.get(id=2) + name_slug = defaultfilters.slugify(user.username) + self.try_url( + 'user_profile', + kwargs={'id': 2, 'slug': name_slug}, + data={'sort':'stats'}, + template='user_stats.html' + ) + self.try_url( + 'user_profile', + kwargs={'id': 2, 'slug': name_slug}, + data={'sort':'recent'}, + template='user_recent.html' + ) + self.try_url( + 'user_profile', + kwargs={'id': 2, 'slug': name_slug}, + data={'sort':'responses'}, + status_code=404, + template='404.html' + ) + self.try_url( + 'user_profile', + kwargs={'id': 2, 'slug': name_slug}, + data={'sort':'reputation'}, + template='user_reputation.html' + ) + self.try_url( + 'user_profile', + kwargs={'id': 2, 'slug': name_slug}, + data={'sort':'votes'}, + status_code=404, + template='404.html' + ) + self.try_url( + 'user_profile', + kwargs={'id': 2, 'slug': name_slug}, + data={'sort':'favorites'}, + template='user_favorites.html' + ) + self.try_url( + 'user_profile', + kwargs={'id': 2, 'slug': name_slug}, + data={'sort':'email_subscriptions'}, + status_code=404, + template='404.html' + ) diff --git a/askbot/tests/permission_assertion_tests.py b/askbot/tests/permission_assertion_tests.py new file mode 100644 index 00000000..dd84a85b --- /dev/null +++ b/askbot/tests/permission_assertion_tests.py @@ -0,0 +1,426 @@ +from django.test import TestCase +from django.core import exceptions +from askbot.tests import utils +from askbot.conf import settings as askbot_settings +from askbot import models + +class PermissionAssertionTestCase(TestCase): + """base TestCase class for permission + assertion tests + + subclass may redefine method extraSetUp + """ + + def setUp(self): + self.user = utils.create_user( + username = 'test', + email = 'test@test.com' + ) + self.extraSetUp() + + def extraSetUp(self): + pass + + def create_other_user(self): + return utils.create_user( + username = 'other', + email = 'other@test.com' + ) + + def post_question(self, author = None): + if author is None: + author = self.user + return author.post_question( + title = 'test question title', + body_text = 'test question body', + tags = 'test' + ) + + def post_answer(self, question = None, author = None): + if author is None: + author = self.user + return author.post_answer( + question = question, + body_text = 'test answer' + ) + + +class CommentPermissionAssertionTests(PermissionAssertionTestCase): + + def extraSetUp(self): + self.min_rep = askbot_settings.MIN_REP_TO_LEAVE_COMMENTS + self.other_user = self.create_other_user() + + def test_blocked_user_cannot_comment_own_question(self): + question = self.post_question() + + self.user.set_status('b') + self.assertRaises( + exceptions.PermissionDenied, + self.user.post_comment, + parent_post = question, + body_text = 'test comment' + ) + + def test_blocked_user_cannot_comment_own_answer(self): + question = self.post_question() + answer = self.post_answer(question) + + self.user.set_status('b') + + self.assertRaises( + exceptions.PermissionDenied, + self.user.post_comment, + parent_post = answer, + body_text = 'test comment' + ) + + def test_blocked_user_cannot_delete_own_comment(self): + question = self.post_question() + comment = self.user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + self.user.set_status('b') + self.assertRaises( + exceptions.PermissionDenied, + self.user.delete_post, + post = comment + ) + + def test_low_rep_user_cannot_delete_others_comment(self): + question = self.post_question() + comment = self.user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + assert( + self.other_user.reputation < \ + askbot_settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS + ) + self.assertRaises( + exceptions.PermissionDenied, + self.other_user.delete_post, + post = comment + ) + + def test_high_rep_user_can_delete_comment(self): + question = self.post_question() + comment = self.user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + self.other_user.reputation = \ + askbot_settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS + + self.other_user.delete_comment(comment) + + def test_low_rep_user_can_delete_own_comment(self): + question = self.post_question() + answer = self.other_user.post_answer( + question = question, + body_text = 'test answer' + ) + comment = self.user.post_comment( + parent_post = answer, + body_text = 'test comment' + ) + assert( + self.user.reputation < \ + askbot_settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS + ) + self.user.delete_comment(comment) + + def test_moderator_can_delete_comment(self): + question = self.post_question() + comment = self.user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + self.other_user.set_status('m') + self.other_user.delete_comment(comment) + + def test_admin_can_delete_comment(self): + question = self.post_question() + comment = self.user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + self.other_user.is_superuser = True + self.other_user.delete_comment(comment) + + def test_high_rep_suspended_user_cannot_delete_others_comment(self): + question = self.post_question() + comment = self.user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + self.other_user.reputation = \ + askbot_settings.MIN_REP_TO_DELETE_OTHERS_COMMENTS + 1 + self.other_user.set_status('s') + self.assertRaises( + exceptions.PermissionDenied, + self.other_user.delete_post, + post = comment + ) + + def test_suspended_user_can_delete_own_comment(self): + question = self.post_question() + comment = self.user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + self.user.set_status('s') + self.user.delete_comment(comment) + + def test_low_rep_user_cannot_comment_others(self): + question = self.post_question( + author = self.other_user + ) + assert(self.user.reputation < self.min_rep) + self.assertRaises( + exceptions.PermissionDenied, + self.user.post_comment, + parent_post = question, + body_text = 'test comment' + ) + + def test_low_rep_user_can_comment_others_answer_to_own_question(self): + question = self.post_question() + assert(self.user.reputation < self.min_rep) + answer = self.other_user.post_answer( + question = question, + body_text = 'test answer' + ) + comment = self.user.post_comment( + parent_post = answer, + body_text = 'test comment' + ) + self.assertTrue(isinstance(comment, models.Comment)) + + def test_high_rep_user_can_comment(self): + question = self.post_question( + author = self.other_user + ) + self.user.reputation = self.min_rep + comment = self.user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + self.assertTrue(isinstance(comment, models.Comment)) + + def test_suspended_user_cannot_comment_others_question(self): + question = self.post_question(author = self.other_user) + self.user.set_status('s') + self.assertRaises( + exceptions.PermissionDenied, + self.user.post_comment, + parent_post = question, + body_text = 'test comment' + ) + + def test_suspended_user_can_comment_own_question(self): + question = self.post_question() + self.user.set_status('s') + comment = self.user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + self.assertTrue(isinstance(comment, models.Comment)) + + def test_low_rep_admin_can_comment_others_question(self): + question = self.post_question() + self.other_user.is_superuser = True + assert(self.other_user.is_administrator()) + assert(self.other_user.reputation < self.min_rep) + comment = self.other_user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + self.assertTrue(isinstance(comment, models.Comment)) + + def test_low_rep_moderator_can_comment_others_question(self): + question = self.post_question() + self.other_user.set_status('m') + assert(self.other_user.is_moderator()) + assert(self.other_user.reputation < self.min_rep) + comment = self.other_user.post_comment( + parent_post = question, + body_text = 'test comment' + ) + self.assertTrue(isinstance(comment, models.Comment)) + +#def user_assert_can_post_comment(self, parent_post): +#def user_assert_can_delete_comment(self, comment = None): + +#def user_assert_can_vote_for_post( +#def user_assert_can_revoke_old_vote(self, vote): + +#def user_assert_can_flag_offensive(self): + +#def user_assert_can_upload_file(request_user): +#def user_assert_can_post_question(self): +#def user_assert_can_post_answer(self): +#def user_assert_can_edit_post(self, post = None): +#def user_assert_can_delete_Post(self, post = None): +#def user_assert_can_close_question(self, question = None): +#def user_assert_can_retag_questions(self): + +class VotePermissionAssertionTests(PermissionAssertionTestCase): + """Tests permission for voting + """ + def extraSetUp(self): + self.min_rep_up = askbot_settings.MIN_REP_TO_VOTE_UP + self.min_rep_down = askbot_settings.MIN_REP_TO_VOTE_DOWN + self.other_user = self.create_other_user() + + def assert_cannot_vote(self, user = None, dir = None): + assert(dir in ('up', 'down')) + + vote_func = self.get_vote_function( + user = user, + dir = dir + ) + + self.assertRaises( + exceptions.PermissionDenied, + vote_func, + self.question, + ) + self.assertRaises( + exceptions.PermissionDenied, + vote_func, + self.answer, + ) + + def prepare_data(self, status = 'a', rep = 1): + self.question = self.post_question() + self.answer = self.post_answer(question = self.question) + self.other_user.reputation = rep + self.other_user.set_status(status) + + def bad_user_cannot_vote(self, status = 'a', rep = 1, dir = 'up'): + """dir - vote direction up/down + rep - reputation + """ + self.prepare_data(status = status) + + self.assert_cannot_vote( + user = self.other_user, + dir = dir + ) + + def get_vote_function(self, dir = None, user = None): + + def vote_func(post): + user.assert_can_vote_for_post(post = post, direction = dir) + + return vote_func + + + def good_user_can_vote(self, user = None, dir = 'up'): + + if user is None: + user = self.other_user + + vote_func = self.get_vote_function(dir = dir, user = user) + + vote_func(self.question) + vote_func(self.answer) + + + def test_blocked_user_cannot_vote(self): + self.bad_user_cannot_vote(status = 'b') + + def test_suspended_user_cannot_vote(self): + self.bad_user_cannot_vote(status = 's') + + def test_low_rep_user_cannot_upvote(self): + self.bad_user_cannot_vote(dir = 'up') + + def test_low_rep_user_cannot_downvote(self): + self.bad_user_cannot_vote(dir = 'down') + + def test_high_rep_user_can_upvote(self): + self.prepare_data(rep = self.min_rep_up) + self.good_user_can_vote(dir = 'up') + + def test_high_rep_user_can_downvote(self): + self.prepare_data(rep = self.min_rep_down) + self.good_user_can_vote(dir = 'down') + + def test_low_rep_admins_can_upvote_others(self): + self.prepare_data() + self.other_user.set_status('m') + self.good_user_can_vote(dir = 'up') + + def test_low_rep_admins_can_downvote_others(self): + self.prepare_data() + self.other_user.set_status('m') + self.good_user_can_vote(dir = 'down') + + def test_admins_cannot_upvote_self(self): + self.prepare_data() + self.user.set_status('m') + self.assert_cannot_vote( + user = self.user, + dir = 'up' + ) + + def test_admins_cannot_downvote_self(self): + self.prepare_data() + self.user.set_status('m') + self.assert_cannot_vote( + user = self.user, + dir = 'down' + ) + +class UploadPermissionAssertionTests(PermissionAssertionTestCase): + """Tests permissions for file uploads + """ + + def extraSetUp(self): + self.min_rep = askbot_settings.MIN_REP_TO_UPLOAD_FILES + + def test_suspended_user_cannot_upload(self): + self.user.set_status('s') + self.assertRaises( + exceptions.PermissionDenied, + self.user.assert_can_upload_file + ) + + def test_blocked_user_cannot_upload(self): + self.user.set_status('b') + self.assertRaises( + exceptions.PermissionDenied, + self.user.assert_can_upload_file + ) + def test_low_rep_user_cannot_upload(self): + self.user.reputation = self.min_rep - 1 + self.assertRaises( + exceptions.PermissionDenied, + self.user.assert_can_upload_file + ) + + def test_high_rep_user_can_upload(self): + self.user.reputation = self.min_rep + try: + self.user.assert_can_upload_file() + except exceptions.PermissionDenied: + self.fail('high rep user must be able to upload') + + def test_low_rep_moderator_can_upload(self): + assert(self.user.reputation < self.min_rep) + self.user.set_status('m') + try: + self.user.assert_can_upload_file() + except exceptions.PermissionDenied: + self.fail('high rep user must be able to upload') + + def test_low_rep_administrator_can_upload(self): + assert(self.user.reputation < self.min_rep) + self.user.is_superuser = True + try: + self.user.assert_can_upload_file() + except exceptions.PermissionDenied: + self.fail('high rep user must be able to upload') diff --git a/askbot/tests/utils.py b/askbot/tests/utils.py new file mode 100644 index 00000000..46d8451a --- /dev/null +++ b/askbot/tests/utils.py @@ -0,0 +1,30 @@ +"""utility functions used by Askbot test cases +""" +from askbot import models + +def create_user( + username = None, + email = None, + notification_schedule = None, + date_joined = None, + status = 'a' + ): + """Creates a user and sets default update subscription + settings""" + user = models.User.objects.create_user(username, email) + if date_joined is not None: + user.date_joined = date_joined + user.save() + user.set_status(status) + if notification_schedule == None: + notification_schedule = models.EmailFeedSetting.NO_EMAIL_SCHEDULE + + for feed_type, frequency in notification_schedule.items(): + feed = models.EmailFeedSetting( + feed_type = feed_type, + frequency = frequency, + subscriber = user + ) + feed.save() + return user + diff --git a/askbot/views/commands.py b/askbot/views/commands.py index 07cc57a5..ca48c9bb 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -253,21 +253,26 @@ def vote(request, id): item = FlaggedItem(user=request.user, content_object=post, flagged_at=datetime.datetime.now()) auth.onFlaggedItem(item, post, request.user) response_data['count'] = post.offensive_flag_count + elif vote_type in ['9', '10']: + #delete question or answer post = question post_id = id if vote_type == '10': post_id = request.POST.get('postId') post = get_object_or_404(Answer, id=post_id) - if not auth.can_delete_post(request.user, post): - response_data['allowed'] = -2 - elif post.deleted == True: + if post.deleted == True: logging.debug('debug restoring post in view') auth.onDeleteCanceled(post, request.user) response_data['status'] = 1 else: - auth.onDeleted(post, request.user) + try: + request.user.delete_post(post = post) + except exceptions.PermissionDenied, e: + response_data['allowed'] = -2 + request.user.message_set.create(message = str(e)) + elif vote_type == '11':#subscribe q updates user = request.user if user.is_authenticated(): |