import json from django.contrib.auth.models import User from django.test import TestCase from django.utils import timezone from feeds.models import Item, Source from .models import EpisodeArticle, PodcastEpisode, PodcastFeed, TranscriptionJob BODY = ('

Zusammenfassung

Teil eins

' '

▶ ab 12:34 und Quelle

') class ArticleTests(TestCase): def setUp(self): self.user = User.objects.create_user(username='alice', password='pw12345678') self.client.force_login(self.user) self.feed = PodcastFeed.objects.create(user=self.user, rss_url='https://p.example/rss', title='Salon', author='Anna Autorin', artwork_url='https://p.example/feed.jpg') self.ep = PodcastEpisode.objects.create(feed=self.feed, guid='e1', title='Folge 1', audio_url='https://p.example/e1.mp3', artwork_url='https://p.example/e1.jpg') def url(self, ep=None): return f'/podcasts/episodes/{(ep or self.ep).id}/article/' def post(self, data=None, ep=None): data = {'title': 'Ein Artikel', 'summary': 'Kurz.', 'body_html': BODY, 'model': 'claude', 'kind': 'Interview'} \ if data is None else data return self.client.post(self.url(ep), json.dumps(data), content_type='application/json') def test_requires_login_and_ownership(self): self.client.logout() self.assertEqual(self.post().status_code, 401) self.assertEqual(self.client.delete(self.url()).status_code, 401) bob = User.objects.create_user(username='bob', password='pw12345678') self.client.force_login(bob) self.assertEqual(self.post().status_code, 404) self.assertEqual(self.client.delete(self.url()).status_code, 404) self.assertFalse(Item.objects.exists()) def test_create(self): r = self.post() self.assertEqual(r.status_code, 200) item = Item.objects.get(pk=r.json()['item_id']) self.assertEqual(item.user, self.user) self.assertEqual(item.kind, Source.BLOG) self.assertEqual(item.title, 'Ein Artikel') self.assertEqual(item.summary, 'Kurz.') self.assertEqual(item.author, 'Anna Autorin') self.assertEqual(item.image_url, 'https://p.example/e1.jpg') self.assertEqual(item.url, 'https://p.example/e1.mp3') self.assertTrue(item.content_full) self.assertGreater(item.words, 3) self.assertLess((timezone.now() - item.published).total_seconds(), 60) art = EpisodeArticle.objects.get(episode=self.ep) self.assertEqual((art.item, art.model, art.kind), (item, 'claude', 'Interview')) self.assertEqual(item.episode_article, art) def test_image_falls_back_to_feed_artwork(self): self.ep.artwork_url = '' self.ep.save() item = Item.objects.get(pk=self.post().json()['item_id']) self.assertEqual(item.image_url, 'https://p.example/feed.jpg') def test_sanitizer_keeps_time_links_only(self): item = Item.objects.get(pk=self.post().json()['item_id']) self.assertNotIn('ab'}) content = Item.objects.get(pk=evil.json()['item_id']).content self.assertNotIn('onfocus', content) self.assertNotIn('#top', content) def test_source_made_once(self): other = PodcastEpisode.objects.create(feed=self.feed, guid='e2', title='Folge 2', audio_url='https://p.example/e2.mp3') self.post() self.post(ep=other) src = Source.objects.get(user=self.user, url='diora://podcast-artikel') self.assertEqual((src.title, src.kind, src.fav, src.synthetic), ('Podcast-Artikel', Source.BLOG, True, True)) self.assertEqual(Source.objects.count(), 1) self.assertEqual(src.items.count(), 2) def test_replace_keeps_one_item(self): first = self.post().json()['item_id'] Item.objects.filter(pk=first).update(read_at=timezone.now(), progress=1) second = self.post({'title': 'Neu', 'body_html': '

Ganz anderer Text

', 'model': 'm2'}).json()['item_id'] self.assertEqual(first, second) self.assertEqual(Item.objects.count(), 1) item = Item.objects.get() self.assertEqual((item.title, item.read_at, item.progress), ('Neu', None, 0)) self.assertIn('Ganz anderer Text', item.content) self.assertEqual(EpisodeArticle.objects.get().model, 'm2') def test_delete(self): self.post() self.assertEqual(self.client.delete(self.url()).status_code, 200) self.assertFalse(Item.objects.exists()) self.assertFalse(EpisodeArticle.objects.exists()) self.assertEqual(Source.objects.count(), 1) self.assertEqual(self.client.delete(self.url()).status_code, 200) # again: nothing to do def test_validation(self): for data in ({}, {'title': '', 'body_html': BODY}, {'title': 'x' * 301, 'body_html': BODY}, {'title': 'x'}, {'title': 'x', 'body_html': ''}, {'title': 'x', 'body_html': 'a' * 1_000_001}, {'title': 'x', 'body_html': BODY, 'model': 'm' * 101}): self.assertEqual(self.post(data).status_code, 400, data and list(data)) self.assertEqual(self.client.post(self.url(), 'nope', content_type='application/json').status_code, 400) self.assertFalse(Item.objects.exists()) self.assertEqual(self.client.get(self.url()).status_code, 405) def test_saving_clears_the_wish(self): TranscriptionJob.objects.create(episode=self.ep, state='done', want_article=True, claimed_by='phone', claimed_until=timezone.now() + timezone.timedelta(minutes=10)) self.post() job = TranscriptionJob.objects.get() self.assertEqual((job.want_article, job.claimed_by, job.claimed_until), (False, '', None)) def test_in_favourite_blogs_with_episode_id(self): item_id = self.post().json()['item_id'] items = self.client.get('/feeds/blogs/?fav=1').json()['items'] self.assertEqual([i['id'] for i in items], [item_id]) self.assertEqual(items[0]['episode_id'], self.ep.id) detail = self.client.get(f'/feeds/items/{item_id}/').json()['item'] self.assertEqual(detail['episode_id'], self.ep.id) self.assertIn('href="#t=754"', detail['content']) def test_ordinary_items_have_no_episode_id(self): src = Source.objects.create(user=self.user, url='https://b.example/feed', title='B', kind=Source.BLOG, fav=True) Item.objects.create(source=src, user=self.user, kind=Source.BLOG, guid_hash='g', title='T', published=timezone.now(), content='

x

', content_full=True) self.post() items = self.client.get('/feeds/blogs/?fav=1').json()['items'] self.assertEqual(len(items), 2) self.assertEqual(sorted('episode_id' in i for i in items), [False, True]) def test_blog_list_needs_no_query_per_article(self): for n in range(5): self.post(ep=PodcastEpisode.objects.create(feed=self.feed, guid=f'x{n}', title=str(n), audio_url='https://p.example/x.mp3')) with self.assertNumQueries(3): self.client.get('/feeds/blogs/?fav=1')