From dea78b89b9e115f345f68ad7a458562ea72aacc7 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Mon, 7 Sep 2026 00:27:51 +0200 Subject: [PATCH] test(library): pin collections, both as a library and as an item "Is Collections broken too?" deserved an answer from the suite rather than from reading the query. Both, and they behave differently. A Collections *library* was hit by the same defect as Books and Photos -- unmapped collection_type, no include_item_types, so the library clause matched every cached row -- and is fixed by the same change. The unknown-type test now covers boxsets, photos, homevideos and the empty collection_type Jellyfin sends for a mixed library, instead of standing on books alone. An individual collection is a different path and keeps working: a BoxSet's members carry parent_id, which the cache does store, so they match the ordinary parent link rather than the library clause. That is worth its own test because narrowing the clause could plausibly have taken collections with it, and "Collections is empty" would look identical to the bug being fixed. --- docs/requirements.md | 1 + src-tauri/src/repository/offline.rs | 65 +++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/docs/requirements.md b/docs/requirements.md index 3b094aa65..0cb04646b 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -819,6 +819,7 @@ Internal architecture, components, and application logic. | UT-246 | Opening a PiP window disarms background audio, and a background signal arriving with the native PiP flag false is still treated as PiP while the frontend's latch says the window is open — without resurrecting one it has already seen close | DR-266 | Done | | UT-247 | A library whose `collection_type` has no mapping — Books, Photos, a mixed library — does not return the server's films, albums and shows from cache | DR-277 | Done | | UT-248 | Narrowing the library clause does not starve the libraries that do have landing pages: music, movies and TV each still list their own media and none of the others | DR-277 | Done | +| UT-249 | Opening an individual collection still lists its own children: a BoxSet's members are matched by the stored `parent_id`, not by the library clause, so narrowing that clause did not empty collections | DR-277 | Done | ### Integration Tests | Test ID | Test Description | Traces To | Status | diff --git a/src-tauri/src/repository/offline.rs b/src-tauri/src/repository/offline.rs index 037754217..e493dd171 100644 --- a/src-tauri/src/repository/offline.rs +++ b/src-tauri/src/repository/offline.rs @@ -5037,15 +5037,67 @@ mod tests { #[tokio::test] async fn test_get_items_unknown_library_type_does_not_return_whole_server() { let db = create_test_db(); - seed_library(&db, "books-lib", "books").await; insert_item(&db, "movie-1", "Movie", None, None, None).await; insert_item(&db, "album-1", "MusicAlbum", None, None, None).await; insert_item(&db, "series-1", "Series", None, None, None).await; + // Every library kind the app has no landing page for, including the + // empty `collection_type` Jellyfin sends for a mixed library. + for collection_type in ["books", "boxsets", "photos", "homevideos", ""] { + let lib = format!("lib-{collection_type}"); + seed_library(&db, &lib, collection_type).await; + + let repo = make_repo(&db); + let ids: Vec = repo + .get_items(&lib, None) + .await + .unwrap() + .items + .iter() + .map(|i| i.id.clone()) + .collect(); + + assert!( + ids.is_empty(), + "a '{collection_type}' library must not serve the server's films, \ + albums and shows; got {:?}", + ids + ); + } + } + + /// Opening an individual collection is a different path and must keep + /// working: a BoxSet's children carry `parent_id`, which the cache does + /// store, so they are matched by the ordinary parent link rather than by + /// the library clause this fix narrowed. + /// + /// Worth pinning separately — narrowing the library clause could plausibly + /// have taken collections with it, and "Collections is empty" would look + /// identical to the bug it was meant to fix. + /// + /// TRACES: UR-007 | DR-277 | UT-249 + #[tokio::test] + async fn test_get_items_collection_lists_its_own_children() { + let db = create_test_db(); + seed_library(&db, "boxset-lib", "boxsets").await; + + insert_item(&db, "boxset-1", "BoxSet", None, None, None).await; + insert_item(&db, "outsider", "Movie", None, None, None).await; + + // A film inside the collection: linked by parent_id, which is what a + // BoxSet's children actually carry. + db.execute(Query::with_params( + "INSERT INTO items (id, server_id, name, item_type, parent_id, synced_at) \ + VALUES ('in-set', 'test-server', 'In The Set', 'Movie', ?1, '2024-01-01')", + vec![QueryParam::String("boxset-1".to_string())], + )) + .await + .unwrap(); + let repo = make_repo(&db); let ids: Vec = repo - .get_items("books-lib", None) + .get_items("boxset-1", None) .await .unwrap() .items @@ -5053,11 +5105,10 @@ mod tests { .map(|i| i.id.clone()) .collect(); - assert!( - !ids.contains(&"movie-1".to_string()) - && !ids.contains(&"album-1".to_string()) - && !ids.contains(&"series-1".to_string()), - "a Books library must not serve the server's films, albums and shows; got {:?}", + assert_eq!( + ids, + vec!["in-set".to_string()], + "a collection lists its own children and nothing else; got {:?}", ids ); }