async def get_application(self, loop=None):
     self.fake_atv = FakeAppleTV(self.loop)
     self.state, self.usecase = self.fake_atv.add_service(Protocol.MRP)
     self.airplay_state, self.airplay_usecase = self.fake_atv.add_service(
         Protocol.AirPlay
     )
     return self.fake_atv.app
Exemple #2
0
 async def get_application(self, loop=None):
     self.fake_atv = FakeAppleTV(self.loop)
     self.state, self.usecase = self.fake_atv.add_service(
         Protocol.DMAP,
         hsgid=HSGID,
         pairing_guid=PAIRING_GUID,
         session_id=SESSION_ID)
     return self.fake_atv.app
Exemple #3
0
class AirPlayAuthTest(AioHTTPTestCase):
    async def setUpAsync(self):
        self.session = ClientSession(loop=self.loop)

    async def tearDownAsync(self):
        await self.session.close()

    async def get_application(self, loop=None):
        self.fake_atv = FakeAppleTV(self.loop)
        self.fake_atv.add_service(Protocol.AirPlay)
        return self.fake_atv.app

    @unittest_run_loop
    async def test_verify_invalid(self):
        http = HttpSession(self.session,
                           "http://127.0.0.1:{0}/".format(self.server.port))
        handler = srp.SRPAuthHandler()
        handler.initialize(INVALID_AUTH_KEY)

        verifier = AuthenticationVerifier(http, handler)
        with self.assertRaises(AuthenticationError):
            await verifier.verify_authed()

    @unittest_run_loop
    async def test_verify_authenticated(self):
        http = HttpSession(self.session,
                           "http://127.0.0.1:{0}/".format(self.server.port))
        handler = srp.SRPAuthHandler()
        handler.initialize(binascii.unhexlify(DEVICE_AUTH_KEY))

        verifier = AuthenticationVerifier(http, handler)
        self.assertTrue((await verifier.verify_authed()))

    @unittest_run_loop
    async def test_auth_failed(self):
        http = HttpSession(self.session,
                           "http://127.0.0.1:{0}/".format(self.server.port))
        handler = srp.SRPAuthHandler()
        handler.initialize(INVALID_AUTH_KEY)

        authenticator = DeviceAuthenticator(http, handler)
        await authenticator.start_authentication()
        with self.assertRaises(AuthenticationError):
            await authenticator.finish_authentication(DEVICE_IDENTIFIER,
                                                      DEVICE_PIN)

    @unittest_run_loop
    async def test_auth_successful(self):
        http = HttpSession(self.session,
                           "http://127.0.0.1:{0}/".format(self.server.port))
        handler = srp.SRPAuthHandler()
        handler.initialize(binascii.unhexlify(DEVICE_AUTH_KEY))

        authenticator = DeviceAuthenticator(http, handler)
        await authenticator.start_authentication()
        self.assertTrue(
            (await authenticator.finish_authentication(DEVICE_IDENTIFIER,
                                                       DEVICE_PIN)))
Exemple #4
0
class DmapPairFunctionalTest(AioHTTPTestCase):
    def setUp(self):
        AioHTTPTestCase.setUp(self)
        self.pairing = None

        self.service = DmapService("dmap_id",
                                   PAIRING_GUID,
                                   port=self.server.port)
        self.conf = AppleTV("127.0.0.1", "Apple TV")
        self.conf.add_service(self.service)

        self.zeroconf = zeroconf_stub.stub(pyatv.dmap.pairing)

    async def tearDownAsync(self):
        await self.pairing.close()
        await super().tearDownAsync()

    async def get_application(self, loop=None):
        self.fake_atv = FakeAppleTV(self.loop)
        self.state, self.usecase = self.fake_atv.add_service(
            Protocol.DMAP,
            hsgid=HSGID,
            pairing_guid=PAIRING_GUID,
            session_id=SESSION_ID)
        return self.fake_atv.app

    async def initiate_pairing(self,
                               name=REMOTE_NAME,
                               pairing_guid=PAIRING_GUID):
        self.usecase.pairing_response(REMOTE_NAME, PAIRINGCODE)

        options = {
            "zeroconf": self.zeroconf,
            "name": name,
            "pairing_guid": pairing_guid,
        }

        self.pairing = await pyatv.pair(self.conf, Protocol.DMAP, self.loop,
                                        **options)

    @unittest_run_loop
    async def test_pairing_with_device(self):
        await self.initiate_pairing()

        self.assertFalse(self.pairing.has_paired)
        self.assertFalse(self.pairing.device_provides_pin)

        await self.pairing.begin()
        self.pairing.pin(PIN_CODE)

        await self.usecase.act_on_bonjour_services(self.zeroconf)

        await self.pairing.finish()

        self.assertTrue(self.pairing.has_paired)
        self.assertEqual(self.service.credentials, PAIRING_GUID)
Exemple #5
0
class PairFunctionalTest(AioHTTPTestCase):
    def setUp(self):
        AioHTTPTestCase.setUp(self)
        self.pairing = None

        self.service = AirPlayService("airplay_id", port=self.server.port)
        self.conf = AppleTV("127.0.0.1", "Apple TV")
        self.conf.add_service(self.service)

    async def tearDownAsync(self):
        await self.pairing.close()
        await super().tearDownAsync()

    async def get_application(self, loop=None):
        self.fake_atv = FakeAppleTV(self.loop)
        _, self.usecase = self.fake_atv.add_service(Protocol.AirPlay)
        return self.fake_atv.app

    async def do_pairing(self, pin=DEVICE_PIN):
        self.usecase.airplay_require_authentication()

        self.pairing = await pair(self.conf, Protocol.AirPlay, self.loop)

        self.assertTrue(self.pairing.device_provides_pin)

        await self.pairing.begin()
        if pin:
            self.pairing.pin(pin)

        self.assertFalse(self.pairing.has_paired)

        await self.pairing.finish()
        self.assertTrue(self.pairing.has_paired)
        self.assertEqual(self.service.credentials, DEVICE_CREDENTIALS)

    @unittest_run_loop
    async def test_pairing_exception_invalid_pin(self):
        with self.assertRaises(exceptions.PairingError):
            await self.do_pairing(9999)

    @unittest_run_loop
    async def test_pairing_exception_no_pin(self):
        with self.assertRaises(exceptions.PairingError):
            await self.do_pairing(None)

    @patch("os.urandom")
    @unittest_run_loop
    async def test_pairing_with_device_new_credentials(self, rand_func):
        rand_func.side_effect = predetermined_key
        await self.do_pairing()

    @unittest_run_loop
    async def test_pairing_with_device_existing_credentials(self):
        self.conf.get_service(
            Protocol.AirPlay).credentials = DEVICE_CREDENTIALS
        await self.do_pairing()
Exemple #6
0
class ScriptTest(AioHTTPTestCase):
    async def setUpAsync(self):
        await AioHTTPTestCase.setUpAsync(self)
        stub_sleep()
        self.setup_environment()
        await self.fake_udns.start()
        self.stdout = None
        self.stderr = None
        self.retcode = None
        self.inputs = []

    def tearDown(self):
        unstub_sleep()
        AioHTTPTestCase.tearDown(self)

    def setup_environment(self):
        airplay_port = self.server.port

        self.fake_udns.add_service(
            fake_udns.homesharing_service(DMAP_ID, "Apple TV 1", "aaaa", address=IP_1)
        )

        self.fake_udns.add_service(
            fake_udns.mrp_service(
                "DDDD",
                "Apple TV 2",
                MRP_ID,
                address=IP_2,
                port=self.fake_atv.get_port(Protocol.MRP),
            )
        )
        self.fake_udns.add_service(
            fake_udns.airplay_service(
                "Apple TV 2", AIRPLAY_ID, address=IP_2, port=airplay_port
            )
        )

        self.airplay_usecase.airplay_playback_playing()
        self.airplay_usecase.airplay_playback_idle()

    async def get_application(self, loop=None):
        self.fake_udns = fake_udns.FakeUdns(self.loop)
        self.fake_udns.ip_filter = IP_2
        self.fake_atv = FakeAppleTV(self.loop)
        self.state, self.usecase = self.fake_atv.add_service(Protocol.MRP)
        self.airplay_state, self.airplay_usecase = self.fake_atv.add_service(
            Protocol.AirPlay
        )
        return self.fake_atv.app

    def user_input(self, text):
        self.inputs.append(text)

    def has_output(self, *strings):
        for string in strings:
            self.assertIn(string, self.stdout)

    def has_error(self, *strings):
        for string in strings:
            self.assertIn(string, self.stderr)

    def exit(self, code):
        self.assertEqual(self.retcode, code)

    async def run_script(self, script, *args):
        argv = [script] + list(args)
        inputs = "\n".join(self.inputs) + "\n"
        with capture_output(argv, inputs) as (out, err):
            udns_port = str(self.fake_udns.port)
            with patch.dict("os.environ", {"PYATV_UDNS_PORT": udns_port}):
                with fake_udns.stub_multicast(self.fake_udns, self.loop):
                    with faketime("pyatv", 0):
                        # Stub away port knocking and ignore result (not tested here)
                        with patch("pyatv.support.knock.knock") as mock_knock:

                            async def _no_action(*args):
                                pass

                            mock_knock.side_effect = _no_action

                            module = import_module(f"pyatv.scripts.{script}")
                            self.retcode = await module.appstart(self.loop)
                            self.stdout = out.getvalue()
                            self.stderr = err.getvalue()
Exemple #7
0
class DMAPFunctionalTest(common_functional_tests.CommonFunctionalTests):
    async def setUpAsync(self):
        await super().setUpAsync()
        self.atv = await self.get_connected_device(HSGID)

    def tearDown(self):
        self.atv.close()
        super().tearDown()

    async def get_application(self, loop=None):
        self.fake_atv = FakeAppleTV(self.loop)
        self.state, self.usecase = self.fake_atv.add_service(
            Protocol.DMAP,
            hsgid=HSGID,
            pairing_guid=PAIRING_GUID,
            session_id=SESSION_ID)
        self.airplay_state, self.airplay_usecase = self.fake_atv.add_service(
            Protocol.AirPlay)
        return self.fake_atv.app

    async def get_connected_device(self, hsgid):
        self.dmap_service = DmapService("dmapid", hsgid, port=self.server.port)
        self.airplay_service = AirPlayService("airplay_id", self.server.port,
                                              DEVICE_CREDENTIALS)
        self.conf = AppleTV(ipaddress.IPv4Address("127.0.0.1"), "Apple TV")
        self.conf.add_service(self.dmap_service)
        self.conf.add_service(self.airplay_service)
        return await connect(self.conf, self.loop)

    @unittest_run_loop
    async def test_app_not_supported(self):
        with self.assertRaises(exceptions.NotSupportedError):
            self.atv.metadata.app

    @unittest_run_loop
    async def test_connect_failed(self):
        # Twice since the client will retry one time
        self.usecase.make_login_fail()
        self.usecase.make_login_fail()

        with self.assertRaises(exceptions.AuthenticationError):
            await self.atv.connect()

    # This test verifies issue #2 (automatic re-login). It uses the artwork
    # API, but it could have been any API since the login code is the same.
    @unittest_run_loop
    async def test_relogin_if_session_expired(self):
        await self.atv.connect()

        # Here, we are logged in and currently have a asession id. These
        # usescases will result in being logged out (HTTP 403) and forcing a
        # re-login with a new session id (1234)
        self.usecase.example_video()
        self.usecase.force_relogin(1234)
        self.usecase.artwork_no_permission()
        self.usecase.change_artwork(ARTWORK_BYTES, ARTWORK_MIMETYPE)

        artwork = await self.atv.metadata.artwork()
        self.assertEqual(artwork.bytes, ARTWORK_BYTES)

    @unittest_run_loop
    async def test_metadata_artwork_size(self):
        self.usecase.example_video()
        self.usecase.change_artwork(ARTWORK_BYTES, ARTWORK_MIMETYPE)
        await self.playing(title="dummy")

        # DMAP does not indicate dimensions of artwork, so -1 is returned here. In the
        # future, extracting dimensions from PNG header should be feasible.
        artwork = await self.atv.metadata.artwork(width=123, height=456)
        self.assertIsNotNone(artwork)
        self.assertEqual(artwork.width, -1)
        self.assertEqual(artwork.height, -1)

        # Verify that the specified width and height was requested
        self.assertEqual(self.state.last_artwork_width, 123)
        self.assertEqual(self.state.last_artwork_height, 456)

    @unittest_run_loop
    async def test_login_with_pairing_guid_succeed(self):
        self.atv.close()
        self.atv = await self.get_connected_device(PAIRING_GUID)
        await self.atv.connect()

    @unittest_run_loop
    async def test_connection_lost(self):
        self.usecase.server_closes_connection()

        device_listener = DummyDeviceListener()
        push_listener = DummyPushListener()
        self.atv.listener = device_listener
        self.atv.push_updater.listener = push_listener
        self.atv.push_updater.start()

        # Callback is scheduled on the event loop, so a semaphore is used
        # to synchronize with the loop
        await asyncio.wait_for(device_listener.lost_sem.acquire(), timeout=3.0)

    @unittest_run_loop
    async def test_button_unsupported_raises(self):
        buttons = ["home", "suspend", "wakeup"]
        for button in buttons:
            with self.assertRaises(exceptions.NotSupportedError):
                await getattr(self.atv.remote_control, button)()

    @unittest_run_loop
    async def test_button_top_menu(self):
        await self.atv.remote_control.top_menu()
        await self.wait_for_button_press("topmenu", InputAction.SingleTap)

    @unittest_run_loop
    async def test_button_play_pause(self):
        await self.atv.remote_control.play_pause()
        await until(lambda: self.state.last_button_pressed == "playpause")

    @unittest_run_loop
    async def test_shuffle_state_albums(self):
        # DMAP does not support "albums" as shuffle state, so it is
        # mapped to "songs"
        self.usecase.example_video(shuffle=ShuffleState.Albums)
        playing = await self.playing(shuffle=ShuffleState.Songs)
        self.assertEqual(playing.shuffle, ShuffleState.Songs)

    @unittest_run_loop
    async def test_set_shuffle_albums(self):
        self.usecase.example_video()

        # DMAP does not support "albums" as shuffle state, so it is
        # mapped to "songs"
        await self.atv.remote_control.set_shuffle(ShuffleState.Albums)
        playing = await self.playing(shuffle=ShuffleState.Songs)
        self.assertEqual(playing.shuffle, ShuffleState.Songs)

    @unittest_run_loop
    async def test_play_url_no_service(self):
        conf = AppleTV("127.0.0.1", "Apple TV")
        conf.add_service(self.dmap_service)

        atv = await connect(conf, self.loop)

        with self.assertRaises(exceptions.NotSupportedError):
            await atv.stream.play_url("http://123")

        atv.close()

    @unittest_run_loop
    async def test_unsupported_power_state(self):

        # Check if power state return PowerState.Unknown as expected
        self.assertEqual(self.atv.power.power_state, PowerState.Unknown)

        # Call turn_on and check for exception
        with self.assertRaises(exceptions.NotSupportedError):
            await self.atv.power.turn_on()

        # Call turn_off and check for exception
        with self.assertRaises(exceptions.NotSupportedError):
            await self.atv.power.turn_off()

    @unittest_run_loop
    async def test_basic_device_info(self):
        self.assertEqual(self.atv.device_info.operating_system,
                         OperatingSystem.Legacy)

    @unittest_run_loop
    async def test_always_available_features(self):
        self.assertFeatures(
            FeatureState.Available,
            FeatureName.Down,
            FeatureName.Left,
            FeatureName.Menu,
            FeatureName.Right,
            FeatureName.Select,
            FeatureName.TopMenu,
            FeatureName.Up,
        )

    @unittest_run_loop
    async def test_unsupported_features(self):
        self.assertFeatures(
            FeatureState.Unsupported,
            FeatureName.Home,
            FeatureName.HomeHold,
            FeatureName.Suspend,
            FeatureName.WakeUp,
            FeatureName.PowerState,
            FeatureName.TurnOn,
            FeatureName.TurnOff,
            FeatureName.App,
        )

    @unittest_run_loop
    async def test_always_unknown_features(self):
        self.assertFeatures(
            FeatureState.Unknown,
            FeatureName.Artwork,
            FeatureName.Next,
            FeatureName.Pause,
            FeatureName.Play,
            FeatureName.PlayPause,
            FeatureName.Previous,
            FeatureName.SetPosition,
            FeatureName.SetRepeat,
            FeatureName.SetShuffle,
            FeatureName.Stop,
            FeatureName.SkipForward,  # Depends on SetPosition
            FeatureName.SkipBackward,  # Depends on SetPosition
        )

    @unittest_run_loop
    async def test_features_shuffle_repeat(self):
        self.usecase.nothing_playing()
        await self.playing()

        self.assertFeatures(
            FeatureState.Unavailable,
            FeatureName.Shuffle,
            FeatureName.Repeat,
        )

        self.usecase.example_music(shuffle=ShuffleState.Albums,
                                   repeat=RepeatState.Track)
        await self.playing(title="music")

        self.assertFeatures(
            FeatureState.Available,
            FeatureName.Shuffle,
            FeatureName.Repeat,
        )

    @unittest_run_loop
    async def test_skip_forward_backward(self):
        self.usecase.example_video()

        prev_position = (await self.playing(title="dummy")).position

        await self.atv.remote_control.skip_forward()
        metadata = await self.playing()
        self.assertEqual(metadata.position, prev_position + SKIP_TIME)
        prev_position = metadata.position

        await self.atv.remote_control.skip_backward()
        metadata = await self.playing()
        self.assertEqual(metadata.position, prev_position - SKIP_TIME)

    @unittest_run_loop
    async def test_reset_revision_if_push_updates_fail(self):
        """Test that revision is reset when an error occurs during push update.

        This test sets up video as playing with revision 0. When the push updater starts,
        that video is returned to the listener. After that, the next update is fetched
        with revision 1. Since no media is configured for that revision, an error occurs
        where new video is configured for revision 0 again (now with "title2" as title),
        which is fetched and returned to the listener.
        """
        class PushListener:
            def __init__(self):
                self.playing = None

            def playstatus_update(self, updater, playstatus):
                _LOGGER.debug("Got playstatus: %s", playstatus)
                self.playing = playstatus

            @staticmethod
            def playstatus_error(updater, exception):
                _LOGGER.warning("Got error: %s", exception)

                # Set new content for revision 0 as an error should reset revision
                self.usecase.example_video(title="video2", revision=0)

        self.usecase.example_video(title="video1", revision=0)

        listener = PushListener()
        self.atv.push_updater.listener = listener
        self.atv.push_updater.start()

        await until(
            lambda: listener.playing and listener.playing.title == "video2")
        self.assertEqual(listener.playing.title, "video2")
