test_views.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. from datetime import datetime, timedelta
  2. from unittest.mock import patch
  3. from django.utils import timezone
  4. import pytest
  5. import time_machine
  6. from django.urls import reverse
  7. from music.models import Track
  8. from podcasts.models import PodcastEpisode
  9. from scrobbles.models import Scrobble
  10. @pytest.mark.django_db
  11. def test_get_not_allowed_from_mopidy(client, valid_auth_token):
  12. url = reverse("scrobbles:mopidy-webhook")
  13. headers = {"Authorization": f"Token {valid_auth_token}"}
  14. response = client.get(url, headers=headers)
  15. assert response.status_code == 405
  16. @pytest.mark.django_db
  17. def test_bad_mopidy_request_data(client, valid_auth_token):
  18. url = reverse("scrobbles:mopidy-webhook")
  19. headers = {"Authorization": f"Token {valid_auth_token}"}
  20. response = client.post(url, headers)
  21. assert response.status_code == 400
  22. assert (
  23. response.data["detail"]
  24. == "JSON parse error - Expecting value: line 1 column 1 (char 0)"
  25. )
  26. @pytest.mark.parametrize(
  27. "seconds, expected_percent_played, expected_scrobble_id",
  28. [
  29. (1, 1, 1),
  30. (58, 96, 1),
  31. (59, 98, 1),
  32. (60, 100, 1),
  33. (1, 1, 1),
  34. (1, 1, 1),
  35. ],
  36. )
  37. @pytest.mark.django_db
  38. def test_scrobble_mopidy_track(
  39. client,
  40. mopidy_track,
  41. valid_auth_token,
  42. seconds,
  43. expected_percent_played,
  44. expected_scrobble_id,
  45. ):
  46. url = reverse("scrobbles:mopidy-webhook")
  47. headers = {"Authorization": f"Token {valid_auth_token}"}
  48. # Start new scrobble
  49. minutes = 0
  50. calc_seconds = seconds
  51. if seconds >= 60:
  52. minutes = 1
  53. calc_seconds = calc_seconds % 10
  54. with time_machine.travel(datetime(2024, 1, 14, 12, minutes, calc_seconds)):
  55. mopidy_track.request_data["playback_time_ticks"] = seconds * 1000
  56. response = client.post(
  57. url,
  58. mopidy_track.request_json,
  59. content_type="application/json",
  60. headers=headers,
  61. )
  62. assert response.status_code == 200
  63. assert response.data == {"scrobble_id": expected_scrobble_id}
  64. scrobble = Scrobble.objects.get(id=1)
  65. assert scrobble.percent_played == expected_percent_played
  66. assert scrobble.media_obj.__class__ == Track
  67. assert scrobble.media_obj.title == "Same in the End"
  68. # @pytest.mark.skip(reason="Allmusic API is unstable")
  69. @pytest.mark.django_db
  70. @patch("music.utils.lookup_artist_from_mb", return_value={})
  71. @patch(
  72. "music.utils.lookup_album_dict_from_mb",
  73. return_value={"year": "1999", "mb_group_id": 1},
  74. )
  75. @patch("music.utils.lookup_track_from_mb", return_value={})
  76. @patch("music.models.lookup_artist_from_tadb", return_value={})
  77. @patch("music.models.lookup_album_from_tadb", return_value={"year": "1999"})
  78. @patch("music.models.Album.fetch_artwork", return_value=None)
  79. @patch("music.models.Album.scrape_allmusic", return_value=None)
  80. def test_scrobble_mopidy_same_track_different_album(
  81. mock_lookup_artist,
  82. mock_lookup_album,
  83. mock_lookup_track,
  84. mock_lookup_artist_tadb,
  85. mock_lookup_album_tadb,
  86. mock_fetch_artwork,
  87. mock_scrape_allmusic,
  88. client,
  89. mopidy_track,
  90. mopidy_track_diff_album_request_data,
  91. valid_auth_token,
  92. ):
  93. url = reverse("scrobbles:mopidy-webhook")
  94. headers = {"Authorization": f"Token {valid_auth_token}"}
  95. response = client.post(
  96. url,
  97. mopidy_track.request_data,
  98. content_type="application/json",
  99. headers=headers,
  100. )
  101. assert response.status_code == 200
  102. assert response.data == {"scrobble_id": 1}
  103. scrobble = Scrobble.objects.last()
  104. assert scrobble.media_obj.album.name == "Sublime"
  105. response = client.post(
  106. url,
  107. mopidy_track_diff_album_request_data,
  108. content_type="application/json",
  109. )
  110. assert response.status_code == 200
  111. assert response.data == {"scrobble_id": 2}
  112. scrobble = Scrobble.objects.last()
  113. assert scrobble.media_obj.__class__ == Track
  114. assert scrobble.media_obj.album.name == "Sublime"
  115. assert scrobble.media_obj.title == "Same in the End"
  116. @pytest.mark.skip(
  117. "Need to add a mock podcast request data, tho Google Podcasts is gone :thinking:"
  118. )
  119. @pytest.mark.django_db
  120. def test_scrobble_mopidy_podcast(
  121. client, mopidy_podcast_request_data, valid_auth_token
  122. ):
  123. url = reverse("scrobbles:mopidy-webhook")
  124. headers = {"Authorization": f"Token {valid_auth_token}"}
  125. response = client.post(
  126. url,
  127. mopidy_podcast_request_data,
  128. content_type="application/json",
  129. headers=headers,
  130. )
  131. assert response.status_code == 200
  132. assert response.data == {"scrobble_id": 1}
  133. scrobble = Scrobble.objects.get(id=1)
  134. assert scrobble.media_obj.__class__ == PodcastEpisode
  135. assert scrobble.media_obj.title == "Up First"
  136. @pytest.mark.django_db
  137. @patch("music.utils.lookup_artist_from_mb", return_value={})
  138. @patch(
  139. "music.utils.lookup_album_dict_from_mb",
  140. return_value={"year": "1999", "mb_group_id": 1},
  141. )
  142. @patch("music.utils.lookup_track_from_mb", return_value={})
  143. @patch("music.models.lookup_artist_from_tadb", return_value={})
  144. @patch("music.models.lookup_album_from_tadb", return_value={"year": "1999"})
  145. @patch("music.models.Album.fetch_artwork", return_value=None)
  146. @patch("music.models.Album.scrape_allmusic", return_value=None)
  147. def test_scrobble_jellyfin_track(
  148. mock_lookup_artist,
  149. mock_lookup_album,
  150. mock_lookup_track,
  151. mock_lookup_artist_tadb,
  152. mock_lookup_album_tadb,
  153. mock_fetch_artwork,
  154. mock_scrape_allmusic,
  155. client,
  156. jellyfin_track,
  157. valid_auth_token,
  158. ):
  159. url = reverse("scrobbles:jellyfin-webhook")
  160. headers = {"Authorization": f"Token {valid_auth_token}"}
  161. with time_machine.travel(datetime(2024, 1, 14, 12, 00, 1)):
  162. jellyfin_track.request_data["UtcTimestamp"] = timezone.now().strftime(
  163. "%Y-%m-%d %H:%M:%S"
  164. )
  165. response = client.post(
  166. url,
  167. jellyfin_track.request_json,
  168. content_type="application/json",
  169. headers=headers,
  170. )
  171. assert response.status_code == 200
  172. assert response.data == {"scrobble_id": 1}
  173. scrobble = Scrobble.objects.get(id=1)
  174. assert scrobble.media_obj.__class__ == Track
  175. assert scrobble.media_obj.title == "Emotion"
  176. @pytest.mark.django_db
  177. @patch("music.utils.lookup_artist_from_mb", return_value={})
  178. @patch(
  179. "music.utils.lookup_album_dict_from_mb",
  180. return_value={"year": "1999", "mb_group_id": 1},
  181. )
  182. @patch("music.utils.lookup_track_from_mb", return_value={})
  183. @patch("music.models.lookup_artist_from_tadb", return_value={})
  184. @patch("music.models.lookup_album_from_tadb", return_value={"year": "1999"})
  185. @patch("music.models.Album.fetch_artwork", return_value=None)
  186. @patch("music.models.Album.scrape_allmusic", return_value=None)
  187. def test_scrobble_jellyfin_track_update(
  188. mock_lookup_artist,
  189. mock_lookup_album,
  190. mock_lookup_track,
  191. mock_lookup_artist_tadb,
  192. mock_lookup_album_tadb,
  193. mock_fetch_artwork,
  194. mock_scrape_allmusic,
  195. test_track,
  196. client,
  197. jellyfin_track,
  198. valid_auth_token,
  199. ):
  200. Scrobble.objects.create(
  201. timestamp=timezone.now() - timedelta(minutes=0.5),
  202. track=Track.objects.first(),
  203. user_id=1,
  204. )
  205. url = reverse("scrobbles:jellyfin-webhook")
  206. headers = {"Authorization": f"Token {valid_auth_token}"}
  207. jellyfin_track.request_data["UtcTimestamp"] = timezone.now().strftime(
  208. "%Y-%m-%d %H:%M:%S"
  209. )
  210. response = client.post(
  211. url,
  212. jellyfin_track.request_json,
  213. content_type="application/json",
  214. headers=headers,
  215. )
  216. assert response.status_code == 200
  217. assert response.data == {"scrobble_id": 1}
  218. scrobble = Scrobble.objects.get(id=1)
  219. assert scrobble.media_obj.__class__ == Track
  220. assert scrobble.media_obj.title == "Emotion"
  221. @pytest.mark.django_db
  222. @patch("music.utils.lookup_artist_from_mb", return_value={})
  223. @patch(
  224. "music.utils.lookup_album_dict_from_mb",
  225. return_value={"year": "1999", "mb_group_id": 1},
  226. )
  227. @patch("music.utils.lookup_track_from_mb", return_value={})
  228. @patch("music.models.lookup_artist_from_tadb", return_value={})
  229. @patch("music.models.lookup_album_from_tadb", return_value={"year": "1999"})
  230. @patch("music.models.Album.fetch_artwork", return_value=None)
  231. @patch("music.models.Album.scrape_allmusic", return_value=None)
  232. def test_scrobble_jellyfin_track_create_new(
  233. mock_lookup_artist,
  234. mock_lookup_album,
  235. mock_lookup_track,
  236. mock_lookup_artist_tadb,
  237. mock_lookup_album_tadb,
  238. mock_fetch_artwork,
  239. mock_scrape_allmusic,
  240. test_track,
  241. client,
  242. jellyfin_track,
  243. valid_auth_token,
  244. ):
  245. url = reverse("scrobbles:jellyfin-webhook")
  246. headers = {"Authorization": f"Token {valid_auth_token}"}
  247. Scrobble.objects.create(
  248. timestamp=timezone.now() - timedelta(minutes=1),
  249. track=Track.objects.first(),
  250. user_id=1,
  251. )
  252. jellyfin_track.request_data["UtcTimestamp"] = timezone.now().strftime(
  253. "%Y-%m-%d %H:%M:%S"
  254. )
  255. response = client.post(
  256. url,
  257. jellyfin_track.request_json,
  258. content_type="application/json",
  259. headers=headers,
  260. )
  261. assert response.status_code == 200
  262. assert response.data == {"scrobble_id": 2}
  263. scrobble = Scrobble.objects.get(id=1)
  264. assert scrobble.media_obj.__class__ == Track
  265. assert scrobble.media_obj.title == "Emotion"