def test_queryset_excludes_when_appropriate(self): # Add a draft post... draft_post = PostFactory.create(online=False, date=now() + timedelta(days=2)) # ...and a future one... future_post = PostFactory.create(online=True, date=now() + timedelta(days=2)) # ...and one that is live now. published_post = PostFactory.create(online=True) # We're not logged in, so we should only see the live post. response = self.client.get(reverse('posts:post_list')) self.assertEqual(len(response.context_data['object_list']), 1) # Check the detail views as well. Published ones should work: response = self.client.get(reverse('posts:post_detail', kwargs={'slug': published_post.slug, 'identifier': published_post.identifier})) self.assertEqual(response.status_code, 200) # But unpublished ones should not. for post in [draft_post, future_post]: response = self.client.get(reverse('posts:post_detail', kwargs={'slug': post.slug, 'identifier': post.identifier})) self.assertEqual(response.status_code, 404) # Now create a staff user and log in to it. get_user_model().objects.create_superuser(username='******', email='*****@*****.**', password='******') self.client.login(username='******', password='******') # When we're logged in we should see all of the draft posts. response = self.client.get(reverse('posts:post_list')) self.assertEqual(len(response.context_data['object_list']), 3) # And so should their detail views. for post in [draft_post, future_post, published_post]: response = self.client.get(reverse('posts:post_detail', kwargs={'slug': post.slug, 'identifier': post.identifier})) self.assertEqual(response.status_code, 200)
def test_rss_feed(self): PostFactory.create_batch(5, online=True) posts_url = reverse('posts:post_feed') # Check the "secret" debug branch with override_settings(DEBUG=True): response = self.client.get(posts_url, {'debug': 'yep'}) self.assertEqual(response.status_code, 200) self.assertEqual(response['Content-Type'], 'text/plain; charset=utf-8') response = self.client.get(posts_url) self.assertEqual(response.status_code, 200) self._rss_response_is_sane(response, expected_item_count=5)
def test_prefetching_categories(self): cat = CategoryFactory.create() cat2 = CategoryFactory.create() PostFactory.create_batch(10, online=True, categories=[cat, cat2]) # Baseload number of queries for loading the list, with no objects, # should be 5. That is getting the current session, getting the # current user, some savepoint thing (two queries, one for grab & one # for release), and getting the total count of posts. Our prefetching # should mean we only add 2 queries to this regardless of the number # of posts with self.assertNumQueries(7): response = self.client.get(reverse('admin:posts_post_changelist')) self.assertEqual(len(response.context_data['cl'].result_list), 10)
def test_post_detail_template_branches(self): # Ensure various branches in the post detail templates are tested. # # Test the "post has an image" branch of post_body.html post = PostFactory.create(image='large-image.jpg', online=True) response = self.client.get(post.get_absolute_url()) self.assertEqual(response.status_code, 200) soup = BeautifulSoup(response.content, features='html.parser') self.assertEqual(len(soup.select('.post__image-wrapper')), 1) # Test the "has meta description" branch of post_detail.html post = PostFactory.create(meta_description='Meta description', online=True) response = self.client.get(post.get_absolute_url()) self.assertEqual(response.status_code, 200) soup = BeautifulSoup(response.content, features='html.parser') self.assertEqual(soup.find('meta', attrs={'name': 'description'})['content'], 'Meta description')
def test_category_feed_view(self): category = CategoryFactory.create() post = PostFactory.create(online=True) post.categories.add(category) # Check queryset exclusion for other categories... PostFactory.create_batch(2, online=True) # ...and ensure that offline posts are being excluded. offline_post = PostFactory.create(online=False) offline_post.categories.add(category.pk) response = self.client.get(reverse('posts:post_category_feed', kwargs={'category': category.slug})) self.assertEqual(response.status_code, 200) self.assertEqual(list(response.context_data['object_list']), [post]) self._rss_response_is_sane(response, expected_item_count=1)
def test_post_status_text(self): # If `online` is false, it should always show as "Draft" post = PostFactory.create(online=False) self.assertEqual(post.status_text, 'Draft') # ...even if the publication date is a future date (when online=True # it'll be "Scheduled"). post = PostFactory.create(online=False, date=now() + timedelta(days=2)) self.assertEqual(post.status_text, 'Draft') # Check future-scheduled posts. post = PostFactory.create(online=True, date=now() + timedelta(days=2)) self.assertEqual(post.status_text, 'Scheduled') # And one that is live now. post = PostFactory.create(online=True) self.assertEqual(post.status_text, 'Published')
def test_category_view(self): category = CategoryFactory.create(meta_description='Test!', description='Test') post = PostFactory.create(online=True) post.categories.add(category) # create another uncategorised post other_post = PostFactory.create(online=True) response = self.client.get(reverse('posts:post_category_list', kwargs={'category': category.slug})) self.assertEqual(response.status_code, 200) self.assertEqual(response.context_data['object'], category) self.assertNotIn(other_post, response.context_data['object_list']) # Ensure that the "has SEO title" branch is checked in the template. category.seo_title = 'Search engine optimised!' category.save() response = self.client.get(reverse('posts:post_category_list', kwargs={'category': category.slug})) self.assertEqual(response.status_code, 200)
def test_detail_redirects(self): post_with_slug = PostFactory.create(slug='slug', online=True) post_without_slug = PostFactory.create(slug='', online=True) possibles = [ # Post with slug called with identifier argument only should redirect. reverse('posts:post_detail', kwargs={'identifier': post_with_slug.identifier}), # Rewrite garbage in the slug. reverse('posts:post_detail', kwargs={'identifier': post_with_slug.identifier, 'slug': 'some-garbage-value'}) ] for path in possibles: response = self.client.get(path) self.assertRedirects(response, post_with_slug.get_absolute_url(), status_code=301, target_status_code=200) # Test that putting a slug after the identifier argument on a post without a # slug redirects. response = self.client.get(reverse('posts:post_detail', kwargs={'identifier': post_without_slug.identifier, 'slug': 'anything-at-all'})) self.assertRedirects(response, post_without_slug.get_absolute_url(), status_code=301, target_status_code=200)
def test_post_str(self): post = PostFactory.create(title='Testing!', online=True) self.assertEqual(str(post), 'Testing!') post = PostFactory.create(title='', text='Body', online=True) self.assertEqual(str(post), 'Body') # Test body truncation. post = PostFactory.create(title='', text='a' * 41, online=True) self.assertEqual(str(post), ('a' * 38) + '...') post = PostFactory.create(title='', text='', online=True) self.assertEqual(str(post), '(Broken post)') # Check with a "real" image. The real tests are already done for # render_multiformat_image. We'll only make sure it looks something # like what we expect. post = PostFactory.create(title='', text='', online=True, image='small-image.jpg') self.assertIs(str(post).startswith('Image post: small-image'), True) self.assertIs(str(post).endswith('.jpg'), True)
def test_categories_sitemap(self): # Ensure unused categories are not shown in the sitemap, which # includes those that are only assigned to a post that is offline. used_category, unused_category, unused_category_2 = CategoryFactory.create_batch( 3) post = PostFactory.create(online=True) post.categories.set([used_category]) offline_post = PostFactory.create(online=False) offline_post.categories.set([unused_category_2]) response = self.client.get( reverse('django.contrib.sitemaps.views.sitemap')) self.assertEqual(response.status_code, 200) tree = ElementTree.fromstring(response.content.decode('utf-8')) child_items = list(tree) self.assertEqual(len(child_items), 2) nsinfo = {'sitemaps': 'http://www.sitemaps.org/schemas/sitemap/0.9'} for obj in [post, used_category]: self.assertEqual( len([ True for child in child_items if child.find('sitemaps:loc', nsinfo).text == f'http://testserver{obj.get_absolute_url()}' ]), 1) for obj in [unused_category, unused_category_2]: self.assertEqual( len([ True for child in child_items if child.find('sitemaps:loc', nsinfo).text == f'http://testserver{obj.get_absolute_url()}' ]), 0)
def test_posts_sitemap(self): PostFactory.create_batch(5, online=True) # Check that offline ones aren't being shown. PostFactory.create(online=False, date=now() - timedelta(minutes=1)) # ...and ones with a future publication date. PostFactory.create(online=True, date=now() + timedelta(days=1)) response = self.client.get( reverse('django.contrib.sitemaps.views.sitemap')) self.assertEqual(response.status_code, 200) tree = ElementTree.fromstring(response.content.decode('utf-8')) self.assertEqual(len(list(tree)), 5)
def test_rss_html(self): # Ensure that all tags are being converted for use in RSS # appropriately. post = PostFactory.create( text='\n'.join([ '<img src="/some-image/">', '<picture>', ' <source src="/another-image/">', '</picture>', '<a href="/boat/">boat</a>', '<div class="image__padder"></div>', '<div class="image" style="max-width: 100px"></div>' ]), online=True, ) soup = BeautifulSoup(rss_post_body(post), 'html.parser') self.assertEqual( soup.find('img')['src'], 'https://example.com/some-image/') self.assertEqual(soup.find('a')['href'], 'https://example.com/boat/') self.assertEqual( soup.find('picture').find('source')['src'], 'https://example.com/another-image/') self.assertIs(soup.find('div', attrs={'class': 'image__padder'}), None) self.assertEqual( soup.find('div', attrs={'class': 'image'})['style'], '')
def test_quality_control_filter(self): # Ensure QualityControlListFilter works. cat = CategoryFactory.create() PostFactory.create(meta_description='test', online=True, categories=[cat]) no_meta_post = PostFactory.create(online=True, categories=[cat]) no_categories_post = PostFactory.create(meta_description='test', online=True) no_alt_text_post = PostFactory.create( title='No alt text', slug='no-alt-text', meta_description='Description', image='small-image.jpg', online=True, ) no_alt_text_post.categories.set([cat]) no_alt_text_in_body_post = PostFactory.create( meta_description='test', text='![](/some-image.jpg)', online=True, ) no_alt_text_in_body_post.categories.set([cat]) admin_url = reverse('admin:posts_post_changelist') response = self.client.get(admin_url) # sanity check self.assertEqual(len(response.context_data['cl'].result_list), 5) response = self.client.get(admin_url, {'quality_control': 'no_meta_description'}) results = response.context_data['cl'].result_list self.assertEqual(len(results), 1) self.assertEqual(results[0], no_meta_post) response = self.client.get(admin_url, {'quality_control': 'no_categories'}) results = response.context_data['cl'].result_list self.assertEqual(len(results), 1) self.assertEqual(results[0], no_categories_post) response = self.client.get(admin_url, {'quality_control': 'no_alt_text'}) self.assertEqual(response.status_code, 200) results = response.context_data['cl'].result_list self.assertEqual(len(results), 1) self.assertEqual(results[0], no_alt_text_post) response = self.client.get(admin_url, {'quality_control': 'no_alt_text_body'}) self.assertEqual(response.status_code, 200) results = response.context_data['cl'].result_list self.assertEqual(len(results), 1) self.assertEqual(results[0], no_alt_text_in_body_post) # Check the fall-through case, just to ensure full coverage. response = self.client.get(admin_url, {'quality_control': 'garbage'}) self.assertEqual(len(response.context_data['cl'].result_list), 5)
def test_post_get_title_link_url(self): post = PostFactory.create(online=True) self.assertEqual(post.get_title_link_url(), post.get_absolute_url()) post = PostFactory.create(link='https://example.invalid', online=True) self.assertEqual(post.get_title_link_url(), 'https://example.invalid')