update
Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled
Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled
This commit is contained in:
329
persistence/genre_repository_test.go
Normal file
329
persistence/genre_repository_test.go
Normal file
@@ -0,0 +1,329 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/deluan/rest"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/id"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("GenreRepository", func() {
|
||||
var repo model.GenreRepository
|
||||
var restRepo model.ResourceRepository
|
||||
var tagRepo model.TagRepository
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = request.WithUser(GinkgoT().Context(), model.User{ID: "userid", UserName: "johndoe", IsAdmin: true})
|
||||
genreRepo := NewGenreRepository(ctx, GetDBXBuilder())
|
||||
repo = genreRepo
|
||||
restRepo = genreRepo.(model.ResourceRepository)
|
||||
tagRepo = NewTagRepository(ctx, GetDBXBuilder())
|
||||
|
||||
// Clear any existing tags to ensure test isolation
|
||||
db := GetDBXBuilder()
|
||||
_, err := db.NewQuery("DELETE FROM tag").Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Ensure library 1 exists and user has access to it
|
||||
_, err = db.NewQuery("INSERT OR IGNORE INTO library (id, name, path, default_new_users) VALUES (1, 'Test Library', '/test', true)").Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = db.NewQuery("INSERT OR IGNORE INTO user_library (user_id, library_id) VALUES ('userid', 1)").Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Add comprehensive test data that covers all test scenarios
|
||||
newTag := func(name, value string) model.Tag {
|
||||
return model.Tag{ID: id.NewTagID(name, value), TagName: model.TagName(name), TagValue: value}
|
||||
}
|
||||
|
||||
err = tagRepo.Add(1,
|
||||
newTag("genre", "rock"),
|
||||
newTag("genre", "pop"),
|
||||
newTag("genre", "jazz"),
|
||||
newTag("genre", "electronic"),
|
||||
newTag("genre", "classical"),
|
||||
newTag("genre", "ambient"),
|
||||
newTag("genre", "techno"),
|
||||
newTag("genre", "house"),
|
||||
newTag("genre", "trance"),
|
||||
newTag("genre", "Alternative Rock"),
|
||||
newTag("genre", "Blues"),
|
||||
newTag("genre", "Country"),
|
||||
// These should not be counted as genres
|
||||
newTag("mood", "happy"),
|
||||
newTag("mood", "ambient"),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
Describe("GetAll", func() {
|
||||
It("should return all genres", func() {
|
||||
genres, err := repo.GetAll()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(genres).To(HaveLen(12))
|
||||
|
||||
// Verify that all returned items are genres (TagName = "genre")
|
||||
genreNames := make([]string, len(genres))
|
||||
for i, genre := range genres {
|
||||
genreNames[i] = genre.Name
|
||||
}
|
||||
Expect(genreNames).To(ContainElement("rock"))
|
||||
Expect(genreNames).To(ContainElement("pop"))
|
||||
Expect(genreNames).To(ContainElement("jazz"))
|
||||
// Should not contain mood tags
|
||||
Expect(genreNames).ToNot(ContainElement("happy"))
|
||||
})
|
||||
|
||||
It("should support query options", func() {
|
||||
// Test with limiting results
|
||||
genres, err := repo.GetAll(model.QueryOptions{Max: 1})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(genres).To(HaveLen(1))
|
||||
})
|
||||
|
||||
It("should handle empty results gracefully", func() {
|
||||
// Clear all genre tags
|
||||
_, err := GetDBXBuilder().NewQuery("DELETE FROM tag WHERE tag_name = 'genre'").Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
genres, err := repo.GetAll()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(genres).To(BeEmpty())
|
||||
})
|
||||
Describe("filtering and sorting", func() {
|
||||
It("should filter by name using like match", func() {
|
||||
// Test filtering by partial name match using the "name" filter which maps to containsFilter("tag_value")
|
||||
options := model.QueryOptions{
|
||||
Filters: squirrel.Like{"tag_value": "%rock%"}, // Direct field access
|
||||
}
|
||||
genres, err := repo.GetAll(options)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(genres).To(HaveLen(2)) // Should match "rock" and "Alternative Rock"
|
||||
|
||||
// Verify all returned genres contain "rock" in their name
|
||||
for _, genre := range genres {
|
||||
Expect(strings.ToLower(genre.Name)).To(ContainSubstring("rock"))
|
||||
}
|
||||
})
|
||||
|
||||
It("should sort by name in ascending order", func() {
|
||||
// Test sorting by name with the fixed mapping
|
||||
options := model.QueryOptions{
|
||||
Filters: squirrel.Like{"tag_value": "%e%"}, // Should match genres containing "e"
|
||||
Sort: "name",
|
||||
}
|
||||
genres, err := repo.GetAll(options)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(genres).To(HaveLen(7))
|
||||
|
||||
Expect(slices.IsSortedFunc(genres, func(a, b model.Genre) int {
|
||||
return strings.Compare(b.Name, a.Name) // Inverted to check descending order
|
||||
}))
|
||||
})
|
||||
|
||||
It("should sort by name in descending order", func() {
|
||||
// Test sorting by name in descending order
|
||||
options := model.QueryOptions{
|
||||
Filters: squirrel.Like{"tag_value": "%e%"}, // Should match genres containing "e"
|
||||
Sort: "name",
|
||||
Order: "desc",
|
||||
}
|
||||
genres, err := repo.GetAll(options)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(genres).To(HaveLen(7))
|
||||
|
||||
Expect(slices.IsSortedFunc(genres, func(a, b model.Genre) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Count", func() {
|
||||
It("should return correct count of genres", func() {
|
||||
count, err := restRepo.Count()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(count).To(Equal(int64(12))) // We have 12 genre tags
|
||||
})
|
||||
|
||||
It("should handle zero count", func() {
|
||||
// Clear all genre tags
|
||||
_, err := GetDBXBuilder().NewQuery("DELETE FROM tag WHERE tag_name = 'genre'").Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
count, err := restRepo.Count()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(count).To(BeZero())
|
||||
})
|
||||
|
||||
It("should only count genre tags", func() {
|
||||
// Add a non-genre tag
|
||||
nonGenreTag := model.Tag{
|
||||
ID: id.NewTagID("mood", "energetic"),
|
||||
TagName: "mood",
|
||||
TagValue: "energetic",
|
||||
}
|
||||
err := tagRepo.Add(1, nonGenreTag)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
count, err := restRepo.Count()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// Count should not include the mood tag
|
||||
Expect(count).To(Equal(int64(12))) // Should still be 12 genre tags
|
||||
})
|
||||
|
||||
It("should filter by name using like match", func() {
|
||||
// Test filtering by partial name match using the "name" filter which maps to containsFilter("tag_value")
|
||||
options := rest.QueryOptions{
|
||||
Filters: map[string]interface{}{"name": "%rock%"},
|
||||
}
|
||||
count, err := restRepo.Count(options)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(count).To(BeNumerically("==", 2))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Read", func() {
|
||||
It("should return existing genre", func() {
|
||||
// Use one of the existing genres from our consolidated dataset
|
||||
genreID := id.NewTagID("genre", "rock")
|
||||
result, err := restRepo.Read(genreID)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
genre := result.(*model.Genre)
|
||||
Expect(genre.ID).To(Equal(genreID))
|
||||
Expect(genre.Name).To(Equal("rock"))
|
||||
})
|
||||
|
||||
It("should return error for non-existent genre", func() {
|
||||
_, err := restRepo.Read("non-existent-id")
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should not return non-genre tags", func() {
|
||||
moodID := id.NewTagID("mood", "happy") // This exists as a mood tag, not genre
|
||||
_, err := restRepo.Read(moodID)
|
||||
Expect(err).To(HaveOccurred()) // Should not find it as a genre
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ReadAll", func() {
|
||||
It("should return all genres through ReadAll", func() {
|
||||
result, err := restRepo.ReadAll()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
genres := result.(model.Genres)
|
||||
Expect(genres).To(HaveLen(12)) // We have 12 genre tags
|
||||
|
||||
genreNames := make([]string, len(genres))
|
||||
for i, genre := range genres {
|
||||
genreNames[i] = genre.Name
|
||||
}
|
||||
// Check for some of our consolidated dataset genres
|
||||
Expect(genreNames).To(ContainElement("rock"))
|
||||
Expect(genreNames).To(ContainElement("pop"))
|
||||
Expect(genreNames).To(ContainElement("jazz"))
|
||||
})
|
||||
|
||||
It("should support rest query options", func() {
|
||||
result, err := restRepo.ReadAll()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(result).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Library Filtering", func() {
|
||||
Context("Headless Processes (No User Context)", func() {
|
||||
var headlessRepo model.GenreRepository
|
||||
var headlessRestRepo model.ResourceRepository
|
||||
|
||||
BeforeEach(func() {
|
||||
// Create a repository with no user context (headless)
|
||||
headlessGenreRepo := NewGenreRepository(context.Background(), GetDBXBuilder())
|
||||
headlessRepo = headlessGenreRepo
|
||||
headlessRestRepo = headlessGenreRepo.(model.ResourceRepository)
|
||||
|
||||
// Add genres to different libraries
|
||||
db := GetDBXBuilder()
|
||||
_, err := db.NewQuery("INSERT OR IGNORE INTO library (id, name, path) VALUES (2, 'Test Library 2', '/test2')").Execute()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Add tags to different libraries
|
||||
newTag := func(name, value string) model.Tag {
|
||||
return model.Tag{ID: id.NewTagID(name, value), TagName: model.TagName(name), TagValue: value}
|
||||
}
|
||||
|
||||
err = tagRepo.Add(2, newTag("genre", "jazz"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should see all genres from all libraries when no user is in context", func() {
|
||||
// Headless processes should see all genres regardless of library
|
||||
genres, err := headlessRepo.GetAll()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Should see genres from all libraries
|
||||
var genreNames []string
|
||||
for _, genre := range genres {
|
||||
genreNames = append(genreNames, genre.Name)
|
||||
}
|
||||
|
||||
// Should include both rock (library 1) and jazz (library 2)
|
||||
Expect(genreNames).To(ContainElement("rock"))
|
||||
Expect(genreNames).To(ContainElement("jazz"))
|
||||
})
|
||||
|
||||
It("should count all genres from all libraries when no user is in context", func() {
|
||||
count, err := headlessRestRepo.Count()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Should count all genres from all libraries
|
||||
Expect(count).To(BeNumerically(">=", 2))
|
||||
})
|
||||
|
||||
It("should allow headless processes to apply explicit library_id filters", func() {
|
||||
// Filter by specific library
|
||||
genres, err := headlessRestRepo.ReadAll(rest.QueryOptions{
|
||||
Filters: map[string]interface{}{"library_id": 2},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
genreList := genres.(model.Genres)
|
||||
// Should see only genres from library 2
|
||||
Expect(genreList).To(HaveLen(1))
|
||||
Expect(genreList[0].Name).To(Equal("jazz"))
|
||||
})
|
||||
|
||||
It("should get individual genres when no user is in context", func() {
|
||||
// Get all genres first to find an ID
|
||||
genres, err := headlessRepo.GetAll()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(genres).ToNot(BeEmpty())
|
||||
|
||||
// Headless process should be able to get the genre
|
||||
genre, err := headlessRestRepo.Read(genres[0].ID)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(genre).ToNot(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("EntityName", func() {
|
||||
It("should return correct entity name", func() {
|
||||
name := restRepo.EntityName()
|
||||
Expect(name).To(Equal("tag")) // Genre repository uses tag table
|
||||
})
|
||||
})
|
||||
|
||||
Describe("NewInstance", func() {
|
||||
It("should return new genre instance", func() {
|
||||
instance := restRepo.NewInstance()
|
||||
Expect(instance).To(BeAssignableToTypeOf(&model.Genre{}))
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user