test: expand label mapping tests and clean up redundant filter logic

- Added comprehensive tests for LabelMapping deduplication and full property coverage in resolver_model_saved_filter_test.go
- Removed redundant hardcoded switch [type]Filter statements across 10 query resolvers in favor of direct models.FilterMode assignment
- Cleaned up unused fmt imports generated by the refactor
This commit is contained in:
notsafeforgit 2026-03-18 02:22:55 -07:00
parent 9e0ff975a6
commit 0c39702792
11 changed files with 176 additions and 259 deletions

View file

@ -2,8 +2,12 @@ package api
import (
"context"
"github.com/stashapp/stash/pkg/models"
"testing"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/mocks"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// We verify the `LabelMapping` function handles parsing the interface mapping without panic and extracts correct lists.
@ -23,3 +27,164 @@ func TestSavedFilterLabelMappingEmpty(t *testing.T) {
t.Errorf("expected empty mapping, got %v", mapping)
}
}
func TestSavedFilterLabelMappingComprehensive(t *testing.T) {
mockDB := mocks.NewDatabase()
resolver := &savedFilterResolver{
Resolver: &Resolver{
repository: mockDB.Repository(),
},
}
ctx := context.Background()
obj := &models.SavedFilter{
ObjectFilter: map[string]interface{}{
"tags": map[string]interface{}{
"value": []interface{}{"1", "2"},
"excludes": []interface{}{"3"},
},
"scene_tags": map[string]interface{}{
"value": []interface{}{"4"},
},
"performers": map[string]interface{}{
"value": []interface{}{"10"},
},
"studios": map[string]interface{}{
"value": []interface{}{"20"},
},
"groups": map[string]interface{}{
"value": []interface{}{"30"},
},
"galleries": map[string]interface{}{
"value": []interface{}{"40"},
},
"folders": map[string]interface{}{
"value": []interface{}{"50"},
},
"scenes": map[string]interface{}{
"value": []interface{}{"60", "61", "62"},
},
"movies": map[string]interface{}{
"value": []interface{}{"70"},
},
},
}
mockDB.Tag.On("FindMany", mock.Anything, mock.MatchedBy(func(ids []int) bool {
return len(ids) == 4
})).Return([]*models.Tag{
{ID: 1, Name: "Tag1"},
{ID: 2, Name: "Tag2"},
{ID: 3, Name: "Tag3"},
{ID: 4, Name: "Tag4"},
}, nil).Once()
mockDB.Performer.On("FindMany", mock.Anything, []int{10}).Return([]*models.Performer{
{ID: 10, Name: "Performer10"},
}, nil).Once()
mockDB.Studio.On("FindMany", mock.Anything, []int{20}).Return([]*models.Studio{
{ID: 20, Name: "Studio20"},
}, nil).Once()
mockDB.Group.On("FindMany", mock.Anything, []int{30}).Return([]*models.Group{
{ID: 30, Name: "Group30"},
}, nil).Once()
mockDB.Gallery.On("FindMany", mock.Anything, []int{40}).Return([]*models.Gallery{
{ID: 40, Title: "Gallery40"},
}, nil).Once()
mockDB.Folder.On("FindMany", mock.Anything, []models.FolderID{50}).Return([]*models.Folder{
{ID: 50, Path: "/folder/50"},
}, nil).Once()
mockDB.Scene.On("FindMany", mock.Anything, mock.MatchedBy(func(ids []int) bool {
return len(ids) == 3
})).Return([]*models.Scene{
{ID: 60, Title: "Scene60"},
{ID: 61, Details: "Scene61 Details"},
{ID: 62, Checksum: "checksum62"},
}, nil).Once()
mapping, err := resolver.LabelMapping(ctx, obj)
assert.NoError(t, err)
assert.NotNil(t, mapping)
assert.Len(t, mapping.Tags, 4)
assert.Equal(t, "Tag1", mapping.Tags[0].Label)
assert.Equal(t, "1", mapping.Tags[0].ID)
assert.Len(t, mapping.Performers, 1)
assert.Equal(t, "Performer10", mapping.Performers[0].Label)
assert.Len(t, mapping.Studios, 1)
assert.Equal(t, "Studio20", mapping.Studios[0].Label)
assert.Len(t, mapping.Groups, 1)
assert.Equal(t, "Group30", mapping.Groups[0].Label)
assert.Equal(t, "30", mapping.Groups[0].ID)
assert.Len(t, mapping.Galleries, 1)
assert.Equal(t, "Gallery40", mapping.Galleries[0].Label)
assert.Equal(t, "40", mapping.Galleries[0].ID)
assert.Len(t, mapping.Folders, 1)
assert.Equal(t, "/folder/50", mapping.Folders[0].Label)
assert.Equal(t, "50", mapping.Folders[0].ID)
assert.Len(t, mapping.Scenes, 3)
assert.Equal(t, "Scene60", mapping.Scenes[0].Label)
assert.Equal(t, "60", mapping.Scenes[0].ID)
assert.Equal(t, "Scene61 Details", mapping.Scenes[1].Label)
assert.Equal(t, "checksum62", mapping.Scenes[2].Label)
// Movies isn't implemented and should be nil
assert.Nil(t, mapping.Movies)
}
func TestSavedFilterLabelMappingDeduplication(t *testing.T) {
mockDB := mocks.NewDatabase()
resolver := &savedFilterResolver{
Resolver: &Resolver{
repository: mockDB.Repository(),
},
}
ctx := context.Background()
obj := &models.SavedFilter{
ObjectFilter: map[string]interface{}{
"tags": map[string]interface{}{
"value": []interface{}{"1", "2"},
"excludes": []interface{}{"2", "3"},
},
"scene_tags": map[string]interface{}{
"value": []interface{}{"1", "3"},
},
},
}
mockDB.Tag.On("FindMany", mock.Anything, mock.MatchedBy(func(ids []int) bool {
if len(ids) != 3 {
return false
}
// IDs should be 1, 2, 3
idMap := map[int]bool{}
for _, id := range ids {
idMap[id] = true
}
return idMap[1] && idMap[2] && idMap[3]
})).Return([]*models.Tag{
{ID: 1, Name: "Tag1"},
{ID: 2, Name: "Tag2"},
{ID: 3, Name: "Tag3"},
}, nil).Once()
mapping, err := resolver.LabelMapping(ctx, obj)
assert.NoError(t, err)
assert.NotNil(t, mapping)
assert.Len(t, mapping.Tags, 3)
}

