import 'dart:convert'; import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:minstrel/api/endpoints/library.dart'; /// Stub adapter that returns a fixed JSON response for any request. The /// body argument can be either a Map or a List (the artist-tracks endpoint /// emits a top-level JSON array). Per-test wiring keeps the routing trivial. class _StubAdapter implements HttpClientAdapter { _StubAdapter(this._body); final Object _body; @override Future fetch( RequestOptions options, Stream? requestStream, Future? cancelFuture, ) async { return ResponseBody.fromString( jsonEncode(_body), 200, headers: const { Headers.contentTypeHeader: ['application/json'], }, ); } @override void close({bool force = false}) {} } Dio _dioWith(Object body) { final d = Dio(BaseOptions(baseUrl: 'http://example.test')); d.httpClientAdapter = _StubAdapter(body); return d; } void main() { group('LibraryApi', () { test('getAlbum parses album fields and tracks list', () async { final api = LibraryApi(_dioWith({ 'id': 'al-1', 'title': 'Geogaddi', 'sort_title': 'geogaddi', 'artist_id': 'art-1', 'artist_name': 'Boards of Canada', 'year': 2002, 'track_count': 1, 'duration_sec': 312, 'cover_url': '/api/albums/al-1/cover', 'tracks': [ { 'id': 't-1', 'title': 'Music Is Math', 'album_id': 'al-1', 'album_title': 'Geogaddi', 'artist_id': 'art-1', 'artist_name': 'Boards of Canada', 'duration_sec': 312, 'track_number': 4, 'stream_url': '/api/tracks/t-1/stream', }, ], })); final r = await api.getAlbum('al-1'); expect(r.album.id, 'al-1'); expect(r.album.title, 'Geogaddi'); expect(r.album.artistName, 'Boards of Canada'); expect(r.album.year, 2002); expect(r.tracks, hasLength(1)); expect(r.tracks.single.title, 'Music Is Math'); expect(r.tracks.single.durationSec, 312); expect(r.tracks.single.trackNumber, 4); }); test('getArtist parses ArtistDetail body as ArtistRef', () async { final api = LibraryApi(_dioWith({ 'id': 'art-1', 'name': 'Boards of Canada', 'sort_name': 'boards of canada', 'album_count': 5, 'cover_url': '/api/albums/al-1/cover', 'albums': const [], })); final a = await api.getArtist('art-1'); expect(a.id, 'art-1'); expect(a.name, 'Boards of Canada'); expect(a.albumCount, 5); }); test('getArtistAlbums extracts the albums array', () async { final api = LibraryApi(_dioWith({ 'id': 'art-1', 'name': 'Boards of Canada', 'albums': [ { 'id': 'al-1', 'title': 'Geogaddi', 'artist_id': 'art-1', 'artist_name': 'Boards of Canada', }, { 'id': 'al-2', 'title': 'The Campfire Headphase', 'artist_id': 'art-1', 'artist_name': 'Boards of Canada', }, ], })); final albums = await api.getArtistAlbums('art-1'); expect(albums, hasLength(2)); expect(albums.first.id, 'al-1'); expect(albums.last.title, 'The Campfire Headphase'); }); test('getArtistTracks parses a top-level JSON array', () async { // Server emits a bare []TrackRef, NOT {"tracks": [...]}. final api = LibraryApi(_dioWith(>[ { 'id': 't-1', 'title': 'Music Is Math', 'album_id': 'al-1', 'album_title': 'Geogaddi', 'artist_id': 'art-1', 'artist_name': 'Boards of Canada', 'duration_sec': 312, }, { 'id': 't-2', 'title': 'Dawn Chorus', 'album_id': 'al-1', 'album_title': 'Geogaddi', 'artist_id': 'art-1', 'artist_name': 'Boards of Canada', 'duration_sec': 168, }, ])); final tracks = await api.getArtistTracks('art-1'); expect(tracks, hasLength(2)); expect(tracks.first.id, 't-1'); expect(tracks.last.durationSec, 168); }); test('getHome parses HomePayload sections', () async { final api = LibraryApi(_dioWith({ 'recently_added_albums': [ { 'id': 'al-1', 'title': 'Geogaddi', 'artist_id': 'art-1', 'artist_name': 'Boards of Canada', }, ], 'rediscover_albums': const [], 'rediscover_artists': const [], 'most_played_tracks': const [], 'last_played_artists': [ {'id': 'art-1', 'name': 'Boards of Canada'}, ], })); final home = await api.getHome(); expect(home.recentlyAddedAlbums.single.title, 'Geogaddi'); expect(home.lastPlayedArtists.single.name, 'Boards of Canada'); expect(home.rediscoverAlbums, isEmpty); }); }); }