Skip to content

Commit

Permalink
SearchForItemsCatalaog implementation
Browse files Browse the repository at this point in the history
Implemented `searchForItemsCatalog` with tests.
  • Loading branch information
filippomenchini committed Nov 20, 2023
1 parent 0bc364f commit 7197789
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 9 deletions.
20 changes: 18 additions & 2 deletions lib/src/search/search_api.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
// ignore_for_file: constant_identifier_names

import '../types/tidal_search_response.dart';

enum TidalSearchType {
UNDEFINED,
ARTISTS,
ALBUMS,
TRACKS,
VIDEOS,
}

enum TidalSearchPopularity {
UNDEFINED,
WORLDWIDE,
COUNTRY,
}

/// An interface for searching Tidal catalog items.
abstract class SearchAPI {
/// Searches for Tidal catalog items based on the specified query.
Expand All @@ -18,7 +34,7 @@ abstract class SearchAPI {
required String countryCode,
int offset = 0,
int limit = 10,
String? type,
String? popularity,
TidalSearchType type = TidalSearchType.UNDEFINED,
TidalSearchPopularity popularity = TidalSearchPopularity.UNDEFINED,
});
}
54 changes: 54 additions & 0 deletions lib/src/search/search_for_catalog_items_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:http/http.dart' as http;

import '../authorization/tidal_auth_token.dart';
import '../commons/handle_http_response.dart';
import '../types/tidal_search_response.dart';
import 'search_api.dart';

const _searchForCatalogItemsEndpointUrl = 'https://openapi.tidal.com/search';
const _acceptHeader = {'accept': 'application/vnd.tidal.v1+json'};
const _contentTypeHeader = {'Content-Type': 'application/vnd.tidal.v1+json'};

