def setUp(self): self.fetcher = ScrobblesFetcher(account=AccountFactory( username='******', realname='Bob Ferris')) # Patch the _send_request() method: self.send_request_patch = patch.object( ScrobblesFetcher, '_send_request') self.send_request = self.send_request_patch.start() self.send_request.return_value = [] self.sleep_patch = patch('time.sleep') self.sleep = self.sleep_patch.start()
def test_no_credentials(self): "__init__ should return error if Account has no API creds." account = AccountFactory(api_key='') result = ScrobblesFetcher(account=account).fetch() self.assertFalse(result['success']) self.assertEqual(result['messages'], ['Account has no API credentials'])
def test_will_not_fetch_inactive_accounts(self): account = AccountFactory(is_active=False, username='******') result = ScrobblesFetcher(account=account).fetch() self.assertFalse(result['success']) self.assertEqual(result['messages'], ['The Account terry is currently marked as inactive.'])
def test_requires_valid_account_object(self): "__init__ should throw error with no Account object." with self.assertRaises(ValueError): result = ScrobblesFetcher(account=None)
def test_requires_account_argument(self): "__init__ should throw error with no account argument." with self.assertRaises(TypeError): result = ScrobblesFetcher()
class ScrobblesFetcherTestCase(TestCase): """ Tests that don't involve sending any actual requests to the API. """ def setUp(self): self.fetcher = ScrobblesFetcher(account=AccountFactory( username='******', realname='Bob Ferris')) # Patch the _send_request() method: self.send_request_patch = patch.object( ScrobblesFetcher, '_send_request') self.send_request = self.send_request_patch.start() self.send_request.return_value = [] self.sleep_patch = patch('time.sleep') self.sleep = self.sleep_patch.start() def tearDown(self): self.send_request_patch.stop() self.sleep_patch.stop() def test_requires_account_argument(self): "__init__ should throw error with no account argument." with self.assertRaises(TypeError): result = ScrobblesFetcher() def test_requires_valid_account_object(self): "__init__ should throw error with no Account object." with self.assertRaises(ValueError): result = ScrobblesFetcher(account=None) def test_no_credentials(self): "__init__ should return error if Account has no API creds." account = AccountFactory(api_key='') result = ScrobblesFetcher(account=account).fetch() self.assertFalse(result['success']) self.assertEqual(result['messages'], ['Account has no API credentials']) def test_will_not_fetch_inactive_accounts(self): account = AccountFactory(is_active=False, username='******') result = ScrobblesFetcher(account=account).fetch() self.assertFalse(result['success']) self.assertEqual(result['messages'], ['The Account terry is currently marked as inactive.']) def test_raises_error_with_invalid_fetch_type(self): "fetch only accepts valid fetch_type's." with self.assertRaises(ValueError): self.fetcher.fetch(fetch_type='bibble') def test_does_not_raise_error_with_valid_fetch_type(self): try: self.fetcher.fetch(fetch_type='all') except ValueError: self.fail( "ScrobblesFetcher().fetch() raised ValueError unexpectedly") def test_raises_error_with_non_integer_days(self): "When fetching for a number of days" with self.assertRaises(ValueError): self.fetcher.fetch(fetch_type='days', days='oops') def test_no_error_with_integer_days(self): "When fetching for a number of days" try: self.fetcher.fetch(fetch_type='days', days=3) except ValueError: self.fail( "ScrobblesFetcher().fetch() raised ValueError unexpectedly") def test_returns_account(self): "Returned data should include the account name" results = self.fetcher.fetch() self.assertEqual(results['account'], 'Bob Ferris') def test_get_slugs_with_semicolon(self): """Successfully handles a URL that contains a semicolon. A track with a URL of 'http://www.last.fm/music/iamamiwhoami/_/;+john' was ending up with an empty track slug because ';' is seen as an alternative to '&' for separating query strings. """ artist_slug, track_slug = self.fetcher._get_slugs( 'http://www.last.fm/music/iamamiwhoami/_/;+john') self.assertEqual(track_slug, ';+john')
def setUp(self): self.account = AccountFactory(username='******', api_key='1234') self.fetcher = ScrobblesFetcher(account=self.account) self.sleep_patch = patch('time.sleep') self.sleep = self.sleep_patch.start()
class ScrobblesFetcherSendTestCase(TestCase): """ Tests that DO involve sending requests to the API, and so we need to mock the requests. """ fixture_path = 'tests/lastfm/fixtures/api/' def setUp(self): self.account = AccountFactory(username='******', api_key='1234') self.fetcher = ScrobblesFetcher(account=self.account) self.sleep_patch = patch('time.sleep') self.sleep = self.sleep_patch.start() def tearDown(self): self.sleep_patch.stop() def load_raw_fixture(self, fixture_name): """ Makes the JSON response to a call to the API. fixture_name -- eg 'messages' to load 'messages.json'. Returns the JSON text. """ json_file = open('%s%s.json' % (self.fixture_path, fixture_name)) json_data = json_file.read() json_file.close() return json_data def load_fixture(self, fixture_name): """ Returns the json.loads() version of the JSON fixture. ie, a python list or dict, not plain text. fixture_name -- eg 'messages' to load 'messages.json'. """ return json.loads(self.load_raw_fixture(fixture_name)) def add_response(self, body, url, status=200): """ Add a mocked response. Keyword arguments: body -- The JSON string representing the body of the response. url -- The request URL. status -- Int, HTTP response status. """ responses.add( responses.GET, url, status=status, body=body, match_querystring=True, content_type='application/json; charset=utf-8' ) def add_recent_tracks_response(self, body=None, page=1, status=200, from_time=None): """ Add a mocked response to the get recent tracks API method. body -- Alternate JSON text response to the default success response. from_time -- A UTC unixtime value. """ url = 'http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=bob&api_key=1234&format=json&page=%s&limit=200' % page if from_time is not None: url += '&from=%s' % from_time if body is None: # Default success response body = self.load_raw_fixture('user_getrecenttracks') self.add_response(body=body, url=url, status=status) @responses.activate def test_returns_error_when_request_fails(self): "If the API request fails, fetch() should return an error." self.add_recent_tracks_response(status=500) results = self.fetcher.fetch() self.assertFalse(results['success']) self.assertIn( 'Error when fetching Scrobbles (page 1): 500 Server Error', results['messages'][0]) @responses.activate def test_returns_error_when_request_contains_error(self): "If API response contains an error message, fetch() should return it." self.add_recent_tracks_response( body='{"error": 10, "message": "Invalid API Key"}') results = self.fetcher.fetch() self.assertFalse(results['success']) self.assertIn( 'Error 10 when fetching Scrobbles (page 1): Invalid API Key', results['messages'][0]) @responses.activate def test_sends_from_time_correctly_for_recent(self): "Sends the correct min time to API if we only want recent results." # We should fetch results from this scrobble's post_time onwards: scrobble = ScrobbleFactory( post_time=datetime.datetime.strptime( '2015-08-11 12:00:00', '%Y-%m-%d %H:%M:%S').replace( tzinfo=pytz.utc)) # Timestamp for 2015-08-11 12:00:00 UTC: self.add_recent_tracks_response(from_time=1439294400) self.fetcher.fetch(fetch_type='recent') self.assertIn('from=1439294400', responses.calls[0].request.url) @responses.activate @freeze_time("2015-08-14 12:00:00", tz_offset=0) def test_sends_from_time_correctly_for_days(self): "Sends the correct min time to API if we only want n days' results." # Timestamp for 2015-08-11 12:00:00 UTC: self.add_recent_tracks_response(from_time=1439294400) self.fetcher.fetch(fetch_type='days', days=3) self.assertIn('from=1439294400', responses.calls[0].request.url) @responses.activate def test_requests_multiple_pages(self): "Fetches multiple pages of results if there are multiple." body = self.load_fixture('user_getrecenttracks') body['recenttracks']['@attr']['totalPages'] = "2" self.add_recent_tracks_response(body=json.dumps(body)) body['recenttracks']['@attr']['page'] = "2" self.add_recent_tracks_response(body=json.dumps(body), page=2) self.fetcher.fetch() self.assertEqual(len(responses.calls), 2) self.assertIn('page=1', responses.calls[0].request.url) self.assertIn('page=2', responses.calls[1].request.url) @responses.activate def test_returns_correct_scrobble_count(self): "Should return the number of scrobbles fetched." self.add_recent_tracks_response() results = self.fetcher.fetch() # We have this many finished scrobbles in our JSON fixture: self.assertEqual(results['fetched'], 3) @responses.activate def test_doesnt_add_nowplaying_track(self): "Doesn't save data from a currently-playing scrobble." self.add_recent_tracks_response() results = self.fetcher.fetch() artists = Artist.objects.filter(name='K.Flay') self.assertEqual(len(artists), 0) @responses.activate def test_creates_new_artist(self): "Creates a new Artist if it doesn't exist" self.add_recent_tracks_response() results = self.fetcher.fetch() artists = Artist.objects.filter(slug='lou+reed') self.assertEqual(len(artists), 1) self.assertEqual(artists[0].slug, 'lou+reed') self.assertEqual(artists[0].original_slug, 'Lou+Reed') self.assertEqual(artists[0].name, 'Lou Reed') self.assertEqual(artists[0].mbid, '9d1ebcfe-4c15-4d18-95d3-d919898638a1') @responses.activate def test_updates_existing_artist(self): "Doesn't create a new Artist if it already exists" ArtistFactory(slug='lou+reed', mbid='') self.add_recent_tracks_response() results = self.fetcher.fetch() artists = Artist.objects.filter(slug='lou+reed') self.assertEqual(len(artists), 1) self.assertEqual(artists[0].slug, 'lou+reed') self.assertEqual(artists[0].original_slug, 'Lou+Reed') self.assertEqual(artists[0].name, 'Lou Reed') self.assertEqual(artists[0].mbid, '9d1ebcfe-4c15-4d18-95d3-d919898638a1') @responses.activate def test_ignores_case_when_finding_artist(self): "Should match an Artist with slug 'Lou+Reed' with 'lou+reed'." ArtistFactory(slug='lou+reed', name='Lou Reed', mbid='') self.add_recent_tracks_response() results = self.fetcher.fetch() artists = Artist.objects.filter(name='Lou Reed') self.assertEqual(len(artists), 1) @responses.activate def test_creates_new_track(self): "Creates a new Track if it doesn't exist" self.add_recent_tracks_response() results = self.fetcher.fetch() loureed = Artist.objects.get(slug='lou+reed') tracks = Track.objects.filter(name='Make Up', slug='make+up') self.assertEqual(len(tracks), 1) self.assertEqual(tracks[0].name,'Make Up') self.assertEqual(tracks[0].slug,'make+up') self.assertEqual(tracks[0].original_slug,'Make+Up') self.assertEqual(tracks[0].mbid,'8e73b23a-6a01-4743-b414-047974f66e22') self.assertEqual(tracks[0].artist, loureed) @responses.activate def test_updates_existing_track(self): "Doesn't create a new Track if it already exists" loureed = ArtistFactory(slug='lou+reed') TrackFactory(artist=loureed, slug='make+up') self.add_recent_tracks_response() results = self.fetcher.fetch() tracks = Track.objects.filter(slug='make+up') self.assertEqual(len(tracks), 1) self.assertEqual(tracks[0].name,'Make Up') self.assertEqual(tracks[0].slug,'make+up') self.assertEqual(tracks[0].original_slug,'Make+Up') self.assertEqual(tracks[0].mbid,'8e73b23a-6a01-4743-b414-047974f66e22') self.assertEqual(tracks[0].artist, loureed) @responses.activate def test_creates_new_album(self): "Creates a new Album if it doesn't exist" self.add_recent_tracks_response() results = self.fetcher.fetch() loureed = Artist.objects.get(slug='lou+reed') albums = Album.objects.filter(slug='transformer') self.assertEqual(len(albums), 1) self.assertEqual(albums[0].slug,'transformer') self.assertEqual(albums[0].original_slug,'Transformer') self.assertEqual(albums[0].name,'Transformer') self.assertEqual(albums[0].mbid,'4ee40d97-630c-3f0d-9ea2-d49fa253c354') self.assertEqual(albums[0].artist, loureed) @responses.activate def test_updates_existing_album(self): "Doesn't create a new Album if it already exists" loureed = ArtistFactory(slug='lou+reed') AlbumFactory(artist=loureed, slug='transformer') self.add_recent_tracks_response() results = self.fetcher.fetch() albums = Album.objects.filter(slug='transformer') self.assertEqual(len(albums), 1) self.assertEqual(albums[0].name,'Transformer') self.assertEqual(albums[0].slug,'transformer') self.assertEqual(albums[0].original_slug,'Transformer') self.assertEqual(albums[0].mbid,'4ee40d97-630c-3f0d-9ea2-d49fa253c354') self.assertEqual(albums[0].artist, loureed) @responses.activate def test_does_not_create_album_if_it_has_no_name(self): "Doesn't create a new Album if the scrobble has none" self.add_recent_tracks_response() results = self.fetcher.fetch() artist = Artist.objects.get(slug='%5bunknown%5d') albums = Album.objects.filter(artist=artist) self.assertEqual(len(albums), 0) @responses.activate @patch('ditto.lastfm.fetch.slugify_name') def test_slugifies_album_name(self, slugify_name): "It should call slugify_name for each scrobbled Album name." slugify_name.side_effect = ['Spotify+Session', 'Transformer',] self.add_recent_tracks_response() results = self.fetcher.fetch() slugify_name.assert_has_calls([ call('Spotify Session'), call('Transformer'), ]) @responses.activate def test_creates_scrobbles(self): "Creates new scrobble objects" self.add_recent_tracks_response() results = self.fetcher.fetch() scrobbles = Scrobble.objects.all() # We have this many finished scrobbles in our JSON fixture: self.assertEqual(len(scrobbles), 3) @responses.activate def test_updates_existing_scrobbles(self): "Updates existing scrobble objects" # Make our existing scrobble: artist = ArtistFactory(slug='lou+reed') track = TrackFactory(artist=artist, slug='make+up') post_time = datetime.datetime.strptime( '2016-09-22 09:23:33', '%Y-%m-%d %H:%M:%S' ).replace(tzinfo=pytz.utc) scrobble = ScrobbleFactory(account=self.account, track=track, post_time=post_time) self.add_recent_tracks_response() results = self.fetcher.fetch(fetch_type='all') scrobbles = Scrobble.objects.all() # We have this many finished scrobbles in our JSON fixture: self.assertEqual(len(scrobbles), 3) scrobble_reloaded = Scrobble.objects.get(artist=artist) self.assertEqual(scrobble.pk, scrobble_reloaded.pk) @responses.activate @freeze_time("2015-08-14 12:00:00", tz_offset=0) def test_sets_scrobble_data(self): "Sets all the scrobble data correctly" self.add_recent_tracks_response() results = self.fetcher.fetch(fetch_type='all') scrobble = Scrobble.objects.get(artist__slug='lou+reed') self.assertEqual(scrobble.title, 'Lou Reed – Make Up') self.assertEqual(scrobble.account, self.account) self.assertEqual(scrobble.track.slug, 'make+up') self.assertEqual(scrobble.album.slug, 'transformer') self.assertEqual(scrobble.post_time, datetime.datetime.strptime( '2016-09-22 09:23:33', '%Y-%m-%d %H:%M:%S' ).replace(tzinfo=pytz.utc)) json_data = self.load_fixture('user_getrecenttracks') scrobble_json = json_data['recenttracks']['track'][2] self.assertEqual(scrobble.raw, json.dumps(scrobble_json)) self.assertEqual(scrobble.fetch_time, datetime_now()) @responses.activate def test_leaves_album_blank(self): "If scrobble has no album, leaves its fields empty" self.add_recent_tracks_response() results = self.fetcher.fetch(fetch_type='all') scrobble = Scrobble.objects.get(artist__slug='%5bunknown%5d') self.assertEqual(scrobble.album, None)