From fc12df2155e3c580cdeaaff9c9cc20b75843de5d Mon Sep 17 00:00:00 2001 From: Hayri Bakici Date: Sun, 18 Feb 2024 11:48:22 +0100 Subject: [PATCH 1/2] adds interceptor and test classes --- lib/src/endpoints/player.dart | 4 +- lib/src/models/_models.g.dart | 11 ++-- lib/src/models/player.dart | 38 ++++++++----- test/data/v1/me/player/play.json | 93 ++++++++++++++++++++++++++++++++ test/spotify_mock.dart | 25 +++++++-- test/spotify_test.dart | 33 ++++++++++++ 6 files changed, 181 insertions(+), 23 deletions(-) create mode 100644 test/data/v1/me/player/play.json diff --git a/lib/src/endpoints/player.dart b/lib/src/endpoints/player.dart index e9b4c9f..f5761ca 100644 --- a/lib/src/endpoints/player.dart +++ b/lib/src/endpoints/player.dart @@ -126,7 +126,7 @@ class PlayerEndpoint extends _MeEndpointBase { assert(trackUris.isNotEmpty, 'Cannot start playback with empty track uris'); assert(positionMs >= 0, 'Position must be greater than or equal to 0'); - var options = StartOrResumeOptions(uris: trackUris, positionMs: positionMs); + var options = StartWithUrisOptions(uris: trackUris, positionMs: positionMs); return startOrResume( deviceId: deviceId, options: options, @@ -148,7 +148,7 @@ class PlayerEndpoint extends _MeEndpointBase { bool retrievePlaybackState = true}) async { assert( contextUri.isNotEmpty, 'Cannot start playback with empty context uri'); - var options = StartOrResumeOptions(contextUri: contextUri, offset: offset); + var options = StartWithContextOptions(contextUri: contextUri, offset: offset); return startOrResume( deviceId: deviceId, options: options, diff --git a/lib/src/models/_models.g.dart b/lib/src/models/_models.g.dart index da11c67..0d978f0 100644 --- a/lib/src/models/_models.g.dart +++ b/lib/src/models/_models.g.dart @@ -595,12 +595,17 @@ Actions _$ActionsFromJson(Map json) => Actions() ..togglingShuffle = json['toggling_shuffle'] as bool? ?? false ..transferringPlayback = json['transferring_playback'] as bool? ?? false; -Map _$StartOrResumeOptionsToJson( - StartOrResumeOptions instance) => +Map _$StartWithContextOptionsToJson( + StartWithContextOptions instance) => { 'context_uri': instance.contextUri, + 'offset': StartWithContextOptions._offsetToJson(instance.offset), + }; + +Map _$StartWithUrisOptionsToJson( + StartWithUrisOptions instance) => + { 'uris': instance.uris, - 'offset': StartOrResumeOptions._offsetToJson(instance.offset), 'position_ms': instance.positionMs, }; diff --git a/lib/src/models/player.dart b/lib/src/models/player.dart index 7da61d5..4dc35c3 100644 --- a/lib/src/models/player.dart +++ b/lib/src/models/player.dart @@ -117,14 +117,36 @@ class Actions extends Object { bool? transferringPlayback; } +abstract class StartOrResumeOptions extends Object { + Map toJson(); +} + @JsonSerializable(createFactory: false) -class StartOrResumeOptions extends Object { +class StartWithContextOptions extends StartOrResumeOptions { + StartWithContextOptions({this.contextUri, this.offset}); + /// Optional. Spotify URI of the context to play. Valid contexts are albums, /// artists & playlists. /// Example: "spotify:album:1Je1IMUlBXcx1Fz0WE7oPT" @JsonKey(name: 'context_uri') String? contextUri; + /// Optional. Indicates from where in the context playback should start. + /// Only available when [contextUri] corresponds to an album or playlist object + @JsonKey(toJson: _offsetToJson) + Offset? offset; + + @override + Map toJson() => _$StartWithContextOptionsToJson(this); + + static Map? _offsetToJson(Offset? offset) => + offset?.toJson(); +} + +@JsonSerializable(createFactory: false) +class StartWithUrisOptions extends StartOrResumeOptions { + StartWithUrisOptions({this.uris, this.positionMs}); + /// Optional. A JSON array of the Spotify track URIs to play. /// /// Example: @@ -136,22 +158,12 @@ class StartOrResumeOptions extends Object { /// ``` List? uris; - /// Optional. Indicates from where in the context playback should start. - /// Only available when context_uri corresponds to an album or playlist object - @JsonKey(toJson: _offsetToJson) - Offset? offset; - /// Optional. The position in milliseconds to start playback. @JsonKey(name: 'position_ms') int? positionMs; - StartOrResumeOptions( - {this.contextUri, this.uris, this.offset, this.positionMs}); - - Map toJson() => _$StartOrResumeOptionsToJson(this); - - static Map _offsetToJson(Offset? offset) => - offset?.toJson() ?? {}; + @override + Map toJson() => _$StartWithUrisOptionsToJson(this); } abstract class Offset { diff --git a/test/data/v1/me/player/play.json b/test/data/v1/me/player/play.json new file mode 100644 index 0000000..0265c19 --- /dev/null +++ b/test/data/v1/me/player/play.json @@ -0,0 +1,93 @@ +{ + "device": { + "id": "123", + "is_active": true, + "is_private_session": false, + "is_restricted": false, + "name": "spotify-player", + "supports_volume": true, + "type": "Speaker", + "volume_percent": 50 + }, + "shuffle_state": false, + "smart_shuffle": false, + "repeat_state": "off", + "timestamp": 1708246313962, + "context": null, + "progress_ms": 96749, + "item": { + "album": { + "album_type": "album", + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/0OcclcP5o8VKH2TRqSY2A7" + }, + "href": "https://api.spotify.com/v1/artists/0OcclcP5o8VKH2TRqSY2A7", + "id": "0OcclcP5o8VKH2TRqSY2A7", + "name": "Howard Shore", + "type": "artist", + "uri": "spotify:artist:0OcclcP5o8VKH2TRqSY2A7" + } + ], + "available_markets": ["AR"], + "external_urls": { + "spotify": "https://open.spotify.com/album/55RTkgUCP7t80hiTUhATMH" + }, + "href": "https://api.spotify.com/v1/albums/55RTkgUCP7t80hiTUhATMH", + "id": "55RTkgUCP7t80hiTUhATMH", + "images": [ + { + "height": 640, + "url": "https://i.scdn.co/image/ab67616d0000b2738236dee9524214e0e6be4a1f", + "width": 640 + } + ], + "name": "The Lord of the Rings: The Fellowship of the Ring - the Complete Recordings", + "release_date": "2001", + "release_date_precision": "year", + "total_tracks": 37, + "type": "album", + "uri": "spotify:album:55RTkgUCP7t80hiTUhATMH" + }, + "artists": [ + { + "external_urls": { + "spotify": "https://open.spotify.com/artist/0OcclcP5o8VKH2TRqSY2A7" + }, + "href": "https://api.spotify.com/v1/artists/0OcclcP5o8VKH2TRqSY2A7", + "id": "0OcclcP5o8VKH2TRqSY2A7", + "name": "Howard Shore", + "type": "artist", + "uri": "spotify:artist:0OcclcP5o8VKH2TRqSY2A7" + } + ], + "available_markets": ["AR"], + "disc_number": 1, + "duration_ms": 149480, + "explicit": false, + "external_ids": { + "isrc": "USRE10501559" + }, + "external_urls": { + "spotify": "https://open.spotify.com/track/6zW80jVqLtgSF1yCtGHiiD" + }, + "href": "https://api.spotify.com/v1/tracks/6zW80jVqLtgSF1yCtGHiiD", + "id": "6zW80jVqLtgSF1yCtGHiiD", + "is_local": false, + "name": "The Shire", + "popularity": 66, + "preview_url": "https://p.scdn.co/mp3-preview/0cb0d3080071ec5c911ea209ec4fc2258e2081d6?cid=a51c360ccea644af8e2a0b1baad8a878", + "track_number": 2, + "type": "track", + "uri": "spotify:track:6zW80jVqLtgSF1yCtGHiiD" + }, + "currently_playing_type": "track", + "actions": { + "disallows": { + "skipping_prev": true, + "toggling_repeat_track": true + } + }, + "is_playing": false + } \ No newline at end of file diff --git a/test/spotify_mock.dart b/test/spotify_mock.dart index 4f46854..4a5eb9c 100644 --- a/test/spotify_mock.dart +++ b/test/spotify_mock.dart @@ -12,6 +12,9 @@ class SpotifyApiMock extends SpotifyApiBase { set mockHttpErrors(Iterator errors) => (client as MockClient)._mockHttpErrors = errors; + + set interceptor(Function(String method, String url, Map? headers, [String? body])? interceptor) => + (client as MockClient).interceptFunction = interceptor; } class MockClient implements oauth2.Client { @@ -22,6 +25,14 @@ class MockClient implements oauth2.Client { _mockHttpErrors = mockHttpErrors; } + Function(String method, String url, Map? headers, [String? body])? interceptFunction; + + void _intercept(String method, String url, Map? headers, [String? body]) { + if (interceptFunction != null) { + interceptFunction!(method, url, headers, body); + } + } + @override String? identifier; @@ -63,6 +74,7 @@ class MockClient implements oauth2.Client { @override Future get(url, {Map? headers}) async { + _intercept('GET', url.toString(), headers); final err = _getMockError(); if (err != null) { return createErrorResponse(err); @@ -71,8 +83,9 @@ class MockClient implements oauth2.Client { } @override - Future head(url, {Map? headers}) { - throw 'Not implemented'; + Future head(url, {Map? headers}) async { + _intercept('HEAD', url.toString(), headers); + return createSuccessResponse(); } @override @@ -84,6 +97,7 @@ class MockClient implements oauth2.Client { @override Future post(url, {Map? headers, body, Encoding? encoding}) async { + _intercept('POST', url.toString(), headers, body.toString()); final err = _getMockError(); if (err != null) { return createErrorResponse(err); @@ -93,8 +107,9 @@ class MockClient implements oauth2.Client { @override Future put(url, - {Map? headers, body, Encoding? encoding}) { - throw 'Not implemented'; + {Map? headers, body, Encoding? encoding}) async { + _intercept('PUT', url.toString(), headers, body.toString()); + return createSuccessResponse(_readPath(url)); } @override @@ -126,7 +141,7 @@ class MockClient implements oauth2.Client { throw 'Not implemented'; } - http.Response createSuccessResponse(String body) { + http.Response createSuccessResponse([String body = ""]) { /// necessary due to using Latin-1 encoding per default. /// https://stackoverflow.com/questions/52990816/dart-json-encodedata-can-not-accept-other-language return http.Response(body, 200, headers: { diff --git a/test/spotify_test.dart b/test/spotify_test.dart index 2ce4e91..fa0073d 100644 --- a/test/spotify_test.dart +++ b/test/spotify_test.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'spotify_mock.dart'; import 'package:test/test.dart'; import 'package:spotify/spotify.dart'; @@ -9,6 +10,10 @@ Future main() async { 'clientSecret', )); + tearDown(() { + spotify.interceptor = null; + }); + group('Albums', () { test('get', () async { var album = await spotify.albums.get('4aawyAB9vmqN3uQ7FjRGTy'); @@ -480,6 +485,34 @@ Future main() async { expect(result.actions?.resuming, false); expect(result.actions?.pausing, true); }); + + + test('startWithContext', () async { + spotify.interceptor = (method, url, headers, [body]) { + // checking sincce startWithContext makes a PUT and a GET request + // to retrieve the current playbackstate + if (method == 'PUT') { + expect(method, 'PUT'); + expect(body, isNotNull); + expect(body, '{"context_uri":"contextUri","offset":{"uri":"urioffset"}}'); + } + }; + await spotify.player.startWithContext('contextUri', offset: UriOffset('urioffset')); + }); + + test('startWithUris', () async { + spotify.interceptor = (method, url, headers, [body]) { + // checking sincce startWithTracks makes a PUT and a GET request + // to retrieve the current playbackstate + if (method == 'PUT') { + expect(method, 'PUT'); + expect(body, isNotNull); + expect(body, '{"uris":["track1"],"position_ms":10}'); + } + }; + await spotify.player.startWithTracks(['track1'], positionMs: 10); + }); + }); group('Tracks', () { From 4631fd50e522076d6a75b3758d6903dd0fa012fd Mon Sep 17 00:00:00 2001 From: Hayri Bakici Date: Sun, 18 Feb 2024 12:40:46 +0100 Subject: [PATCH 2/2] fixes code --- lib/src/models/player.dart | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/src/models/player.dart b/lib/src/models/player.dart index 3042e45..335f963 100644 --- a/lib/src/models/player.dart +++ b/lib/src/models/player.dart @@ -123,6 +123,7 @@ abstract class StartOrResumeOptions extends Object { @JsonSerializable(createFactory: false) class StartWithContextOptions extends StartOrResumeOptions { + StartWithContextOptions({this.contextUri, this.offset}); /// Optional. Spotify URI of the context to play. Valid contexts are albums, @@ -145,6 +146,7 @@ class StartWithContextOptions extends StartOrResumeOptions { @JsonSerializable(createFactory: false) class StartWithUrisOptions extends StartOrResumeOptions { + StartWithUrisOptions({this.uris, this.positionMs}); /// Optional. A JSON array of the Spotify track URIs to play. @@ -158,21 +160,13 @@ class StartWithUrisOptions extends StartOrResumeOptions { /// ``` List? uris; - /// Optional. Indicates from where in the context playback should start. - /// Only available when [contextUri] corresponds to an album or playlist object - @JsonKey(toJson: _offsetToJson) - Offset? offset; - /// Optional. The position in milliseconds to start playback. @JsonKey(name: 'position_ms') int? positionMs; - StartOrResumeOptions( - {this.contextUri, this.uris, this.offset, this.positionMs}); - - Map toJson() => _$StartOrResumeOptionsToJson(this); + @override + Map toJson() => _$StartWithUrisOptionsToJson(this); - static Map? _offsetToJson(Offset? offset) => offset?.toJson(); } abstract class Offset {