Exemple #8
0
async def mrp_atv(event_loop):
    atv = FakeAppleTV(event_loop)
    atv.add_service(Protocol.MRP)
    await atv.start()
    yield atv
    await atv.stop()
Exemple #9
0
 async def get_application(self, loop=None):
     self.fake_atv = FakeAppleTV(self.loop)
     self.fake_atv.add_service(Protocol.MRP)
     self.state, self.usecase = self.fake_atv.add_service(Protocol.Companion)
     return self.fake_atv.app
Exemple #10
0
class MRPFunctionalTest(common_functional_tests.CommonFunctionalTests):
    async def setUpAsync(self):
        await super().setUpAsync()
        self.conf = AppleTV(IPv4Address("127.0.0.1"), "Test device")
        self.conf.add_service(
            MrpService("mrp_id", self.fake_atv.get_port(Protocol.MRP)))
        self.conf.add_service(
            AirPlayService("airplay_id", self.server.port, DEVICE_CREDENTIALS))
        self.atv = await self.get_connected_device()

    def tearDown(self):
        self.atv.close()
        super().tearDown()

    async def get_application(self, loop=None):
        self.fake_atv = FakeAppleTV(self.loop)
        self.state, self.usecase = self.fake_atv.add_service(Protocol.MRP)
        self.airplay_state, self.airplay_usecase = self.fake_atv.add_service(
            Protocol.AirPlay)
        return self.fake_atv.app

    async def get_connected_device(self):
        return await pyatv.connect(self.conf, loop=self.loop)

    @unittest_run_loop
    async def test_button_up_actions(self):
        await self.atv.remote_control.up(action=InputAction.DoubleTap)
        await self.wait_for_button_press("up", InputAction.DoubleTap)

        await self.atv.remote_control.up(action=InputAction.Hold)
        await self.wait_for_button_press("up", InputAction.Hold)

    @unittest_run_loop
    async def test_button_down_actions(self):
        await self.atv.remote_control.down(action=InputAction.DoubleTap)
        await self.wait_for_button_press("down", InputAction.DoubleTap)

        await self.atv.remote_control.down(action=InputAction.Hold)
        await self.wait_for_button_press("down", InputAction.Hold)

    @unittest_run_loop
    async def test_button_left_actions(self):
        await self.atv.remote_control.left(action=InputAction.DoubleTap)
        await self.wait_for_button_press("left", InputAction.DoubleTap)

        await self.atv.remote_control.left(action=InputAction.Hold)
        await self.wait_for_button_press("left", InputAction.Hold)

    @unittest_run_loop
    async def test_button_right_actions(self):
        await self.atv.remote_control.right(action=InputAction.DoubleTap)
        await self.wait_for_button_press("right", InputAction.DoubleTap)

        await self.atv.remote_control.right(action=InputAction.Hold)
        await self.wait_for_button_press("right", InputAction.Hold)

    @unittest_run_loop
    async def test_button_top_menu(self):
        await self.atv.remote_control.top_menu()
        await self.wait_for_button_press("top_menu", InputAction.SingleTap)

    @unittest_run_loop
    async def test_button_home(self):
        await self.atv.remote_control.home()
        await self.wait_for_button_press("home", InputAction.SingleTap)

        await self.atv.remote_control.home(action=InputAction.DoubleTap)
        await self.wait_for_button_press("home", InputAction.DoubleTap)

        await self.atv.remote_control.home(action=InputAction.Hold)
        await self.wait_for_button_press("home", InputAction.Hold)

    @unittest_run_loop
    async def test_button_select_actions(self):
        await self.atv.remote_control.select(action=InputAction.DoubleTap)
        await self.wait_for_button_press("select", InputAction.DoubleTap)

        await self.atv.remote_control.select(action=InputAction.Hold)
        await self.wait_for_button_press("select", InputAction.Hold)

    @unittest_run_loop
    async def test_button_menu_actions(self):
        await self.atv.remote_control.menu(action=InputAction.DoubleTap)
        await self.wait_for_button_press("menu", InputAction.DoubleTap)

        await self.atv.remote_control.menu(action=InputAction.Hold)
        await self.wait_for_button_press("menu", InputAction.Hold)

    @unittest_run_loop
    async def test_button_suspend(self):
        await self.atv.remote_control.suspend()
        await until(lambda: self.state.last_button_pressed == "suspend")

    @unittest_run_loop
    async def test_button_wakeup(self):
        await self.atv.remote_control.wakeup()
        await until(lambda: self.state.last_button_pressed == "wakeup")

    @unittest_run_loop
    async def test_shuffle_state_albums(self):
        self.usecase.example_video(shuffle=ShuffleState.Albums)
        playing = await self.playing(shuffle=ShuffleState.Albums)
        self.assertEqual(playing.shuffle, ShuffleState.Albums)

    @unittest_run_loop
    async def test_set_shuffle_albums(self):
        self.usecase.example_video()

        await self.atv.remote_control.set_shuffle(ShuffleState.Albums)
        playing = await self.playing(shuffle=ShuffleState.Albums)
        self.assertEqual(playing.shuffle, ShuffleState.Albums)

    @unittest_run_loop
    async def test_metadata_artwork_id(self):
        self.usecase.example_video()
        self.usecase.change_artwork(ARTWORK_BYTES, ARTWORK_MIMETYPE,
                                    ARTWORK_ID)

        await self.playing(title="dummy")
        self.assertEqual(self.atv.metadata.artwork_id, ARTWORK_ID)

    @unittest_run_loop
    async def test_metadata_artwork_id_no_identifier(self):
        self.usecase.example_video(identifier="some_id")
        self.usecase.change_artwork(ARTWORK_BYTES, ARTWORK_MIMETYPE, None)

        await self.playing(title="dummy")
        self.assertEqual(self.atv.metadata.artwork_id, "some_id")

    @unittest_run_loop
    async def test_metadata_artwork_width_and_height(self):
        self.usecase.example_video()
        self.usecase.change_artwork(ARTWORK_BYTES,
                                    ARTWORK_MIMETYPE,
                                    width=111,
                                    height=222)

        await self.playing(title="dummy")

        # Request one size but simulate that a smaller artwork was returned
        artwork = await self.atv.metadata.artwork(width=123, height=456)
        self.assertEqual(artwork.width, 111)
        self.assertEqual(artwork.height, 222)

    @unittest_run_loop
    async def test_item_updates(self):
        self.usecase.video_playing(False,
                                   "dummy",
                                   100,
                                   1,
                                   identifier="id",
                                   artist="some artist")

        with faketime("pyatv", 0):
            await self.playing(title="dummy")

            # Trigger update of single item by changing title
            self.usecase.change_metadata(title="foobar", identifier="id")
            playing = await self.playing(title="foobar")

            # Make sure other metadata is untouched
            self.assertEqual(playing.title, "foobar")
            self.assertEqual(playing.artist, "some artist")
            self.assertEqual(playing.total_time, 100)
            self.assertEqual(playing.position, 1)

    @unittest_run_loop
    async def test_item_id_hash(self):
        initial_hash = (await self.atv.metadata.playing()).hash

        # Verify thar content identifier is used as hash
        self.usecase.example_video(identifier="some_id")
        playing = await self.playing(title="dummy")
        self.assertEqual(playing.hash, "some_id")

        # Ensure that we fall back to initial hash if nothing is playing
        self.usecase.nothing_playing()
        nothing_playing = await self.playing(device_state=DeviceState.Idle)
        self.assertEqual(nothing_playing.hash, initial_hash)

    @unittest_run_loop
    async def test_metadata_playback_rate_device_state(self):
        self.usecase.example_video(paused=False, playback_rate=0.0)

        playing = await self.playing(title="dummy")
        self.assertEqual(playing.device_state, DeviceState.Paused)

        self.usecase.change_metadata(title="dummy2", playback_rate=1.0)
        playing = await self.playing(title="dummy2")
        self.assertEqual(playing.device_state, DeviceState.Playing)

        self.usecase.change_metadata(title="dummy3", playback_rate=0.0)
        playing = await self.playing(title="dummy3")
        self.assertEqual(playing.device_state, DeviceState.Paused)

    @unittest_run_loop
    async def test_power_state(self):
        class PowerListener:
            def __init__(self):
                self.old_state = None
                self.new_state = None

            def powerstate_update(self, old_state, new_state):
                self.old_state = old_state
                self.new_state = new_state

        listener = PowerListener()
        self.atv.power.listener = listener

        # Check initial power state during connect
        self.assertEqual(self.atv.power.power_state, PowerState.On)

        # Check if power state changes after turn_off command
        await self.atv.power.turn_off()
        assert math.isclose(stub_sleep(), 0.1)
        await until(lambda: self.atv.power.power_state == PowerState.Off)
        await until(lambda: listener.old_state == PowerState.On)
        await until(lambda: listener.new_state == PowerState.Off)

        # Check if power state changes after turn_on command
        await self.atv.power.turn_on()
        await until(lambda: self.atv.power.power_state == PowerState.On)
        await until(lambda: listener.old_state == PowerState.Off)
        await until(lambda: listener.new_state == PowerState.On)

    @unittest_run_loop
    async def test_power_state_acknowledgement(self):
        self.assertEqual(self.atv.power.power_state, PowerState.On)
        await self.atv.power.turn_off(await_new_state=True)
        self.assertEqual(self.atv.power.power_state, PowerState.Off)
        await self.atv.power.turn_on(await_new_state=True)
        self.assertEqual(self.atv.power.power_state, PowerState.On)

    @unittest_run_loop
    async def test_basic_device_info(self):
        self.assertEqual(self.atv.device_info.operating_system,
                         OperatingSystem.TvOS)

    @unittest_run_loop
    async def test_always_available_features(self):
        self.assertFeatures(
            FeatureState.Available,
            FeatureName.Down,
            FeatureName.Home,
            FeatureName.HomeHold,
            FeatureName.Left,
            FeatureName.Menu,
            FeatureName.Right,
            FeatureName.Select,
            FeatureName.TopMenu,
            FeatureName.Up,
            FeatureName.TurnOn,
            FeatureName.TurnOff,
            FeatureName.PowerState,
        )

    @unittest_run_loop
    async def test_features_artwork(self):
        self.assertFeatures(FeatureState.Unavailable, FeatureName.Artwork)

        self.usecase.example_video()
        self.usecase.change_artwork(ARTWORK_BYTES, ARTWORK_MIMETYPE,
                                    ARTWORK_ID)
        await self.playing(title="dummy")

        self.assertFeatures(FeatureState.Available, FeatureName.Artwork)

    @unittest_run_loop
    async def test_features_with_supported_commands(self):
        feature_map = {
            FeatureName.Next: CommandInfo_pb2.NextTrack,
            FeatureName.Pause: CommandInfo_pb2.Pause,
            FeatureName.Play: CommandInfo_pb2.Play,
            FeatureName.PlayPause: CommandInfo_pb2.TogglePlayPause,
            FeatureName.Previous: CommandInfo_pb2.PreviousTrack,
            FeatureName.Stop: CommandInfo_pb2.Stop,
            FeatureName.SetPosition: CommandInfo_pb2.SeekToPlaybackPosition,
            FeatureName.SetRepeat: CommandInfo_pb2.ChangeRepeatMode,
            FeatureName.SetShuffle: CommandInfo_pb2.ChangeShuffleMode,
            FeatureName.Shuffle: CommandInfo_pb2.ChangeShuffleMode,
            FeatureName.Repeat: CommandInfo_pb2.ChangeRepeatMode,
            FeatureName.SkipForward: CommandInfo_pb2.SkipForward,
            FeatureName.SkipBackward: CommandInfo_pb2.SkipBackward,
        }

        # No supported commands by default
        self.usecase.example_video()
        await self.playing(title="dummy")
        self.assertFeatures(FeatureState.Unavailable, *feature_map.keys())

        # Inject all expected commands to be enabled
        self.usecase.example_video(title="dummy2",
                                   supported_commands=list(
                                       feature_map.values()))
        await self.playing(title="dummy2")
        self.assertFeatures(FeatureState.Available, *feature_map.keys())

    @unittest_run_loop
    async def test_playing_app(self):
        self.usecase.nothing_playing()

        # Nothing playing => no app running
        self.assertIsNone(self.atv.metadata.app)
        self.assertEqual(
            self.atv.features.get_feature(FeatureName.App).state,
            FeatureState.Unavailable,
        )

        self.usecase.example_video()
        await self.playing(title="dummy")

        # Video playing with default app
        self.assertEqual(self.atv.metadata.app.name, APP_NAME)
        self.assertEqual(self.atv.metadata.app.identifier, PLAYER_IDENTIFIER)
        self.assertEqual(
            self.atv.features.get_feature(FeatureName.App).state,
            FeatureState.Available)

        # Change app display_name name
        self.usecase.update_client(display_name=DEMO_APP_NAME)
        self.usecase.change_metadata(title="dummy2")
        await self.playing(title="dummy2")
        self.assertEqual(self.atv.metadata.app.name, DEMO_APP_NAME)

        # Do not include display name and re-use previous one
        self.usecase.update_client(display_name=None)
        self.usecase.change_metadata(title="dummy3")
        await self.playing(title="dummy3")
        self.assertEqual(self.atv.metadata.app.name, DEMO_APP_NAME)

    @unittest_run_loop
    async def test_skip_forward_backward(self):
        self.usecase.example_video(
            supported_commands=[
                CommandInfo_pb2.SkipForward,
                CommandInfo_pb2.SkipBackward,
            ],
            skip_time=12,
        )

        # Get initial position and use as base
        prev_position = (await self.playing(title="dummy")).position

        await self.atv.remote_control.skip_forward()
        self.usecase.change_metadata(title="dummy2")
        metadata = await self.playing(title="dummy2")
        self.assertEqual(metadata.position, prev_position + 12)
        prev_position = metadata.position

        # Change skip time 8 to verify that we respect provided values
        self.usecase.change_state(title="dummy3", skip_time=8)
        metadata = await self.playing(title="dummy3")

        await self.atv.remote_control.skip_backward()
        self.usecase.change_metadata(title="dummy4")
        metadata = await self.playing(title="dummy4")
        self.assertEqual(metadata.position, prev_position - 8)

    @unittest_run_loop
    async def test_button_play_pause(self):
        self.usecase.example_video(
            supported_commands=[CommandInfo_pb2.TogglePlayPause])

        await self.playing(title="dummy")
        await self.atv.remote_control.play_pause()
        await until(lambda: self.state.last_button_pressed == "playpause")

    @unittest_run_loop
    async def test_play_pause_emulation(self):
        self.usecase.example_video(paused=False)
        await self.playing(device_state=DeviceState.Playing)
        self.assertFeatures(FeatureState.Unavailable, FeatureName.PlayPause)

        await self.atv.remote_control.play_pause()
        await until(lambda: self.state.last_button_pressed == "pause")

        self.usecase.example_video(
            paused=True,
            supported_commands=[CommandInfo_pb2.Play, CommandInfo_pb2.Pause],
        )
        await self.playing(device_state=DeviceState.Paused)
        self.assertFeatures(FeatureState.Available, FeatureName.PlayPause)

        await self.atv.remote_control.play_pause()
        await until(lambda: self.state.last_button_pressed == "play")

    @unittest_run_loop
    async def test_update_client_before_setstate(self):
        self.usecase.update_client(APP_NAME, TEST_PLAYER)
        self.usecase.example_video(title="test",
                                   player=TEST_PLAYER,
                                   app_name=None)

        await self.playing(title="test")
        self.assertEqual(self.atv.metadata.app.name, APP_NAME)
        self.assertEqual(self.atv.metadata.app.identifier, TEST_PLAYER)

    @unittest_run_loop
    async def test_set_default_commands(self):
        self.usecase.default_supported_commands(
            [CommandInfo_pb2.Play, CommandInfo_pb2.Pause])
        self.usecase.example_video()

        await self.playing(title="dummy")
        self.assertFeatures(FeatureState.Available, FeatureName.Play,
                            FeatureName.Pause)

    @unittest_run_loop
    async def test_playing_immutable_update_content_item(self):
        self.usecase.example_video(position=1)
        playing = await self.playing(title="dummy")

        self.usecase.change_metadata(position=100)
        await self.playing(position=100)

        self.assertEqual(playing.position, 1)
Exemple #11
0
class AirPlayPlayerTest(AioHTTPTestCase):
    async def setUpAsync(self):
        await AioHTTPTestCase.setUpAsync(self)

        # This is a hack that overrides asyncio.sleep to avoid making the test
        # slow. It also counts number of calls, since this is quite important
        # to the general function.
        player.asyncio.sleep = self.fake_asyncio_sleep
        self.no_of_sleeps = 0

        self.session = ClientSession()
        http = net.HttpSession(
            self.session, "http://127.0.0.1:{0}/".format(self.server.port))
        self.player = player.AirPlayPlayer(self.loop, http)

    async def tearDownAsync(self):
        await self.session.close()
        await AioHTTPTestCase.tearDownAsync(self)

    async def get_application(self, loop=None):
        self.fake_atv = FakeAppleTV(self.loop)
        self.state, self.usecase = self.fake_atv.add_service(Protocol.AirPlay)
        return self.fake_atv.app

    async def fake_asyncio_sleep(self, time, loop=None):
        self.no_of_sleeps += 1

    @unittest_run_loop
    async def test_play_video(self):
        self.usecase.airplay_playback_idle()
        self.usecase.airplay_playback_playing()
        self.usecase.airplay_playback_idle()

        await self.player.play_url(STREAM, position=START_POSITION)

        self.assertEqual(self.state.last_airplay_url, STREAM)
        self.assertEqual(self.state.last_airplay_start, START_POSITION)
        self.assertIsNotNone(self.state.last_airplay_uuid)
        self.assertEqual(self.no_of_sleeps, 2)  # playback + idle = 3

    @unittest_run_loop
    async def test_play_video_no_permission(self):
        self.usecase.airplay_playback_playing_no_permission()

        with self.assertRaises(exceptions.NoCredentialsError):
            await self.player.play_url(STREAM, position=START_POSITION)

    @unittest_run_loop
    async def test_play_with_retries(self):
        self.usecase.airplay_play_failure(2)
        self.usecase.airplay_playback_playing()
        self.usecase.airplay_playback_idle()

        await self.player.play_url(STREAM, position=START_POSITION)

        self.assertEqual(self.state.play_count, 3)  # Two retries + success

    @unittest_run_loop
    async def test_play_with_too_many_retries(self):
        self.usecase.airplay_play_failure(10)
        self.usecase.airplay_playback_playing()
        self.usecase.airplay_playback_idle()

        with self.assertRaises(exceptions.PlaybackError):
            await self.player.play_url(STREAM, position=START_POSITION)
