From 2c1a03887512dc8f0a31e9842674dadeae25ce81 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 13 Jul 2021 11:55:59 +0200 Subject: [PATCH 01/22] First attempt at oauth tests fixing --- examples/oauth_tokens.rs | 46 +++++++++----- rspotify-model/src/album.rs | 4 +- rspotify-model/src/artist.rs | 2 +- rspotify-model/src/idtypes.rs | 6 +- src/clients/oauth.rs | 2 +- tests/test_with_oauth.rs | 111 ++++++++++++++++++---------------- 6 files changed, 97 insertions(+), 74 deletions(-) diff --git a/examples/oauth_tokens.rs b/examples/oauth_tokens.rs index 6a1baea3..8b49b504 100644 --- a/examples/oauth_tokens.rs +++ b/examples/oauth_tokens.rs @@ -7,6 +7,8 @@ use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; +use std::{io::{self, Write}, env}; + #[tokio::main] async fn main() { // You can use any logger for debugging. @@ -18,23 +20,23 @@ async fn main() { // Using every possible scope let scopes = scopes!( - "user-read-email", - "user-read-private", - "user-top-read", - "user-read-recently-played", - "user-follow-read", - "user-library-read", - "user-read-currently-playing", - "user-read-playback-state", - "user-read-playback-position", + "playlist-modify-private", + "playlist-modify-public", "playlist-read-collaborative", "playlist-read-private", + "ugc-image-upload", "user-follow-modify", + "user-follow-read", "user-library-modify", + "user-library-read", "user-modify-playback-state", - "playlist-modify-public", - "playlist-modify-private", - "ugc-image-upload" + "user-read-currently-playing", + "user-read-email", + "user-read-playback-position", + "user-read-playback-state", + "user-read-private", + "user-read-recently-played", + "user-top-read" ); let oauth = OAuth::from_env(scopes).unwrap(); @@ -44,6 +46,22 @@ async fn main() { spotify.prompt_for_token(&url).await.unwrap(); let token = spotify.token.as_ref().unwrap(); - println!("Access token: {}", &token.access_token); - println!("Refresh token: {}", token.refresh_token.as_ref().unwrap()); + let access_token = &token.access_token; + let refresh_token = token.refresh_token.as_ref().unwrap(); + println!("Access token: {}", access_token); + println!("Refresh token: {}", refresh_token); + + print!("Export to the environment? [y/N]: "); + io::stdout().flush().unwrap(); + + let stdin = io::stdin(); + let mut input = String::new(); + stdin.read_line(&mut input).unwrap(); + + let input = input.trim(); + if input == "" || input == "y" || input == "Y" { + env::set_var("RSPOTIFY_ACCESS_TOKEN", access_token); + env::set_var("RSPOTIFY_REFRESH_TOKEN", refresh_token); + println!("Exported RSPOTIFY_ACCESS_TOKEN and RSPOTIFY_REFRESH_TOKEN"); + } } diff --git a/rspotify-model/src/album.rs b/rspotify-model/src/album.rs index 1e45432b..39632fa4 100644 --- a/rspotify-model/src/album.rs +++ b/rspotify-model/src/album.rs @@ -42,7 +42,7 @@ pub struct SimplifiedAlbum { /// Full Album Object /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#object-albumobject) -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct FullAlbum { pub artists: Vec, pub album_type: AlbumType, @@ -83,7 +83,7 @@ pub struct PageSimpliedAlbums { /// Saved Album object /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#object-savedalbumobject) -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct SavedAlbum { pub added_at: DateTime, pub album: FullAlbum, diff --git a/rspotify-model/src/artist.rs b/rspotify-model/src/artist.rs index 2e4aa034..5dc62727 100644 --- a/rspotify-model/src/artist.rs +++ b/rspotify-model/src/artist.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; /// Simplified Artist Object /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#object-simplifiedartistobject) -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct SimplifiedArtist { pub external_urls: HashMap, pub href: Option, diff --git a/rspotify-model/src/idtypes.rs b/rspotify-model/src/idtypes.rs index a2f0622c..dc7f5459 100644 --- a/rspotify-model/src/idtypes.rs +++ b/rspotify-model/src/idtypes.rs @@ -26,7 +26,7 @@ pub trait PlayContextIdType: IdType {} macro_rules! sealed_types { ($($name:ident),+) => { $( - #[derive(Clone, Copy, Debug, PartialEq, Eq)] + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum $name {} impl private::Sealed for $name {} impl IdType for $name { @@ -65,7 +65,7 @@ pub type EpisodeIdBuf = IdBuf; /// /// This is a not-owning type, it stores a `&str` only. See /// [IdBuf](crate::idtypes::IdBuf) for owned version of the type. -#[derive(Debug, PartialEq, Eq, Serialize)] +#[derive(Debug, PartialEq, Eq, Serialize, Hash)] pub struct Id { #[serde(default)] _type: PhantomData, @@ -80,7 +80,7 @@ pub struct Id { /// /// Use `Id::from_id(val).to_owned()`, `Id::from_uri(val).to_owned()` or /// `Id::from_id_or_uri(val).to_owned()` to construct an instance of this type. -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash)] pub struct IdBuf { #[serde(default)] _type: PhantomData, diff --git a/src/clients/oauth.rs b/src/clients/oauth.rs index 997b5335..1fe6b869 100644 --- a/src/clients/oauth.rs +++ b/src/clients/oauth.rs @@ -433,7 +433,7 @@ pub trait OAuthClient: BaseClient { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-recently-played) async fn current_user_playing_track(&self) -> ClientResult> { let result = self - .get("me/player/currently-playing", None, &Query::new()) + .endpoint_get("me/player/currently-playing", &Query::new()) .await?; if result.is_empty() { Ok(None) diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 9bebee7f..3324708e 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -1,32 +1,36 @@ //! Most of tests currently require a Spotify Premium account where the tests //! can be ran, which will be ignored in the CI for now. The tests are written -//! so that no user data is modified. +//! so that no user data is modified (or, in case it fails, minimal changes are +//! done to the acount). //! //! You can run all of them with: //! -//! ``` -//! $ cargo test --features=cli,env-file -- --ignored -//! ``` +//! cargo test --features=cli,env-file -- --ignored //! //! The access token must be obtained previously, and this test file will try //! to authenticate with the access token from the `RSPOTIFY_ACCESS_TOKEN` -//! environment variable or the refresh token from `RSPOTIFY_REFRESH_TOKEN` -//! (these tokens must have been generated for all available scopes, see -//! the `oauth_tokens` example). +//! environment variable or the refresh token from `RSPOTIFY_REFRESH_TOKEN`. +//! These tokens must have been generated for all available scopes, for example +//! with the `oauth_tokens` example: +//! +//! cargo run --example oauth_tokens --features=env-file,cli use rspotify::{ model::{ - Country, EpisodeId, Id, Market, Offset, RepeatState, SearchType, ShowId, TimeRange, + Country, EpisodeId, AlbumId, Id, Market, Offset, RepeatState, SearchType, ShowId, TimeRange, TrackId, TrackPositions, }, prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth, Token, }; +use std::env; +use std::collections::HashSet; + use chrono::prelude::*; use maybe_async::maybe_async; use serde_json::map::Map; -use std::env; +use futures::stream::TryStreamExt; /// Generating a new OAuth client for the requests. #[maybe_async] @@ -51,23 +55,23 @@ pub async fn oauth_client() -> AuthCodeSpotify { }); let scopes = scopes!( - "user-read-email", - "user-read-private", - "user-top-read", - "user-read-recently-played", - "user-follow-read", - "user-library-read", - "user-read-currently-playing", - "user-read-playback-state", - "user-read-playback-position", + "playlist-modify-private", + "playlist-modify-public", "playlist-read-collaborative", "playlist-read-private", + "ugc-image-upload", "user-follow-modify", + "user-follow-read", "user-library-modify", + "user-library-read", "user-modify-playback-state", - "playlist-modify-public", - "playlist-modify-private", - "ugc-image-upload" + "user-read-currently-playing", + "user-read-email", + "user-read-playback-position", + "user-read-playback-state", + "user-read-private", + "user-read-recently-played", + "user-top-read" ); // Using every possible scope let oauth = OAuth::from_env(scopes).unwrap(); @@ -93,7 +97,7 @@ async fn test_categories() { None, Some(&Market::Country(Country::UnitedStates)), Some(10), - Some(0), + None, ) .await .unwrap(); @@ -108,7 +112,7 @@ async fn test_category_playlists() { "pop", Some(&Market::Country(Country::UnitedStates)), Some(10), - Some(0), + None, ) .await .unwrap(); @@ -176,40 +180,41 @@ async fn test_current_user_recently_played() { #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] -async fn test_current_user_saved_albums_add() { - let mut album_ids = vec![]; - let album_id1 = "6akEvsycLGftJxYudPjmqK"; - let album_id2 = "628oezqK2qfmCjC6eXNors"; - album_ids.push(Id::from_id(album_id2).unwrap()); - album_ids.push(Id::from_id(album_id1).unwrap()); - oauth_client() - .await - .current_user_saved_albums_add(album_ids) - .await - .unwrap(); -} +async fn test_current_user_saved_albums() { + let mut album_ids = HashSet::new(); + album_ids.insert(AlbumId::from_id("6akEvsycLGftJxYudPjmqK").unwrap()); + album_ids.insert(AlbumId::from_id("628oezqK2qfmCjC6eXNors").unwrap()); -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_current_user_saved_albums_delete() { - let mut album_ids = vec![]; - let album_id1 = "6akEvsycLGftJxYudPjmqK"; - let album_id2 = "628oezqK2qfmCjC6eXNors"; - album_ids.push(Id::from_id(album_id2).unwrap()); - album_ids.push(Id::from_id(album_id1).unwrap()); - oauth_client() - .await - .current_user_saved_albums_delete(album_ids) + let client = oauth_client().await; + + // First adding the albums + client + .current_user_saved_albums_add(album_ids.clone()) .await .unwrap(); -} -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_current_user_saved_albums() { - oauth_client() - .await - .current_user_saved_albums_manual(Some(10), Some(0)) + let all_albums = { + // Making sure the new albums appear + #[cfg(feature = "__async")] + { + client.current_user_saved_albums().try_collect::>().await.unwrap() + } + + #[cfg(feature = "__sync")] + { + client.current_user_saved_albums().collect::>() + } + }; + + let all_uris = all_albums + .into_iter() + .map(|a| AlbumId::from_uri(&a.album.uri).unwrap()) + .collect::>(); + assert!(album_ids.is_subset(&all_uris), "couldn't find the new saved albums"); + + // And then removing them + client + .current_user_saved_albums_delete(album_ids) .await .unwrap(); } From 2404190ae1aeca636a7d9d4b12adeb911c68b679 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Tue, 13 Jul 2021 12:26:44 +0200 Subject: [PATCH 02/22] Test for saved albums done --- rspotify-model/src/album.rs | 4 ++-- rspotify-model/src/artist.rs | 2 +- tests/test_with_oauth.rs | 23 +++++++++++------------ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/rspotify-model/src/album.rs b/rspotify-model/src/album.rs index 39632fa4..293597d4 100644 --- a/rspotify-model/src/album.rs +++ b/rspotify-model/src/album.rs @@ -42,7 +42,7 @@ pub struct SimplifiedAlbum { /// Full Album Object /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#object-albumobject) -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct FullAlbum { pub artists: Vec, pub album_type: AlbumType, @@ -83,7 +83,7 @@ pub struct PageSimpliedAlbums { /// Saved Album object /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#object-savedalbumobject) -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct SavedAlbum { pub added_at: DateTime, pub album: FullAlbum, diff --git a/rspotify-model/src/artist.rs b/rspotify-model/src/artist.rs index 5dc62727..2e4aa034 100644 --- a/rspotify-model/src/artist.rs +++ b/rspotify-model/src/artist.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; /// Simplified Artist Object /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#object-simplifiedartistobject) -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct SimplifiedArtist { pub external_urls: HashMap, pub href: Option, diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 3324708e..5fd7e4fa 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -25,12 +25,10 @@ use rspotify::{ }; use std::env; -use std::collections::HashSet; use chrono::prelude::*; use maybe_async::maybe_async; use serde_json::map::Map; -use futures::stream::TryStreamExt; /// Generating a new OAuth client for the requests. #[maybe_async] @@ -181,9 +179,10 @@ async fn test_current_user_recently_played() { #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_saved_albums() { - let mut album_ids = HashSet::new(); - album_ids.insert(AlbumId::from_id("6akEvsycLGftJxYudPjmqK").unwrap()); - album_ids.insert(AlbumId::from_id("628oezqK2qfmCjC6eXNors").unwrap()); + let album_ids = vec![ + AlbumId::from_id("6akEvsycLGftJxYudPjmqK").unwrap(), + AlbumId::from_id("628oezqK2qfmCjC6eXNors").unwrap(), + ]; let client = oauth_client().await; @@ -193,24 +192,24 @@ async fn test_current_user_saved_albums() { .await .unwrap(); + // Making sure the new albums appear let all_albums = { - // Making sure the new albums appear #[cfg(feature = "__async")] { - client.current_user_saved_albums().try_collect::>().await.unwrap() + use futures::stream::TryStreamExt; + client.current_user_saved_albums().try_collect::>().await.unwrap() } #[cfg(feature = "__sync")] { - client.current_user_saved_albums().collect::>() + client.current_user_saved_albums().filter_map(|a| a.ok()).collect::>() } }; - let all_uris = all_albums .into_iter() - .map(|a| AlbumId::from_uri(&a.album.uri).unwrap()) - .collect::>(); - assert!(album_ids.is_subset(&all_uris), "couldn't find the new saved albums"); + .map(|a| AlbumId::from_uri(&a.album.uri).unwrap().to_owned()) + .collect::>(); + assert!(album_ids.iter().all(|item| all_uris.contains(&(*item).to_owned())), "couldn't find the new saved albums"); // And then removing them client From ed46d2142d76eefa7732f3ea543b260a5e5918a6 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 20:49:57 +0200 Subject: [PATCH 03/22] playback tests, some fixes --- rspotify-model/src/enums/types.rs | 2 +- tests/test_with_oauth.rs | 146 +++++++++++++++--------------- 2 files changed, 76 insertions(+), 72 deletions(-) diff --git a/rspotify-model/src/enums/types.rs b/rspotify-model/src/enums/types.rs index df97d39a..98d48b8a 100644 --- a/rspotify-model/src/enums/types.rs +++ b/rspotify-model/src/enums/types.rs @@ -124,7 +124,7 @@ pub enum DeviceType { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#object-recommendationseedobject) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, AsRefStr)] -#[serde(rename_all = "snake_case")] +#[serde(rename_all = "lower_case")] pub enum RecommendationsSeedType { Artist, Track, diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 5fd7e4fa..0c1e33c0 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -18,7 +18,7 @@ use rspotify::{ model::{ Country, EpisodeId, AlbumId, Id, Market, Offset, RepeatState, SearchType, ShowId, TimeRange, - TrackId, TrackPositions, + TrackId, TrackPositions, PlayableItem }, prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth, Token, @@ -231,11 +231,7 @@ async fn test_current_user_saved_tracks_add() { .current_user_saved_tracks_add(tracks_ids) .await .unwrap(); -} -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_current_user_saved_tracks_contains() { let mut tracks_ids = vec![]; let track_id1 = "spotify:track:4iV5W9uYEdYUVa79Axb7Rh"; let track_id2 = "spotify:track:1301WleyT98MSxVHPZCA6M"; @@ -246,11 +242,13 @@ async fn test_current_user_saved_tracks_contains() { .current_user_saved_tracks_contains(tracks_ids) .await .unwrap(); -} -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_current_user_saved_tracks_delete() { + oauth_client() + .await + .current_user_saved_tracks_manual(Some(10), Some(0)) + .await + .unwrap(); + let mut tracks_ids = vec![]; let track_id1 = "spotify:track:4iV5W9uYEdYUVa79Axb7Rh"; let track_id2 = "spotify:track:1301WleyT98MSxVHPZCA6M"; @@ -263,16 +261,6 @@ async fn test_current_user_saved_tracks_delete() { .unwrap(); } -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_current_user_saved_tracks() { - oauth_client() - .await - .current_user_saved_tracks_manual(Some(10), Some(0)) - .await - .unwrap(); -} - #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_top_artists() { @@ -293,12 +281,6 @@ async fn test_current_user_top_tracks() { .unwrap(); } -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_device() { - oauth_client().await.device().await.unwrap(); -} - #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_featured_playlists() { @@ -338,45 +320,85 @@ async fn test_new_releases_with_from_token() { #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] -async fn test_next_playback() { - let device_id = "74ASZWbe4lXaubB36ztrGX"; - oauth_client() - .await - .next_track(Some(device_id)) - .await - .unwrap(); -} +async fn test_playback() { + let client = oauth_client().await; + let uris = vec![ + TrackId::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap(), + TrackId::from_uri("spotify:track:2DzSjFQKetFhkFCuDWhioi").unwrap(), + TrackId::from_uri("spotify:track:50CvpOXJuMiIGOx81Nq3Yx").unwrap(), + ]; + let devices = client.device().await.unwrap(); + + // Save current playback data to be restored later + // TODO: the backup restore will only start playing the song. If it's an + // album that was playing, its context will be lost. + let backup = client.current_playback(None, None::<&[_]>).await.unwrap(); + + for (i, device) in devices.iter().enumerate() { + let device_id = device.id.as_ref().unwrap(); + let next_device_id = devices.get(i + 1).unwrap_or(&devices[0]).id.as_ref().unwrap(); + + // Starting playback of some songs + client + .start_uris_playback(uris.clone(), Some(&device_id), Some(Offset::for_position(0)), None) + .await + .unwrap(); + + for _ in &uris { + client + .next_track(Some(&device_id)) + .await + .unwrap(); + + client + .pause_playback(Some(&device_id)) + .await + .unwrap(); + } -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_pause_playback() { - let device_id = "74ASZWbe4lXaubB36ztrGX"; - oauth_client() - .await - .pause_playback(Some(device_id)) - .await - .unwrap(); -} + for _ in &uris { + client + .previous_track(Some(&device_id)) + .await + .unwrap(); + } -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_previous_playback() { - let device_id = "74ASZWbe4lXaubB36ztrGX"; - oauth_client() - .await - .previous_track(Some(device_id)) - .await - .unwrap(); + client + .transfer_playback(&next_device_id, Some(true)) + .await + .unwrap(); + } + + // Restore the original playback data + if let Some(backup) = &backup { + let uri = backup.item + .as_ref() + .map(|b| match b { + PlayableItem::Track(t) => TrackId::from_uri(&t.uri).unwrap().to_owned(), + // TODO: use EpisodeId after https://github.com/ramsayleung/rspotify/issues/203 + PlayableItem::Episode(e) => TrackId::from_uri(&e.uri).unwrap().to_owned(), + }); + let offset = None; + let device = backup.device.id.as_deref(); + let position = backup.progress.map(|p| p.as_millis() as u32); + client.start_uris_playback(uri.as_deref(), device, offset, position).await.unwrap(); + } + // Pause the playback by default, unless it was playing before + if !backup.map(|b| b.is_playing).unwrap_or(false) { + client.pause_playback(None).await.unwrap(); + } } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_recommendations() { - let mut payload = Map::new(); let seed_artists = vec![Id::from_id("4NHQUGzhtTLFvgF5SZesLK").unwrap()]; let seed_tracks = vec![Id::from_id("0c6xIDDpzE81m2q797ordA").unwrap()]; + + let mut payload = Map::new(); payload.insert("min_energy".to_owned(), 0.4.into()); payload.insert("min_popularity".to_owned(), 50.into()); + oauth_client() .await .recommendations( @@ -478,27 +500,9 @@ async fn test_shuffle() { oauth_client().await.shuffle(true, None).await.unwrap(); } -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_start_playback() { - let device_id = "74ASZWbe4lXaubB36ztrGX"; - let uris = vec![TrackId::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap()]; - oauth_client() - .await - .start_uris_playback(uris, Some(device_id), Some(Offset::for_position(0)), None) - .await - .unwrap(); -} - #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_transfer_playback() { - let device_id = "74ASZWbe4lXaubB36ztrGX"; - oauth_client() - .await - .transfer_playback(device_id, Some(true)) - .await - .unwrap(); } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] From a250c8ad310b92d6f7921fb3e8a868e55dba6a8d Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 20:50:10 +0200 Subject: [PATCH 04/22] fix #225 --- rspotify-model/src/enums/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rspotify-model/src/enums/types.rs b/rspotify-model/src/enums/types.rs index 98d48b8a..ac4bec92 100644 --- a/rspotify-model/src/enums/types.rs +++ b/rspotify-model/src/enums/types.rs @@ -124,7 +124,7 @@ pub enum DeviceType { /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#object-recommendationseedobject) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, AsRefStr)] -#[serde(rename_all = "lower_case")] +#[serde(rename_all = "UPPERCASE")] pub enum RecommendationsSeedType { Artist, Track, From 750d9fe7389a0a2b6232fb8f723751d70596e55b Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 21:06:35 +0200 Subject: [PATCH 05/22] test_repeat --- tests/test_with_oauth.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 0c1e33c0..f7f33b7b 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -416,11 +416,19 @@ async fn test_recommendations() { #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_repeat() { - oauth_client() - .await - .repeat(&RepeatState::Context, None) + let client = oauth_client().await; + + // Saving the previous state to restore it later + let backup = client.current_playback(None, None::<&[_]>).await.unwrap(); + + client + .repeat(&RepeatState::Off, None) .await .unwrap(); + + if let Some(backup) = backup { + client.repeat(&backup.repeat_state, None).await.unwrap() + } } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] From 25ab6c215eb845e2ab1ae376a23bf83de7216bc7 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 22:55:33 +0200 Subject: [PATCH 06/22] playlist tests --- examples/oauth_tokens.rs | 5 +- rspotify-model/src/playlist.rs | 2 +- tests/test_with_oauth.rs | 455 ++++++++++++++++----------------- 3 files changed, 224 insertions(+), 238 deletions(-) diff --git a/examples/oauth_tokens.rs b/examples/oauth_tokens.rs index 8b49b504..89a7a529 100644 --- a/examples/oauth_tokens.rs +++ b/examples/oauth_tokens.rs @@ -7,7 +7,10 @@ use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; -use std::{io::{self, Write}, env}; +use std::{ + env, + io::{self, Write}, +}; #[tokio::main] async fn main() { diff --git a/rspotify-model/src/playlist.rs b/rspotify-model/src/playlist.rs index 0de6acf4..9cca9a70 100644 --- a/rspotify-model/src/playlist.rs +++ b/rspotify-model/src/playlist.rs @@ -52,7 +52,7 @@ pub struct SimplifiedPlaylist { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct FullPlaylist { pub collaborative: bool, - pub description: String, + pub description: Option, pub external_urls: HashMap, pub followers: Followers, pub href: String, diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index f7f33b7b..0975d08f 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -17,11 +17,12 @@ use rspotify::{ model::{ - Country, EpisodeId, AlbumId, Id, Market, Offset, RepeatState, SearchType, ShowId, TimeRange, - TrackId, TrackPositions, PlayableItem + AlbumId, Country, CurrentPlaybackContext, EpisodeId, Id, Market, Offset, PlayableItem, + RepeatState, SearchType, ShowId, TimeRange, TrackId, TrackPositions, FullPlaylist, PlaylistId }, prelude::*, - scopes, AuthCodeSpotify, Credentials, OAuth, Token, + scopes, AuthCodeSpotify, Credentials, OAuth, Token, ClientResult, + clients::pagination::Paginator }; use std::env; @@ -86,6 +87,22 @@ pub async fn oauth_client() -> AuthCodeSpotify { } } +#[maybe_async] +async fn fetch_all<'a, T>(paginator: Paginator<'a, ClientResult>) -> Vec +{ + #[cfg(feature = "__async")] + { + use futures::stream::TryStreamExt; + + paginator.try_collect::>().await.unwrap() + } + + #[cfg(feature = "__sync")] + { + paginator.filter_map(|a| a.ok()).collect::>() + } +} + #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_categories() { @@ -156,16 +173,6 @@ async fn test_current_user_playing_track() { .unwrap(); } -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_current_user_playlists() { - oauth_client() - .await - .current_user_playlists_manual(Some(10), None) - .await - .unwrap(); -} - #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_recently_played() { @@ -179,7 +186,7 @@ async fn test_current_user_recently_played() { #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_current_user_saved_albums() { - let album_ids = vec![ + let album_ids = [ AlbumId::from_id("6akEvsycLGftJxYudPjmqK").unwrap(), AlbumId::from_id("628oezqK2qfmCjC6eXNors").unwrap(), ]; @@ -188,28 +195,22 @@ async fn test_current_user_saved_albums() { // First adding the albums client - .current_user_saved_albums_add(album_ids.clone()) + .current_user_saved_albums_add(album_ids) .await .unwrap(); // Making sure the new albums appear - let all_albums = { - #[cfg(feature = "__async")] - { - use futures::stream::TryStreamExt; - client.current_user_saved_albums().try_collect::>().await.unwrap() - } - - #[cfg(feature = "__sync")] - { - client.current_user_saved_albums().filter_map(|a| a.ok()).collect::>() - } - }; + let all_albums = fetch_all(client.current_user_saved_albums()).await; let all_uris = all_albums .into_iter() .map(|a| AlbumId::from_uri(&a.album.uri).unwrap().to_owned()) .collect::>(); - assert!(album_ids.iter().all(|item| all_uris.contains(&(*item).to_owned())), "couldn't find the new saved albums"); + assert!( + album_ids + .iter() + .all(|item| all_uris.contains(&(*item).to_owned())), + "couldn't find the new saved albums" + ); // And then removing them client @@ -336,31 +337,32 @@ async fn test_playback() { for (i, device) in devices.iter().enumerate() { let device_id = device.id.as_ref().unwrap(); - let next_device_id = devices.get(i + 1).unwrap_or(&devices[0]).id.as_ref().unwrap(); + let next_device_id = devices + .get(i + 1) + .unwrap_or(&devices[0]) + .id + .as_ref() + .unwrap(); // Starting playback of some songs client - .start_uris_playback(uris.clone(), Some(&device_id), Some(Offset::for_position(0)), None) + .start_uris_playback( + uris.clone(), + Some(&device_id), + Some(Offset::for_position(0)), + None, + ) .await .unwrap(); for _ in &uris { - client - .next_track(Some(&device_id)) - .await - .unwrap(); - - client - .pause_playback(Some(&device_id)) - .await - .unwrap(); + client.next_track(Some(&device_id)).await.unwrap(); + + client.pause_playback(Some(&device_id)).await.unwrap(); } for _ in &uris { - client - .previous_track(Some(&device_id)) - .await - .unwrap(); + client.previous_track(Some(&device_id)).await.unwrap(); } client @@ -371,17 +373,18 @@ async fn test_playback() { // Restore the original playback data if let Some(backup) = &backup { - let uri = backup.item - .as_ref() - .map(|b| match b { - PlayableItem::Track(t) => TrackId::from_uri(&t.uri).unwrap().to_owned(), - // TODO: use EpisodeId after https://github.com/ramsayleung/rspotify/issues/203 - PlayableItem::Episode(e) => TrackId::from_uri(&e.uri).unwrap().to_owned(), - }); + let uri = backup.item.as_ref().map(|b| match b { + PlayableItem::Track(t) => TrackId::from_uri(&t.uri).unwrap().to_owned(), + // TODO: use EpisodeId after https://github.com/ramsayleung/rspotify/issues/203 + PlayableItem::Episode(e) => TrackId::from_uri(&e.uri).unwrap().to_owned(), + }); let offset = None; let device = backup.device.id.as_deref(); let position = backup.progress.map(|p| p.as_millis() as u32); - client.start_uris_playback(uri.as_deref(), device, offset, position).await.unwrap(); + client + .start_uris_playback(uri.as_deref(), device, offset, position) + .await + .unwrap(); } // Pause the playback by default, unless it was playing before if !backup.map(|b| b.is_playing).unwrap_or(false) { @@ -421,10 +424,7 @@ async fn test_repeat() { // Saving the previous state to restore it later let backup = client.current_playback(None, None::<&[_]>).await.unwrap(); - client - .repeat(&RepeatState::Off, None) - .await - .unwrap(); + client.repeat(&RepeatState::Off, None).await.unwrap(); if let Some(backup) = backup { client.repeat(&backup.repeat_state, None).await.unwrap() @@ -499,256 +499,239 @@ async fn test_search_track() { #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_seek_track() { - oauth_client().await.seek_track(25000, None).await.unwrap(); + let client = oauth_client().await; + + // Saving the previous state to restore it later + let backup = client.current_playback(None, None::<&[_]>).await.unwrap(); + + client.seek_track(25000, None).await.unwrap(); + + if let Some(CurrentPlaybackContext { + progress: Some(progress), + .. + }) = backup + { + client + .seek_track(progress.as_millis() as u32, None) + .await + .unwrap(); + } } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_shuffle() { - oauth_client().await.shuffle(true, None).await.unwrap(); -} + let client = oauth_client().await; -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_transfer_playback() { + // Saving the previous state to restore it later + let backup = client.current_playback(None, None::<&[_]>).await.unwrap(); + + client.shuffle(true, None).await.unwrap(); + + if let Some(backup) = backup { + client.shuffle(backup.shuffle_state, None).await.unwrap(); + } } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_user_follow_artist() { - let mut artists = vec![]; - let artist_id1 = "74ASZWbe4lXaubB36ztrGX"; - let artist_id2 = "08td7MxkoHQkXnWAYD8d6Q"; - artists.push(Id::from_id(artist_id2).unwrap()); - artists.push(Id::from_id(artist_id1).unwrap()); - oauth_client() - .await - .user_follow_artists(artists) - .await - .unwrap(); -} + let client = oauth_client().await; + let artists = [ + Id::from_id("74ASZWbe4lXaubB36ztrGX").unwrap(), + Id::from_id("08td7MxkoHQkXnWAYD8d6Q").unwrap(), + ]; -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_user_unfollow_artist() { - let mut artists = vec![]; - let artist_id1 = "74ASZWbe4lXaubB36ztrGX"; - let artist_id2 = "08td7MxkoHQkXnWAYD8d6Q"; - artists.push(Id::from_id(artist_id2).unwrap()); - artists.push(Id::from_id(artist_id1).unwrap()); - oauth_client() - .await - .user_unfollow_artists(artists) - .await - .unwrap(); + client.user_follow_artists(artists).await.unwrap(); + client.user_unfollow_artists(artists).await.unwrap(); } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_user_follow_users() { - let mut users = vec![]; - let user_id1 = Id::from_id("exampleuser01").unwrap(); - users.push(user_id1); - oauth_client().await.user_follow_users(users).await.unwrap(); + let client = oauth_client().await; + let users = [ + Id::from_id("exampleuser01").unwrap(), + Id::from_id("john").unwrap() + ]; + + client.user_follow_users(users).await.unwrap(); + client.user_unfollow_users(users).await.unwrap(); } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] -async fn test_user_unfollow_users() { - let mut users = vec![]; - let user_id1 = Id::from_id("exampleuser01").unwrap(); - users.push(user_id1); - oauth_client() - .await - .user_unfollow_users(users) +async fn test_user_follow_playlist() { + let client = oauth_client().await; + let playlist_id = Id::from_id("2v3iNvBX8Ay1Gt2uXtUKUT").unwrap(); + + client + .playlist_follow(playlist_id, Some(true)) .await .unwrap(); -} -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_playlist_add_tracks() { - let playlist_id = Id::from_id("5jAOgWXCBKuinsGiZxjDQ5").unwrap(); - let mut tracks_ids = vec![]; - let track_id1 = Id::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap(); - tracks_ids.push(track_id1); - let track_id2 = Id::from_uri("spotify:track:1301WleyT98MSxVHPZCA6M").unwrap(); - tracks_ids.push(track_id2); - oauth_client() - .await - .playlist_add_tracks(playlist_id, tracks_ids, None) + // TODO: https://github.com/ramsayleung/rspotify/issues/227 + client + .playlist_unfollow(&playlist_id.uri()) .await .unwrap(); } -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_playlist_change_detail() { - let playlist_id = "5jAOgWXCBKuinsGiZxjDQ5"; - let playlist_name = "A New Playlist-update"; - oauth_client() - .await - .playlist_change_detail(playlist_id, Some(playlist_name), Some(false), None, None) +#[maybe_async] +async fn check_playlist_create(client: &AuthCodeSpotify) -> FullPlaylist { + let user_id = client.me().await.unwrap().id; + let user_id = Id::from_id(&user_id).unwrap(); + let name = "A New Playlist"; + + // First creating the base playlist over which the tests will be ran + let playlist = client + .user_playlist_create(user_id, name, Some(false), None, None) .await .unwrap(); -} + let playlist_id = Id::from_id(&playlist.id).unwrap(); -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_playlist_check_follow() { - let playlist_id = Id::from_id("2v3iNvBX8Ay1Gt2uXtUKUT").unwrap(); - let mut user_ids: Vec<_> = vec![]; - let user_id1 = Id::from_id("possan").unwrap(); - user_ids.push(user_id1); - let user_id2 = Id::from_id("elogain").unwrap(); - user_ids.push(user_id2); - oauth_client() - .await - .playlist_check_follow(playlist_id, &user_ids) + // Making sure that the playlist has been added to the user's profile + let fetched_playlist = client + .user_playlist(user_id, Some(playlist_id), None) .await .unwrap(); -} + assert_eq!(playlist.id, fetched_playlist.id); + let user_playlists = fetch_all(client.user_playlists(user_id)).await; + let current_user_playlists = fetch_all(client.current_user_playlists()).await; + assert_eq!(user_playlists.len(), current_user_playlists.len()); -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_user_playlist_create() { - let user_id = Id::from_id("2257tjys2e2u2ygfke42niy2q").unwrap(); - let playlist_name = "A New Playlist"; - oauth_client() - .await - .user_playlist_create(user_id, playlist_name, Some(false), None, None) + // Modifying the playlist details + let name = "A New Playlist-update"; + let description = "A random description"; + // TODO: https://github.com/ramsayleung/rspotify/issues/227 + client + .playlist_change_detail(&playlist.id, Some(name), Some(true), Some(description), Some(false)) .await .unwrap(); + + playlist } -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_playlist_follow_playlist() { - let playlist_id = Id::from_id("2v3iNvBX8Ay1Gt2uXtUKUT").unwrap(); - oauth_client() - .await - .playlist_follow(playlist_id, Some(true)) - .await - .unwrap(); + +#[maybe_async] +async fn check_num_tracks(client: &AuthCodeSpotify, playlist_id: &PlaylistId, num: i32) { + let fetched_tracks = fetch_all(client.playlist_tracks(playlist_id, None, None)).await; + assert_eq!(fetched_tracks.len() as i32, num); } -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_playlist_recorder_tracks() { - let uris = Some(vec![EpisodeId::from_id("0lbiy3LKzIY2fnyjioC11p").unwrap()]); - let playlist_id = Id::from_id("5jAOgWXCBKuinsGiZxjDQ5").unwrap(); - let range_start = 0; - let insert_before = 1; - let range_length = 1; - oauth_client() +#[maybe_async] +async fn check_playlist_tracks(client: &AuthCodeSpotify, playlist: &FullPlaylist) { + let playlist_id = Id::from_id(&playlist.id).unwrap(); + // The tracks in the playlist, some of them repeated + // TODO: include episodes after https://github.com/ramsayleung/rspotify/issues/203 + let tracks = [ + TrackId::from_uri("spotify:track:5iKndSu1XI74U2OZePzP8L").unwrap(), + TrackId::from_uri("spotify:track:5iKndSu1XI74U2OZePzP8L").unwrap(), + TrackId::from_uri("spotify:track:3dSB2y7Loc4q7DKtOpE8vR").unwrap(), + TrackId::from_uri("spotify:track:2xG86EOAqAk0SEg1GmUCka").unwrap(), + ]; + + // Firstly adding some tracks + client + .playlist_add_tracks(playlist_id, tracks, None) .await + .unwrap(); + check_num_tracks(client, playlist_id, tracks.len() as i32).await; + + // Reordering some tracks + client .playlist_reorder_tracks( playlist_id, - uris, - Some(range_start), - Some(insert_before), - Some(range_length), + None::>, + Some(0), + Some(3), + Some(2), None, ) .await .unwrap(); -} + // Making sure the number of tracks is the same + check_num_tracks(client, playlist_id, tracks.len() as i32).await; -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_playlist_remove_all_occurrences_of_tracks() { - let playlist_id = Id::from_id("5jAOgWXCBKuinsGiZxjDQ5").unwrap(); - let mut tracks_ids = vec![]; - let track_id1 = Id::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap(); - let track_id2 = Id::from_uri("spotify:track:1301WleyT98MSxVHPZCA6M").unwrap(); - tracks_ids.push(track_id2); - tracks_ids.push(track_id1); - oauth_client() - .await - .playlist_remove_all_occurrences_of_tracks(playlist_id, tracks_ids, None) + // Replacing the tracks + // TODO: include episodes after https://github.com/ramsayleung/rspotify/issues/203 + let replaced_tracks = [ + TrackId::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap(), + TrackId::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap(), + TrackId::from_uri("spotify:track:1301WleyT98MSxVHPZCA6M").unwrap(), + TrackId::from_uri("spotify:track:0b18g3G5spr4ZCkz7Y6Q0Q").unwrap(), + TrackId::from_uri("spotify:track:5m2en2ndANCPembKOYr1xL").unwrap(), + TrackId::from_uri("spotify:track:5m2en2ndANCPembKOYr1xL").unwrap(), + TrackId::from_uri("spotify:track:5m2en2ndANCPembKOYr1xL").unwrap(), + ]; + client + .playlist_replace_tracks( + playlist_id, + replaced_tracks, + ) .await .unwrap(); -} + // Making sure the number of tracks is updated + check_num_tracks(client, playlist_id, replaced_tracks.len() as i32).await; -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_playlist_remove_specific_occurrences_of_tracks() { - let playlist_id = Id::from_id("5jAOgWXCBKuinsGiZxjDQ5").unwrap(); - let track_position_0_3 = TrackPositions::new( + // Removes a few specific tracks + let tracks = [TrackPositions::new( Id::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap(), - vec![0, 3], - ); - let track_position_7 = TrackPositions::new( - Id::from_uri("spotify:track:1301WleyT98MSxVHPZCA6M").unwrap(), - vec![7], - ); - let tracks = vec![&track_position_0_3, &track_position_7]; - oauth_client() - .await - .playlist_remove_specific_occurrences_of_tracks(playlist_id, tracks, None::<&str>) + vec![0], + ), TrackPositions::new( + Id::from_uri("spotify:track:5m2en2ndANCPembKOYr1xL").unwrap(), + vec![4, 6], + )]; + client + .playlist_remove_specific_occurrences_of_tracks(playlist_id, tracks.as_ref(), None) .await .unwrap(); -} + // Making sure three tracks were removed + check_num_tracks(client, playlist_id, replaced_tracks.len() as i32 - 3).await; -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_playlist_replace_tracks() { - let playlist_id = Id::from_id("5jAOgWXCBKuinsGiZxjDQ5").unwrap(); - let mut tracks_ids = vec![]; - let track_id1 = Id::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap(); - let track_id2 = Id::from_uri("spotify:track:1301WleyT98MSxVHPZCA6M").unwrap(); - tracks_ids.push(track_id2); - tracks_ids.push(track_id1); - oauth_client() - .await - .playlist_replace_tracks(playlist_id, tracks_ids) + // Removes all occurrences of two tracks + let to_remove = [ + Id::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap(), + Id::from_uri("spotify:track:1301WleyT98MSxVHPZCA6M").unwrap(), + ]; + client + .playlist_remove_all_occurrences_of_tracks(playlist_id, to_remove, None) .await .unwrap(); + // Making sure two more tracks were removed + check_num_tracks(client, playlist_id, replaced_tracks.len() as i32 - 5).await; } -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_user_playlist() { - let user_id = Id::from_id("spotify").unwrap(); - let playlist_id = Id::from_id("59ZbFPES4DQwEjBpWHzrtC").unwrap(); - oauth_client() - .await - .user_playlist(user_id, Some(playlist_id), None) - .await - .unwrap(); -} +#[maybe_async] +async fn check_playlist_follow(client: &AuthCodeSpotify, playlist: &FullPlaylist) { + let playlist_id = Id::from_id(&playlist.id).unwrap(); + let user_ids = [ + Id::from_id("possan").unwrap(), + Id::from_id("elogain").unwrap(), + ]; -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_user_playlists() { - let user_id = Id::from_id("2257tjys2e2u2ygfke42niy2q").unwrap(); - oauth_client() - .await - .user_playlists_manual(user_id, Some(10), None) + // It's a new playlist, so it shouldn't have any followers + let following = client + .playlist_check_follow(playlist_id, &user_ids) .await .unwrap(); -} + assert_eq!(following, vec![false, false]); -#[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] -#[ignore] -async fn test_playlist_tracks() { - let playlist_id = Id::from_uri("spotify:playlist:59ZbFPES4DQwEjBpWHzrtC").unwrap(); - oauth_client() - .await - .playlist_tracks_manual(playlist_id, None, None, Some(2), None) + // Finally unfollowing the playlist in order to clean it up + client + .playlist_unfollow(&playlist.id) .await .unwrap(); } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] -async fn test_playlist_unfollow() { - let playlist_id = "65V6djkcVRyOStLd8nza8E"; - oauth_client() - .await - .playlist_unfollow(playlist_id) - .await - .unwrap(); +async fn test_playlist() { + let client = oauth_client().await; + + let playlist = check_playlist_create(&client).await; + check_playlist_tracks(&client, &playlist).await; + check_playlist_follow(&client, &playlist).await; } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] From d49d23fb98e08e8ce68336d547e251d89856ce66 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 23:02:39 +0200 Subject: [PATCH 07/22] clean up the rest of tests --- tests/test_with_oauth.rs | 100 ++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 0975d08f..f89220eb 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -16,13 +16,14 @@ //! cargo run --example oauth_tokens --features=env-file,cli use rspotify::{ + clients::pagination::Paginator, model::{ - AlbumId, Country, CurrentPlaybackContext, EpisodeId, Id, Market, Offset, PlayableItem, - RepeatState, SearchType, ShowId, TimeRange, TrackId, TrackPositions, FullPlaylist, PlaylistId + AlbumId, Country, CurrentPlaybackContext, Device, EpisodeId, FullPlaylist, Id, Market, + Offset, PlayableItem, PlaylistId, RepeatState, SearchType, ShowId, TimeRange, TrackId, + TrackPositions, }, prelude::*, - scopes, AuthCodeSpotify, Credentials, OAuth, Token, ClientResult, - clients::pagination::Paginator + scopes, AuthCodeSpotify, ClientResult, Credentials, OAuth, Token, }; use std::env; @@ -88,8 +89,7 @@ pub async fn oauth_client() -> AuthCodeSpotify { } #[maybe_async] -async fn fetch_all<'a, T>(paginator: Paginator<'a, ClientResult>) -> Vec -{ +async fn fetch_all<'a, T>(paginator: Paginator<'a, ClientResult>) -> Vec { #[cfg(feature = "__async")] { use futures::stream::TryStreamExt; @@ -552,7 +552,7 @@ async fn test_user_follow_users() { let client = oauth_client().await; let users = [ Id::from_id("exampleuser01").unwrap(), - Id::from_id("john").unwrap() + Id::from_id("john").unwrap(), ]; client.user_follow_users(users).await.unwrap(); @@ -571,10 +571,7 @@ async fn test_user_follow_playlist() { .unwrap(); // TODO: https://github.com/ramsayleung/rspotify/issues/227 - client - .playlist_unfollow(&playlist_id.uri()) - .await - .unwrap(); + client.playlist_unfollow(&playlist_id.uri()).await.unwrap(); } #[maybe_async] @@ -605,14 +602,19 @@ async fn check_playlist_create(client: &AuthCodeSpotify) -> FullPlaylist { let description = "A random description"; // TODO: https://github.com/ramsayleung/rspotify/issues/227 client - .playlist_change_detail(&playlist.id, Some(name), Some(true), Some(description), Some(false)) + .playlist_change_detail( + &playlist.id, + Some(name), + Some(true), + Some(description), + Some(false), + ) .await .unwrap(); playlist } - #[maybe_async] async fn check_num_tracks(client: &AuthCodeSpotify, playlist_id: &PlaylistId, num: i32) { let fetched_tracks = fetch_all(client.playlist_tracks(playlist_id, None, None)).await; @@ -665,23 +667,23 @@ async fn check_playlist_tracks(client: &AuthCodeSpotify, playlist: &FullPlaylist TrackId::from_uri("spotify:track:5m2en2ndANCPembKOYr1xL").unwrap(), ]; client - .playlist_replace_tracks( - playlist_id, - replaced_tracks, - ) + .playlist_replace_tracks(playlist_id, replaced_tracks) .await .unwrap(); // Making sure the number of tracks is updated check_num_tracks(client, playlist_id, replaced_tracks.len() as i32).await; // Removes a few specific tracks - let tracks = [TrackPositions::new( - Id::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap(), - vec![0], - ), TrackPositions::new( - Id::from_uri("spotify:track:5m2en2ndANCPembKOYr1xL").unwrap(), - vec![4, 6], - )]; + let tracks = [ + TrackPositions::new( + Id::from_uri("spotify:track:4iV5W9uYEdYUVa79Axb7Rh").unwrap(), + vec![0], + ), + TrackPositions::new( + Id::from_uri("spotify:track:5m2en2ndANCPembKOYr1xL").unwrap(), + vec![4, 6], + ), + ]; client .playlist_remove_specific_occurrences_of_tracks(playlist_id, tracks.as_ref(), None) .await @@ -718,10 +720,7 @@ async fn check_playlist_follow(client: &AuthCodeSpotify, playlist: &FullPlaylist assert_eq!(following, vec![false, false]); // Finally unfollowing the playlist in order to clean it up - client - .playlist_unfollow(&playlist.id) - .await - .unwrap(); + client.playlist_unfollow(&playlist.id).await.unwrap(); } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] @@ -737,12 +736,30 @@ async fn test_playlist() { #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_volume() { - oauth_client().await.volume(78, None).await.unwrap(); + let client = oauth_client().await; + + // Saving the previous state to restore it later + let backup = client.current_playback(None, None::<&[_]>).await.unwrap(); + + client.volume(78, None).await.unwrap(); + + if let Some(CurrentPlaybackContext { + device: Device { + volume_percent: Some(volume), + .. + }, + .. + }) = backup + { + client.volume(volume as u8, None).await.unwrap(); + } } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_add_queue() { + // NOTE: unfortunately it's impossible to revert this test + let birdy_uri = TrackId::from_uri("spotify:track:6rqhFgbbKwnb9MLmUQDhG6").unwrap(); oauth_client() .await @@ -754,15 +771,14 @@ async fn test_add_queue() { #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_get_several_shows() { + let shows = [ + ShowId::from_id("5CfCWKI5pZ28U0uOzXkDHe").unwrap(), + ShowId::from_id("5as3aKmN2k11yfDDDSrvaZ").unwrap(), + ]; + oauth_client() .await - .get_several_shows( - vec![ - ShowId::from_id("5CfCWKI5pZ28U0uOzXkDHe").unwrap(), - ShowId::from_id("5as3aKmN2k11yfDDDSrvaZ").unwrap(), - ], - None, - ) + .get_several_shows(shows, None) .await .unwrap(); } @@ -770,15 +786,13 @@ async fn test_get_several_shows() { #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] #[ignore] async fn test_get_several_episodes() { + let episodes = [ + EpisodeId::from_id("0lbiy3LKzIY2fnyjioC11p").unwrap(), + EpisodeId::from_id("4zugY5eJisugQj9rj8TYuh").unwrap(), + ]; oauth_client() .await - .get_several_episodes( - vec![ - EpisodeId::from_id("0lbiy3LKzIY2fnyjioC11p").unwrap(), - EpisodeId::from_id("4zugY5eJisugQj9rj8TYuh").unwrap(), - ], - None, - ) + .get_several_episodes(episodes, None) .await .unwrap(); } From e6ef9256318b979614ffaa0535dd8a5745e304d5 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 23:03:32 +0200 Subject: [PATCH 08/22] clarify TODO --- tests/test_with_oauth.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index f89220eb..368e0e83 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -331,8 +331,8 @@ async fn test_playback() { let devices = client.device().await.unwrap(); // Save current playback data to be restored later - // TODO: the backup restore will only start playing the song. If it's an - // album that was playing, its context will be lost. + // NOTE: unfortunately it's impossible to revert the entire queue, this will + // just restore the song playing at the moment. let backup = client.current_playback(None, None::<&[_]>).await.unwrap(); for (i, device) in devices.iter().enumerate() { From 94ac8cee8d861646f319f3f954137b048a88a117 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 23:06:31 +0200 Subject: [PATCH 09/22] Revert changes to example --- examples/oauth_tokens.rs | 49 ++++++++++++---------------------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/examples/oauth_tokens.rs b/examples/oauth_tokens.rs index 89a7a529..6a1baea3 100644 --- a/examples/oauth_tokens.rs +++ b/examples/oauth_tokens.rs @@ -7,11 +7,6 @@ use rspotify::{prelude::*, scopes, AuthCodeSpotify, Credentials, OAuth}; -use std::{ - env, - io::{self, Write}, -}; - #[tokio::main] async fn main() { // You can use any logger for debugging. @@ -23,23 +18,23 @@ async fn main() { // Using every possible scope let scopes = scopes!( - "playlist-modify-private", - "playlist-modify-public", + "user-read-email", + "user-read-private", + "user-top-read", + "user-read-recently-played", + "user-follow-read", + "user-library-read", + "user-read-currently-playing", + "user-read-playback-state", + "user-read-playback-position", "playlist-read-collaborative", "playlist-read-private", - "ugc-image-upload", "user-follow-modify", - "user-follow-read", "user-library-modify", - "user-library-read", "user-modify-playback-state", - "user-read-currently-playing", - "user-read-email", - "user-read-playback-position", - "user-read-playback-state", - "user-read-private", - "user-read-recently-played", - "user-top-read" + "playlist-modify-public", + "playlist-modify-private", + "ugc-image-upload" ); let oauth = OAuth::from_env(scopes).unwrap(); @@ -49,22 +44,6 @@ async fn main() { spotify.prompt_for_token(&url).await.unwrap(); let token = spotify.token.as_ref().unwrap(); - let access_token = &token.access_token; - let refresh_token = token.refresh_token.as_ref().unwrap(); - println!("Access token: {}", access_token); - println!("Refresh token: {}", refresh_token); - - print!("Export to the environment? [y/N]: "); - io::stdout().flush().unwrap(); - - let stdin = io::stdin(); - let mut input = String::new(); - stdin.read_line(&mut input).unwrap(); - - let input = input.trim(); - if input == "" || input == "y" || input == "Y" { - env::set_var("RSPOTIFY_ACCESS_TOKEN", access_token); - env::set_var("RSPOTIFY_REFRESH_TOKEN", refresh_token); - println!("Exported RSPOTIFY_ACCESS_TOKEN and RSPOTIFY_REFRESH_TOKEN"); - } + println!("Access token: {}", &token.access_token); + println!("Refresh token: {}", token.refresh_token.as_ref().unwrap()); } From 3f1f571ec4cc8dfcd0189be4ed475db98f55ecfa Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 23:10:59 +0200 Subject: [PATCH 10/22] fixes #227 --- src/clients/oauth.rs | 8 ++++---- tests/test_with_oauth.rs | 8 +++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/clients/oauth.rs b/src/clients/oauth.rs index 1fe6b869..3f04f6d4 100644 --- a/src/clients/oauth.rs +++ b/src/clients/oauth.rs @@ -189,7 +189,7 @@ pub trait OAuthClient: BaseClient { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-change-playlist-details) async fn playlist_change_detail( &self, - playlist_id: &str, + playlist_id: &PlaylistId, name: Option<&str>, public: Option, description: Option<&str>, @@ -202,7 +202,7 @@ pub trait OAuthClient: BaseClient { optional "description": description, }; - let url = format!("playlists/{}", playlist_id); + let url = format!("playlists/{}", playlist_id.id()); self.endpoint_put(&url, ¶ms).await } @@ -212,8 +212,8 @@ pub trait OAuthClient: BaseClient { /// - playlist_id - the id of the playlist /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-unfollow-playlist) - async fn playlist_unfollow(&self, playlist_id: &str) -> ClientResult { - let url = format!("playlists/{}/followers", playlist_id); + async fn playlist_unfollow(&self, playlist_id: &PlaylistId) -> ClientResult { + let url = format!("playlists/{}/followers", playlist_id.id()); self.endpoint_delete(&url, &json!({})).await } diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 368e0e83..2d418d57 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -570,8 +570,7 @@ async fn test_user_follow_playlist() { .await .unwrap(); - // TODO: https://github.com/ramsayleung/rspotify/issues/227 - client.playlist_unfollow(&playlist_id.uri()).await.unwrap(); + client.playlist_unfollow(playlist_id).await.unwrap(); } #[maybe_async] @@ -600,10 +599,9 @@ async fn check_playlist_create(client: &AuthCodeSpotify) -> FullPlaylist { // Modifying the playlist details let name = "A New Playlist-update"; let description = "A random description"; - // TODO: https://github.com/ramsayleung/rspotify/issues/227 client .playlist_change_detail( - &playlist.id, + &playlist_id, Some(name), Some(true), Some(description), @@ -720,7 +718,7 @@ async fn check_playlist_follow(client: &AuthCodeSpotify, playlist: &FullPlaylist assert_eq!(following, vec![false, false]); // Finally unfollowing the playlist in order to clean it up - client.playlist_unfollow(&playlist.id).await.unwrap(); + client.playlist_unfollow(playlist_id).await.unwrap(); } #[maybe_async::test(feature = "__sync", async(feature = "__async", tokio::test))] From 4839423b28f0eddbf36b254e01ce88f2b86a6d77 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 23:12:34 +0200 Subject: [PATCH 11/22] fixes #228 --- src/clients/oauth.rs | 5 +---- tests/test_with_oauth.rs | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/clients/oauth.rs b/src/clients/oauth.rs index 3f04f6d4..31da753e 100644 --- a/src/clients/oauth.rs +++ b/src/clients/oauth.rs @@ -278,18 +278,15 @@ pub trait OAuthClient: BaseClient { /// - snapshot_id - optional playlist's snapshot ID /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-reorder-or-replace-playlists-tracks) - async fn playlist_reorder_tracks<'a, T: PlayableIdType + 'a>( + async fn playlist_reorder_tracks( &self, playlist_id: &PlaylistId, - uris: Option> + 'a>, range_start: Option, insert_before: Option, range_length: Option, snapshot_id: Option<&str>, ) -> ClientResult { - let uris = uris.map(|u| u.into_iter().map(|id| id.uri()).collect::>()); let params = build_json! { - optional "uris": uris, optional "range_start": range_start, optional "insert_before": insert_before, optional "range_length": range_length, diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 2d418d57..06d2a175 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -642,7 +642,6 @@ async fn check_playlist_tracks(client: &AuthCodeSpotify, playlist: &FullPlaylist client .playlist_reorder_tracks( playlist_id, - None::>, Some(0), Some(3), Some(2), From 65ce1eca72a3260c234cf3c7d806dc8c2767467f Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 23:15:54 +0200 Subject: [PATCH 12/22] fixes #229 --- src/clients/oauth.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/clients/oauth.rs b/src/clients/oauth.rs index 31da753e..d476c08c 100644 --- a/src/clients/oauth.rs +++ b/src/clients/oauth.rs @@ -212,9 +212,11 @@ pub trait OAuthClient: BaseClient { /// - playlist_id - the id of the playlist /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-unfollow-playlist) - async fn playlist_unfollow(&self, playlist_id: &PlaylistId) -> ClientResult { + async fn playlist_unfollow(&self, playlist_id: &PlaylistId) -> ClientResult<()> { let url = format!("playlists/{}/followers", playlist_id.id()); - self.endpoint_delete(&url, &json!({})).await + self.endpoint_delete(&url, &json!({})).await; + + Ok(()) } /// Adds tracks to a playlist. From 60abe6fb9e8d26327d8a4461c76c14c2ab29cb4a Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 23:16:24 +0200 Subject: [PATCH 13/22] formatting --- tests/test_with_oauth.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 06d2a175..1821723d 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -640,13 +640,7 @@ async fn check_playlist_tracks(client: &AuthCodeSpotify, playlist: &FullPlaylist // Reordering some tracks client - .playlist_reorder_tracks( - playlist_id, - Some(0), - Some(3), - Some(2), - None, - ) + .playlist_reorder_tracks(playlist_id, Some(0), Some(3), Some(2), None) .await .unwrap(); // Making sure the number of tracks is the same From 27e7e41bb9148149f3a3e640aeaa83556c402cd7 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 23:21:21 +0200 Subject: [PATCH 14/22] fix clippy --- src/clients/oauth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clients/oauth.rs b/src/clients/oauth.rs index d476c08c..f9047912 100644 --- a/src/clients/oauth.rs +++ b/src/clients/oauth.rs @@ -214,7 +214,7 @@ pub trait OAuthClient: BaseClient { /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-unfollow-playlist) async fn playlist_unfollow(&self, playlist_id: &PlaylistId) -> ClientResult<()> { let url = format!("playlists/{}/followers", playlist_id.id()); - self.endpoint_delete(&url, &json!({})).await; + self.endpoint_delete(&url, &json!({})).await?; Ok(()) } From 6c2eb9b9347b6c1b8a69a2cf3dcd20a4eddf3e2f Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Wed, 14 Jul 2021 23:57:52 +0200 Subject: [PATCH 15/22] fix #231 --- src/clients/oauth.rs | 36 +++++++++++++++++++++++++++++++++--- tests/test_with_oauth.rs | 24 ++++++++++++++++++------ 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/clients/oauth.rs b/src/clients/oauth.rs index f9047912..cba4409d 100644 --- a/src/clients/oauth.rs +++ b/src/clients/oauth.rs @@ -834,9 +834,9 @@ pub trait OAuthClient: BaseClient { /// /// Parameters: /// - market: Optional. an ISO 3166-1 alpha-2 country code or the string from_token. - /// - additional_types: Optional. A comma-separated list of item types that - /// your client supports besides the default track type. Valid types are: - /// `track` and `episode`. + /// - additional_types: Optional. A list of item types that your client + /// supports besides the default track type. Valid types are: `track` and + /// `episode`. /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-get-information-about-the-users-current-playback) async fn current_playback<'a>( @@ -956,6 +956,15 @@ pub trait OAuthClient: BaseClient { Ok(()) } + /// Start a user's playback + /// + /// Parameters: + /// - uris + /// - device_id + /// - offset + /// - position_ms + /// + /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-start-a-users-playback) async fn start_uris_playback<'a, T: PlayableIdType + 'a>( &self, uris: impl IntoIterator> + 'a, @@ -991,6 +1000,27 @@ pub trait OAuthClient: BaseClient { Ok(()) } + /// Resume a User’s Playback. + /// + /// Parameters: + /// - device_id - device target for playback + /// + /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-start-a-users-playback) + async fn resume_playback( + &self, + device_id: Option<&str>, + position_ms: Option, + ) -> ClientResult<()> { + let params = build_json! { + optional "position_ms": position_ms, + }; + + let url = append_device_id("me/player/play", device_id); + self.endpoint_put(&url, ¶ms).await?; + + Ok(()) + } + /// Skip User’s Playback To Next Track. /// /// Parameters: diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 1821723d..43b666e4 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -355,14 +355,26 @@ async fn test_playback() { .await .unwrap(); - for _ in &uris { + for i in 0..uris.len() - 1 { client.next_track(Some(&device_id)).await.unwrap(); - client.pause_playback(Some(&device_id)).await.unwrap(); - } - - for _ in &uris { - client.previous_track(Some(&device_id)).await.unwrap(); + // Also trying to go to the previous track + if i != 0 { + client.previous_track(Some(&device_id)).await.unwrap(); + client.next_track(Some(&device_id)).await.unwrap(); + } + + // Making sure pause/resume also works + let playback = client.current_playback(None, None::<&[_]>).await.unwrap(); + if let Some(playback) = playback { + if playback.is_playing { + client.pause_playback(Some(&device_id)).await.unwrap(); + client.resume_playback(None, None).await.unwrap(); + } else { + client.resume_playback(None, None).await.unwrap(); + client.pause_playback(Some(&device_id)).await.unwrap(); + } + } } client From bd945a284bb50c20cff7c0aae25c88fe929c8a16 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Thu, 15 Jul 2021 00:33:11 +0200 Subject: [PATCH 16/22] cleanup and ready for review --- rspotify-model/Cargo.toml | 4 +--- src/clients/base.rs | 8 ++++---- tests/test_models.rs | 2 +- tests/test_with_oauth.rs | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/rspotify-model/Cargo.toml b/rspotify-model/Cargo.toml index 78d4f7e6..2d71efdd 100644 --- a/rspotify-model/Cargo.toml +++ b/rspotify-model/Cargo.toml @@ -16,8 +16,6 @@ edition = "2018" [dependencies] chrono = { version = "0.4.13", features = ["serde", "rustc-serialize"] } serde = { version = "1.0.115", features = ["derive"] } +serde_json = "1.0.57" strum = { version = "0.21", features = ["derive"] } thiserror = "1.0.20" - -[dev-dependencies] -serde_json = "1.0.57" diff --git a/src/clients/base.rs b/src/clients/base.rs index d711358c..db960530 100644 --- a/src/clients/base.rs +++ b/src/clients/base.rs @@ -913,11 +913,11 @@ where let seed_tracks = seed_tracks.map(join_ids); let limit = limit.map(|x| x.to_string()); let mut params = build_map! { - optional "seed_artists": seed_artists.as_ref(), - optional "seed_genres": seed_genres.as_ref(), - optional "seed_tracks": seed_tracks.as_ref(), + optional "seed_artists": seed_artists.as_deref(), + optional "seed_genres": seed_genres.as_deref(), + optional "seed_tracks": seed_tracks.as_deref(), optional "market": market.map(|x| x.as_ref()), - optional "limit": limit.as_ref(), + optional "limit": limit.as_deref(), }; let attributes = [ diff --git a/tests/test_models.rs b/tests/test_models.rs index 688b0dbb..ef3cca81 100644 --- a/tests/test_models.rs +++ b/tests/test_models.rs @@ -326,7 +326,7 @@ fn test_recommendations_seed() { "afterRelinkingSize": 365, "href": "https://api.spotify.com/v1/artists/4NHQUGzhtTLFvgF5SZesLK", "id": "4NHQUGzhtTLFvgF5SZesLK", - "type": "artist" + "type": "ARTIST" } "#; let seed: RecommendationsSeed = serde_json::from_str(&json_str).unwrap(); diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 43b666e4..0e0d9186 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -5,7 +5,7 @@ //! //! You can run all of them with: //! -//! cargo test --features=cli,env-file -- --ignored +//! cargo test --features=cli,env-file -- --ignored --test-threads=1 //! //! The access token must be obtained previously, and this test file will try //! to authenticate with the access token from the `RSPOTIFY_ACCESS_TOKEN` From 6c9bea8a2bed4d457f74a21600a8f14d67b66fc2 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Thu, 15 Jul 2021 00:37:51 +0200 Subject: [PATCH 17/22] restore rspotify-model/Cargo.toml --- rspotify-model/Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rspotify-model/Cargo.toml b/rspotify-model/Cargo.toml index 2d71efdd..78d4f7e6 100644 --- a/rspotify-model/Cargo.toml +++ b/rspotify-model/Cargo.toml @@ -16,6 +16,8 @@ edition = "2018" [dependencies] chrono = { version = "0.4.13", features = ["serde", "rustc-serialize"] } serde = { version = "1.0.115", features = ["derive"] } -serde_json = "1.0.57" strum = { version = "0.21", features = ["derive"] } thiserror = "1.0.20" + +[dev-dependencies] +serde_json = "1.0.57" From 34ba32f3f52a57eb0b65b7dd52e62b7269e8ae77 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Thu, 15 Jul 2021 00:40:30 +0200 Subject: [PATCH 18/22] fix typo --- tests/test_with_oauth.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_with_oauth.rs b/tests/test_with_oauth.rs index 0e0d9186..a94f8e0f 100644 --- a/tests/test_with_oauth.rs +++ b/tests/test_with_oauth.rs @@ -1,7 +1,7 @@ //! Most of tests currently require a Spotify Premium account where the tests //! can be ran, which will be ignored in the CI for now. The tests are written -//! so that no user data is modified (or, in case it fails, minimal changes are -//! done to the acount). +//! so that no user data is modified (or at least minimizing the changes done to +//! the account). //! //! You can run all of them with: //! From 987b87a288897a9b64854e6fdea19173958b74f6 Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sun, 18 Jul 2021 00:27:36 +0200 Subject: [PATCH 19/22] Apply some suggestions --- CHANGELOG.md | 1 + rspotify-model/src/album.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d71db7f0..9a0792f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ More in the [`examples` directory](https://github.com/ramsayleung/rspotify/tree/ - Rspotify now uses macros internally to make the endpoints as concise as possible and nice to read. - Add `add_item_to_queue` endpoint. - Add `category_playlists` endpoint ([#153](https://github.com/ramsayleung/rspotify/pull/153)). +- Add `resume_playback` endpoint. - Fix race condition when using a single client from multiple threads ([#114](https://github.com/ramsayleung/rspotify/pull/114)). - Rspotify should now be considerably lighter and less bloated ([discussion in #108](https://github.com/ramsayleung/rspotify/issues/108)): + Remove unused dependencies: `base64`, `env_logger`, `random`, `url`. diff --git a/rspotify-model/src/album.rs b/rspotify-model/src/album.rs index 293597d4..83bc661a 100644 --- a/rspotify-model/src/album.rs +++ b/rspotify-model/src/album.rs @@ -42,7 +42,7 @@ pub struct SimplifiedAlbum { /// Full Album Object /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#object-albumobject) -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct FullAlbum { pub artists: Vec, pub album_type: AlbumType, From 2d1f4935fd70be2a7445fcc99509c43bbc662a2c Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Fri, 6 Aug 2021 12:39:56 +0200 Subject: [PATCH 20/22] Fix compilation issue --- rspotify-model/src/album.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rspotify-model/src/album.rs b/rspotify-model/src/album.rs index 83bc661a..1e45432b 100644 --- a/rspotify-model/src/album.rs +++ b/rspotify-model/src/album.rs @@ -83,7 +83,7 @@ pub struct PageSimpliedAlbums { /// Saved Album object /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#object-savedalbumobject) -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct SavedAlbum { pub added_at: DateTime, pub album: FullAlbum, From ea0694458bd1d9daecfe3aaadc0edb60c9db97ac Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 7 Aug 2021 01:55:35 +0200 Subject: [PATCH 21/22] nitpick --- src/clients/oauth.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/clients/oauth.rs b/src/clients/oauth.rs index cba4409d..1fab1f0b 100644 --- a/src/clients/oauth.rs +++ b/src/clients/oauth.rs @@ -1004,6 +1004,7 @@ pub trait OAuthClient: BaseClient { /// /// Parameters: /// - device_id - device target for playback + /// - position_ms /// /// [Reference](https://developer.spotify.com/documentation/web-api/reference/#endpoint-start-a-users-playback) async fn resume_playback( From 9d8b86afd0de136bcb7a35c1955971bf82d607cf Mon Sep 17 00:00:00 2001 From: Mario Ortiz Manero Date: Sat, 7 Aug 2021 13:25:52 +0200 Subject: [PATCH 22/22] add backtrace --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f308752..aa5153b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,6 +59,8 @@ jobs: test: name: Test and Lint for each Client runs-on: ubuntu-latest + env: + RUST_BACKTRACE: 1 strategy: matrix: features: