"""test utility functions""" from datetime import datetime, timezone, timedelta from unittest.mock import patch from django.core.management import call_command from django.test import TestCase from blogs import models class FeedParserItemMock(object): title = "" tags = [] author = "" link = "" summary = "" updated_parsed = ((),) published_parsed = ((),) id = "" def __init__( self, title, tags, author, link, summary, updated_parsed, published_parsed, id ): self.title = title self.tags = tags self.author = author self.link = link self.summary = summary self.updated_parsed = updated_parsed self.published_parsed = published_parsed self.id = id class FeedParserEditionMock(object): title = "" author = "" link = "" summary = "" updated_parsed = ((),) published_parsed = ((),) id = "" def __init__( self, title, author, link, summary, updated_parsed, published_parsed, id ): self.title = title self.author self.link = link self.summary = summary self.updated_parsed = updated_parsed self.published_parsed = published_parsed self.id = id class FeedParserTagMock(object): term = "" def __init__(self, term): self.term = term class FeedParserMock(object): entries = [] def __init__(self, entries): self.entries = entries class CommandsTestCase(TestCase): """test management command functions""" def setUp(self): """set up test conf""" self.blog = models.Blog.objects.create( title="My awesome blog", url="https://test.com", feed="https://test.com/feed.xml", category="LIB", approved=True, suspended=False, ) models.Newsletter.objects.create( name="My awesome newsletter", url="https://test.news", feed="https://test.news/feed", category="ARC", approved=True, ) tag_one = FeedParserTagMock(term="testing") tag_two = FeedParserTagMock(term="python") tag_three = FeedParserTagMock(term="notglam") updated_parsed = (datetime.now(timezone.utc) - timedelta(hours=3)).timetuple() published_parsed = (datetime.now(timezone.utc) - timedelta(hours=3)).timetuple() article = FeedParserItemMock( title="My amazing blog post", tags=[tag_one, tag_two], author="Hugh Rundle", link="https://test.com/1", summary="A short summary of my post", updated_parsed=updated_parsed, published_parsed=published_parsed, id="1", ) article_two = FeedParserItemMock( title="My really amazing blog post", tags=[tag_one, tag_two, tag_three], author="Hugh Rundle", link="https://test.com/2", summary="A short summary of my next post", updated_parsed=updated_parsed, published_parsed=published_parsed, id="999", ) article_three = FeedParserItemMock( title="My really old blog post", tags=[tag_one, tag_two], author="Hugh Rundle", link="https://test.com/2", summary="A short summary of my next post", updated_parsed=(2023, 1, 3, 19, 48, 21, 3, 1, 0), published_parsed=(2023, 1, 3, 19, 48, 21, 3, 1, 0), id="999", ) article_four = FeedParserItemMock( title="My new nice post", tags=[tag_one, tag_two], author="Hugh Rundle", link="https://test.com/3", summary="A short summary of my next post", updated_parsed=updated_parsed, published_parsed=published_parsed, id="333", ) edition = FeedParserEditionMock( title="My amazing newsletter edition", author="Hugh Rundle", link="https://news.letter/1", summary="A summary of my edition", updated_parsed=updated_parsed, published_parsed=published_parsed, id="1", ) self.feedparser = FeedParserMock(entries=[article]) self.feedparser_old = FeedParserMock(entries=[article_three]) self.feedparser_exclude = FeedParserMock(entries=[article_two]) self.feedparser_new = FeedParserMock(entries=[article_four]) self.feedparser_edition = FeedParserMock(entries=[edition]) def test_check_feeds(self): """test parse a feed for basic blog info""" args = {"-q": True, "-blogs": True} opts = {} self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) with patch("feedparser.parse", return_value=self.feedparser): value = call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 1) self.assertEqual(models.Tag.objects.count(), 2) article = models.Article.objects.all().first() self.assertEqual(article.title, "My amazing blog post") # should be set to be announced self.assertEqual(models.Announcement.objects.count(), 1) def test_check_edition_feeds(self): """test parse a feed for newsletter edition info""" args = {"-q": True, "-newsletters": True} opts = {} self.assertEqual(models.Edition.objects.count(), 0) with patch("feedparser.parse", return_value=self.feedparser_edition): value = call_command("check_feeds", *args, **opts) self.assertEqual(models.Edition.objects.count(), 1) edition = models.Edition.objects.all().first() self.assertEqual(edition.title, "My amazing newsletter edition") # should be set to be announced self.assertEqual(models.Announcement.objects.count(), 1) def test_check_feeds_duplicate(self): """test we do not ingest the same post twice""" args = {"-q": True, "-blogs": True} opts = {} self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) with patch("feedparser.parse", return_value=self.feedparser): call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 1) self.assertEqual(models.Tag.objects.count(), 2) article = models.Article.objects.all().first() self.assertEqual(article.title, "My amazing blog post") with patch("feedparser.parse", return_value=self.feedparser): call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 1) self.assertEqual(models.Tag.objects.count(), 2) def test_check_feeds_new(self): """test we ingest new post if id is different""" args = {"-q": True, "-blogs": True} opts = {} self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) with patch("feedparser.parse", return_value=self.feedparser): call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 1) self.assertEqual(models.Tag.objects.count(), 2) article = models.Article.objects.all().first() self.assertEqual(article.title, "My amazing blog post") with patch("feedparser.parse", return_value=self.feedparser_new): call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 2) self.assertEqual(models.Tag.objects.count(), 2) article = models.Article.objects.all().last() self.assertEqual(article.title, "My new nice post") def test_check_feeds_old_post(self): """test parse a feed with a post older than a week""" args = {"-q": True, "-blogs": True} opts = {} self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) with patch("feedparser.parse", return_value=self.feedparser_old): value = call_command("check_feeds", *args, **opts) # should be ingested self.assertEqual(models.Article.objects.count(), 1) self.assertEqual(models.Tag.objects.count(), 2) # should not be announced self.assertEqual(models.Announcement.objects.count(), 0) def test_check_feeds_exclude_tag(self): """test parse a feed with exclude tag""" self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) with patch("feedparser.parse", return_value=self.feedparser_exclude): args = {"-q": True, "-blogs": True} opts = {} value = call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) def test_check_feeds_unapproved(self): """test check unapproved blog feed""" self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) self.blog.approved = False self.blog.save() with patch("feedparser.parse", return_value=self.feedparser): args = {"-q": True, "-blogs": True} opts = {} value = call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) def test_check_feeds_inactive(self): """test ignore inactive blog feed""" self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) self.blog.active = False self.blog.save() with patch("feedparser.parse", return_value=self.feedparser): args = {"-q": True, "-blogs": True} opts = {} value = call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) def test_check_feeds_suspended(self): """test check suspended blog feed""" self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) self.blog.suspended = True self.blog.save() with patch("feedparser.parse", return_value=self.feedparser): args = {"-q": True, "-blogs": True} opts = {} value = call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) def test_check_feeds_previously_suspended(self): """test blog published prior to suspension lifted is not ingested""" self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) self.blog.suspended = False self.blog.suspension_lifted = datetime.now(timezone.utc) - timedelta(minutes=1) self.blog.save() with patch("feedparser.parse", return_value=self.feedparser): args = {"-q": False} opts = {} value = call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) def test_check_feeds_previously_suspended_post_after(self): """test blog published after suspension lifted is ingested""" self.assertEqual(models.Article.objects.count(), 0) self.assertEqual(models.Tag.objects.count(), 0) self.blog.suspended = False self.blog.suspension_lifted = datetime( 2023, 12, 31, 0, 0, 0, 0, tzinfo=timezone.utc ) self.blog.save() with patch("feedparser.parse", return_value=self.feedparser): args = {"-q": False} opts = {} value = call_command("check_feeds", *args, **opts) self.assertEqual(models.Article.objects.count(), 1) self.assertEqual(models.Tag.objects.count(), 2)