/// Retrieves a data set of items given a search query.
///
/// Parameters:
/// - [client]: The HTTP client for making the API request.
/// - [tidalAuthToken]: The Tidal authentication token.
/// - [query]: The search query.
/// - [countryCode]: The country code for localized results.
/// - [offset]: The offset for paginating through search results (default is 0).
/// - [limit]: The maximum number of items to retrieve per request (default is 10).
/// - [type]: Optional parameter to filter results by item type.
/// - [popularity]: Optional parameter to filter results by popularity.
///
/// Returns a [TidalMedia] instance representing the retrieved track.
Future<TidalSearchResponse> searchForCatalogItemsImpl(
http.Client client, {
required TidalAuthToken tidalAuthToken,
required String query,
required String countryCode,
int offset = 0,
int limit = 10,
TidalSearchType type = TidalSearchType.UNDEFINED,
TidalSearchPopularity popularity = TidalSearchPopularity.UNDEFINED,
}) async {
String typeQuery =
type != TidalSearchType.UNDEFINED ? "&type=${type.name}" : "";
String popularityQuery = popularity != TidalSearchPopularity.UNDEFINED
? "&popularity=${popularity.name}"
: "";
final response = await client.get(
Uri.parse(
'$_searchForCatalogItemsEndpointUrl?query=$query&offset=$offset&limit=$limit&countryCode=$countryCode$typeQuery$popularityQuery'),
headers: {
..._acceptHeader,
..._contentTypeHeader,
...tidalAuthToken.header,
},
);

return handleHttpResponse(
response: response,
onSuccessfulResponse: TidalSearchResponse.fromJson,
);
}
7 changes: 4 additions & 3 deletions lib/src/types/multiple_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import 'multiple_response_item.dart';
/// - [metadata]: A map containing additional metadata related to the response.
class MultipleResponse<T> extends Equatable {
final List<MultipleResponseItem<T>> items;
final Map<String, dynamic> metadata;
final Map<String, dynamic>? metadata;

const MultipleResponse({
required this.items,
required this.metadata,
this.metadata,
});

/// Creates a [MultipleResponse] from a JSON map, using the provided [itemFactory] function
Expand All @@ -30,7 +30,8 @@ class MultipleResponse<T> extends Equatable {
MultipleResponse.fromJson({
required Map<String, dynamic> json,
required T Function(Map<String, dynamic> json) itemFactory,
}) : items = (json is List ? json as List : json["data"] as List)
String? itemsFieldName,
}) : items = (json[itemsFieldName ?? "data"] as List)
.map((e) => MultipleResponseItem.fromJson(
json: e, itemFactory: itemFactory))
.toList(),
Expand Down
12 changes: 8 additions & 4 deletions lib/src/types/tidal_search_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,24 @@ class TidalSearchResponse extends Equatable {
/// - [json]: The JSON map containing search data.
TidalSearchResponse.fromJson(Map<String, dynamic> json)
: albums = MultipleResponse.fromJson(
json: json["albums"],
json: json,
itemFactory: TidalAlbum.fromJson,
itemsFieldName: "albums",
),
artists = MultipleResponse.fromJson(
json: json["artists"],
json: json,
itemFactory: TidalArtist.fromJson,
itemsFieldName: "artists",
),
tracks = MultipleResponse.fromJson(
json: json["tracks"],
json: json,
itemFactory: TidalMedia.fromJson,
itemsFieldName: "tracks",
),
videos = MultipleResponse.fromJson(
json: json["videos"],
json: json,
itemFactory: TidalMedia.fromJson,
itemsFieldName: "videos",
);

@override
Expand Down
150 changes: 150 additions & 0 deletions test/search/search_for_catalog_items_impl_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import 'package:http/http.dart';
import 'package:http/testing.dart';
import 'package:test/test.dart';
import 'package:tidal/src/authorization/tidal_auth_token.dart';
import 'package:tidal/src/search/search_api.dart';
import 'package:tidal/src/search/search_for_catalog_items_impl.dart';
import 'package:tidal/src/types/multiple_response.dart';
import 'package:tidal/src/types/multiple_response_item.dart';
import 'package:tidal/src/types/tidal_album.dart';
import 'package:tidal/src/types/tidal_artist.dart';
import 'package:tidal/src/types/tidal_image.dart';
import 'package:tidal/src/types/tidal_media.dart';
import 'package:tidal/src/types/tidal_search_response.dart';

void main() {
group('Given a search query', () {
test('Should return a dataset of searched items', () async {
// Arrange
String actualUrl = '';
final client = MockClient((request) async {
actualUrl = request.url.toString();
return Response(
'''{ "albums": [ { "resource": { "id": "75413011", "barcodeId": "00854242007552", "title": "4:44", "artists": [ { "id": "7804", "name": "JAY Z", "picture": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ], "main": true } ], "duration": 2777, "releaseDate": "2017-06-30", "imageCover": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ], "videoCover": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ], "numberOfVolumes": 1, "numberOfTracks": 13, "numberOfVideos": 0, "type": "ALBUM", "copyright": "(p)(c) 2017 S. CARTER ENTERPRISES, LLC. MARKETED BY ROC NATION & DISTRIBUTED BY ROC NATION/UMG RECORDINGS INC.", "mediaMetadata": { "tags": "HIRES_LOSSLESS" }, "properties": { "content": "explicit" } }, "id": "4328473", "status": 200, "message": "success" } ], "artists": [ { "resource": { "id": "7804", "name": "JAY Z", "picture": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ] }, "id": "4328473", "status": 200, "message": "success" } ], "tracks": [ { "resource": { "id": "75623239", "title": "Kill Jay Z", "version": "Kill Jay Z", "artists": [ { "id": "7804", "name": "JAY Z", "picture": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ], "main": true } ], "album": { "id": "75413011", "title": "4:44", "imageCover": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ], "videoCover": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ] }, "duration": 30, "trackNumber": 30, "volumeNumber": 30, "isrc": "TIDAL2274", "copyright": "(p)(c) 2017 S. CARTER ENTERPRISES, LLC. MARKETED BY ROC NATION & DISTRIBUTED BY ROC NATION/UMG RECORDINGS INC.", "mediaMetadata": { "tags": "HIRES_LOSSLESS" }, "properties": { "content": "explicit" }, "artifactType": "string" }, "id": "4328473", "status": 200, "message": "success" } ], "videos": [ { "resource": { "id": "75623239", "title": "Kill Jay Z", "version": "Kill Jay Z", "image": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ], "album": { "id": "75413011", "title": "4:44", "imageCover": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ], "videoCover": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ] }, "releaseDate": "2017-06-27", "artists": [ { "id": "7804", "name": "JAY Z", "picture": [ { "url": "https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg", "width": 80, "height": 80 } ], "main": true } ], "duration": 30, "trackNumber": 30, "volumeNumber": 30, "isrc": "TIDAL2274", "copyright": "(p)(c) 2017 S. CARTER ENTERPRISES, LLC. MARKETED BY ROC NATION & DISTRIBUTED BY ROC NATION/UMG RECORDINGS INC.", "properties": { "video-type": "live-stream", "content": "explicit" }, "artifactType": "string" }, "id": "4328473", "status": 200, "message": "success" } ]}''',
200,
);
});
final tidalAuthToken = TidalAuthToken(
accessToken: 'accessToken',
tokenType: 'tokenType',
expiresIn: 86400,
createdAt: DateTime(2023),
);
const query = 'jayz';
const countryCode = 'US';
const expectedUrl =
'https://openapi.tidal.com/search?query=jayz&offset=0&limit=10&countryCode=US&popularity=WORLDWIDE';

final expectedImage = TidalImage(
url:
"https://resources.tidal.com/images/717dfdae/beb0/4aea/a553/a70064c30386/80x80.jpg",
width: 80,
height: 80,
);
final expectedArtist = TidalMediaArtist(
main: true,
id: "7804",
name: "JAY Z",
picture: [
expectedImage,
],
);
final expectedAlbum = TidalAlbum(
id: "75413011",
barcodeId: "00854242007552",
title: "4:44",
duration: 2777,
releaseDate: DateTime(2017, 06, 30),
numberOfVolumes: 1,
numberOfTracks: 13,
numberOfVideos: 0,
type: "ALBUM",
copyright:
"(p)(c) 2017 S. CARTER ENTERPRISES, LLC. MARKETED BY ROC NATION & DISTRIBUTED BY ROC NATION/UMG RECORDINGS INC.",
artists: [
expectedArtist,
],
imageCover: [expectedImage],
videoCover: [expectedImage],
mediaMetadata: {"tags": "HIRES_LOSSLESS"},
properties: {"content": "explicit"},
);

final expectedMedia = TidalMedia(
id: '75623239',
version: 'Kill Jay Z',
duration: 30,
title: 'Kill Jay Z',
copyright:
"(p)(c) 2017 S. CARTER ENTERPRISES, LLC. MARKETED BY ROC NATION & DISTRIBUTED BY ROC NATION/UMG RECORDINGS INC.",
artists: [expectedArtist],
album: expectedAlbum,
trackNumber: 30,
volumeNumber: 30,
isrc: "TIDAL2274",
providerId: "string",
albumId: "string",
artifactType: "string",
properties: {
"content": "explicit",
},
mediaMetadata: {"tags": "HIRES_LOSSLESS"},
);

final expectedResult = TidalSearchResponse(
albums: MultipleResponse(
items: [
MultipleResponseItem(
id: "4328473",
status: 200,
message: "success",
data: expectedAlbum,
),
],
),
artists: MultipleResponse(
items: [
MultipleResponseItem(
id: "4328473",
status: 200,
message: "success",
data: expectedArtist,
),
],
),
tracks: MultipleResponse(
items: [
MultipleResponseItem(
id: "4328473",
status: 200,
message: "success",
data: expectedMedia,
),
],
),
videos: MultipleResponse(
items: [
MultipleResponseItem(
id: "4328473",
status: 200,
message: "success",
data: expectedMedia,
),
],
),
);

// Act
final result = await searchForCatalogItemsImpl(
client,
tidalAuthToken: tidalAuthToken,
query: query,
countryCode: countryCode,
popularity: TidalSearchPopularity.WORLDWIDE,
);
// Assert
expect(actualUrl, expectedUrl);
expect(result, expectedResult);
});
});
}

0 comments on commit 7197789

Please sign in to comment.