View file

@ -61,35 +61,7 @@ func (r *queryResolver) FindFiles(
var finalFilter *models.FileFilterType
if savedFilterID != nil {
finalFilter = &models.FileFilterType{}
var mode models.FilterMode
switch "fileFilter" {
case "sceneFilter":
mode = models.FilterModeScenes
case "performerFilter":
mode = models.FilterModePerformers
case "studioFilter":
mode = models.FilterModeStudios
case "galleryFilter":
mode = models.FilterModeGalleries
case "sceneMarkerFilter":
mode = models.FilterModeSceneMarkers
case "movieFilter":
mode = models.FilterModeMovies
case "groupFilter":
mode = models.FilterModeGroups
case "tagFilter":
mode = models.FilterModeTags
case "imageFilter":
mode = models.FilterModeImages
default:
return nil, fmt.Errorf("saved filters are not supported for %s", "fileFilter")
}
mergedFindFilter, err := r.resolveSavedFilter(ctx, *savedFilterID, mode, finalFilter, filter)
if err != nil {
return nil, err
}
filter = mergedFindFilter
return nil, fmt.Errorf("saved filters are not supported for %s", "fileFilter")
} else {
finalFilter = fileFilter
}

View file

@ -56,35 +56,7 @@ func (r *queryResolver) FindFolders(
var finalFilter *models.FolderFilterType
if savedFilterID != nil {
finalFilter = &models.FolderFilterType{}
var mode models.FilterMode
switch "folderFilter" {
case "sceneFilter":
mode = models.FilterModeScenes
case "performerFilter":
mode = models.FilterModePerformers
case "studioFilter":
mode = models.FilterModeStudios
case "galleryFilter":
mode = models.FilterModeGalleries
case "sceneMarkerFilter":
mode = models.FilterModeSceneMarkers
case "movieFilter":
mode = models.FilterModeMovies
case "groupFilter":
mode = models.FilterModeGroups
case "tagFilter":
mode = models.FilterModeTags
case "imageFilter":
mode = models.FilterModeImages
default:
return nil, fmt.Errorf("saved filters are not supported for %s", "folderFilter")
}
mergedFindFilter, err := r.resolveSavedFilter(ctx, *savedFilterID, mode, finalFilter, filter)
if err != nil {
return nil, err
}
filter = mergedFindFilter
return nil, fmt.Errorf("saved filters are not supported for %s", "folderFilter")
} else {
finalFilter = folderFilter
}

View file

@ -1,8 +1,6 @@
package api
import (
"fmt"
"errors"
"context"
@ -41,29 +39,7 @@ func (r *queryResolver) FindGalleries(
var finalFilter *models.GalleryFilterType
if savedFilterID != nil {
finalFilter = &models.GalleryFilterType{}
var mode models.FilterMode
switch "galleryFilter" {
case "sceneFilter":
mode = models.FilterModeScenes
case "performerFilter":
mode = models.FilterModePerformers
case "studioFilter":
mode = models.FilterModeStudios
case "galleryFilter":
mode = models.FilterModeGalleries
case "sceneMarkerFilter":
mode = models.FilterModeSceneMarkers
case "movieFilter":
mode = models.FilterModeMovies
case "groupFilter":
mode = models.FilterModeGroups
case "tagFilter":
mode = models.FilterModeTags
case "imageFilter":
mode = models.FilterModeImages
default:
return nil, fmt.Errorf("saved filters are not supported for %s", "galleryFilter")
}
mode := models.FilterModeGalleries
mergedFindFilter, err := r.resolveSavedFilter(ctx, *savedFilterID, mode, finalFilter, filter)
if err != nil {

View file

@ -1,8 +1,6 @@
package api
import (
"fmt"
"errors"
"context"
@ -41,29 +39,7 @@ func (r *queryResolver) FindGroups(
var finalFilter *models.GroupFilterType
if savedFilterID != nil {
finalFilter = &models.GroupFilterType{}
var mode models.FilterMode
switch "groupFilter" {
case "sceneFilter":
mode = models.FilterModeScenes
case "performerFilter":
mode = models.FilterModePerformers
case "studioFilter":
mode = models.FilterModeStudios
case "galleryFilter":
mode = models.FilterModeGalleries
case "sceneMarkerFilter":
mode = models.FilterModeSceneMarkers
case "movieFilter":
mode = models.FilterModeMovies
case "groupFilter":
mode = models.FilterModeGroups
case "tagFilter":
mode = models.FilterModeTags
case "imageFilter":
mode = models.FilterModeImages
default:
return nil, fmt.Errorf("saved filters are not supported for %s", "groupFilter")
}
mode := models.FilterModeGroups
mergedFindFilter, err := r.resolveSavedFilter(ctx, *savedFilterID, mode, finalFilter, filter)
if err != nil {

View file

@ -1,8 +1,6 @@
package api
import (
"fmt"
"errors"
"context"
@ -65,29 +63,7 @@ func (r *queryResolver) FindImages(
var finalFilter *models.ImageFilterType
if savedFilterID != nil {
finalFilter = &models.ImageFilterType{}
var mode models.FilterMode
switch "imageFilter" {
case "sceneFilter":
mode = models.FilterModeScenes
case "performerFilter":
mode = models.FilterModePerformers
case "studioFilter":
mode = models.FilterModeStudios
case "galleryFilter":
mode = models.FilterModeGalleries
case "sceneMarkerFilter":
mode = models.FilterModeSceneMarkers
case "movieFilter":
mode = models.FilterModeMovies
case "groupFilter":
mode = models.FilterModeGroups
case "tagFilter":
mode = models.FilterModeTags
case "imageFilter":
mode = models.FilterModeImages
default:
return nil, fmt.Errorf("saved filters are not supported for %s", "imageFilter")
}
mode := models.FilterModeImages
mergedFindFilter, err := r.resolveSavedFilter(ctx, *savedFilterID, mode, finalFilter, filter)
if err != nil {

View file

@ -1,8 +1,6 @@
package api
import (
"fmt"
"errors"
"context"
@ -41,29 +39,7 @@ func (r *queryResolver) FindMovies(
var finalFilter *models.GroupFilterType
if savedFilterID != nil {
finalFilter = &models.GroupFilterType{}
var mode models.FilterMode
switch "movieFilter" {
case "sceneFilter":
mode = models.FilterModeScenes
case "performerFilter":
mode = models.FilterModePerformers
case "studioFilter":
mode = models.FilterModeStudios
case "galleryFilter":
mode = models.FilterModeGalleries
case "sceneMarkerFilter":
mode = models.FilterModeSceneMarkers
case "movieFilter":
mode = models.FilterModeMovies
case "groupFilter":
mode = models.FilterModeGroups
case "tagFilter":
mode = models.FilterModeTags
case "imageFilter":
mode = models.FilterModeImages
default:
return nil, fmt.Errorf("saved filters are not supported for %s", "movieFilter")
}
mode := models.FilterModeMovies
mergedFindFilter, err := r.resolveSavedFilter(ctx, *savedFilterID, mode, finalFilter, filter)
if err != nil {

View file

@ -1,8 +1,6 @@
package api
import (
"fmt"
"errors"
"context"
@ -41,29 +39,7 @@ func (r *queryResolver) FindPerformers(
var finalFilter *models.PerformerFilterType
if savedFilterID != nil {
finalFilter = &models.PerformerFilterType{}
var mode models.FilterMode
switch "performerFilter" {
case "sceneFilter":
mode = models.FilterModeScenes
case "performerFilter":
mode = models.FilterModePerformers
case "studioFilter":
mode = models.FilterModeStudios
case "galleryFilter":
mode = models.FilterModeGalleries
case "sceneMarkerFilter":
mode = models.FilterModeSceneMarkers
case "movieFilter":
mode = models.FilterModeMovies
case "groupFilter":
mode = models.FilterModeGroups
case "tagFilter":
mode = models.FilterModeTags
case "imageFilter":
mode = models.FilterModeImages
default:
return nil, fmt.Errorf("saved filters are not supported for %s", "performerFilter")
}
mode := models.FilterModePerformers
mergedFindFilter, err := r.resolveSavedFilter(ctx, *savedFilterID, mode, finalFilter, filter)
if err != nil {

View file

@ -1,8 +1,6 @@
package api
import (
"fmt"
"errors"
"context"
@ -93,29 +91,7 @@ func (r *queryResolver) FindScenes(
var finalFilter *models.SceneFilterType
if savedFilterID != nil {
finalFilter = &models.SceneFilterType{}
var mode models.FilterMode
switch "sceneFilter" {
case "sceneFilter":
mode = models.FilterModeScenes
case "performerFilter":
mode = models.FilterModePerformers
case "studioFilter":
mode = models.FilterModeStudios
case "galleryFilter":
mode = models.FilterModeGalleries
case "sceneMarkerFilter":
mode = models.FilterModeSceneMarkers
case "movieFilter":
mode = models.FilterModeMovies
case "groupFilter":
mode = models.FilterModeGroups
case "tagFilter":
mode = models.FilterModeTags
case "imageFilter":
mode = models.FilterModeImages
default:
return nil, fmt.Errorf("saved filters are not supported for %s", "sceneFilter")
}
mode := models.FilterModeScenes
mergedFindFilter, err := r.resolveSavedFilter(ctx, *savedFilterID, mode, finalFilter, filter)
if err != nil {

View file

@ -1,8 +1,6 @@
package api
import (
"fmt"
"errors"
"context"
@ -42,29 +40,7 @@ func (r *queryResolver) FindStudios(
var finalFilter *models.StudioFilterType
if savedFilterID != nil {
finalFilter = &models.StudioFilterType{}
var mode models.FilterMode
switch "studioFilter" {
case "sceneFilter":
mode = models.FilterModeScenes
case "performerFilter":
mode = models.FilterModePerformers
case "studioFilter":
mode = models.FilterModeStudios
case "galleryFilter":
mode = models.FilterModeGalleries
case "sceneMarkerFilter":
mode = models.FilterModeSceneMarkers
case "movieFilter":
mode = models.FilterModeMovies
case "groupFilter":
mode = models.FilterModeGroups
case "tagFilter":
mode = models.FilterModeTags
case "imageFilter":
mode = models.FilterModeImages
default:
return nil, fmt.Errorf("saved filters are not supported for %s", "studioFilter")
}
mode := models.FilterModeStudios
mergedFindFilter, err := r.resolveSavedFilter(ctx, *savedFilterID, mode, finalFilter, filter)
if err != nil {

View file

@ -1,8 +1,6 @@
package api
import (
"fmt"
"errors"
"context"
@ -41,29 +39,7 @@ func (r *queryResolver) FindTags(
var finalFilter *models.TagFilterType
if savedFilterID != nil {
finalFilter = &models.TagFilterType{}
var mode models.FilterMode
switch "tagFilter" {
case "sceneFilter":
mode = models.FilterModeScenes
case "performerFilter":
mode = models.FilterModePerformers
case "studioFilter":
mode = models.FilterModeStudios
case "galleryFilter":
mode = models.FilterModeGalleries
case "sceneMarkerFilter":
mode = models.FilterModeSceneMarkers
case "movieFilter":
mode = models.FilterModeMovies
case "groupFilter":
mode = models.FilterModeGroups
case "tagFilter":
mode = models.FilterModeTags
case "imageFilter":
mode = models.FilterModeImages
default:
return nil, fmt.Errorf("saved filters are not supported for %s", "tagFilter")
}
mode := models.FilterModeTags
mergedFindFilter, err := r.resolveSavedFilter(ctx, *savedFilterID, mode, finalFilter, filter)
if err != nil {