plex-api 0.0.12

Library for communication with Plex server. Work in progress, not ready for any use. See github for details.
Documentation
mod fixtures;

mod offline {
    use super::fixtures::offline::{client::*, myplex::*, Mocked};
    use httpmock::Method::{DELETE, GET, POST};
    use plex_api::{
        url::{MYPLEX_SIGNIN_PATH, MYPLEX_SIGNOUT_PATH, MYPLEX_USER_INFO_PATH},
        Error, HttpClient, MyPlex, MyPlexBuilder,
    };

    #[plex_api_test_helper::offline_test]
    async fn signin_free_user(client_anonymous: Mocked<HttpClient>) {
        let (client_anonymous, mock_server) = client_anonymous.split();

        let m = mock_server.mock(|when, then| {
            when.method(POST)
                .path(MYPLEX_SIGNIN_PATH)
                .form_urlencoded_tuple("login", "username")
                .form_urlencoded_tuple("password", "password")
                .form_urlencoded_tuple("rememberMe", "true");
            then.status(200)
                .header("content-type", "text/json")
                .body_from_file("tests/mocks/myplex/api/v2/user/user_info_free.json");
        });

        let plex_result = MyPlexBuilder::default()
            .set_client(client_anonymous)
            .set_username_and_password("username", "password".to_string())
            .build()
            .await;
        m.assert();

        let plex = plex_result.expect("failed to login");

        assert_eq!(
            plex.client().x_plex_token(),
            "auth_token",
            "unexpected auth token"
        );
    }

    #[plex_api_test_helper::offline_test]
    async fn signin_plexpass_user(client_anonymous: Mocked<HttpClient>) {
        let (client_anonymous, mock_server) = client_anonymous.split();

        let m = mock_server.mock(|when, then| {
            when.method(POST)
                .path(MYPLEX_SIGNIN_PATH)
                .form_urlencoded_tuple("login", "username")
                .form_urlencoded_tuple("password", "password")
                .form_urlencoded_tuple("rememberMe", "true");
            then.status(200)
                .header("content-type", "text/json")
                .body_from_file("tests/mocks/myplex/api/v2/user/user_info_plexpass.json");
        });

        let plex_result = MyPlexBuilder::default()
            .set_client(client_anonymous)
            .set_username_and_password("username", "password".to_string())
            .build()
            .await;
        m.assert();

        let plex = plex_result.expect("failed to login");

        assert_eq!(
            plex.client().x_plex_token(),
            "auth_token",
            "unexpected auth token"
        );
    }

    #[plex_api_test_helper::offline_test]
    async fn signin_without_required_otp(client_anonymous: Mocked<HttpClient>) {
        let (client_anonymous, mock_server) = client_anonymous.split();

        let m = mock_server.mock(|when, then| {
            when.method(POST)
                .path(MYPLEX_SIGNIN_PATH)
                .form_urlencoded_tuple("login", "username")
                .form_urlencoded_tuple("password", "password")
                .form_urlencoded_tuple("rememberMe", "true");
            then.status(401)
                .header("content-type", "text/json")
                .body(r#"{"errors":[{"code": 1029, "message": "OTP required", "status": 401}]}"#);
        });

        let plex_result = MyPlexBuilder::default()
            .set_client(client_anonymous)
            .set_username_and_password("username", "password".to_string())
            .build()
            .await;
        m.assert();

        let err = plex_result.expect_err("error expected");

        assert!(matches!(err, Error::OtpRequired), "unexpected error");
    }

    #[plex_api_test_helper::offline_test]
    async fn signin_with_otp(client_anonymous: Mocked<HttpClient>) {
        let (client_anonymous, mock_server) = client_anonymous.split();

        let m = mock_server.mock(|when, then| {
            when.method(POST)
                .path(MYPLEX_SIGNIN_PATH)
                .form_urlencoded_tuple("login", "username")
                .form_urlencoded_tuple("password", "password")
                .form_urlencoded_tuple("rememberMe", "true")
                .form_urlencoded_tuple("verificationCode", "123456");
            then.status(200)
                .header("content-type", "text/json")
                .body_from_file("tests/mocks/myplex/api/v2/user/user_info_free.json");
        });

        let plex_result = MyPlexBuilder::default()
            .set_client(client_anonymous)
            .set_username_and_password("username", "password".to_string())
            .set_otp("123456".to_string())
            .build()
            .await;
        m.assert();

        let plex = plex_result.expect("failed to login");

        assert_eq!(
            plex.client().x_plex_token(),
            "auth_token",
            "unexpected auth token"
        );
    }

    #[plex_api_test_helper::offline_test]
    #[case::free_user("tests/mocks/myplex/api/v2/user/user_info_free.json")]
    #[case::free_user_managed_guest("tests/mocks/myplex/api/v2/user/user_info_free_guest.json")]
    #[case::plexpass_user("tests/mocks/myplex/api/v2/user/user_info_plexpass.json")]
    #[case::plexpass_user_managed_guest(
        "tests/mocks/myplex/api/v2/user/user_info_plexpass_guest.json"
    )]
    async fn signin_with_token(
        client_authenticated: Mocked<HttpClient>,
        #[case] mock_data_file: &str,
    ) {
        let (client_authenticated, mock_server) = client_authenticated.split();

        let m = mock_server.mock(|when, then| {
            when.method(GET)
                .path(MYPLEX_USER_INFO_PATH)
                .header("X-Plex-Token", "fixture_auth_token");
            then.status(200)
                .header("content-type", "text/json")
                .body_from_file(mock_data_file);
        });

        let plex_result = MyPlexBuilder::default()
            .set_client(client_authenticated)
            .build()
            .await;
        m.assert();

        let plex = plex_result.expect("failed to login");

        assert_eq!(
            plex.client().x_plex_token(),
            "auth_token",
            "unexpected auth token"
        );
    }

    #[plex_api_test_helper::offline_test]
    async fn signout(#[future] myplex: Mocked<MyPlex>) {
        let (myplex, mock_server) = myplex.split();

        let m = mock_server.mock(|when, then| {
            when.method(DELETE)
                .path(MYPLEX_SIGNOUT_PATH)
                .header("X-Plex-Token", "auth_token");
            then.status(204).header("content-type", "text/json");
        });

        let signout_result = myplex.signout().await;
        m.assert();

        signout_result.expect("failed to signout");
    }

    #[plex_api_test_helper::offline_test]
    async fn signin_failures(client_anonymous: Mocked<HttpClient>) {
        let (client_anonymous, _) = client_anonymous.split();

        let client2 = client_anonymous.clone();

        let plex_result = MyPlexBuilder::default()
            .set_client(client_anonymous)
            .set_username_and_password("username", "password".to_string())
            .build()
            .await;
        assert!(
            plex_result.is_err(),
            "MyPlex::login() with set auth_token should fail"
        );

        let client3 = client2.clone();

        let plex_result = MyPlexBuilder::default()
            .set_client(client2)
            .set_username_and_password("username", "password".to_string())
            .set_otp("123456".to_string())
            .build()
            .await;
        assert!(
            plex_result.is_err(),
            "MyPlex::login_with_otp() with set auth_token should fail"
        );

        let client3 = client3.set_x_plex_token("".to_owned());
        let plex_result = MyPlexBuilder::default().set_client(client3).build().await;
        assert!(
            plex_result.is_err(),
            "MyPlex::new() without set auth_token should fail"
        );
    }
}