feat(artists): show provider-neutral upcoming concerts

This commit is contained in:
zarzet
2026-09-25 10:58:19 +07:00
parent 3ca881b169
commit a9898e8b30
18 changed files with 804 additions and 3 deletions
@@ -88,6 +88,7 @@ impl Backend {
"listeners".into(),
json!(value["listeners"].as_i64().unwrap_or_default()),
);
info.insert("concerts".into(), array(value, "concerts").into());
for key in ["albums", "releases"] {
let values = array(value, key);
if !values.is_empty() {
@@ -450,6 +451,7 @@ fn response(kind: &str, value: &Value, check: &Check<'_>) -> Result<Value, Resol
);
info.insert("images".into(), json!(image));
info.insert("cover_url".into(), json!(cover));
info.insert("concerts".into(), array(value, "concerts").into());
if value["listeners"].as_i64().unwrap_or_default() > 0 {
info.insert("listeners".into(), value["listeners"].clone());
}
@@ -19,9 +19,13 @@ function collection(id) {
}
function getArtist(id) {
return {id, name: "Example Artist", image_url: "https://example.invalid/portrait.jpg",
concerts: id === "bad-concerts" ? {} : [{id: "event-1", location: "Example City",
venue: "Example Hall", startAt: "2026-10-07T01:00:00Z", timeZone: "America/New_York",
url: "https://example.invalid/events/1"}],
headerLogo: "https://example.invalid/logo.png", albumsNext: "artist-page-2", albums: []};
}
registerExtension({getAlbum: collection, getPlaylist: collection, getArtist});
function handleUrl() { return {type: "artist", artist: getArtist("artist-1")}; }
registerExtension({getAlbum: collection, getPlaylist: collection, getArtist, handleUrl});
"#;
fn fixture() -> (tempfile::TempDir, Backend) {
@@ -31,7 +35,8 @@ fn fixture() -> (tempfile::TempDir, Backend) {
std::fs::write(
source.join("manifest.json"),
json!({"name":ID,"displayName":"Example Metadata","version":"1",
"description":"Generic metadata fixture","type":["metadata_provider"]})
"description":"Generic metadata fixture","type":["metadata_provider"],
"urlHandler":{"enabled":true,"patterns":["example.invalid"]}})
.to_string(),
)
.unwrap();
@@ -104,6 +109,37 @@ fn artist_metadata_preserves_logo_separately_from_portrait() {
backend.shutdown();
}
#[test]
fn artist_concerts_survive_metadata_and_url_routes_without_provider_special_cases() {
let (_root, backend) = fixture();
let metadata: Value = serde_json::from_str(
&backend
.get_provider_metadata_json(ID, "artist", "artist-1", &|| Ok(()))
.unwrap(),
)
.unwrap();
let concerts = &metadata["artist_info"]["concerts"];
assert_eq!(concerts[0]["start_at"], "2026-10-07T01:00:00Z");
assert_eq!(concerts[0]["time_zone"], "America/New_York");
assert_eq!(concerts[0]["venue"], "Example Hall");
let handled: Value = serde_json::from_str(
&backend
.handle_url_json("https://example.invalid/artist/1")
.unwrap(),
)
.unwrap();
assert_eq!(&handled["artist"]["concerts"], concerts);
let malformed: Value = serde_json::from_str(
&backend
.get_provider_metadata_json(ID, "artist", "bad-concerts", &|| Ok(()))
.unwrap(),
)
.unwrap();
assert_eq!(malformed["artist_info"]["concerts"], json!([]));
assert_eq!(malformed["artist_info"]["name"], "Example Artist");
backend.shutdown();
}
#[test]
fn metadata_value_bridge_preserves_album_playlist_and_public_json() {
let (_root, backend) = fixture();
@@ -200,8 +200,16 @@
if (albums.length) result.albums = albums;
if (releases.length) result.releases = releases;
if (tracks.length) result.top_tracks = tracks;
const concertItems = first(object(value), ["concerts"]);
const concerts = isArray(concertItems) ? array(concertItems.slice(0, 500), concert) : [];
if (concerts.length) result.concerts = concerts;
return result;
}
function concert(value) {
return fields(value, [["id","s"],["location","s"],["venue","s"],
["start_at","s","startAt"],["time_zone","s","timeZone"],
["url","s"]], ["id","location","start_at"]);
}
function decryption(value) {
if (empty(value)) return null;
const result = fields(value, [["strategy","s"],["key","s"],["iv","s"],