diora-web/podcasts/test_discover.py
mrwnslz 3fa16b9653
Some checks failed
Test / e2e (push) Waiting to run
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Has been cancelled
Podcasts stöbern: Volltextsuche (FTS5), ähnliche Folgen, Personen, Themen, Vergessenes, Personen anderswo (Apple/fyyd), Empfehlungen (fyyd); Abo-Favoriten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:50:35 +02:00

149 lines
9 KiB
Python

"""Stöbern (podcasts/discover.py): the full-text index, similar episodes, people, an episode
from outside onto the Merkliste."""
import json
from datetime import datetime, timedelta, timezone
from unittest.mock import patch
from django.contrib.auth.models import User
from django.core.cache import cache
from django.test import TestCase
from . import discover, people
from .models import EpisodeProgress, PodcastEpisode, PodcastFeed, PodcastLater
class PeopleTests(TestCase):
def test_names_from_titles(self):
eps = [
(1, 1, 'Episode 651: Die Brandmauer mit Peter Decker'),
(2, 1, 'Episode 568: Trumps Zollkrieg mit Peter Decker'),
(3, 2, 'Imperialismus heute - Peter Decker - 2006'),
(4, 3, 'Gespräch mit Peter Schadt über Arbeit'),
(5, 3, 'Ulrike Eifler im Gespräch mit Peter Thiel'),
(6, 4, 'Was tun? Mit Ulrike Herrmann'),
(9, 4, 'Streik! Diskussion mit Ulrike Eifler'),
(7, 5, 'Gespräch mit Ulrike Herrmann über das Ende des Kapitalismus'),
(8, 6, 'Live aus Berlin: Die Linke und Friedrich Merz'),
]
found = {p['name']: p for p in people.people(eps)}
self.assertEqual(found['Peter Decker']['episodes'], 3)
self.assertEqual(found['Peter Decker']['feeds'], 2)
self.assertIn('Ulrike Herrmann', found)
self.assertNotIn('Die Brandmauer', found)
self.assertNotIn('Die Linke', found)
class IndexTests(TestCase):
def setUp(self):
cache.clear()
self.user = User.objects.create_user(username='finder', password='pw12345678')
self.client.force_login(self.user)
self.other = User.objects.create_user(username='other', password='pw12345678')
self.f1 = PodcastFeed.objects.create(user=self.user, rss_url='https://example.org/1', title='Eins')
self.f2 = PodcastFeed.objects.create(user=self.user, rss_url='https://example.org/2', title='Zwei')
self.f3 = PodcastFeed.objects.create(user=self.user, rss_url='https://example.org/3', title='Drei',
new_episodes=PodcastFeed.NEW_IGNORE)
theirs = PodcastFeed.objects.create(user=self.other, rss_url='https://example.org/1', title='Eins')
def ep(feed, guid, title, desc=''):
return PodcastEpisode.objects.create(feed=feed, guid=guid, title=title, description=desc,
audio_url=f'https://example.org/{guid}.mp3',
pub_date=datetime(2026, 1, 1, tzinfo=timezone.utc))
self.a = ep(self.f1, 'a', 'Über die Ökonomie der Plattformen', '<p>Mit Gästen über <b>Plattformkapitalismus</b> und Arbeit.</p>')
self.b = ep(self.f2, 'b', 'Plattformkapitalismus erklärt', 'Wie Plattformen Arbeit organisieren.')
self.c = ep(self.f2, 'c', 'Gartenarbeit im Herbst', 'Tomaten, Kürbisse und Laub.')
self.d = ep(self.f3, 'd', 'Noch mehr Plattformkapitalismus', 'Auf Abruf.')
for i in range(30): # words everywhere say nothing
ep(self.f1, f'x{i}', f'Folge {i}', 'Arbeit und Gesellschaft')
ep(theirs, 't', 'Plattformkapitalismus bei jemand anderem', '')
self.assertGreaterEqual(discover.index_new(), 35)
self.assertEqual(discover.index_new(), 0, 'nothing twice')
def test_search_titles_shownotes_prefix_folding(self):
r = self.client.get('/podcasts/discover/search/?q=plattformkap').json()
self.assertEqual({e['id'] for e in r['episodes']}, {self.a.id, self.b.id, self.d.id})
self.assertEqual(r['episodes'][0]['id'] in (self.b.id, self.d.id), True, 'a title match first')
r = self.client.get('/podcasts/discover/search/?q=okonomie').json()
self.assertEqual([e['id'] for e in r['episodes']], [self.a.id])
r = self.client.get('/podcasts/discover/search/?q=plattformkapitalismus&stream=1').json()
self.assertNotIn(self.d.id, [e['id'] for e in r['episodes']], 'Auf Abruf is not in the Strom')
def test_similar_from_other_podcasts(self):
r = self.client.get(f'/podcasts/discover/similar/{self.b.id}/').json()
ids = [e['id'] for e in r['episodes']]
self.assertIn(self.a.id, ids)
self.assertNotIn(self.c.id, ids, 'same podcast, and about gardens')
self.assertEqual(self.client.get('/podcasts/discover/similar/999999/').status_code, 404)
def test_person_episodes(self):
PodcastEpisode.objects.create(feed=self.f1, guid='p', title='Arbeit mit Peter Decker', audio_url='https://example.org/p.mp3')
r = self.client.get('/podcasts/discover/person/?name=Peter Decker').json()
self.assertEqual([e['title'] for e in r['episodes']], ['Arbeit mit Peter Decker'])
def test_forgotten(self):
old = PodcastEpisode.objects.create(feed=self.f2, guid='old', title='Alt', audio_url='https://example.org/old.mp3',
pub_date=datetime.now(timezone.utc) - timedelta(days=200))
for e in (self.b, self.c, self.a):
EpisodeProgress.objects.create(user=self.user, episode=e, played=True)
PodcastEpisode.objects.create(feed=self.f2, guid='h1', title='H1', audio_url='https://example.org/h1.mp3')
heard = PodcastEpisode.objects.create(feed=self.f2, guid='h2', title='H2', audio_url='https://example.org/h2.mp3')
EpisodeProgress.objects.create(user=self.user, episode=heard, played=True)
r = self.client.get('/podcasts/discover/forgotten/').json()
self.assertEqual([e['id'] for e in r['loved']], [old.id])
class OutsideTests(TestCase):
def setUp(self):
cache.clear()
self.user = User.objects.create_user(username='out', password='pw12345678')
self.client.force_login(self.user)
PodcastFeed.objects.create(user=self.user, rss_url='https://mine.example/feed', title='Mein Podcast')
def test_elsewhere_keeps_only_the_person_and_unknown_podcasts(self):
apple = {'results': [
{'collectionName': 'Fremd', 'feedUrl': 'https://other.example/feed', 'trackName': 'Gespräch mit Peter Decker',
'episodeUrl': 'https://other.example/1.mp3', 'episodeGuid': 'g1', 'releaseDate': '2026-01-02T00:00:00Z',
'trackTimeMillis': 60000},
{'collectionName': 'Mein Podcast', 'feedUrl': 'https://mine.example/feed', 'trackName': 'Mit Peter Decker',
'episodeUrl': 'https://mine.example/1.mp3'},
{'collectionName': 'Fremd', 'feedUrl': 'https://other.example/feed', 'trackName': 'Peter Pan',
'episodeUrl': 'https://other.example/2.mp3'},
]}
def fake(url, **params):
return apple if 'itunes' in url else {'data': []}
with patch.object(discover, '_get_json', fake):
r = self.client.get('/podcasts/discover/elsewhere/?name=Peter Decker').json()
self.assertEqual([e['title'] for e in r['episodes']], ['Gespräch mit Peter Decker'])
self.assertEqual(r['episodes'][0]['duration_seconds'], 60)
def test_take_subscribes_auf_abruf_and_merklists(self):
item = {'podcast': 'Fremd', 'feed_url': 'https://other.example/feed', 'title': 'Gespräch mit Peter Decker',
'audio_url': 'https://other.example/1.mp3', 'guid': 'g1', 'pub_date': '2026-01-02T00:00:00Z',
'duration_seconds': 60}
with patch('podcasts.views._refresh_feed', side_effect=OSError('offline')):
r = self.client.post('/podcasts/discover/take/', json.dumps(item), content_type='application/json').json()
self.assertTrue(r['subscribed'])
feed = PodcastFeed.objects.get(pk=r['feed_id'])
self.assertEqual(feed.new_episodes, PodcastFeed.NEW_IGNORE)
self.assertTrue(PodcastLater.objects.filter(user=self.user, episode_id=r['episode_id']).exists())
with patch('podcasts.views._refresh_feed', side_effect=OSError('offline')):
r2 = self.client.post('/podcasts/discover/take/', json.dumps(item), content_type='application/json').json()
self.assertEqual((r2['episode_id'], r2['subscribed']), (r['episode_id'], False))
def test_recommend_counts_and_leaves_out_mine(self):
EpisodeProgress.objects.create(
user=self.user, played=True,
episode=PodcastEpisode.objects.create(feed=PodcastFeed.objects.get(title='Mein Podcast'), guid='m',
title='M', audio_url='https://mine.example/m.mp3'))
def fake(url, **params):
if url.endswith('search/podcast'):
return {'data': [{'id': 7, 'title': 'Mein Podcast', 'xmlURL': 'https://mine.example/feed'}]}
if url.endswith('recommend'):
return {'data': [{'id': 8, 'title': 'Neu', 'xmlURL': 'https://new.example/feed'},
{'id': 7, 'title': 'Mein Podcast', 'xmlURL': 'https://mine.example/feed'}]}
return None
with patch.object(discover, '_get_json', fake):
r = self.client.get('/podcasts/discover/recommend/').json()
self.assertEqual([(p['title'], p['because']) for p in r['podcasts']], [('Neu', ['Mein Podcast'])])