Exemple #12
0
class CompanionAuthFunctionalTest(AioHTTPTestCase):
    async def setUpAsync(self):
        self.service = CompanionService(self.fake_atv.get_port(Protocol.Companion))
        self.conf = AppleTV(IPv4Address("127.0.0.1"), "Test device")
        self.conf.add_service(
            MrpService("mrp_id", self.fake_atv.get_port(Protocol.MRP))
        )
        self.conf.add_service(self.service)

    async def tearDownAsync(self):
        await self.handle.close()
        await super().tearDownAsync()

    async def get_application(self, loop=None):
        self.fake_atv = FakeAppleTV(self.loop)
        self.fake_atv.add_service(Protocol.MRP)
        self.state, self.usecase = self.fake_atv.add_service(Protocol.Companion)
        return self.fake_atv.app

    @unittest_run_loop
    async def test_pairing_with_device(self):
        self.handle = await pyatv.pair(self.conf, Protocol.Companion, self.loop)

        self.assertIsNone(self.service.credentials)
        self.assertTrue(self.handle.device_provides_pin)

        await self.handle.begin()
        self.handle.pin(PIN_CODE)

        await self.handle.finish()

        self.assertTrue(self.handle.has_paired)
        self.assertTrue(self.state.has_paired)
        self.assertIsNotNone(self.service.credentials)

    @unittest_run_loop
    async def test_pairing_with_existing_credentials(self):
        self.service.credentials = CLIENT_CREDENTIALS

        self.handle = await pyatv.pair(self.conf, Protocol.Companion, self.loop)

        self.assertFalse(self.handle.has_paired)
        self.assertIsNotNone(self.service.credentials)
        self.assertTrue(self.handle.device_provides_pin)

        await self.handle.begin()
        self.handle.pin(PIN_CODE)

        await self.handle.finish()

        self.assertTrue(self.handle.has_paired)
        self.assertTrue(self.state.has_paired)
        self.assertIsNotNone(self.service.credentials)

    @unittest_run_loop
    async def test_pairing_no_pin(self):
        self.handle = await pyatv.pair(self.conf, Protocol.Companion, self.loop)

        await self.handle.begin()
        with self.assertRaises(exceptions.PairingError):
            await self.handle.finish()

    @unittest_run_loop
    async def test_pairing_with_bad_pin(self):
        self.handle = await pyatv.pair(self.conf, Protocol.Companion, self.loop)

        self.assertIsNone(self.service.credentials)
        self.assertTrue(self.handle.device_provides_pin)

        await self.handle.begin()
        self.handle.pin(PIN_CODE + 1)

        with self.assertRaises(exceptions.PairingError):
            await self.handle.finish()

        self.assertFalse(self.handle.has_paired)
        self.assertFalse(self.state.has_paired)
        self.assertIsNone(self.service.credentials)
