def test_purging_videos_to_completed_with_deletion(self, path_cls_mock): path_inst = path_cls_mock.return_value path_inst.exists.return_value = False video1 = Mock() video1.id = IdentityService.id_video("mock1") video1.streamable.return_value = False video2 = Mock() video2.id = IdentityService.id_video("mock2") video2.location = None self.video_repo.list.return_value = [video1, video2] self.workflow.to_PURGING_VIDEOS() cmd = self.expect_dispatch(VideoCmd.DeleteVideo, video2.id) self.raise_event( VideoEvt.VideoDeleted, cmd.id, video2.id, ) self.assertTrue(self.workflow.is_PURGING_VIDEOS()) cmd = self.expect_dispatch(VideoCmd.DeleteVideo, video1.id) self.raise_event( VideoEvt.VideoDeleted, cmd.id, video1.id, ) self.assertTrue(self.workflow.is_COMPLETED())
async def queue(self, req): source = req.query["url"] video_id = IdentityService.id_video(source) playlist_id = self._player_repo.get_player().queue if self._source_service.is_playlist(source): collection_id = IdentityService.random() self._evt_dispatcher.dispatch( Notification( collection_id, NotifLevel.INFO, "unfolding playlist", {"source": source}, )) loop = asyncio.get_running_loop() with ThreadPoolExecutor() as pool: sources = await loop.run_in_executor( pool, partial(self._source_service.unfold, source)) if not sources: return self._internal_error( "Could not unfold the playlist URL") self._evt_dispatcher.dispatch( Notification( collection_id, NotifLevel.INFO, "downloading playlist", { "source": source, "count": f"{len(sources)} media" }, )) videos = [ Video(IdentityService.id_video(source), source, collection_id) for source in sources ] workflow_id = IdentityService.id_workflow( QueuePlaylistWorkflow, video_id) self._start_workflow( QueuePlaylistWorkflow, workflow_id, self._data_facade, videos, playlist_id, ) return self._no_content() video = Video(video_id, source, collection_id=None) self._start_workflow( QueueVideoWorkflow, video_id, self._data_facade, video, playlist_id, queue_front=False, ) return self._no_content()
def test_remove(self): self.playlist.ids = [ IdentityService.id_video("source1"), IdentityService.id_video("source2"), ] self.playlist.release_events() self.playlist.remove(IdentityService.id_video("source1")) self.expect_events(self.playlist, Evt.PlaylistContentUpdated)
def test_delete_video_updates_artist(self): artist_id = IdentityService.id_artist("artist") video_id_1 = IdentityService.id_video("source1") video_id_2 = IdentityService.id_video("source2") self.data_producer.artist(artist_id, "artist").video( "source1", artist_id=artist_id).video( "source2", artist_id=artist_id).populate(self.data_facade) self.evt_expecter.expect(Evt.ArtistVideosUpdated, artist_id, [video_id_2]).from_event( VideoEvt.VideoDeleted, video_id_1, )
def test_toggle_player_state(self, datetime_mock): now = datetime.now() datetime_mock.now.return_value = now self.data_producer.player().video("source").play("source").populate( self.data_facade) video_id = IdentityService.id_video("source") self.evt_expecter.expect( Evt.PlayerStateUpdated, self.player_id, PlayerState.PLAYING, PlayerState.PAUSED, ).expect( VideoEvt.VideoStateUpdated, video_id, VideoState.PLAYING, VideoState.PAUSED, timedelta(), now, ).from_(Cmd.TogglePlayerState, self.player_id) self.evt_expecter.expect( Evt.PlayerStateUpdated, self.player_id, PlayerState.PAUSED, PlayerState.PLAYING, ).expect( VideoEvt.VideoStateUpdated, video_id, VideoState.PAUSED, VideoState.PLAYING, timedelta(), now, ).from_(Cmd.TogglePlayerState, self.player_id)
def test_seek_video(self): video_id = IdentityService.id_video("source") self.player.play(video_id) self.player.release_events() self.player.seek_video(100) self.expect_events(self.player, Evt.VideoSeeked)
def test_create_artist_no_deezer_data(self): video_id = IdentityService.id_video("source") artist_id = IdentityService.id_artist("artist") self.data_producer.video("source", artist_id=artist_id).populate( self.data_facade) metadata = { "title": "title", "duration": 300, "source_protocol": "http", "artist": "artist", "album": "album", "thumbnail": "thumbnail_url", } self.downloader.download_metadata.return_value = metadata self.deezer.search.return_value = [] self.evt_expecter.expect(Evt.ArtistCreated, artist_id, "artist", [video_id], None).from_event( VideoEvt.VideoCreated, video_id, "source", None, artist_id, None, "title", 300, "http", "thumbnail_url", VideoState.CREATED, )
def test_create_video(self): source = "source" video_id = IdentityService.id_video(source) collection_id = IdentityService.random() metadata = { "title": "title", "duration": 300, "source_protocol": "http", "artist": "artist", "album": "album", "thumbnail": "thumbnail_url", } self.downloader.download_metadata.return_value = metadata self.deezer.search.return_value = [] attrs = deepcopy(metadata) attrs["artist_id"] = IdentityService.id_artist(attrs.pop("artist")) attrs["album_id"] = IdentityService.id_album(attrs.pop("album")) self.evt_expecter.expect( VideoEvt.VideoCreated, video_id, source, collection_id, **attrs, state=VideoState.CREATED, ).from_(Cmd.CreateVideo, video_id, source, collection_id)
def test_construction(self): content = [IdentityService.id_video("")] playlist = Playlist(None, "name", content) self.assertEqual("name", playlist.name) self.assertEqual(content, playlist.ids) self.assertEqual(False, playlist.generated) self.expect_events(playlist, Evt.PlaylistCreated)
def test_create_disk_video(self, path_cls_mock): path_inst = path_cls_mock.return_value source = "source" video_id = IdentityService.id_video(source) collection_id = None path_inst.is_file.return_value = True metadata = { "artist": None, "album": None, "title": "test_title", "duration": None, "source_protocol": None, "thumbnail": None, } path_inst.stem = metadata["title"] attrs = deepcopy(metadata) attrs["artist_id"] = None attrs["album_id"] = None del attrs["artist"] del attrs["album"] self.evt_expecter.expect( VideoEvt.VideoCreated, video_id, source, collection_id, **attrs, state=VideoState.CREATED, ).from_(Cmd.CreateVideo, video_id, source, collection_id)
def setUp(self): super(QueueVideoWorkflowTest, self).setUp() self.video_repo = self.data_facade.video_repo self.playlist_repo = self.data_facade.playlist_repo self.video_repo.exists.return_value = True self.player_playlist = Mock() self.player_playlist.ids = [] self.playlist_repo.get.return_value = self.player_playlist self.player_id = IdentityService.id_player() self.player_playlist_id = IdentityService.id_playlist() player = Mock() player.queue = self.player_playlist_id self.data_facade.player_repo.get_player.return_value = player self.video = Video( IdentityService.id_video("source"), "source", collection_id=None, ) self.workflow = self.make_workflow( QueueVideoWorkflow, self.video, self.player_playlist_id, queue_front=False, )
def test_creating_player_to_purging_videos(self, identityMock): player_id = IdentityService.id_player() identityMock.id_player.return_value = player_id video = Video(IdentityService.id_video("source"), "source", location="unknown") self.video_repo.list.return_value = [video] self.workflow.to_CREATING_PLAYER() createPlaylistId = IdentityService.id_command( PlaylistCmd.CreatePlaylist, HOME_PLAYLIST.id) createPlayerId = IdentityService.id_command(PlayerCmd.CreatePlayer, player_id) expected_cmds = [ PlaylistCmd.CreatePlaylist(createPlaylistId, HOME_PLAYLIST.id, HOME_PLAYLIST.name, [], True), PlayerCmd.CreatePlayer(createPlayerId, player_id, HOME_PLAYLIST.id), ] self.expect_dispatch_l(expected_cmds) self.raise_event( PlayerEvt.PlayerCreated, createPlayerId, player_id, HOME_PLAYLIST.id, PlayerState.STOPPED, True, 0, 70, ) self.assertTrue(self.workflow.is_PURGING_VIDEOS())
async def test_stop(self): self.data_producer.player().video("source").play("source").populate( self.data_facade) video_id = IdentityService.id_video("source") self.expect_and_raise( make_cmd(PlayerCmd.StopPlayer, self.player_id), [ { "type": PlayerEvt.PlayerStateUpdated, "args": { "old_state": PlayerState.PLAYING, "new_state": PlayerState.STOPPED, }, }, { "type": PlayerEvt.PlayerVideoUpdated, "args": { "old_video_id": video_id, "new_video_id": None, }, }, ], ) resp = await self.client.post("/api/player/stop") body = await resp.json() player = self.data_facade.player_repo.get_player() self.assertEqual(200, resp.status) self.assertEqual(player.to_dict(), body)
def test_stop_player(self, datetime_mock): now = datetime.now() datetime_mock.now.return_value = now self.data_producer.player().video("source").play("source").populate( self.data_facade) video_id = IdentityService.id_video("source") self.evt_expecter.expect( Evt.PlayerStateUpdated, self.player_id, PlayerState.PLAYING, PlayerState.STOPPED, ).expect( Evt.PlayerVideoUpdated, self.player_id, video_id, None, ).expect( VideoEvt.VideoStateUpdated, video_id, VideoState.PLAYING, VideoState.READY, timedelta(), now, ).from_(Cmd.StopPlayer, self.player_id)
def test_remove_unknown(self): video_id = IdentityService.id_video("source1") with self.assertRaises(DomainError) as ctx: self.album.remove(video_id) self.assertEqual( "video not in album", str(ctx.exception), )
def setUp(self): self.evt_dispatcher = Mock() self.manager = WorkflowManager(self.evt_dispatcher) video_id = IdentityService.id_video("source") self.workflow = Mock() self.queue_workflow_id = IdentityService.id_workflow( QueueVideoWorkflow, video_id)
async def test_play_video_not_found(self): video_id = IdentityService.id_video("source") resp = await self.client.post( "/api/player/play", params={"id": str(video_id)}, ) self.assertEqual(404, resp.status)
def test_toggle_pause(self): video_id = IdentityService.id_video("source") self.player.play(video_id) self.player.release_events() self.player.toggle_pause() self.assertEqual(PlayerState.PAUSED, self.player.state) self.expect_events(self.player, Evt.PlayerStateUpdated)
def play(self, source: str): video_id = IdentityService.id_video(source) player = self._population.last(Player) player.play(video_id) video = self._population.find(Video, video_id) video.state = VideoState.READY video.start() return self
def make_test_workflow(self, video_count=2): collection_id = IdentityService.random() sources = [f"src{i}" for i in range(video_count)] videos = [ Video(IdentityService.id_video(source), source, collection_id) for source in sources ] return self.make_workflow(StreamPlaylistWorkflow, videos, self.player_playlist_id)
def test_retrieve_video_from_disk_success(self, path_cls_mock): video_id = IdentityService.id_video("/tmp/video.mp4") self.data_producer.video("/tmp/video.mp4").populate(self.data_facade) path_cls_mock.return_value.is_file.return_value = True output_dir = settings["downloader.output_directory"] self.evt_expecter.expect( VideoEvt.VideoRetrieved, video_id, "/tmp/video.mp4" ).from_(Cmd.RetrieveVideo, video_id, output_dir)
def test_next_invalid_video(self): self.data_producer.player().video("source1", state=VideoState.READY).video( "source2", state=VideoState.READY ).populate(self.data_facade) video_id = IdentityService.id_video("source3") self.assertEqual( None, self.service.next_video(self.queue_id, video_id, loop_last="track") )
def test_init_to_purging_videos(self): self.player_repo.exists.return_value = True video = Video(IdentityService.id_video("source"), "source", location="unknown") self.video_repo.list.return_value = [video] self.workflow.start() self.assertTrue(self.workflow.is_PURGING_VIDEOS())
def test_update_playlist_content(self): playlist_id = IdentityService.id_playlist() self.data_producer.playlist(playlist_id, "name", []).populate(self.data_facade) playlist = self.playlist_repo.get(playlist_id) new_content = [IdentityService.id_video("source")] self.evt_expecter.expect(Evt.PlaylistContentUpdated, playlist.id, new_content).from_(Cmd.UpdatePlaylistContent, playlist.id, new_content)
def test_create_video_missing_metadata(self): source = "source" video_id = IdentityService.id_video(source) metadata = None self.downloader.download_metadata.return_value = metadata self.evt_expecter.expect( OperationError, "no media found", {"source": source} ).from_(Cmd.CreateVideo, video_id, source, collection_id=None)
def test_play(self): video_id = IdentityService.id_video("source") self.player.play(video_id) self.assertEqual(PlayerState.PLAYING, self.player.state) self.assertEqual(video_id, self.player.video_id) self.expect_events( self.player, Evt.PlayerVideoUpdated, Evt.PlayerStateUpdated, )
def test_delete_video_deletes_album(self): album_id = IdentityService.id_album("album") video_id = IdentityService.id_video("source") self.data_producer.album(album_id, "album").video( "source", album_id=album_id).populate(self.data_facade) self.evt_expecter.expect(Evt.AlbumDeleted, album_id, []).from_event( VideoEvt.VideoDeleted, video_id, )
def test_download_video_error(self): self.ydl.download.side_effect = RuntimeError("error") op_id = IdentityService.random() video_id = IdentityService.id_video("url") self.downloader.download_video(op_id, video_id, "url", "/tmp/media.mp4") self.dispatcher.dispatch.assert_called_with( DownloadError(op_id, "error"))
def test_download_video(self, path_cls): path_mock = path_cls.return_value path_mock.exists.return_value = True op_id = IdentityService.random() video_id = IdentityService.id_video("url") self.downloader.download_video(op_id, video_id, "url", "/tmp/media.mp4") self.dispatcher.dispatch.assert_called_with(DownloadSuccess(op_id))
def test_download_video_missing(self, path_cls): path_mock = path_cls.return_value path_mock.exists.return_value = False op_id = IdentityService.random() video_id = IdentityService.id_video("url") self.downloader.download_video(op_id, video_id, "url", "/tmp/media.mp4") self.dispatcher.dispatch.assert_called_with( DownloadError(op_id, "video path points to non existent file"))