# test_timelines.py # # Part of kepi. # Copyright (c) 2018-2021 Marnanel Thurman. # Licensed under the GNU Public License v2. import logging logger = logging.getLogger(name='kepi') from rest_framework.test import APIClient, force_authenticate from kepi.trilby_api.views import * from kepi.trilby_api.tests import * from kepi.trilby_api.models import * from kepi.bowler_pub.tests import create_remote_person from django.conf import settings from unittest import skip import httpretty """ Tests for timelines. API docs are here: https://docs.joinmastodon.org/methods/timelines/ """ class TimelineTestCase(TrilbyTestCase): def add_status(self, source, visibility, content, remote_url = None): status = Status( account = source, content_source = content, visibility = visibility, remote_url = remote_url, ) status.save() logger.info("Created status: %s", status) return status def timeline_contents(self, path, data = None, as_user = None, ): logger.info("Timeline contents for %s as %s...", path, as_user) found = self.get( path = path, data = data, as_user = as_user, ) logger.info(" -- retrieved") details = sorted([x['content'] for x in found]) logger.debug(" -- sorted as %s", details) result = '' for detail in details: if detail.startswith('

') and detail.endswith('

'): detail = detail[3:-4] result += detail logger.info(" -- contents are %s", result) return result class TestPublicTimeline(TimelineTestCase): def test_as_anon(self): alice = create_local_person("alice") self.add_status(source=alice, content='A', visibility='A') self.add_status(source=alice, content='B', visibility='U') self.add_status(source=alice, content='C', visibility='X') self.add_status(source=alice, content='D', visibility='D') self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', as_user = None, ), 'A', ) def test_as_user(self): alice = create_local_person("alice") self.add_status(source=alice, content='A', visibility='A') self.add_status(source=alice, content='B', visibility='U') self.add_status(source=alice, content='C', visibility='X') self.add_status(source=alice, content='D', visibility='D') self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', as_user = alice, ), 'A', ) def test_as_stranger(self): alice = create_local_person("alice") henry = create_local_person("henry") self.add_status(source=alice, content='A', visibility='A') self.add_status(source=alice, content='B', visibility='U') self.add_status(source=alice, content='C', visibility='X') self.add_status(source=alice, content='D', visibility='D') self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', as_user = henry, ), 'A', ) @httpretty.activate() def test_local_and_remote(self): alice = create_local_person("alice") peter = create_remote_person( remote_url = "https://example.com/users/peter", name = "peter", auto_fetch = True, ) self.add_status(source=alice, content='A', visibility='A') self.add_status(source=peter, content='B', visibility='A', remote_url = 'https://example.com/users/peter/B') self.add_status(source=alice, content='C', visibility='A') self.add_status(source=peter, content='D', visibility='A', remote_url = 'https://example.com/users/peter/D') self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', ), 'ABCD', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', data = {'local': 'true'}, ), 'AC', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', data = {'local': 'false'}, ), 'ABCD', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', data = {'remote': 'true'}, ), 'BD', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', data = {'remote': 'false'}, ), 'ABCD', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', data = {'local': 'true', 'remote': 'true'}, ), '', ) def test_only_media(self): # We don't support added media at present anyway, # so turning this on will always get the empty set alice = create_local_person("alice") self.add_status(source=alice, content='A', visibility='A') self.add_status(source=alice, content='B', visibility='A') self.add_status(source=alice, content='C', visibility='A') self.add_status(source=alice, content='D', visibility='A') self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', data = {'only_media': 'true'}, ), '', ) def test_max_since_and_min(self): alice = create_local_person("alice") self.add_status(source=alice, content='A', visibility='A') self.add_status(source=alice, content='B', visibility='A') status_c = self.add_status(source=alice, content='C', visibility='A') self.add_status(source=alice, content='D', visibility='A') c_id = str(status_c.id) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', data = {'since_id': status_c.id}, ), 'D', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', data = {'max_id': status_c.id}, ), 'ABC', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', data = {'min_id': status_c.id}, ), 'CD', ) def test_limit(self): alice = create_local_person("alice") alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' for i in range(len(alphabet)): self.add_status( source=alice, content=alphabet[i], visibility='A', ) for i in range(1, len(alphabet)): self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', data = {'limit': i}, ), alphabet[:i], ) # the default is specified as 20 self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/public', ), alphabet[:20], msg = 'default is 20', ) class TestHomeTimeline(TimelineTestCase): def add_standard_statuses(self): self.alice = create_local_person("alice") self.bob = create_local_person("bob") self.carol = create_local_person("carol") self.add_status(source=self.bob, content='A', visibility='A') self.add_status(source=self.carol, content='B', visibility='A') self.add_status(source=self.carol, content='C', visibility='A') self.add_status(source=self.bob, content='D', visibility='A') Follow( follower=self.alice, following=self.bob, offer=None).save() def follow_carol(self): Follow( follower=self.alice, following=self.carol, offer=None).save() def test_not_anon(self): found = self.get( path = '/api/v1/timelines/home', as_user = None, expect_result = 401, ) def test_0_simple(self): self.add_standard_statuses() self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', as_user = self.alice, ), 'AD', ) self.follow_carol() self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', as_user = self.alice, ), 'ABCD', ) def test_max_since_and_min(self): self.add_standard_statuses() c_id = '3' # FIXME hack self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', data = {'since_id': c_id}, as_user = self.alice, ), 'D', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', data = {'max_id': c_id}, as_user = self.alice, ), 'A', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', data = {'min_id': c_id}, as_user = self.alice, ), 'D', ) self.follow_carol() self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', data = {'since_id': c_id}, as_user = self.alice, ), 'D', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', data = {'max_id': c_id}, as_user = self.alice, ), 'ABC', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', data = {'min_id': c_id}, as_user = self.alice, ), 'CD', ) def test_limit(self): self.alice = create_local_person("alice") self.bob = create_local_person("bob") self.carol = create_local_person("carol") Follow( follower=self.alice, following=self.bob, offer=None).save() alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' for i in range(len(alphabet)): self.add_status( source=self.bob, content=alphabet[i], visibility='A', ) self.add_status( source=self.carol, content=alphabet[i].lower(), visibility='A', ) for i in range(1, len(alphabet)): self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', data = {'limit': i}, as_user = self.alice, ), alphabet[:i], ) # the default is specified as 20 self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', as_user = self.alice, ), alphabet[:20], msg = 'default is 20', ) @httpretty.activate() def test_local(self): self.add_standard_statuses() self.peter = create_remote_person( remote_url = "https://example.com/users/peter", name = "peter", auto_fetch = True, ) for letter in 'PQ': self.add_status(source=self.peter, remote_url = 'https://example.com/users/peter/{}'.format( letter, ), content=letter, visibility='A') Follow( follower = self.alice, following = self.peter, offer = None, ).save() self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', as_user = self.alice, ), 'ADPQ', ) self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', data = {'local': 'true'}, as_user = self.alice, ), 'AD', ) def test_as_follower(self): alice = create_local_person("alice") george = create_local_person("george") follow = Follow( follower = george, following = alice, offer = None, ) follow.save() self.add_status(source=alice, content='A', visibility='A') self.add_status(source=alice, content='B', visibility='U') self.add_status(source=alice, content='C', visibility='X') self.add_status(source=alice, content='D', visibility='D') self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', as_user = george, ), 'A', ) follow = Follow( follower = alice, following = george, offer = None, ) follow.save() # they are now mutuals self.assertEqual( self.timeline_contents( path = '/api/v1/timelines/home', as_user = george, ), 'AC', ) class TestTimelinesNotImplemented(TimelineTestCase): @skip("to be implemented later") def test_hashtag(self): raise NotImplementedError() @skip("to be implemented later") def test_account_statuses(self): # Special case: this isn't considered a timeline method # in the API, but it's similar enough that we test it here raise NotImplementedError() @skip("to be implemented later") def test_list(self): raise NotImplementedError()