Exemple #13
0
 async def get_application(self, loop=None):
     self.fake_atv = FakeAppleTV(self.loop)
     self.fake_atv.add_service(Protocol.AirPlay)
     return self.fake_atv.app
class CompanionFunctionalTest(AioHTTPTestCase):
    async def setUpAsync(self):
        await super().setUpAsync()
        self.conf = AppleTV(IPv4Address("127.0.0.1"), "Test device")
        self.conf.add_service(
            MrpService("mrp_id", self.fake_atv.get_port(Protocol.MRP)))
        self.conf.add_service(
            CompanionService(
                self.fake_atv.get_port(Protocol.Companion),
                credentials=CLIENT_CREDENTIALS,
            ))
        self.atv = await self.get_connected_device()

    def tearDown(self):
        self.atv.close()
        super().tearDown()

    async def get_application(self, loop=None):
        self.fake_atv = FakeAppleTV(self.loop)
        self.fake_atv.add_service(Protocol.MRP)
        self.state, self.usecase = self.fake_atv.add_service(
            Protocol.Companion)
        return self.fake_atv.app

    async def get_connected_device(self):
        return await pyatv.connect(self.conf, loop=self.loop)

    @unittest_run_loop
    async def test_connect_only_companion(self):
        conf = AppleTV(IPv4Address("127.0.0.1"), "Test device")
        conf.add_service(
            CompanionService(self.fake_atv.get_port(Protocol.Companion)))

        with pytest.raises(exceptions.DeviceIdMissingError):
            await pyatv.connect(conf, loop=self.loop)

    @unittest_run_loop
    async def test_launch_app(self):
        await self.atv.apps.launch_app(TEST_APP)
        await until(lambda: self.state.active_app == TEST_APP)

    @unittest_run_loop
    async def test_app_list(self):
        self.usecase.set_installed_apps({
            TEST_APP: TEST_APP_NAME,
            TEST_APP2: TEST_APP_NAME2,
        })

        apps = await self.atv.apps.app_list()

        expected_apps = [
            App(TEST_APP_NAME, TEST_APP),
            App(TEST_APP_NAME2, TEST_APP2)
        ]
        assert not DeepDiff(expected_apps, apps)

    @unittest_run_loop
    async def test_features(self):
        assert (self.atv.features.get_feature(
            FeatureName.LaunchApp).state == FeatureState.Available)
        assert (self.atv.features.get_feature(
            FeatureName.AppList).state == FeatureState.Available)

    @unittest_run_loop
    async def test_power_functions(self):
        assert self.state.powered_on

        await self.atv.power.turn_off()
        assert not self.state.powered_on

        await self.atv.power.turn_on()
        assert self.state.powered_on

    @unittest_run_loop
    async def test_session_start(self):
        # All commands should trigger a session start, so just use one and verify
        assert self.state.sid == 0
        await self.atv.power.turn_off()
        assert self.state.sid != 0
        assert self.state.service_type == "com.apple.tvremoteservices"
