quintodrome/persistence/share_repository_test.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

396 lines
15 KiB
Go
Raw Normal View History

fix(server): headless library access improvements (#4362) * fix: enable library access for headless processes Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present. Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations. Key changes: - Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId - Refactored tag repository to use helper method for library filtering logic - Fixed SQL aggregation bug in tag statistics calculation across multiple libraries - Added comprehensive test coverage for headless process scenarios - Updated genre repository to support proper column mappings for aggregated data This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access. Signed-off-by: Deluan <deluan@navidrome.org> * fix: resolve SQL ambiguity errors in share queries Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR. The Multi-library changes introduced JOINs between album and library tables, both of which have 'id' columns, causing 'ambiguous column name: id' errors when unqualified column references were used in WHERE clauses. Changes made: - Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums - Updated persistence/share_repository.go to use 'album.id' in album share loading - Updated persistence/sql_participations.go to use 'artist.id' for consistency - Added regression tests to prevent future SQL ambiguity issues This resolves HTTP 500 errors that users experienced when accessing existing share URLs after the Multi-library feature was merged. Signed-off-by: Deluan <deluan@navidrome.org> * fix: improve headless library access handling Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit) Squashed commits: [a28c6965b] fix: remove headless library access guard Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering. The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship. * fix: simplify genre selection query in genre repository Signed-off-by: Deluan <deluan@navidrome.org> * fix: enhance tag library filtering tests for headless access Signed-off-by: Deluan <deluan@navidrome.org> * test: add comprehensive test coverage for headless library access Added extensive test coverage for headless library access improvements including: - Added 17 new tests across 4 test files covering headless access scenarios - artist_repository_test.go: Added headless process tests for GetAll, Count, Get operations and explicit library_id filtering functionality - genre_repository_test.go: Added library filtering tests for headless processes including GetAll, Count, ReadAll, and Read operations - sql_base_repository_test.go: Added applyLibraryFilter method tests covering admin users, regular users, and headless processes with/without custom table names - share_repository_test.go: Added headless access tests and SQL ambiguity verification for the album.id vs id fix in loadMedia function - Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context() and removing unnecessary configtest.SetupConfig() calls for better test isolation These tests ensure that headless processes (background operations without user context) can access all libraries while respecting explicit filters, and verify that the SQL ambiguity fixes work correctly without breaking existing functionality. * revert: remove user context handling from scrobble buffer getParticipants Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869. The artist repository no longer requires user context for proper library filtering, so the workaround of temporarily injecting user context into the scrobbleBufferRepository.Next method is no longer needed. This simplifies the code and removes the dependency on fetching user information during background scrobbling operations. * fix: improve library access filtering for artists Enhanced artist repository filtering to properly handle library access restrictions and prevent artists with no accessible content from appearing in results. Backend changes: - Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance - Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content) - Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering - Added condition to exclude artists where library_artist.stats = '{}' (empty content) Frontend changes: - Added null-checking in getCounter function to prevent TypeError when accessing undefined records - Improved optional chaining for safer property access in role-based statistics display These changes ensure that users only see artists that have actual accessible content in their permitted libraries, fixing issues where artists appeared in the list despite having no albums or songs available to the user. * fix: update library access logic for non-admin users and enhance test coverage Signed-off-by: Deluan <deluan@navidrome.org> * fix: refine library artist query and implement cleanup for empty entries Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate artist repository tests to eliminate duplication Significantly refactored artist_repository_test.go to reduce code duplication and improve maintainability by ~27% (930 to 680 lines). Key improvements include: - Added test helper functions createTestArtistWithMBID() and createUserWithLibraries() to eliminate repetitive test data creation - Consolidated duplicate MBID search tests using DescribeTable for parameterized testing - Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that duplicated functionality already covered in other test contexts - Reorganized search tests into cohesive 'MBID and Text Search' section with proper setup/teardown and shared test infrastructure - Streamlined missing artist tests and moved them to dedicated section - Maintained 100% test coverage while eliminating redundant test patterns All tests continue to pass with identical functionality and coverage. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-20 11:58:21 -08:00
package persistence
import (
"context"
"time"
"github.com/deluan/rest"
fix(server): headless library access improvements (#4362) * fix: enable library access for headless processes Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present. Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations. Key changes: - Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId - Refactored tag repository to use helper method for library filtering logic - Fixed SQL aggregation bug in tag statistics calculation across multiple libraries - Added comprehensive test coverage for headless process scenarios - Updated genre repository to support proper column mappings for aggregated data This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access. Signed-off-by: Deluan <deluan@navidrome.org> * fix: resolve SQL ambiguity errors in share queries Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR. The Multi-library changes introduced JOINs between album and library tables, both of which have 'id' columns, causing 'ambiguous column name: id' errors when unqualified column references were used in WHERE clauses. Changes made: - Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums - Updated persistence/share_repository.go to use 'album.id' in album share loading - Updated persistence/sql_participations.go to use 'artist.id' for consistency - Added regression tests to prevent future SQL ambiguity issues This resolves HTTP 500 errors that users experienced when accessing existing share URLs after the Multi-library feature was merged. Signed-off-by: Deluan <deluan@navidrome.org> * fix: improve headless library access handling Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit) Squashed commits: [a28c6965b] fix: remove headless library access guard Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering. The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship. * fix: simplify genre selection query in genre repository Signed-off-by: Deluan <deluan@navidrome.org> * fix: enhance tag library filtering tests for headless access Signed-off-by: Deluan <deluan@navidrome.org> * test: add comprehensive test coverage for headless library access Added extensive test coverage for headless library access improvements including: - Added 17 new tests across 4 test files covering headless access scenarios - artist_repository_test.go: Added headless process tests for GetAll, Count, Get operations and explicit library_id filtering functionality - genre_repository_test.go: Added library filtering tests for headless processes including GetAll, Count, ReadAll, and Read operations - sql_base_repository_test.go: Added applyLibraryFilter method tests covering admin users, regular users, and headless processes with/without custom table names - share_repository_test.go: Added headless access tests and SQL ambiguity verification for the album.id vs id fix in loadMedia function - Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context() and removing unnecessary configtest.SetupConfig() calls for better test isolation These tests ensure that headless processes (background operations without user context) can access all libraries while respecting explicit filters, and verify that the SQL ambiguity fixes work correctly without breaking existing functionality. * revert: remove user context handling from scrobble buffer getParticipants Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869. The artist repository no longer requires user context for proper library filtering, so the workaround of temporarily injecting user context into the scrobbleBufferRepository.Next method is no longer needed. This simplifies the code and removes the dependency on fetching user information during background scrobbling operations. * fix: improve library access filtering for artists Enhanced artist repository filtering to properly handle library access restrictions and prevent artists with no accessible content from appearing in results. Backend changes: - Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance - Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content) - Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering - Added condition to exclude artists where library_artist.stats = '{}' (empty content) Frontend changes: - Added null-checking in getCounter function to prevent TypeError when accessing undefined records - Improved optional chaining for safer property access in role-based statistics display These changes ensure that users only see artists that have actual accessible content in their permitted libraries, fixing issues where artists appeared in the list despite having no albums or songs available to the user. * fix: update library access logic for non-admin users and enhance test coverage Signed-off-by: Deluan <deluan@navidrome.org> * fix: refine library artist query and implement cleanup for empty entries Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate artist repository tests to eliminate duplication Significantly refactored artist_repository_test.go to reduce code duplication and improve maintainability by ~27% (930 to 680 lines). Key improvements include: - Added test helper functions createTestArtistWithMBID() and createUserWithLibraries() to eliminate repetitive test data creation - Consolidated duplicate MBID search tests using DescribeTable for parameterized testing - Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that duplicated functionality already covered in other test contexts - Reorganized search tests into cohesive 'MBID and Text Search' section with proper setup/teardown and shared test infrastructure - Streamlined missing artist tests and moved them to dedicated section - Maintained 100% test coverage while eliminating redundant test patterns All tests continue to pass with identical functionality and coverage. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-20 11:58:21 -08:00
"github.com/navidrome/navidrome/conf/configtest"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("ShareRepository", func() {
var repo model.ShareRepository
var ctx context.Context
var adminUser = model.User{ID: "admin", UserName: "admin", IsAdmin: true}
BeforeEach(func() {
DeferCleanup(configtest.SetupConfig())
ctx = request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
fix(server): headless library access improvements (#4362) * fix: enable library access for headless processes Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present. Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations. Key changes: - Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId - Refactored tag repository to use helper method for library filtering logic - Fixed SQL aggregation bug in tag statistics calculation across multiple libraries - Added comprehensive test coverage for headless process scenarios - Updated genre repository to support proper column mappings for aggregated data This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access. Signed-off-by: Deluan <deluan@navidrome.org> * fix: resolve SQL ambiguity errors in share queries Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR. The Multi-library changes introduced JOINs between album and library tables, both of which have 'id' columns, causing 'ambiguous column name: id' errors when unqualified column references were used in WHERE clauses. Changes made: - Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums - Updated persistence/share_repository.go to use 'album.id' in album share loading - Updated persistence/sql_participations.go to use 'artist.id' for consistency - Added regression tests to prevent future SQL ambiguity issues This resolves HTTP 500 errors that users experienced when accessing existing share URLs after the Multi-library feature was merged. Signed-off-by: Deluan <deluan@navidrome.org> * fix: improve headless library access handling Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit) Squashed commits: [a28c6965b] fix: remove headless library access guard Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering. The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship. * fix: simplify genre selection query in genre repository Signed-off-by: Deluan <deluan@navidrome.org> * fix: enhance tag library filtering tests for headless access Signed-off-by: Deluan <deluan@navidrome.org> * test: add comprehensive test coverage for headless library access Added extensive test coverage for headless library access improvements including: - Added 17 new tests across 4 test files covering headless access scenarios - artist_repository_test.go: Added headless process tests for GetAll, Count, Get operations and explicit library_id filtering functionality - genre_repository_test.go: Added library filtering tests for headless processes including GetAll, Count, ReadAll, and Read operations - sql_base_repository_test.go: Added applyLibraryFilter method tests covering admin users, regular users, and headless processes with/without custom table names - share_repository_test.go: Added headless access tests and SQL ambiguity verification for the album.id vs id fix in loadMedia function - Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context() and removing unnecessary configtest.SetupConfig() calls for better test isolation These tests ensure that headless processes (background operations without user context) can access all libraries while respecting explicit filters, and verify that the SQL ambiguity fixes work correctly without breaking existing functionality. * revert: remove user context handling from scrobble buffer getParticipants Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869. The artist repository no longer requires user context for proper library filtering, so the workaround of temporarily injecting user context into the scrobbleBufferRepository.Next method is no longer needed. This simplifies the code and removes the dependency on fetching user information during background scrobbling operations. * fix: improve library access filtering for artists Enhanced artist repository filtering to properly handle library access restrictions and prevent artists with no accessible content from appearing in results. Backend changes: - Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance - Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content) - Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering - Added condition to exclude artists where library_artist.stats = '{}' (empty content) Frontend changes: - Added null-checking in getCounter function to prevent TypeError when accessing undefined records - Improved optional chaining for safer property access in role-based statistics display These changes ensure that users only see artists that have actual accessible content in their permitted libraries, fixing issues where artists appeared in the list despite having no albums or songs available to the user. * fix: update library access logic for non-admin users and enhance test coverage Signed-off-by: Deluan <deluan@navidrome.org> * fix: refine library artist query and implement cleanup for empty entries Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate artist repository tests to eliminate duplication Significantly refactored artist_repository_test.go to reduce code duplication and improve maintainability by ~27% (930 to 680 lines). Key improvements include: - Added test helper functions createTestArtistWithMBID() and createUserWithLibraries() to eliminate repetitive test data creation - Consolidated duplicate MBID search tests using DescribeTable for parameterized testing - Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that duplicated functionality already covered in other test contexts - Reorganized search tests into cohesive 'MBID and Text Search' section with proper setup/teardown and shared test infrastructure - Streamlined missing artist tests and moved them to dedicated section - Maintained 100% test coverage while eliminating redundant test patterns All tests continue to pass with identical functionality and coverage. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-20 11:58:21 -08:00
repo = NewShareRepository(ctx, GetDBXBuilder())
// Insert the admin user into the database (required for foreign key constraint)
ur := NewUserRepository(ctx, GetDBXBuilder())
err := ur.Put(&adminUser)
Expect(err).ToNot(HaveOccurred())
// Clean up shares
db := GetDBXBuilder()
_, err = db.NewQuery("DELETE FROM share").Execute()
Expect(err).ToNot(HaveOccurred())
})
Describe("Headless Access", func() {
Context("Repository creation and basic operations", func() {
It("should create repository successfully with no user context", func() {
// Create repository with no user context (headless)
headlessRepo := NewShareRepository(GinkgoT().Context(), GetDBXBuilder())
fix(server): headless library access improvements (#4362) * fix: enable library access for headless processes Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present. Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations. Key changes: - Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId - Refactored tag repository to use helper method for library filtering logic - Fixed SQL aggregation bug in tag statistics calculation across multiple libraries - Added comprehensive test coverage for headless process scenarios - Updated genre repository to support proper column mappings for aggregated data This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access. Signed-off-by: Deluan <deluan@navidrome.org> * fix: resolve SQL ambiguity errors in share queries Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR. The Multi-library changes introduced JOINs between album and library tables, both of which have 'id' columns, causing 'ambiguous column name: id' errors when unqualified column references were used in WHERE clauses. Changes made: - Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums - Updated persistence/share_repository.go to use 'album.id' in album share loading - Updated persistence/sql_participations.go to use 'artist.id' for consistency - Added regression tests to prevent future SQL ambiguity issues This resolves HTTP 500 errors that users experienced when accessing existing share URLs after the Multi-library feature was merged. Signed-off-by: Deluan <deluan@navidrome.org> * fix: improve headless library access handling Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit) Squashed commits: [a28c6965b] fix: remove headless library access guard Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering. The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship. * fix: simplify genre selection query in genre repository Signed-off-by: Deluan <deluan@navidrome.org> * fix: enhance tag library filtering tests for headless access Signed-off-by: Deluan <deluan@navidrome.org> * test: add comprehensive test coverage for headless library access Added extensive test coverage for headless library access improvements including: - Added 17 new tests across 4 test files covering headless access scenarios - artist_repository_test.go: Added headless process tests for GetAll, Count, Get operations and explicit library_id filtering functionality - genre_repository_test.go: Added library filtering tests for headless processes including GetAll, Count, ReadAll, and Read operations - sql_base_repository_test.go: Added applyLibraryFilter method tests covering admin users, regular users, and headless processes with/without custom table names - share_repository_test.go: Added headless access tests and SQL ambiguity verification for the album.id vs id fix in loadMedia function - Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context() and removing unnecessary configtest.SetupConfig() calls for better test isolation These tests ensure that headless processes (background operations without user context) can access all libraries while respecting explicit filters, and verify that the SQL ambiguity fixes work correctly without breaking existing functionality. * revert: remove user context handling from scrobble buffer getParticipants Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869. The artist repository no longer requires user context for proper library filtering, so the workaround of temporarily injecting user context into the scrobbleBufferRepository.Next method is no longer needed. This simplifies the code and removes the dependency on fetching user information during background scrobbling operations. * fix: improve library access filtering for artists Enhanced artist repository filtering to properly handle library access restrictions and prevent artists with no accessible content from appearing in results. Backend changes: - Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance - Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content) - Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering - Added condition to exclude artists where library_artist.stats = '{}' (empty content) Frontend changes: - Added null-checking in getCounter function to prevent TypeError when accessing undefined records - Improved optional chaining for safer property access in role-based statistics display These changes ensure that users only see artists that have actual accessible content in their permitted libraries, fixing issues where artists appeared in the list despite having no albums or songs available to the user. * fix: update library access logic for non-admin users and enhance test coverage Signed-off-by: Deluan <deluan@navidrome.org> * fix: refine library artist query and implement cleanup for empty entries Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate artist repository tests to eliminate duplication Significantly refactored artist_repository_test.go to reduce code duplication and improve maintainability by ~27% (930 to 680 lines). Key improvements include: - Added test helper functions createTestArtistWithMBID() and createUserWithLibraries() to eliminate repetitive test data creation - Consolidated duplicate MBID search tests using DescribeTable for parameterized testing - Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that duplicated functionality already covered in other test contexts - Reorganized search tests into cohesive 'MBID and Text Search' section with proper setup/teardown and shared test infrastructure - Streamlined missing artist tests and moved them to dedicated section - Maintained 100% test coverage while eliminating redundant test patterns All tests continue to pass with identical functionality and coverage. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-20 11:58:21 -08:00
Expect(headlessRepo).ToNot(BeNil())
})
It("should handle GetAll for headless processes", func() {
// Create a simple share directly in database
shareID := "headless-test-share"
_, err := GetDBXBuilder().NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
2026-02-08 05:57:30 -09:00
`).Bind(map[string]any{
fix(server): headless library access improvements (#4362) * fix: enable library access for headless processes Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present. Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations. Key changes: - Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId - Refactored tag repository to use helper method for library filtering logic - Fixed SQL aggregation bug in tag statistics calculation across multiple libraries - Added comprehensive test coverage for headless process scenarios - Updated genre repository to support proper column mappings for aggregated data This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access. Signed-off-by: Deluan <deluan@navidrome.org> * fix: resolve SQL ambiguity errors in share queries Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR. The Multi-library changes introduced JOINs between album and library tables, both of which have 'id' columns, causing 'ambiguous column name: id' errors when unqualified column references were used in WHERE clauses. Changes made: - Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums - Updated persistence/share_repository.go to use 'album.id' in album share loading - Updated persistence/sql_participations.go to use 'artist.id' for consistency - Added regression tests to prevent future SQL ambiguity issues This resolves HTTP 500 errors that users experienced when accessing existing share URLs after the Multi-library feature was merged. Signed-off-by: Deluan <deluan@navidrome.org> * fix: improve headless library access handling Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit) Squashed commits: [a28c6965b] fix: remove headless library access guard Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering. The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship. * fix: simplify genre selection query in genre repository Signed-off-by: Deluan <deluan@navidrome.org> * fix: enhance tag library filtering tests for headless access Signed-off-by: Deluan <deluan@navidrome.org> * test: add comprehensive test coverage for headless library access Added extensive test coverage for headless library access improvements including: - Added 17 new tests across 4 test files covering headless access scenarios - artist_repository_test.go: Added headless process tests for GetAll, Count, Get operations and explicit library_id filtering functionality - genre_repository_test.go: Added library filtering tests for headless processes including GetAll, Count, ReadAll, and Read operations - sql_base_repository_test.go: Added applyLibraryFilter method tests covering admin users, regular users, and headless processes with/without custom table names - share_repository_test.go: Added headless access tests and SQL ambiguity verification for the album.id vs id fix in loadMedia function - Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context() and removing unnecessary configtest.SetupConfig() calls for better test isolation These tests ensure that headless processes (background operations without user context) can access all libraries while respecting explicit filters, and verify that the SQL ambiguity fixes work correctly without breaking existing functionality. * revert: remove user context handling from scrobble buffer getParticipants Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869. The artist repository no longer requires user context for proper library filtering, so the workaround of temporarily injecting user context into the scrobbleBufferRepository.Next method is no longer needed. This simplifies the code and removes the dependency on fetching user information during background scrobbling operations. * fix: improve library access filtering for artists Enhanced artist repository filtering to properly handle library access restrictions and prevent artists with no accessible content from appearing in results. Backend changes: - Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance - Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content) - Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering - Added condition to exclude artists where library_artist.stats = '{}' (empty content) Frontend changes: - Added null-checking in getCounter function to prevent TypeError when accessing undefined records - Improved optional chaining for safer property access in role-based statistics display These changes ensure that users only see artists that have actual accessible content in their permitted libraries, fixing issues where artists appeared in the list despite having no albums or songs available to the user. * fix: update library access logic for non-admin users and enhance test coverage Signed-off-by: Deluan <deluan@navidrome.org> * fix: refine library artist query and implement cleanup for empty entries Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate artist repository tests to eliminate duplication Significantly refactored artist_repository_test.go to reduce code duplication and improve maintainability by ~27% (930 to 680 lines). Key improvements include: - Added test helper functions createTestArtistWithMBID() and createUserWithLibraries() to eliminate repetitive test data creation - Consolidated duplicate MBID search tests using DescribeTable for parameterized testing - Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that duplicated functionality already covered in other test contexts - Reorganized search tests into cohesive 'MBID and Text Search' section with proper setup/teardown and shared test infrastructure - Streamlined missing artist tests and moved them to dedicated section - Maintained 100% test coverage while eliminating redundant test patterns All tests continue to pass with identical functionality and coverage. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-20 11:58:21 -08:00
"id": shareID,
"user": adminUser.ID,
"desc": "Headless Test Share",
"type": "song",
"ids": "song-1",
"created": time.Now(),
"updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
// Headless process should see all shares
headlessRepo := NewShareRepository(GinkgoT().Context(), GetDBXBuilder())
fix(server): headless library access improvements (#4362) * fix: enable library access for headless processes Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present. Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations. Key changes: - Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId - Refactored tag repository to use helper method for library filtering logic - Fixed SQL aggregation bug in tag statistics calculation across multiple libraries - Added comprehensive test coverage for headless process scenarios - Updated genre repository to support proper column mappings for aggregated data This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access. Signed-off-by: Deluan <deluan@navidrome.org> * fix: resolve SQL ambiguity errors in share queries Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR. The Multi-library changes introduced JOINs between album and library tables, both of which have 'id' columns, causing 'ambiguous column name: id' errors when unqualified column references were used in WHERE clauses. Changes made: - Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums - Updated persistence/share_repository.go to use 'album.id' in album share loading - Updated persistence/sql_participations.go to use 'artist.id' for consistency - Added regression tests to prevent future SQL ambiguity issues This resolves HTTP 500 errors that users experienced when accessing existing share URLs after the Multi-library feature was merged. Signed-off-by: Deluan <deluan@navidrome.org> * fix: improve headless library access handling Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit) Squashed commits: [a28c6965b] fix: remove headless library access guard Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering. The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship. * fix: simplify genre selection query in genre repository Signed-off-by: Deluan <deluan@navidrome.org> * fix: enhance tag library filtering tests for headless access Signed-off-by: Deluan <deluan@navidrome.org> * test: add comprehensive test coverage for headless library access Added extensive test coverage for headless library access improvements including: - Added 17 new tests across 4 test files covering headless access scenarios - artist_repository_test.go: Added headless process tests for GetAll, Count, Get operations and explicit library_id filtering functionality - genre_repository_test.go: Added library filtering tests for headless processes including GetAll, Count, ReadAll, and Read operations - sql_base_repository_test.go: Added applyLibraryFilter method tests covering admin users, regular users, and headless processes with/without custom table names - share_repository_test.go: Added headless access tests and SQL ambiguity verification for the album.id vs id fix in loadMedia function - Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context() and removing unnecessary configtest.SetupConfig() calls for better test isolation These tests ensure that headless processes (background operations without user context) can access all libraries while respecting explicit filters, and verify that the SQL ambiguity fixes work correctly without breaking existing functionality. * revert: remove user context handling from scrobble buffer getParticipants Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869. The artist repository no longer requires user context for proper library filtering, so the workaround of temporarily injecting user context into the scrobbleBufferRepository.Next method is no longer needed. This simplifies the code and removes the dependency on fetching user information during background scrobbling operations. * fix: improve library access filtering for artists Enhanced artist repository filtering to properly handle library access restrictions and prevent artists with no accessible content from appearing in results. Backend changes: - Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance - Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content) - Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering - Added condition to exclude artists where library_artist.stats = '{}' (empty content) Frontend changes: - Added null-checking in getCounter function to prevent TypeError when accessing undefined records - Improved optional chaining for safer property access in role-based statistics display These changes ensure that users only see artists that have actual accessible content in their permitted libraries, fixing issues where artists appeared in the list despite having no albums or songs available to the user. * fix: update library access logic for non-admin users and enhance test coverage Signed-off-by: Deluan <deluan@navidrome.org> * fix: refine library artist query and implement cleanup for empty entries Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate artist repository tests to eliminate duplication Significantly refactored artist_repository_test.go to reduce code duplication and improve maintainability by ~27% (930 to 680 lines). Key improvements include: - Added test helper functions createTestArtistWithMBID() and createUserWithLibraries() to eliminate repetitive test data creation - Consolidated duplicate MBID search tests using DescribeTable for parameterized testing - Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that duplicated functionality already covered in other test contexts - Reorganized search tests into cohesive 'MBID and Text Search' section with proper setup/teardown and shared test infrastructure - Streamlined missing artist tests and moved them to dedicated section - Maintained 100% test coverage while eliminating redundant test patterns All tests continue to pass with identical functionality and coverage. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-20 11:58:21 -08:00
shares, err := headlessRepo.GetAll()
Expect(err).ToNot(HaveOccurred())
found := false
for _, s := range shares {
if s.ID == shareID {
found = true
break
}
}
Expect(found).To(BeTrue(), "Headless process should see all shares")
})
It("should handle individual share retrieval for headless processes", func() {
// Create a simple share
shareID := "headless-get-share"
_, err := GetDBXBuilder().NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
2026-02-08 05:57:30 -09:00
`).Bind(map[string]any{
fix(server): headless library access improvements (#4362) * fix: enable library access for headless processes Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present. Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations. Key changes: - Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId - Refactored tag repository to use helper method for library filtering logic - Fixed SQL aggregation bug in tag statistics calculation across multiple libraries - Added comprehensive test coverage for headless process scenarios - Updated genre repository to support proper column mappings for aggregated data This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access. Signed-off-by: Deluan <deluan@navidrome.org> * fix: resolve SQL ambiguity errors in share queries Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR. The Multi-library changes introduced JOINs between album and library tables, both of which have 'id' columns, causing 'ambiguous column name: id' errors when unqualified column references were used in WHERE clauses. Changes made: - Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums - Updated persistence/share_repository.go to use 'album.id' in album share loading - Updated persistence/sql_participations.go to use 'artist.id' for consistency - Added regression tests to prevent future SQL ambiguity issues This resolves HTTP 500 errors that users experienced when accessing existing share URLs after the Multi-library feature was merged. Signed-off-by: Deluan <deluan@navidrome.org> * fix: improve headless library access handling Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit) Squashed commits: [a28c6965b] fix: remove headless library access guard Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering. The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship. * fix: simplify genre selection query in genre repository Signed-off-by: Deluan <deluan@navidrome.org> * fix: enhance tag library filtering tests for headless access Signed-off-by: Deluan <deluan@navidrome.org> * test: add comprehensive test coverage for headless library access Added extensive test coverage for headless library access improvements including: - Added 17 new tests across 4 test files covering headless access scenarios - artist_repository_test.go: Added headless process tests for GetAll, Count, Get operations and explicit library_id filtering functionality - genre_repository_test.go: Added library filtering tests for headless processes including GetAll, Count, ReadAll, and Read operations - sql_base_repository_test.go: Added applyLibraryFilter method tests covering admin users, regular users, and headless processes with/without custom table names - share_repository_test.go: Added headless access tests and SQL ambiguity verification for the album.id vs id fix in loadMedia function - Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context() and removing unnecessary configtest.SetupConfig() calls for better test isolation These tests ensure that headless processes (background operations without user context) can access all libraries while respecting explicit filters, and verify that the SQL ambiguity fixes work correctly without breaking existing functionality. * revert: remove user context handling from scrobble buffer getParticipants Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869. The artist repository no longer requires user context for proper library filtering, so the workaround of temporarily injecting user context into the scrobbleBufferRepository.Next method is no longer needed. This simplifies the code and removes the dependency on fetching user information during background scrobbling operations. * fix: improve library access filtering for artists Enhanced artist repository filtering to properly handle library access restrictions and prevent artists with no accessible content from appearing in results. Backend changes: - Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance - Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content) - Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering - Added condition to exclude artists where library_artist.stats = '{}' (empty content) Frontend changes: - Added null-checking in getCounter function to prevent TypeError when accessing undefined records - Improved optional chaining for safer property access in role-based statistics display These changes ensure that users only see artists that have actual accessible content in their permitted libraries, fixing issues where artists appeared in the list despite having no albums or songs available to the user. * fix: update library access logic for non-admin users and enhance test coverage Signed-off-by: Deluan <deluan@navidrome.org> * fix: refine library artist query and implement cleanup for empty entries Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate artist repository tests to eliminate duplication Significantly refactored artist_repository_test.go to reduce code duplication and improve maintainability by ~27% (930 to 680 lines). Key improvements include: - Added test helper functions createTestArtistWithMBID() and createUserWithLibraries() to eliminate repetitive test data creation - Consolidated duplicate MBID search tests using DescribeTable for parameterized testing - Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that duplicated functionality already covered in other test contexts - Reorganized search tests into cohesive 'MBID and Text Search' section with proper setup/teardown and shared test infrastructure - Streamlined missing artist tests and moved them to dedicated section - Maintained 100% test coverage while eliminating redundant test patterns All tests continue to pass with identical functionality and coverage. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-20 11:58:21 -08:00
"id": shareID,
"user": adminUser.ID,
"desc": "Headless Get Share",
"type": "song",
"ids": "song-2",
"created": time.Now(),
"updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
// Headless process should be able to get the share
headlessRepo := NewShareRepository(GinkgoT().Context(), GetDBXBuilder())
fix(server): headless library access improvements (#4362) * fix: enable library access for headless processes Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present. Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations. Key changes: - Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId - Refactored tag repository to use helper method for library filtering logic - Fixed SQL aggregation bug in tag statistics calculation across multiple libraries - Added comprehensive test coverage for headless process scenarios - Updated genre repository to support proper column mappings for aggregated data This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access. Signed-off-by: Deluan <deluan@navidrome.org> * fix: resolve SQL ambiguity errors in share queries Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR. The Multi-library changes introduced JOINs between album and library tables, both of which have 'id' columns, causing 'ambiguous column name: id' errors when unqualified column references were used in WHERE clauses. Changes made: - Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums - Updated persistence/share_repository.go to use 'album.id' in album share loading - Updated persistence/sql_participations.go to use 'artist.id' for consistency - Added regression tests to prevent future SQL ambiguity issues This resolves HTTP 500 errors that users experienced when accessing existing share URLs after the Multi-library feature was merged. Signed-off-by: Deluan <deluan@navidrome.org> * fix: improve headless library access handling Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit) Squashed commits: [a28c6965b] fix: remove headless library access guard Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering. The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship. * fix: simplify genre selection query in genre repository Signed-off-by: Deluan <deluan@navidrome.org> * fix: enhance tag library filtering tests for headless access Signed-off-by: Deluan <deluan@navidrome.org> * test: add comprehensive test coverage for headless library access Added extensive test coverage for headless library access improvements including: - Added 17 new tests across 4 test files covering headless access scenarios - artist_repository_test.go: Added headless process tests for GetAll, Count, Get operations and explicit library_id filtering functionality - genre_repository_test.go: Added library filtering tests for headless processes including GetAll, Count, ReadAll, and Read operations - sql_base_repository_test.go: Added applyLibraryFilter method tests covering admin users, regular users, and headless processes with/without custom table names - share_repository_test.go: Added headless access tests and SQL ambiguity verification for the album.id vs id fix in loadMedia function - Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context() and removing unnecessary configtest.SetupConfig() calls for better test isolation These tests ensure that headless processes (background operations without user context) can access all libraries while respecting explicit filters, and verify that the SQL ambiguity fixes work correctly without breaking existing functionality. * revert: remove user context handling from scrobble buffer getParticipants Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869. The artist repository no longer requires user context for proper library filtering, so the workaround of temporarily injecting user context into the scrobbleBufferRepository.Next method is no longer needed. This simplifies the code and removes the dependency on fetching user information during background scrobbling operations. * fix: improve library access filtering for artists Enhanced artist repository filtering to properly handle library access restrictions and prevent artists with no accessible content from appearing in results. Backend changes: - Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance - Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content) - Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering - Added condition to exclude artists where library_artist.stats = '{}' (empty content) Frontend changes: - Added null-checking in getCounter function to prevent TypeError when accessing undefined records - Improved optional chaining for safer property access in role-based statistics display These changes ensure that users only see artists that have actual accessible content in their permitted libraries, fixing issues where artists appeared in the list despite having no albums or songs available to the user. * fix: update library access logic for non-admin users and enhance test coverage Signed-off-by: Deluan <deluan@navidrome.org> * fix: refine library artist query and implement cleanup for empty entries Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate artist repository tests to eliminate duplication Significantly refactored artist_repository_test.go to reduce code duplication and improve maintainability by ~27% (930 to 680 lines). Key improvements include: - Added test helper functions createTestArtistWithMBID() and createUserWithLibraries() to eliminate repetitive test data creation - Consolidated duplicate MBID search tests using DescribeTable for parameterized testing - Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that duplicated functionality already covered in other test contexts - Reorganized search tests into cohesive 'MBID and Text Search' section with proper setup/teardown and shared test infrastructure - Streamlined missing artist tests and moved them to dedicated section - Maintained 100% test coverage while eliminating redundant test patterns All tests continue to pass with identical functionality and coverage. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-20 11:58:21 -08:00
share, err := headlessRepo.Get(shareID)
Expect(err).ToNot(HaveOccurred())
Expect(share.ID).To(Equal(shareID))
Expect(share.Description).To(Equal("Headless Get Share"))
})
})
})
Describe("SQL ambiguity fix verification", func() {
It("should handle share operations without SQL ambiguity errors", func() {
// This test verifies that the loadMedia function doesn't cause SQL ambiguity
// The key fix was using "album.id" instead of "id" in the album query filters
// Create a share that would trigger the loadMedia function
shareID := "sql-test-share"
_, err := GetDBXBuilder().NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
2026-02-08 05:57:30 -09:00
`).Bind(map[string]any{
fix(server): headless library access improvements (#4362) * fix: enable library access for headless processes Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present. Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations. Key changes: - Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId - Refactored tag repository to use helper method for library filtering logic - Fixed SQL aggregation bug in tag statistics calculation across multiple libraries - Added comprehensive test coverage for headless process scenarios - Updated genre repository to support proper column mappings for aggregated data This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access. Signed-off-by: Deluan <deluan@navidrome.org> * fix: resolve SQL ambiguity errors in share queries Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR. The Multi-library changes introduced JOINs between album and library tables, both of which have 'id' columns, causing 'ambiguous column name: id' errors when unqualified column references were used in WHERE clauses. Changes made: - Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums - Updated persistence/share_repository.go to use 'album.id' in album share loading - Updated persistence/sql_participations.go to use 'artist.id' for consistency - Added regression tests to prevent future SQL ambiguity issues This resolves HTTP 500 errors that users experienced when accessing existing share URLs after the Multi-library feature was merged. Signed-off-by: Deluan <deluan@navidrome.org> * fix: improve headless library access handling Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit) Squashed commits: [a28c6965b] fix: remove headless library access guard Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering. The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship. * fix: simplify genre selection query in genre repository Signed-off-by: Deluan <deluan@navidrome.org> * fix: enhance tag library filtering tests for headless access Signed-off-by: Deluan <deluan@navidrome.org> * test: add comprehensive test coverage for headless library access Added extensive test coverage for headless library access improvements including: - Added 17 new tests across 4 test files covering headless access scenarios - artist_repository_test.go: Added headless process tests for GetAll, Count, Get operations and explicit library_id filtering functionality - genre_repository_test.go: Added library filtering tests for headless processes including GetAll, Count, ReadAll, and Read operations - sql_base_repository_test.go: Added applyLibraryFilter method tests covering admin users, regular users, and headless processes with/without custom table names - share_repository_test.go: Added headless access tests and SQL ambiguity verification for the album.id vs id fix in loadMedia function - Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context() and removing unnecessary configtest.SetupConfig() calls for better test isolation These tests ensure that headless processes (background operations without user context) can access all libraries while respecting explicit filters, and verify that the SQL ambiguity fixes work correctly without breaking existing functionality. * revert: remove user context handling from scrobble buffer getParticipants Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869. The artist repository no longer requires user context for proper library filtering, so the workaround of temporarily injecting user context into the scrobbleBufferRepository.Next method is no longer needed. This simplifies the code and removes the dependency on fetching user information during background scrobbling operations. * fix: improve library access filtering for artists Enhanced artist repository filtering to properly handle library access restrictions and prevent artists with no accessible content from appearing in results. Backend changes: - Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance - Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content) - Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering - Added condition to exclude artists where library_artist.stats = '{}' (empty content) Frontend changes: - Added null-checking in getCounter function to prevent TypeError when accessing undefined records - Improved optional chaining for safer property access in role-based statistics display These changes ensure that users only see artists that have actual accessible content in their permitted libraries, fixing issues where artists appeared in the list despite having no albums or songs available to the user. * fix: update library access logic for non-admin users and enhance test coverage Signed-off-by: Deluan <deluan@navidrome.org> * fix: refine library artist query and implement cleanup for empty entries Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate artist repository tests to eliminate duplication Significantly refactored artist_repository_test.go to reduce code duplication and improve maintainability by ~27% (930 to 680 lines). Key improvements include: - Added test helper functions createTestArtistWithMBID() and createUserWithLibraries() to eliminate repetitive test data creation - Consolidated duplicate MBID search tests using DescribeTable for parameterized testing - Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that duplicated functionality already covered in other test contexts - Reorganized search tests into cohesive 'MBID and Text Search' section with proper setup/teardown and shared test infrastructure - Streamlined missing artist tests and moved them to dedicated section - Maintained 100% test coverage while eliminating redundant test patterns All tests continue to pass with identical functionality and coverage. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-20 11:58:21 -08:00
"id": shareID,
"user": adminUser.ID,
"desc": "SQL Test Share",
"type": "album",
"ids": "non-existent-album", // Won't find albums, but shouldn't cause SQL errors
"created": time.Now(),
"updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
// The Get operation should work without SQL ambiguity errors
// even if no albums are found
share, err := repo.Get(shareID)
Expect(err).ToNot(HaveOccurred())
Expect(share.ID).To(Equal(shareID))
// Albums array should be empty since we used non-existent album ID
Expect(share.Albums).To(BeEmpty())
})
})
Describe("Ownership Checks", func() {
var ownerUser = model.User{ID: "2222", UserName: "regular-user"}
var otherUser = model.User{ID: "3333", UserName: "third-user"}
insertShare := func(shareID, userID string) {
_, err := GetDBXBuilder().NewQuery(`
INSERT INTO share (id, user_id, description, resource_type, resource_ids, created_at, updated_at)
VALUES ({:id}, {:user}, {:desc}, {:type}, {:ids}, {:created}, {:updated})
`).Bind(map[string]any{
"id": shareID,
"user": userID,
"desc": "Test Share",
"type": "media_file",
"ids": "1001",
"created": time.Now(),
"updated": time.Now(),
}).Execute()
Expect(err).ToNot(HaveOccurred())
}
Describe("Delete", func() {
It("allows a non-admin user to delete their own share", func() {
insertShare("own-share-del", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), ownerUser)
repo := NewShareRepository(ctx, GetDBXBuilder())
err := repo.(rest.Persistable).Delete("own-share-del")
Expect(err).ToNot(HaveOccurred())
})
It("denies a non-admin user from deleting another user's share", func() {
insertShare("other-share-del", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), otherUser)
repo := NewShareRepository(ctx, GetDBXBuilder())
err := repo.(rest.Persistable).Delete("other-share-del")
Expect(err).To(Equal(rest.ErrPermissionDenied))
// The share was not deleted: the owner can still read it.
ownerCtx := request.WithUser(log.NewContext(GinkgoT().Context()), ownerUser)
ownerRepo := NewShareRepository(ownerCtx, GetDBXBuilder())
_, err = ownerRepo.(rest.Repository).Read("other-share-del")
Expect(err).ToNot(HaveOccurred())
})
It("allows an admin to delete any user's share", func() {
insertShare("admin-del-share", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
repo := NewShareRepository(ctx, GetDBXBuilder())
err := repo.(rest.Persistable).Delete("admin-del-share")
Expect(err).ToNot(HaveOccurred())
})
It("allows headless context (no user) to delete a share", func() {
insertShare("headless-del-share", ownerUser.ID)
repo := NewShareRepository(GinkgoT().Context(), GetDBXBuilder())
err := repo.(rest.Persistable).Delete("headless-del-share")
Expect(err).ToNot(HaveOccurred())
})
})
Describe("Update", func() {
It("allows a non-admin user to update their own share", func() {
insertShare("own-share-upd", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), ownerUser)
repo := NewShareRepository(ctx, GetDBXBuilder())
err := repo.(rest.Persistable).Update("own-share-upd", &model.Share{Description: "Updated"}, "description")
Expect(err).ToNot(HaveOccurred())
})
It("denies a non-admin user from updating another user's share", func() {
insertShare("other-share-upd", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), otherUser)
repo := NewShareRepository(ctx, GetDBXBuilder())
err := repo.(rest.Persistable).Update("other-share-upd", &model.Share{Description: "Hacked"}, "description")
Expect(err).To(Equal(rest.ErrPermissionDenied))
})
It("allows an admin to update any user's share", func() {
insertShare("admin-upd-share", ownerUser.ID)
ctx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
repo := NewShareRepository(ctx, GetDBXBuilder())
err := repo.(rest.Persistable).Update("admin-upd-share", &model.Share{Description: "Admin Updated"}, "description")
Expect(err).ToNot(HaveOccurred())
})
It("allows headless context (no user) to update a share", func() {
insertShare("headless-upd-share", ownerUser.ID)
repo := NewShareRepository(GinkgoT().Context(), GetDBXBuilder())
err := repo.(rest.Persistable).Update("headless-upd-share", &model.Share{Description: "Headless"}, "description")
Expect(err).ToNot(HaveOccurred())
})
fix: enforce ownership atomically on player and share updates (#5563) * fix(player): enforce ownership atomically on player update The native API PUT /api/player/{id} authorized writes using the userId in the request body via isPermitted, while the actual write targeted the row by the URL id. A non-admin user could set userId to their own id in the body to pass the check, then overwrite and reassign ownership of another user's player row identified by the URL id (cross-tenant takeover). Add updateOwned on the base repository: an atomic, ownership-restricted UPDATE that folds the owner predicate (user_id = caller) into the WHERE clause for non-admins, so a row owned by another user simply does not match and no write happens. It also never writes user_id, so ownership is immutable on update and no caller (admin included) can reassign a player to a different owner. Unlike put, it never falls through to an INSERT, so a non-matching id returns ErrNotFound instead of creating a row. playerRepository.Update now uses updateOwned. Extract filterUpdateValues, shared by put and updateOwned, so the update-column filtering lives in one place. The create path (Save) keeps the body-based isPermitted check, which is correct for new records. Add regression tests covering the spoofed-userId hijack, regular-user and admin ownership reassignment, legitimate owner updates, and the nonexistent-player case. * fix(share): enforce ownership atomically on share update shareRepository.Update authorized writes with a separate checkOwnership SELECT, then wrote the row via put(). The check and the write were two statements (a TOCTOU window), put() could fall through to an INSERT on a missing id, and put() would write user_id if present in the update columns, so ownership was mutable on update. Switch Update to updateOwned, which folds the owner predicate into the UPDATE's WHERE clause, never writes user_id, and never inserts. This makes the write atomic and ownership immutable, and drops the extra ownership SELECT on the happy path. To preserve the previous 403/404 distinction, updateOwned now classifies a non-matching id: it runs a follow-up existence check only on the failure path (count == 0, where no write happened, so no TOCTOU) and returns ErrPermissionDenied when the row exists but is owned by another user, ErrNotFound when the id is missing. The player path inherits this: its tests now expect ErrPermissionDenied for a non-owner targeting an existing row, and ErrNotFound only for a genuinely missing id. Add share regression tests for the nonexistent-id and ownership- reassignment cases. checkOwnership remains in use by Delete. * refactor(persistence): extract canonical ownerFilter predicate The non-admin owner-restriction predicate (user_id = me, exempting admins and headless contexts) was spelled out independently in updateOwned and in playerRepository.addRestriction. The two copies had drifted: addRestriction did not exempt the headless/invalid user, so a headless context restricted to user_id = "-1" (matching nothing) while updateOwned exempted it. Extract sqlRepository.ownerFilter as the single definition and route both call sites through it. addRestriction now exempts the headless user too; that path is only reachable from the authenticated native API, so there is no production behavior change, but the latent divergence is removed. playlistRepository.userFilter is intentionally left alone: it encodes a different policy (public OR owner_id = me, on the owner_id column). * fix(share): preserve all-columns update path in Update shareRepository.Update unconditionally appended "updated_at" to cols. filterUpdateValues treats an empty cols as "update every column", so when a caller passes no columns, appending "updated_at" turned an all-columns update into an updated_at-only one, silently dropping every other field. The REST controller always populates cols from the request-body field names, so this path is not reachable through the native API and the behavior was latent (and pre-existing). Guard the append so the all-columns path is preserved, and add a regression test that updates with no columns and asserts the other fields persist. Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
2026-06-04 15:43:13 -08:00
It("returns not found when updating a nonexistent share", func() {
ctx := request.WithUser(log.NewContext(context.TODO()), ownerUser)
repo := NewShareRepository(ctx, GetDBXBuilder())
err := repo.(rest.Persistable).Update("does-not-exist", &model.Share{Description: "Ghost"}, "description")
Expect(err).To(Equal(rest.ErrNotFound))
})
It("updates all columns when no specific columns are given", func() {
insertShare("all-cols-share", ownerUser.ID)
ctx := request.WithUser(log.NewContext(context.TODO()), ownerUser)
repo := NewShareRepository(ctx, GetDBXBuilder())
// No cols: the update must write every column, not just updated_at.
err := repo.(rest.Persistable).Update("all-cols-share",
&model.Share{Description: "All Updated", MaxBitRate: 192, ResourceType: "album", ResourceIDs: "2002"})
Expect(err).ToNot(HaveOccurred())
got, err := repo.(rest.Repository).Read("all-cols-share")
Expect(err).ToNot(HaveOccurred())
share := got.(*model.Share)
Expect(share.Description).To(Equal("All Updated"))
Expect(share.MaxBitRate).To(Equal(192))
Expect(share.ResourceType).To(Equal("album"))
})
It("does not let an owner reassign their share to another user", func() {
insertShare("reassign-share", ownerUser.ID)
ctx := request.WithUser(log.NewContext(context.TODO()), ownerUser)
repo := NewShareRepository(ctx, GetDBXBuilder())
err := repo.(rest.Persistable).Update("reassign-share",
&model.Share{UserID: otherUser.ID, Description: "Given away"}, "user_id", "description")
Expect(err).ToNot(HaveOccurred())
// Ownership must not have moved, even though user_id was passed in the body and cols.
got, err := repo.(rest.Repository).Read("reassign-share")
Expect(err).ToNot(HaveOccurred())
Expect(got.(*model.Share).UserID).To(Equal(ownerUser.ID))
})
})
Describe("Read scoping", func() {
BeforeEach(func() {
// Persist owner/other users so the JOIN in selectShare resolves.
ur := NewUserRepository(ctx, GetDBXBuilder())
Expect(ur.Put(&ownerUser)).To(Succeed())
Expect(ur.Put(&otherUser)).To(Succeed())
insertShare("share-owner-1", ownerUser.ID)
insertShare("share-owner-2", ownerUser.ID)
insertShare("share-other-1", otherUser.ID)
})
Context("non-admin user", func() {
var nonAdminRepo model.ShareRepository
var nonAdminRest rest.Repository
BeforeEach(func() {
nonAdminCtx := request.WithUser(log.NewContext(GinkgoT().Context()), ownerUser)
nonAdminRepo = NewShareRepository(nonAdminCtx, GetDBXBuilder())
nonAdminRest = nonAdminRepo.(rest.Repository)
})
It("GetAll returns only own shares", func() {
shares, err := nonAdminRepo.GetAll()
Expect(err).ToNot(HaveOccurred())
ids := make([]string, len(shares))
for i, s := range shares {
ids[i] = s.ID
}
Expect(ids).To(ConsistOf("share-owner-1", "share-owner-2"))
})
It("ReadAll returns only own shares", func() {
res, err := nonAdminRest.ReadAll()
Expect(err).ToNot(HaveOccurred())
shares := res.(model.Shares)
ids := make([]string, len(shares))
for i, s := range shares {
ids[i] = s.ID
}
Expect(ids).To(ConsistOf("share-owner-1", "share-owner-2"))
})
It("Get returns own share", func() {
s, err := nonAdminRepo.Get("share-owner-1")
Expect(err).ToNot(HaveOccurred())
Expect(s.ID).To(Equal("share-owner-1"))
})
It("Get returns ErrNotFound for another user's share", func() {
_, err := nonAdminRepo.Get("share-other-1")
Expect(err).To(MatchError(model.ErrNotFound))
})
It("Read returns ErrNotFound for another user's share", func() {
_, err := nonAdminRest.Read("share-other-1")
Expect(err).To(MatchError(model.ErrNotFound))
})
It("Exists returns true for own share", func() {
exists, err := nonAdminRepo.Exists("share-owner-1")
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeTrue())
})
It("Exists returns false for another user's share", func() {
exists, err := nonAdminRepo.Exists("share-other-1")
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeFalse())
})
It("CountAll counts only own shares", func() {
count, err := nonAdminRepo.CountAll()
Expect(err).ToNot(HaveOccurred())
Expect(count).To(BeNumerically("==", 2))
})
It("Count (rest) counts only own shares", func() {
count, err := nonAdminRest.Count()
Expect(err).ToNot(HaveOccurred())
Expect(count).To(BeNumerically("==", 2))
})
})
Context("admin user", func() {
It("GetAll returns all shares", func() {
adminCtx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
adminRepo := NewShareRepository(adminCtx, GetDBXBuilder())
shares, err := adminRepo.GetAll()
Expect(err).ToNot(HaveOccurred())
ids := make([]string, len(shares))
for i, s := range shares {
ids[i] = s.ID
}
Expect(ids).To(ConsistOf("share-owner-1", "share-owner-2", "share-other-1"))
})
It("CountAll counts all shares", func() {
adminCtx := request.WithUser(log.NewContext(GinkgoT().Context()), adminUser)
adminRepo := NewShareRepository(adminCtx, GetDBXBuilder())
count, err := adminRepo.CountAll()
Expect(err).ToNot(HaveOccurred())
Expect(count).To(BeNumerically("==", 3))
})
})
Context("headless context (public share route)", func() {
It("GetAll returns all shares", func() {
headlessRepo := NewShareRepository(GinkgoT().Context(), GetDBXBuilder())
shares, err := headlessRepo.GetAll()
Expect(err).ToNot(HaveOccurred())
Expect(shares).To(HaveLen(3))
})
It("Get returns another user's share", func() {
headlessRepo := NewShareRepository(GinkgoT().Context(), GetDBXBuilder())
s, err := headlessRepo.Get("share-other-1")
Expect(err).ToNot(HaveOccurred())
Expect(s.ID).To(Equal("share-other-1"))
})
It("Exists returns true for any share", func() {
headlessRepo := NewShareRepository(GinkgoT().Context(), GetDBXBuilder())
exists, err := headlessRepo.Exists("share-other-1")
Expect(err).ToNot(HaveOccurred())
Expect(exists).To(BeTrue())
})
})
})
})
fix(server): headless library access improvements (#4362) * fix: enable library access for headless processes Fixed multi-library filtering to allow headless processes (shares, external providers) to access data by skipping library restrictions when no user context is present. Previously, the library filtering system returned empty results (WHERE 1=0) for processes without user authentication, breaking functionality like public shares and external service integrations. Key changes: - Modified applyLibraryFilter methods to skip filtering when user.ID == invalidUserId - Refactored tag repository to use helper method for library filtering logic - Fixed SQL aggregation bug in tag statistics calculation across multiple libraries - Added comprehensive test coverage for headless process scenarios - Updated genre repository to support proper column mappings for aggregated data This preserves the secure "safe by default" approach for authenticated users while restoring backward compatibility for background processes that need unrestricted data access. Signed-off-by: Deluan <deluan@navidrome.org> * fix: resolve SQL ambiguity errors in share queries Fixed SQL ambiguity errors that were breaking share links after the Multi-library PR. The Multi-library changes introduced JOINs between album and library tables, both of which have 'id' columns, causing 'ambiguous column name: id' errors when unqualified column references were used in WHERE clauses. Changes made: - Updated core/share.go to use 'album.id' instead of 'id' in contentsLabelFromAlbums - Updated persistence/share_repository.go to use 'album.id' in album share loading - Updated persistence/sql_participations.go to use 'artist.id' for consistency - Added regression tests to prevent future SQL ambiguity issues This resolves HTTP 500 errors that users experienced when accessing existing share URLs after the Multi-library feature was merged. Signed-off-by: Deluan <deluan@navidrome.org> * fix: improve headless library access handling Added proper user context validation and reordered joins in applyLibraryFilterToArtistQuery to ensure library filtering works correctly for both authenticated and headless operations. The user_library join is now only applied when a valid user context exists, while the library_artist join is always applied to maintain proper data relationships. (+1 squashed commit) Squashed commits: [a28c6965b] fix: remove headless library access guard Removed the invalidUserId guard condition in applyLibraryFilterToArtistQuery that was preventing proper library filtering for headless operations. This fix ensures that library filtering joins are always applied consistently, allowing headless library access to work correctly with the library_artist junction table filtering. The previous guard was skipping all library filtering when no user context was present, which could cause issues with headless operations that still need to respect library boundaries through the library_artist relationship. * fix: simplify genre selection query in genre repository Signed-off-by: Deluan <deluan@navidrome.org> * fix: enhance tag library filtering tests for headless access Signed-off-by: Deluan <deluan@navidrome.org> * test: add comprehensive test coverage for headless library access Added extensive test coverage for headless library access improvements including: - Added 17 new tests across 4 test files covering headless access scenarios - artist_repository_test.go: Added headless process tests for GetAll, Count, Get operations and explicit library_id filtering functionality - genre_repository_test.go: Added library filtering tests for headless processes including GetAll, Count, ReadAll, and Read operations - sql_base_repository_test.go: Added applyLibraryFilter method tests covering admin users, regular users, and headless processes with/without custom table names - share_repository_test.go: Added headless access tests and SQL ambiguity verification for the album.id vs id fix in loadMedia function - Cleaned up test setup by replacing log.NewContext usage with GinkgoT().Context() and removing unnecessary configtest.SetupConfig() calls for better test isolation These tests ensure that headless processes (background operations without user context) can access all libraries while respecting explicit filters, and verify that the SQL ambiguity fixes work correctly without breaking existing functionality. * revert: remove user context handling from scrobble buffer getParticipants Reverts commit 5b8ef74f05109ecf30ddfc936361b84314522869. The artist repository no longer requires user context for proper library filtering, so the workaround of temporarily injecting user context into the scrobbleBufferRepository.Next method is no longer needed. This simplifies the code and removes the dependency on fetching user information during background scrobbling operations. * fix: improve library access filtering for artists Enhanced artist repository filtering to properly handle library access restrictions and prevent artists with no accessible content from appearing in results. Backend changes: - Modified roleFilter to use direct JSON_EXTRACT instead of EXISTS subquery for better performance - Enhanced applyLibraryFilterToArtistQuery to filter out artists with empty stats (no content) - Changed from LEFT JOIN to INNER JOIN with library_artist table for stricter filtering - Added condition to exclude artists where library_artist.stats = '{}' (empty content) Frontend changes: - Added null-checking in getCounter function to prevent TypeError when accessing undefined records - Improved optional chaining for safer property access in role-based statistics display These changes ensure that users only see artists that have actual accessible content in their permitted libraries, fixing issues where artists appeared in the list despite having no albums or songs available to the user. * fix: update library access logic for non-admin users and enhance test coverage Signed-off-by: Deluan <deluan@navidrome.org> * fix: refine library artist query and implement cleanup for empty entries Signed-off-by: Deluan <deluan@navidrome.org> * refactor: consolidate artist repository tests to eliminate duplication Significantly refactored artist_repository_test.go to reduce code duplication and improve maintainability by ~27% (930 to 680 lines). Key improvements include: - Added test helper functions createTestArtistWithMBID() and createUserWithLibraries() to eliminate repetitive test data creation - Consolidated duplicate MBID search tests using DescribeTable for parameterized testing - Removed entire 'Permission-Based Behavior Comparison' section (~150 lines) that duplicated functionality already covered in other test contexts - Reorganized search tests into cohesive 'MBID and Text Search' section with proper setup/teardown and shared test infrastructure - Streamlined missing artist tests and moved them to dedicated section - Maintained 100% test coverage while eliminating redundant test patterns All tests continue to pass with identical functionality and coverage. --------- Signed-off-by: Deluan <deluan@navidrome.org>
2025-07-20 11:58:21 -08:00
})