def test_more_comments_children(client, logged_in_profile): """Retrieve more comments specifying child elements""" image_url = "/deserunt/consequatur.jpg" name = "Brooke Robles" username = "******" headline = "a test headline" logged_in_profile.image_small = image_url logged_in_profile.image_file_small = None logged_in_profile.name = name logged_in_profile.headline = headline logged_in_profile.save() post_id = "1" url = "{base}?post_id={post_id}&parent_id=&children=e9l&children=e9m".format( base=reverse("morecomments-detail"), post_id=post_id) resp = client.get(url) assert resp.status_code == status.HTTP_200_OK assert resp.json() == [ { "id": "e9l", "parent_id": None, "post_id": "1", "text": "shallow comment 25", "author_id": username, "score": 1, "upvoted": True, "downvoted": False, "removed": False, "deleted": False, "subscribed": False, "created": "2017-11-09T16:35:55+00:00", "profile_image": image_uri(logged_in_profile), "author_name": name, "author_headline": headline, "edited": False, "comment_type": "comment", "num_reports": None, }, { "id": "e9m", "parent_id": None, "post_id": "1", "text": "shallow comment 26", "author_id": username, "score": 1, "upvoted": True, "downvoted": False, "removed": False, "deleted": False, "subscribed": False, "created": "2017-11-09T16:36:00+00:00", "profile_image": image_uri(logged_in_profile), "author_name": name, "author_headline": headline, "edited": False, "comment_type": "comment", "num_reports": None, }, ]
def default_comment_response_data(post, comment, user): """ Helper function. Returns a dict containing some of the data that we expect from the API given a post, comment, and user. """ # For some reason, the default values are different for staff and non-staff users if user.is_staff: user_dependent_defaults = {"num_reports": 0} else: user_dependent_defaults = {"num_reports": None} return { "author_id": user.username, "author_name": user.profile.name, "author_headline": user.profile.headline, "comment_type": "comment", "created": comment.created, "deleted": False, "downvoted": False, "edited": False, "id": comment.id, "parent_id": None, "post_id": post.id, "profile_image": image_uri(user.profile), "removed": False, "score": 1, "subscribed": False, "upvoted": False, "text": comment.text, **user_dependent_defaults, }
def test_list_comments_anonymous(client, public_channel, reddit_factories): """List comments as an anonymous user""" user = UserFactory.create(username="******") post = reddit_factories.text_post("a post with comments", user, channel=public_channel) comment = reddit_factories.comment("comment", user, post_id=post.id) url = reverse("comment-list", kwargs={"post_id": post.id}) resp = client.get(url) assert resp.status_code == status.HTTP_200_OK assert resp.json() == [{ "id": comment.id, "parent_id": None, "post_id": post.id, "text": comment.text, "author_id": user.username, "score": 1, "upvoted": False, "downvoted": False, "removed": False, "deleted": False, "subscribed": False, "created": comment.created, "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "comment_type": "comment", "num_reports": None, }]
def test_get_comment(user_client, private_channel_and_contributor, reddit_factories): """ should be able to GET a comment """ channel, user = private_channel_and_contributor post = reddit_factories.text_post("my geat post", user, channel=channel) comment = reddit_factories.comment("comment", user, post_id=post.id) url = reverse("comment-detail", kwargs={"comment_id": comment.id}) resp = user_client.get(url) assert resp.status_code == status.HTTP_200_OK assert resp.json() == [{ "author_id": user.username, "created": comment.created, "id": comment.id, "parent_id": None, "post_id": post.id, "score": 1, "text": comment.text, "upvoted": True, "downvoted": False, "removed": False, "deleted": False, "subscribed": False, "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "comment_type": "comment", "num_reports": None, }]
def test_es_comment_serializer(has_author): """ Test that ESCommentSerializer correctly serializes a comment object """ comment = CommentFactory.create() serialized = ESCommentSerializer(instance=comment).data assert serialized == { "object_type": COMMENT_TYPE, "author_id": comment.author.username if comment.author is not None else None, "author_name": comment.author.profile.name if comment.author is not None else None, "author_headline": comment.author.profile.headline if comment.author is not None else None, "author_avatar_small": image_uri( comment.author.profile if comment.author is not None else None ), "text": comment.text, "score": comment.score, "removed": comment.removed, "created": drf_datetime(comment.created_on), "deleted": comment.deleted, "comment_id": comment.comment_id, "parent_comment_id": comment.parent_id, "channel_name": comment.post.channel.name, "channel_title": comment.post.channel.title, "channel_type": comment.post.channel.channel_type, "post_id": comment.post.post_id, "post_title": comment.post.title, "post_slug": comment.post.slug, }
def test_update_comment_ignore_reports(staff_client, private_channel_and_contributor, reddit_factories): """Update a comment to ignore reports as a moderator""" channel, user = private_channel_and_contributor post = reddit_factories.text_post("post", user, channel=channel) comment = reddit_factories.comment("comment", user, post_id=post.id) url = reverse("comment-detail", kwargs={"comment_id": comment.id}) resp = staff_client.patch(url, type="json", data={"ignore_reports": True}) assert resp.status_code == status.HTTP_200_OK assert resp.json() == { "author_id": user.username, "created": comment.created, "id": comment.id, "parent_id": None, "post_id": post.id, "score": 1, "text": comment.text, "upvoted": False, "downvoted": False, "removed": False, "deleted": False, "subscribed": False, "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "comment_type": "comment", "num_reports": 0, }
def get_author_avatar_small(self, instance): """Returns the small profile image of the author""" profile = ( instance.author.profile if instance.author and instance.author.profile else None ) return image_uri(profile)
def test_profile_img_url_micromaster_image(): """ Test that the correct profile image URL is returned for a profile with a micromasters profile URL """ profile = UserFactory.create().profile profile.image_file = profile.image_medium_file = profile.image_small_file = None profile.image_medium = "http://testserver/profiles/image.jpg" profile.save() assert image_uri(profile, "image_medium").endswith(profile.image_medium)
def test_create_comment( user_client, reddit_factories, private_channel_and_contributor, mock_notify_subscribed_users, extra_params, extra_expected, score, ): # pylint: disable=too-many-arguments """Create a comment""" channel, user = private_channel_and_contributor post = reddit_factories.text_post("a post", user, channel=channel) url = reverse("comment-list", kwargs={"post_id": post.id}) resp = user_client.post(url, data={ "text": "reply_to_post 2", **extra_params }) assert resp.status_code == status.HTTP_201_CREATED assert resp.json() == { "author_id": user.username, "created": any_instance_of(str), "id": any_instance_of(str), "parent_id": None, "post_id": post.id, "score": 1, "text": "reply_to_post 2", "upvoted": True, "downvoted": False, "removed": False, "deleted": False, "subscribed": True, "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "comment_type": "comment", "num_reports": None, **extra_expected, } assert_properties_eq( Comment.objects.get(comment_id=resp.json()["id"]), { "author": user, "text": "reply_to_post 2", "score": score, "removed": False, "deleted": False, "edited": False, "created_on": any_instance_of(datetime), }, ) mock_notify_subscribed_users.assert_called_once_with( post.id, None, resp.json()["id"])
def test_profile_img_url_uploaded_image(): """ Test that the correct profile image URL is returned for a profile with an uploaded image """ profile = UserFactory.create().profile image = Image.new("RGBA", size=(50, 50), color=(155, 0, 0)) profile.image_small_file.save("/profiles/realimage.jpg", BytesIO(image.tobytes()), True) profile.save() assert image_uri(profile, "image_small") == profile.image_small_file.url
def default_post_response_data(channel, post, user): """ Helper function. Returns a dict containing some of the data that we expect from the API given a channel, post, and user. """ # For some reason, the default values are different for staff and non-staff users if user.is_staff: user_dependent_defaults = {"upvoted": False, "num_reports": 0} else: user_dependent_defaults = {"upvoted": True, "num_reports": None} post_obj = Post.objects.get(post_id=post.id) article = Article.objects.filter(post=post_obj).first() text = post.text if not text and not post.url: text = "" if article: plain_text = render_article_text(article.content) elif text: plain_text = markdown_to_plain_text(text) else: plain_text = None return { "url": post.url, "url_domain": urlparse(post.url).hostname if post.url else None, "cover_image": None, "thumbnail": None, "text": text, "article_content": article.content if article is not None else None, "plain_text": plain_text, "post_type": post_obj.post_type, "title": post.title, "removed": False, "deleted": False, "subscribed": False, "score": 1, "author_id": user.username, "id": post.id, "slug": get_reddit_slug(post.permalink), "created": post.created, "num_comments": 0, "channel_name": channel.name, "channel_title": channel.title, "channel_type": channel.channel_type, "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "stickied": False, **user_dependent_defaults, }
def test_profile_img_url_gravatar_nameless(): """ Test that the correct profile gravatar image URL is returned for a profile with no name """ profile = UserFactory.create().profile profile.image = profile.image_small = profile.image_medium = None profile.image_file = profile.image_medium_file = profile.image_small_file = None profile.name = None profile.save() profile_image = image_uri(profile, "image_small") assert profile_image.startswith("https://www.gravatar.com/avatar/") params_d = parse_qs(urlparse(profile_image).query)["d"][0] assert params_d.endswith(DEFAULT_PROFILE_IMAGE)
def test_profile_img_url_gravatar_fullname(): """ Test that the correct profile gravatar image URL is returned for a profile with a name """ profile = UserFactory.create().profile profile.image = profile.image_small = profile.image_medium = None profile.image_file = profile.image_medium_file = profile.image_small_file = None profile.save() profile_image = image_uri(profile, "image_small") assert profile_image.startswith("https://www.gravatar.com/avatar/") params_d = parse_qs(urlparse(profile_image).query)["d"][0] assert params_d.endswith("profile/{}/64/fff/579cf9.png".format( profile.user.username))
def test_list_reports(staff_client, private_channel_and_contributor, reddit_factories, staff_api): """List reported content""" channel, user = private_channel_and_contributor post = reddit_factories.text_post("post", user, channel=channel) comment = reddit_factories.comment("comment", user, post_id=post.id) # report both with a regular user api = Api(user) api.report_comment(comment.id, "spam") api.report_post(post.id, "bad") # report both with a moderator user staff_api.report_comment(comment.id, "spam") staff_api.report_post(post.id, "junk") url = reverse("channel-reports", kwargs={"channel_name": channel.name}) resp = staff_client.get(url) assert resp.status_code == status.HTTP_200_OK assert resp.json() == [ { "post": None, "comment": { "author_id": user.username, "created": comment.created, "id": comment.id, "parent_id": None, "post_id": post.id, "score": 1, "text": comment.text, "upvoted": False, "downvoted": False, "removed": False, "deleted": False, "subscribed": False, "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "comment_type": "comment", "num_reports": 2, }, "reasons": ["spam"], }, { "post": { **default_post_response_data(channel, post, user), "num_comments": 1, "num_reports": 2, "upvoted": False, }, "comment": None, "reasons": ["bad", "junk"], }, ]
def test_create_article_post(user_client, private_channel_and_contributor): """ Create a new article post """ channel, user = private_channel_and_contributor url = reverse("post-list", kwargs={"channel_name": channel.name}) article_text = "some text" article_content = [{ "key": "value", "nested": { "number": 4 }, "text": article_text }] resp = user_client.post(url, { "title": "parameterized testing", "article_content": article_content }) assert resp.status_code == status.HTTP_201_CREATED assert resp.json() == { "title": "parameterized testing", "text": "", "article_content": article_content, "plain_text": article_text, "url": None, "url_domain": None, "cover_image": None, "thumbnail": None, "author_id": user.username, "created": any_instance_of(str), "upvoted": True, "removed": False, "deleted": False, "subscribed": True, "id": any_instance_of(str), "slug": "parameterized-testing", "num_comments": 0, "score": 1, "channel_name": channel.name, "channel_title": channel.title, "channel_type": "private", "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "stickied": False, "num_reports": None, "post_type": EXTENDED_POST_TYPE_ARTICLE, } article = Article.objects.filter(post__post_id=resp.json()["id"]) assert article.exists() assert article.first().content == article_content
def test_es_profile_serializer(mocker, user): """ Test that ESProfileSerializer correctly serializes a profile object """ mocker.patch( "search.serializers.get_channels", return_value={"channel01", "channel02"} ) return_value = [("channel01", datetime.now()), ("channel02", datetime.now())] mocker.patch("search.serializers.get_channel_join_dates", return_value=return_value) serialized = ESProfileSerializer().serialize(user.profile) assert serialized == { "object_type": PROFILE_TYPE, "author_id": user.username, "author_name": user.profile.name, "author_avatar_small": image_uri(user.profile), "author_avatar_medium": image_uri(user.profile, IMAGE_MEDIUM), "author_bio": user.profile.bio, "author_headline": user.profile.headline, "author_channel_membership": ["channel01", "channel02"], "author_channel_join_data": [ {"name": name, "joined": created_on} for name, created_on in return_value ], }
def test_list_deleted_comments(client, logged_in_profile): """List comments which are deleted according to reddit""" user = UserFactory.create(username="******") url = reverse("comment-list", kwargs={"post_id": "p"}) resp = client.get(url) assert resp.status_code == status.HTTP_200_OK assert resp.json() == [ { "author_id": "[deleted]", "comment_type": "comment", "created": "2017-09-27T16:03:42+00:00", "downvoted": False, "parent_id": None, "post_id": "p", "profile_image": DEFAULT_PROFILE_IMAGE, "score": 1, "text": DELETED_COMMENT_OR_POST_TEXT, "upvoted": False, "removed": False, "deleted": True, "subscribed": False, "id": "1s", "edited": False, "author_name": "[deleted]", "author_headline": None, "num_reports": None, }, { "author_id": user.username, "created": "2017-09-27T16:03:51+00:00", "comment_type": "comment", "downvoted": False, "id": "1t", "parent_id": "1s", "post_id": "p", "profile_image": image_uri(user.profile), "score": 1, "text": "reply to parent which is not deleted", "upvoted": False, "removed": False, "edited": False, "deleted": False, "subscribed": False, "author_name": user.profile.name, "author_headline": user.profile.headline, "num_reports": None, }, ]
def test_get_post_no_profile(user_client, private_channel_and_contributor, reddit_factories): """Get an existing post for a user with no profile""" channel, user = private_channel_and_contributor user.profile.delete() post = reddit_factories.text_post("my geat post", user, channel=channel) url = reverse("post-detail", kwargs={"post_id": post.id}) resp = user_client.get(url) assert resp.status_code == status.HTTP_200_OK assert resp.json() == { **default_post_response_data(channel, post, user), "author_name": "[deleted]", "author_headline": None, "profile_image": image_uri(None), }
def test_create_url_post_existing_meta(user_client, private_channel_and_contributor, mocker, settings): """ Create a new url post """ settings.EMBEDLY_KEY = "FAKE" channel, user = private_channel_and_contributor link_url = "http://micromasters.mit.edu/🐨" thumbnail = "http://fake/thumb.jpg" embedly_stub = mocker.patch("channels.utils.get_embedly_summary") LinkMetaFactory.create(url=link_url, thumbnail=thumbnail) url = reverse("post-list", kwargs={"channel_name": channel.name}) resp = user_client.post(url, {"title": "url title 🐨", "url": link_url}) embedly_stub.assert_not_called() assert resp.status_code == status.HTTP_201_CREATED assert resp.json() == { "title": "url title 🐨", "post_type": LINK_TYPE_LINK, "url": link_url, "url_domain": "micromasters.mit.edu", "cover_image": None, "thumbnail": thumbnail, "text": None, "article_content": None, "plain_text": None, "author_id": user.username, "created": any_instance_of(str), "upvoted": True, "id": any_instance_of(str), "slug": "url-title", "num_comments": 0, "removed": False, "deleted": False, "subscribed": True, "score": 1, "channel_name": channel.name, "channel_title": channel.title, "channel_type": channel.channel_type, "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "stickied": False, "num_reports": None, }
def test_create_text_post(user_client, private_channel_and_contributor): """ Create a new text post """ channel, user = private_channel_and_contributor url = reverse("post-list", kwargs={"channel_name": channel.name}) resp = user_client.post(url, { "title": "parameterized testing", "text": "tests are great" }) assert resp.status_code == status.HTTP_201_CREATED assert resp.json() == { "title": "parameterized testing", "text": "tests are great", "article_content": None, "plain_text": "tests are great", "url": None, "url_domain": None, "cover_image": None, "thumbnail": None, "author_id": user.username, "created": any_instance_of(str), "upvoted": True, "removed": False, "deleted": False, "subscribed": True, "id": any_instance_of(str), "slug": "parameterized-testing", "num_comments": 0, "score": 1, "channel_name": channel.name, "channel_title": channel.title, "channel_type": channel.channel_type, "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "stickied": False, "num_reports": None, "post_type": LINK_TYPE_SELF, }
def test_create_comment_reply_to_comment( user_client, reddit_factories, private_channel_and_contributor, mock_notify_subscribed_users, ): """Create a comment that's a reply to another comment""" channel, user = private_channel_and_contributor post = reddit_factories.text_post("a post", user, channel=channel) comment = reddit_factories.comment("comment", user, post_id=post.id) url = reverse("comment-list", kwargs={"post_id": post.id}) resp = user_client.post(url, data={ "text": "reply_to_comment 3", "comment_id": comment.id }) assert resp.status_code == status.HTTP_201_CREATED assert resp.json() == { "author_id": user.username, "created": any_instance_of(str), "id": any_instance_of(str), "parent_id": comment.id, "post_id": post.id, "score": 1, "text": "reply_to_comment 3", "upvoted": True, "downvoted": False, "removed": False, "deleted": False, "subscribed": True, "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "comment_type": "comment", "num_reports": None, } mock_notify_subscribed_users.assert_called_once_with( post.id, comment.id, resp.json()["id"])
def test_more_comments_anonymous(client, public_channel, reddit_factories): """List more comments as an anonymous user""" user = UserFactory.create(username="******") post = reddit_factories.text_post("a post with comments", user, channel=public_channel) # 51 to show a morecomments link comments = [ reddit_factories.comment("comment{}".format(number), user, post_id=post.id) for number in range(51) ] last_comment = comments[-1] url = "{base}?post_id={post_id}&children={comment_id}".format( base=reverse("morecomments-detail"), post_id=post.id, comment_id=last_comment.id) resp = client.get(url) assert resp.status_code == status.HTTP_200_OK assert resp.json() == [{ "author_id": user.username, "author_name": user.profile.name, "author_headline": user.profile.headline, "comment_type": "comment", "created": last_comment.created, "deleted": False, "downvoted": False, "edited": False, "id": last_comment.id, "num_reports": None, "parent_id": None, "post_id": post.id, "profile_image": image_uri(user.profile), "removed": False, "score": 1, "subscribed": False, "text": last_comment.text, "upvoted": False, }]
def test_create_post_without_upvote(user_client, private_channel_and_contributor): """Test creating a post without an upvote in the body""" channel, user = private_channel_and_contributor url = reverse("post-list", kwargs={"channel_name": channel.name}) resp = user_client.post(url, {"title": "x", "text": "y", "upvoted": False}) assert resp.status_code == status.HTTP_201_CREATED assert resp.json() == { "title": "x", "text": "y", "article_content": None, "plain_text": "y", "url": None, "url_domain": None, "cover_image": None, "thumbnail": None, "author_id": user.username, "created": "2018-08-24T18:14:32+00:00", "upvoted": False, "removed": False, "deleted": False, "subscribed": True, "id": "43", "slug": "x", "num_comments": 0, "score": 1, "channel_name": channel.name, "channel_title": channel.title, "channel_type": channel.channel_type, "profile_image": image_uri(user.profile), "author_name": user.profile.name, "author_headline": user.profile.headline, "edited": False, "stickied": False, "num_reports": None, "post_type": LINK_TYPE_SELF, }
def test_es_post_serializer(factory_kwargs): """ Test that ESPostSerializer correctly serializes a post object """ post = PostFactory.create(**factory_kwargs) serialized = ESPostSerializer(instance=post).data assert serialized == { "object_type": POST_TYPE, "article_content": post.article.content if getattr(post, "article", None) is not None else None, "plain_text": post.plain_text, "author_id": post.author.username if post.author is not None else None, "author_name": post.author.profile.name if post.author is not None else None, "author_headline": post.author.profile.headline if post.author is not None else None, "author_avatar_small": image_uri( post.author.profile if post.author is not None else None ), "channel_name": post.channel.name, "channel_title": post.channel.title, "channel_type": post.channel.channel_type, "text": post.text, "score": post.score, "removed": post.removed, "created": drf_datetime(post.created_on), "deleted": post.deleted, "num_comments": post.num_comments, "post_id": post.post_id, "post_title": post.title, "post_link_url": post.url, "post_link_thumbnail": post.thumbnail_url, "post_slug": post.slug, "post_type": post.post_type, }
def get_profile_image(self, instance): """Find the profile image for the post author""" return image_uri(self._get_profile(instance))
def get_profile_image(self, instance): """Find the Profile for the comment author""" return image_uri(self._get_profile(instance))
def get_profile_image_medium(self, obj): """ Custom getter for medium profile image """ return image_uri(obj, IMAGE_MEDIUM)
def test_list_comments(cassette_exists, user_client, user, reddit_factories, public_channel, missing_user): # pylint: disable=too-many-arguments,too-many-locals """List all comments in the comment tree""" if missing_user: user.username = "******" user.save() profile_image = DEFAULT_PROFILE_IMAGE name = "[deleted]" author_id = "[deleted]" headline = None else: profile_image = image_uri(user.profile) author_id = user.username headline = user.profile.headline name = user.profile.name post = reddit_factories.text_post("a post with comments", user, channel=public_channel) comments = [] for idx in range(2): comment = reddit_factories.comment(f"comment-{idx}", user, post_id=post.id) comments.append((None, comment)) comments.extend([( comment.id, reddit_factories.comment(f"comment-nested-{idx2}", user, comment_id=comment.id), ) for idx2 in range(3)]) if not cassette_exists: # if we're writing the cassette, wait for the backend to asynchronously # finish updating the comment tree so we see everything time.sleep(10) url = reverse("comment-list", kwargs={"post_id": post.id}) resp = user_client.get(url) assert resp.status_code == status.HTTP_200_OK json = resp.json() # the order isn't entirely deterministic when testing, so just assert the number of elements and the presence of all of them assert len(json) == len(comments) for parent_id, comment in comments: assert { "id": comment.id, "parent_id": parent_id, "post_id": post.id, "text": comment.text, "author_id": author_id, "score": 1, "upvoted": True, "downvoted": False, "removed": False, "deleted": False, "subscribed": False, "created": comment.created, "profile_image": profile_image, "author_name": name, "author_headline": headline, "edited": False, "comment_type": "comment", "num_reports": None, } in json
def get_profile_image_small(self, obj): """ Custom getter for small profile image """ return image_uri(obj, IMAGE_SMALL)