Exemple #15
0
async def appstart(loop):  # pylint: disable=too-many-branches,too-many-statements
    """Script starts here."""
    parser = argparse.ArgumentParser()
    parser.add_argument("--local-ip", default="127.0.0.1", help="local IP address")
    parser.add_argument(
        "--demo", default=False, action="store_true", help="enable demo mode"
    )
    parser.add_argument(
        "-d", "--debug", default=False, action="store_true", help="enable debug logs"
    )

    protocols = parser.add_argument_group("protocols")
    protocols.add_argument(
        "--mrp", default=False, action="store_true", help="enable MRP protocol"
    )
    protocols.add_argument(
        "--dmap", default=False, action="store_true", help="enable DMAP protocol"
    )
    protocols.add_argument(
        "--airplay", default=False, action="store_true", help="enable AirPlay protocol"
    )
    protocols.add_argument(
        "--companion",
        default=False,
        action="store_true",
        help="enable Companion protocol",
    )
    protocols.add_argument(
        "--raop",
        default=False,
        action="store_true",
        help="enable RAOP protocol",
    )
    args = parser.parse_args()

    if not (args.mrp or args.dmap or args.airplay or args.companion or args.raop):
        parser.error("no protocol enabled (see --help)")

    level = logging.DEBUG if args.debug else logging.WARNING
    logging.basicConfig(
        level=level,
        stream=sys.stdout,
        datefmt="%Y-%m-%d %H:%M:%S",
        format="%(asctime)s %(levelname)s: %(message)s",
    )

    tasks = []
    unpublishers = []
    zconf = Zeroconf()
    fake_atv = FakeAppleTV(loop, test_mode=False)
    if args.mrp:
        _, usecase = fake_atv.add_service(Protocol.MRP)
        if args.demo:
            tasks.append(asyncio.ensure_future(_alter_playing(usecase)))

    if args.dmap:
        _, usecase = fake_atv.add_service(
            Protocol.DMAP, hsgid=HSGID, pairing_guid=PAIRING_GUID, session_id=SESSION_ID
        )
        if args.demo:
            tasks.append(asyncio.ensure_future(_alter_playing(usecase)))

    if args.airplay:
        fake_atv.add_service(Protocol.AirPlay)

    if args.companion:
        fake_atv.add_service(Protocol.Companion)

    if args.raop:
        fake_atv.add_service(Protocol.RAOP)

    await fake_atv.start()

    if args.mrp:
        unpublishers.append(
            await publish_mrp_zeroconf(
                loop, zconf, args.local_ip, fake_atv.get_port(Protocol.MRP)
            )
        )

    if args.dmap:
        unpublishers.append(
            await publish_dmap_zeroconf(
                loop, zconf, args.local_ip, fake_atv.get_port(Protocol.DMAP)
            )
        )

    if args.airplay:
        unpublishers.append(
            await publish_airplay_zeroconf(
                loop, zconf, args.local_ip, fake_atv.get_port(Protocol.AirPlay)
            )
        )

    if args.companion:
        unpublishers.append(
            await publish_companion_zeroconf(
                loop, zconf, args.local_ip, fake_atv.get_port(Protocol.Companion)
            )
        )

    if args.raop:
        unpublishers.append(
            await publish_raop_zeroconf(
                loop, zconf, args.local_ip, fake_atv.get_port(Protocol.RAOP)
            )
        )

    print("Press ENTER to quit")
    await loop.run_in_executor(None, sys.stdin.readline)

    await fake_atv.stop()

    for task in tasks:
        task.cancel()

    for unpublisher in unpublishers:
        await unpublisher()

    print("Exiting")

    return 0
Exemple #16
0
async def raop_device_fixture(event_loop):
    fake_atv = FakeAppleTV(event_loop, test_mode=False)
    fake_atv.add_service(Protocol.RAOP)
    await fake_atv.start()
    yield fake_atv
    await fake_atv.stop()
Exemple #17
0
class MrpAuthFunctionalTest(AioHTTPTestCase):
    def setUp(self):
        AioHTTPTestCase.setUp(self)

        self.service = MrpService(CLIENT_IDENTIFIER,
                                  self.fake_atv.get_port(Protocol.MRP))
        self.conf = AppleTV("127.0.0.1", "Apple TV")
        self.conf.add_service(self.service)

    async def tearDownAsync(self):
        if inspect.iscoroutinefunction(self.handle.close):
            await self.handle.close()
        else:
            self.handle.close()
        await super().tearDownAsync()

    async def get_application(self, loop=None):
        self.fake_atv = FakeAppleTV(self.loop)
        self.state, self.usecase = self.fake_atv.add_service(Protocol.MRP)
        return self.fake_atv.app

    @unittest_run_loop
    async def test_pairing_with_device(self):
        self.handle = await pyatv.pair(self.conf, Protocol.MRP, self.loop)

        self.assertIsNone(self.service.credentials)
        self.assertTrue(self.handle.device_provides_pin)

        await self.handle.begin()
        self.handle.pin(PIN_CODE)

        await self.handle.finish()

        self.assertTrue(self.handle.has_paired)
        self.assertTrue(self.state.has_paired)
        self.assertIsNotNone(self.service.credentials)

    @unittest_run_loop
    async def test_pairing_with_existing_credentials(self):
        self.service.credentials = CLIENT_CREDENTIALS

        self.handle = await pyatv.pair(self.conf, Protocol.MRP, self.loop)

        self.assertFalse(self.handle.has_paired)
        self.assertIsNotNone(self.service.credentials)
        self.assertTrue(self.handle.device_provides_pin)

        await self.handle.begin()
        self.handle.pin(PIN_CODE)

        await self.handle.finish()

        self.assertTrue(self.handle.has_paired)
        self.assertTrue(self.state.has_paired)
        self.assertIsNotNone(self.service.credentials)

    @unittest_run_loop
    async def test_pairing_with_bad_pin(self):
        self.handle = await pyatv.pair(self.conf, Protocol.MRP, self.loop)

        self.assertIsNone(self.service.credentials)
        self.assertTrue(self.handle.device_provides_pin)

        await self.handle.begin()
        self.handle.pin(PIN_CODE + 1)

        with self.assertRaises(exceptions.PairingError):
            await self.handle.finish()

        self.assertFalse(self.handle.has_paired)
        self.assertFalse(self.state.has_paired)
        self.assertIsNone(self.service.credentials)

    @unittest_run_loop
    async def test_authentication(self):
        self.service.credentials = CLIENT_CREDENTIALS

        self.handle = await pyatv.connect(self.conf, self.loop)

        self.assertTrue(self.state.has_authenticated)