def test_toggle_pause_twice(self): video_id = IdentityService.id_video("source") self.player.play(video_id) self.player.release_events() self.player.toggle_pause() self.player.toggle_pause() self.assertEqual(PlayerState.PLAYING, self.player.state) self.expect_events( self.player, Evt.PlayerStateUpdated, Evt.PlayerStateUpdated, )
def from_event(self, evt_cls, model_id, *args, **kwargs): cmd_id = IdentityService.random() for cls, handler_data in self.evt_to_handler.items(): self.evt_dispatcher.observe_result(cmd_id, {cls: handler_data.functor}) evt = evt_cls(cmd_id, model_id, *args, **kwargs) self.evt_dispatcher.dispatch(evt) for cls, handler_data in self.evt_to_handler.items(): handler_data.functor.assert_called_once_with( cls(cmd_id, *handler_data.attrs, **handler_data.dict_attrs))
def test_deleting_to_aborted(self): cmd = Cmd.CreateVideo( IdentityService.random(), self.video.id, "source", collection_id=None ) error = OperationError(cmd, "") self.workflow.to_DELETING(error) cmd = self.expect_dispatch(Cmd.DeleteVideo, self.video.id) self.raise_event( Evt.VideoDeleted, cmd.id, self.video.id, ) self.assertTrue(self.workflow.is_ABORTED())
async def test_event_listening(self): async with self.client.ws_connect("/api/events") as ws: playlist_id = IdentityService.id_playlist() cmd_id = IdentityService.id_command(PlaylistCmd.CreatePlaylist, playlist_id) created_evt = PlaylistEvt.PlaylistCreated( cmd_id, playlist_id, "name", [], False, ) self.evt_dispatcher.dispatch(created_evt) await self.expect_ws_events(ws, [created_evt]) update_cmd_id = IdentityService.id_command( PlaylistCmd.UpdatePlaylistContent, playlist_id) updated_evt = PlaylistEvt.PlaylistContentUpdated( update_cmd_id, playlist_id, []) self.evt_dispatcher.dispatch(created_evt) self.evt_dispatcher.dispatch(updated_evt) await self.expect_ws_events(ws, [created_evt, updated_evt])
def test_add_video_updates_artist_thumbnail(self): video_id_1 = IdentityService.id_video("source") video_id_2 = IdentityService.id_video("source2") artist_id = IdentityService.id_artist("artist") self.data_producer.video("source2", artist_id=artist_id).populate( self.data_facade) self.data_producer.artist(artist_id, "artist", thumbnail=None).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 deezer_data = [{"artist": {"picture_medium": "picture"}}] self.deezer.search.return_value = deezer_data self.evt_expecter.expect(Evt.ArtistVideosUpdated, artist_id, [video_id_1, video_id_2]).expect( Evt.ArtistThumbnailUpdated, artist_id, "picture").from_event( VideoEvt.VideoCreated, video_id_2, "source2", None, artist_id, None, "title", 300, "http", "thumbnail", VideoState.CREATED, )
def test_retrieving_to_parsing(self): event = Evt.VideoCreated( IdentityService.random(), *self.video.to_tuple(), IdentityService.id_artist("artist"), IdentityService.id_album("album"), "title", 300, "http", "thumbnail", VideoState.CREATED, ) self.workflow.to_RETRIEVING(event) cmd = self.expect_dispatch( Cmd.RetrieveVideo, self.video.id, settings["downloader.output_directory"] ) self.raise_event( Evt.VideoRetrieved, cmd.id, self.video.id, f"{settings['downloader.output_directory']}/video.mp4", ) self.assertTrue(self.workflow.is_PARSING())
def test_creating_player_to_aborted(self, identityMock): player_id = IdentityService.id_player() identityMock.id_player.return_value = player_id 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_error(expected_cmds[-1]) self.assertTrue(self.workflow.is_ABORTED())
def test_list_containing(self): videos = [IdentityService.id_video(f"source{i}") for i in range(4)] albums = [ Album(IdentityService.random(), "album_1", videos[:2]), Album(IdentityService.random(), "album_2", videos[2:]), Album( IdentityService.random(), "album_3", [videos[0], videos[2]], ), ] for album in albums: self.repo.create(album) result = self.repo.list_containing(videos[0]) self.assertEqual([albums[0], albums[2]], result) result = self.repo.list_containing(videos[3]) self.assertEqual([albums[1]], result) result = self.repo.list_containing(IdentityService.random()) self.assertEqual([], result)
def test_list_containing(self): videos = [IdentityService.id_video(f"source{i}") for i in range(4)] artists = [ Artist(IdentityService.random(), "artist_1", videos[:2]), Artist(IdentityService.random(), "artist_2", videos[2:]), Artist( IdentityService.random(), "artist_3", [videos[0], videos[2]], ), ] for artist in artists: self.repo.create(artist) result = self.repo.list_containing(videos[0]) self.assertEqual([artists[0], artists[2]], result) result = self.repo.list_containing(videos[3]) self.assertEqual([artists[1]], result) result = self.repo.list_containing(IdentityService.random()) self.assertEqual([], result)
def on_enter_CREATING_PLAYER(self): cmd = make_cmd( PlaylistCmd.CreatePlaylist, HOME_PLAYLIST.id, HOME_PLAYLIST.name, generated=True, ) self._cmd_dispatcher.dispatch(cmd) self._observe_dispatch( PlayerEvt.PlayerCreated, PlayerCmd.CreatePlayer, IdentityService.id_player(), HOME_PLAYLIST.id, )
def make_cmd(cmd_cls, model_id, *args, **kwargs): """Command factory method. Args: cmd_cls: The class of the command. model_id: The ID of the related model. *args: Variable length argument list, forwarded to the command's constructor **kwargs: Arbitrary keyword arguments, forwarded to the command's constructor Returns: Command: The created command. """ cmd_id = IdentityService.id_command(cmd_cls, model_id) return cmd_cls(cmd_id, model_id, *args, **kwargs)
def on_enter_QUEUEING(self, _): video = self.videos.pop() workflow_id = IdentityService.id_workflow(QueueVideoWorkflow, video.id) workflow = self._factory.make_queue_video_workflow( workflow_id, self._app_facade, self._data_facade, video, self.playlist_id, queue_front=True, ) self._observe_start( workflow, )
def test_retrieve_video_download_success(self): video_id = IdentityService.id_video("source") video_title = "video_title" self.data_producer.video("source", title=video_title).populate(self.data_facade) def dispatch_downloaded(op_id, *args): self.app_facade.evt_dispatcher.dispatch(DownloadSuccess(op_id)) self.downloader.download_video.side_effect = dispatch_downloaded output_dir = settings["downloader.output_directory"] location = str(Path(output_dir) / f"{video_title}.mp4") self.evt_expecter.expect(VideoEvt.VideoRetrieved, video_id, location).from_( Cmd.RetrieveVideo, video_id, output_dir )
async def test_play_not_queued(self): video_id = IdentityService.id_video("source") self.data_producer.video("source").populate(self.data_facade) resp = await self.client.post( "/api/player/play", params={"id": str(video_id)}, ) body = await resp.json() self.assertEqual(403, resp.status) self.assertEqual({ "message": "the video is not queued", "details": {} }, body)
def from_(self, cmd_cls, model_id, *args, **kwargs): cmd_id = IdentityService.id_command(cmd_cls, model_id) for evt_cls, handler_data in self.evt_to_handler.items(): self.evt_dispatcher.observe_result(cmd_id, {evt_cls: handler_data.functor}) cmd = cmd_cls(cmd_id, model_id, *args, **kwargs) self.cmd_dispatcher.dispatch(cmd) for evt_cls, handler_data in self.evt_to_handler.items(): handler_data.functor.assert_called_once_with( evt_cls(cmd_id, *handler_data.attrs, **handler_data.dict_attrs))
def test_add_video_updates_album(self): video_id_1 = IdentityService.id_video("source") video_id_2 = IdentityService.id_video("source2") album_id = IdentityService.id_album("album") self.data_producer.video("source2", album_id=album_id).populate(self.data_facade) self.data_producer.album(album_id, "album").video( "source", album_id=album_id).populate(self.data_facade) self.evt_expecter.expect(Evt.AlbumVideosUpdated, album_id, [video_id_1, video_id_2]).from_event( VideoEvt.VideoCreated, video_id_2, "source2", None, None, album_id, "title", 300, "http", "thumbnail", VideoState.CREATED, )
async def test_play_error(self): self.data_producer.select( Player, IdentityService.id_player()).video("source").populate( self.data_facade) video_id = IdentityService.id_video("source") self.expect_and_error( make_cmd(PlayerCmd.PlayVideo, self.player_id, video_id), error="Error message", ) resp = await self.client.post( "/api/player/play", params={"id": str(video_id)}, ) body = await resp.json() self.assertEqual(500, resp.status) self.assertEqual( { "message": "Error message", "details": {}, }, body, )
def test_retrieve_video_from_stream_error(self): video_id = IdentityService.id_video("http://url") title = "title" self.data_producer.video( "http://url", title=title, source_protocol="m3u8" ).populate(self.data_facade) metadata = {} self.downloader.download_metadata.return_value = metadata output_dir = settings["downloader.output_directory"] self.evt_expecter.expect( OperationError, "Unavailable stream URL", {"title": title} ).from_(Cmd.RetrieveVideo, video_id, output_dir)
def test_parsing_to_sub_fetching(self): event = Evt.VideoRetrieved( IdentityService.random(), self.video.id, f"{settings['downloader.output_directory']}/video.mp4", ) self.workflow.to_PARSING(event) cmd = self.expect_dispatch(Cmd.ParseVideo, self.video.id) self.raise_event( Evt.VideoParsed, cmd.id, self.video.id, {}, ) self.assertTrue(self.workflow.is_SUB_RETRIEVING())
def test_delete_video(self): self.data_producer.player().video("source").video("source2").play( "source" ).populate(self.data_facade) player = self.player_repo.get_player() video_id = IdentityService.id_video("source") self.evt_expecter.expect(VideoEvt.VideoDeleted, video_id).expect( PlayerEvt.PlayerStateUpdated, player.id, PlayerState.PLAYING, PlayerState.STOPPED, ).expect(PlayerEvt.PlayerVideoUpdated, player.id, video_id, None,).expect( PlaylistEvt.PlaylistContentUpdated, player.queue, [IdentityService.id_video("source2")], ).from_( Cmd.DeleteVideo, video_id ) other_video_id = IdentityService.id_video("source2") self.assertListEqual( [self.video_repo.get(other_video_id)], self.video_repo.list() )
def test_queue_no_merge_past_index(self): collection_id = IdentityService.random() self.data_producer.player().video("source1", collection_id=collection_id).video( "source2" ).play("source2").parent_producer().video( "source3", collection_id=collection_id ).populate( self.data_facade ) queue = self.playlist_repo.get(self.queue_id) videos = self.video_repo.list() queue.ids = self.service.queue(queue, videos[2].id, front=True) expected = [videos[0].id, videos[1].id, videos[2].id] self.assertListEqual(expected, queue.ids)
def test_retrieve_video_from_stream_success(self): video_id = IdentityService.id_video("http://url") self.data_producer.video("http://url", source_protocol="m3u8").populate( self.data_facade ) metadata = { "url": "http://stream-url.m3u8", } self.downloader.download_metadata.return_value = metadata output_dir = settings["downloader.output_directory"] self.evt_expecter.expect( VideoEvt.VideoRetrieved, video_id, metadata["url"] ).from_(Cmd.RetrieveVideo, video_id, output_dir)
def test_finalizing_to_completed(self): event = Evt.VideoSubtitleFetched( IdentityService.random(), self.video.id, Path() ) self.workflow.to_FINALIZING(event) cmd = self.expect_dispatch(Cmd.SetVideoReady, self.video.id) self.raise_event( Evt.VideoStateUpdated, cmd.id, self.video.id, VideoState.CREATED, VideoState.READY, timedelta(), None, )
def test_retrieve_video_download_error(self): video_id = IdentityService.id_video("source") video_title = "video_title" self.data_producer.video("source", title=video_title).populate(self.data_facade) def dispatch_error(op_id, *args): self.app_facade.evt_dispatcher.dispatch( DownloadError(op_id, "Download error") ) self.downloader.download_video.side_effect = dispatch_error output_dir = settings["downloader.output_directory"] self.evt_expecter.expect(OperationError, "Download error").from_( Cmd.RetrieveVideo, video_id, output_dir )
def test_parsing_to_finalizing(self): settings["subtitle.enabled"] = False event = Evt.VideoRetrieved( IdentityService.random(), self.video.id, f"{settings['downloader.output_directory']}/video.mp4", ) self.workflow.to_PARSING(event) cmd = self.expect_dispatch(Cmd.ParseVideo, self.video.id) self.raise_event( Evt.VideoParsed, cmd.id, self.video.id, {}, ) self.assertTrue(self.workflow.is_FINALIZING())
def test_next_loop_last_album_no_album(self): collection_id = IdentityService.random() self.data_producer.player().video( "source1", collection_id=collection_id, state=VideoState.READY ).video("source2", state=VideoState.READY).video( "source3", state=VideoState.READY ).populate( self.data_facade ) videos = self.video_repo.list() expected = videos[2].id self.assertEqual( expected, self.service.next_video(self.queue_id, videos[2].id, loop_last="album"), )
def test_fetch_video_subtitle(self): self.data_producer.video("source", location="/tmp/source.mp4").populate( self.data_facade ) # Load from disk self.file_service.list_directory.return_value = [] # Download from source source_subtitle = "/tmp/source.vtt" self.downloader.download_subtitle.return_value = source_subtitle video_id = IdentityService.id_video("source") subtitle_language = settings["subtitle.language"] self.evt_expecter.expect( VideoEvt.VideoSubtitleFetched, video_id, Path(source_subtitle) ).from_(Cmd.FetchVideoSubtitle, video_id, subtitle_language)
def test_parse_video(self): self.data_producer.video("source", location="/tmp/source.mp4").populate( self.data_facade ) streams = [ (0, "video", None), (1, "audio", None), (2, "subtitle", "subtitle_lang"), ] self.video_parser.parse_streams.return_value = streams expected = [Stream(*stream) for stream in streams] video_id = IdentityService.id_video("source") self.evt_expecter.expect(VideoEvt.VideoParsed, video_id, expected).from_( Cmd.ParseVideo, video_id )
async def test_update_error(self): req_body = { "name": "test_playlist", "ids": [str(IdentityService.random())] } playlist = self.playlist_repo.get(self.playlist_id) self.expect_and_raise_l([ { "cmd": make_cmd(PlaylistCmd.RenamePlaylist, playlist.id, req_body["name"]), "evt": PlaylistEvt.PlaylistRenamed, "args": { "name": req_body["name"] }, }, { "cmd": make_cmd(PlaylistCmd.UpdatePlaylistContent, playlist.id, req_body["ids"]), "evt": OperationError, "args": { "error": "Error message" }, }, ]) resp = await self.client.patch(f"/api/playlists/{playlist.id}", json=req_body) body = await resp.json() self.assertEqual(500, resp.status) self.assertEqual( { "message": "Error message", "details": {}, }, body, )
async def create(self, req): data = await req.json() channel = self._io_factory.make_janus_channel() def on_success(evt): playlist = self._playlist_repo.get(evt.model_id) channel.send(self._ok(playlist)) def on_error(evt): channel.send(self._internal_error(evt.error, evt.details)) self._observe_dispatch( { PlaylistEvt.PlaylistCreated: on_success, OperationError: on_error }, Cmd.CreatePlaylist, IdentityService.id_playlist(), **data, ) return await channel.receive()