//go:build integration // +build integration package sqlite_test import ( "database/sql" "fmt" "math" "regexp" "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/stashapp/stash/pkg/hash/md5" "github.com/stashapp/stash/pkg/models" ) func TestSceneFind(t *testing.T) { withTxn(func(r models.Repository) error { // assume that the first scene is sceneWithGalleryPath sqb := r.Scene() const sceneIdx = 0 sceneID := sceneIDs[sceneIdx] scene, err := sqb.Find(sceneID) if err != nil { t.Errorf("Error finding scene: %s", err.Error()) } assert.Equal(t, getSceneStringValue(sceneIdx, "Path"), scene.Path) sceneID = 0 scene, err = sqb.Find(sceneID) if err != nil { t.Errorf("Error finding scene: %s", err.Error()) } assert.Nil(t, scene) return nil }) } func TestSceneFindByPath(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() const sceneIdx = 1 scenePath := getSceneStringValue(sceneIdx, "Path") scene, err := sqb.FindByPath(scenePath) if err != nil { t.Errorf("Error finding scene: %s", err.Error()) } assert.Equal(t, sceneIDs[sceneIdx], scene.ID) assert.Equal(t, scenePath, scene.Path) scenePath = "not exist" scene, err = sqb.FindByPath(scenePath) if err != nil { t.Errorf("Error finding scene: %s", err.Error()) } assert.Nil(t, scene) return nil }) } func TestSceneCountByPerformerID(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() count, err := sqb.CountByPerformerID(performerIDs[performerIdxWithScene]) if err != nil { t.Errorf("Error counting scenes: %s", err.Error()) } assert.Equal(t, 1, count) count, err = sqb.CountByPerformerID(0) if err != nil { t.Errorf("Error counting scenes: %s", err.Error()) } assert.Equal(t, 0, count) return nil }) } func TestSceneWall(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() const sceneIdx = 2 wallQuery := getSceneStringValue(sceneIdx, "Details") scenes, err := sqb.Wall(&wallQuery) if err != nil { t.Errorf("Error finding scenes: %s", err.Error()) } assert.Len(t, scenes, 1) scene := scenes[0] assert.Equal(t, sceneIDs[sceneIdx], scene.ID) assert.Equal(t, getSceneStringValue(sceneIdx, "Path"), scene.Path) wallQuery = "not exist" scenes, err = sqb.Wall(&wallQuery) if err != nil { t.Errorf("Error finding scene: %s", err.Error()) } assert.Len(t, scenes, 0) return nil }) } func TestSceneQueryQ(t *testing.T) { const sceneIdx = 2 q := getSceneStringValue(sceneIdx, titleField) withTxn(func(r models.Repository) error { sqb := r.Scene() sceneQueryQ(t, sqb, q, sceneIdx) return nil }) } func queryScene(t *testing.T, sqb models.SceneReader, sceneFilter *models.SceneFilterType, findFilter *models.FindFilterType) []*models.Scene { t.Helper() result, err := sqb.Query(models.SceneQueryOptions{ QueryOptions: models.QueryOptions{ FindFilter: findFilter, }, SceneFilter: sceneFilter, }) if err != nil { t.Errorf("Error querying scene: %v", err) } scenes, err := result.Resolve() if err != nil { t.Errorf("Error resolving scenes: %v", err) } return scenes } func sceneQueryQ(t *testing.T, sqb models.SceneReader, q string, expectedSceneIdx int) { filter := models.FindFilterType{ Q: &q, } scenes := queryScene(t, sqb, nil, &filter) assert.Len(t, scenes, 1) scene := scenes[0] assert.Equal(t, sceneIDs[expectedSceneIdx], scene.ID) // no Q should return all results filter.Q = nil scenes = queryScene(t, sqb, nil, &filter) assert.Len(t, scenes, totalScenes) } func TestSceneQueryPath(t *testing.T) { const sceneIdx = 1 scenePath := getSceneStringValue(sceneIdx, "Path") pathCriterion := models.StringCriterionInput{ Value: scenePath, Modifier: models.CriterionModifierEquals, } verifyScenesPath(t, pathCriterion) pathCriterion.Modifier = models.CriterionModifierNotEquals verifyScenesPath(t, pathCriterion) pathCriterion.Modifier = models.CriterionModifierMatchesRegex pathCriterion.Value = "scene_.*1_Path" verifyScenesPath(t, pathCriterion) pathCriterion.Modifier = models.CriterionModifierNotMatchesRegex verifyScenesPath(t, pathCriterion) } func TestSceneQueryURL(t *testing.T) { const sceneIdx = 1 scenePath := getSceneStringValue(sceneIdx, urlField) urlCriterion := models.StringCriterionInput{ Value: scenePath, Modifier: models.CriterionModifierEquals, } filter := models.SceneFilterType{ URL: &urlCriterion, } verifyFn := func(s *models.Scene) { t.Helper() verifyNullString(t, s.URL, urlCriterion) } verifySceneQuery(t, filter, verifyFn) urlCriterion.Modifier = models.CriterionModifierNotEquals verifySceneQuery(t, filter, verifyFn) urlCriterion.Modifier = models.CriterionModifierMatchesRegex urlCriterion.Value = "scene_.*1_URL" verifySceneQuery(t, filter, verifyFn) urlCriterion.Modifier = models.CriterionModifierNotMatchesRegex verifySceneQuery(t, filter, verifyFn) urlCriterion.Modifier = models.CriterionModifierIsNull urlCriterion.Value = "" verifySceneQuery(t, filter, verifyFn) urlCriterion.Modifier = models.CriterionModifierNotNull verifySceneQuery(t, filter, verifyFn) } func TestSceneQueryPathOr(t *testing.T) { const scene1Idx = 1 const scene2Idx = 2 scene1Path := getSceneStringValue(scene1Idx, "Path") scene2Path := getSceneStringValue(scene2Idx, "Path") sceneFilter := models.SceneFilterType{ Path: &models.StringCriterionInput{ Value: scene1Path, Modifier: models.CriterionModifierEquals, }, Or: &models.SceneFilterType{ Path: &models.StringCriterionInput{ Value: scene2Path, Modifier: models.CriterionModifierEquals, }, }, } withTxn(func(r models.Repository) error { sqb := r.Scene() scenes := queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 2) assert.Equal(t, scene1Path, scenes[0].Path) assert.Equal(t, scene2Path, scenes[1].Path) return nil }) } func TestSceneQueryPathAndRating(t *testing.T) { const sceneIdx = 1 scenePath := getSceneStringValue(sceneIdx, "Path") sceneRating := getRating(sceneIdx) sceneFilter := models.SceneFilterType{ Path: &models.StringCriterionInput{ Value: scenePath, Modifier: models.CriterionModifierEquals, }, And: &models.SceneFilterType{ Rating: &models.IntCriterionInput{ Value: int(sceneRating.Int64), Modifier: models.CriterionModifierEquals, }, }, } withTxn(func(r models.Repository) error { sqb := r.Scene() scenes := queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 1) assert.Equal(t, scenePath, scenes[0].Path) assert.Equal(t, sceneRating.Int64, scenes[0].Rating.Int64) return nil }) } func TestSceneQueryPathNotRating(t *testing.T) { const sceneIdx = 1 sceneRating := getRating(sceneIdx) pathCriterion := models.StringCriterionInput{ Value: "scene_.*1_Path", Modifier: models.CriterionModifierMatchesRegex, } ratingCriterion := models.IntCriterionInput{ Value: int(sceneRating.Int64), Modifier: models.CriterionModifierEquals, } sceneFilter := models.SceneFilterType{ Path: &pathCriterion, Not: &models.SceneFilterType{ Rating: &ratingCriterion, }, } withTxn(func(r models.Repository) error { sqb := r.Scene() scenes := queryScene(t, sqb, &sceneFilter, nil) for _, scene := range scenes { verifyString(t, scene.Path, pathCriterion) ratingCriterion.Modifier = models.CriterionModifierNotEquals verifyInt64(t, scene.Rating, ratingCriterion) } return nil }) } func TestSceneIllegalQuery(t *testing.T) { assert := assert.New(t) const sceneIdx = 1 subFilter := models.SceneFilterType{ Path: &models.StringCriterionInput{ Value: getSceneStringValue(sceneIdx, "Path"), Modifier: models.CriterionModifierEquals, }, } sceneFilter := &models.SceneFilterType{ And: &subFilter, Or: &subFilter, } withTxn(func(r models.Repository) error { sqb := r.Scene() queryOptions := models.SceneQueryOptions{ SceneFilter: sceneFilter, } _, err := sqb.Query(queryOptions) assert.NotNil(err) sceneFilter.Or = nil sceneFilter.Not = &subFilter _, err = sqb.Query(queryOptions) assert.NotNil(err) sceneFilter.And = nil sceneFilter.Or = &subFilter _, err = sqb.Query(queryOptions) assert.NotNil(err) return nil }) } func verifySceneQuery(t *testing.T, filter models.SceneFilterType, verifyFn func(s *models.Scene)) { withTxn(func(r models.Repository) error { t.Helper() sqb := r.Scene() scenes := queryScene(t, sqb, &filter, nil) // assume it should find at least one assert.Greater(t, len(scenes), 0) for _, scene := range scenes { verifyFn(scene) } return nil }) } func verifyScenesPath(t *testing.T, pathCriterion models.StringCriterionInput) { withTxn(func(r models.Repository) error { sqb := r.Scene() sceneFilter := models.SceneFilterType{ Path: &pathCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) for _, scene := range scenes { verifyString(t, scene.Path, pathCriterion) } return nil }) } func verifyNullString(t *testing.T, value sql.NullString, criterion models.StringCriterionInput) { t.Helper() assert := assert.New(t) if criterion.Modifier == models.CriterionModifierIsNull { if value.Valid && value.String == "" { // correct return } assert.False(value.Valid, "expect is null values to be null") } if criterion.Modifier == models.CriterionModifierNotNull { assert.True(value.Valid, "expect is null values to be null") assert.Greater(len(value.String), 0) } if criterion.Modifier == models.CriterionModifierEquals { assert.Equal(criterion.Value, value.String) } if criterion.Modifier == models.CriterionModifierNotEquals { assert.NotEqual(criterion.Value, value.String) } if criterion.Modifier == models.CriterionModifierMatchesRegex { assert.True(value.Valid) assert.Regexp(regexp.MustCompile(criterion.Value), value) } if criterion.Modifier == models.CriterionModifierNotMatchesRegex { if !value.Valid { // correct return } assert.NotRegexp(regexp.MustCompile(criterion.Value), value) } } func verifyString(t *testing.T, value string, criterion models.StringCriterionInput) { t.Helper() assert := assert.New(t) if criterion.Modifier == models.CriterionModifierEquals { assert.Equal(criterion.Value, value) } if criterion.Modifier == models.CriterionModifierNotEquals { assert.NotEqual(criterion.Value, value) } if criterion.Modifier == models.CriterionModifierMatchesRegex { assert.Regexp(regexp.MustCompile(criterion.Value), value) } if criterion.Modifier == models.CriterionModifierNotMatchesRegex { assert.NotRegexp(regexp.MustCompile(criterion.Value), value) } } func TestSceneQueryRating(t *testing.T) { const rating = 3 ratingCriterion := models.IntCriterionInput{ Value: rating, Modifier: models.CriterionModifierEquals, } verifyScenesRating(t, ratingCriterion) ratingCriterion.Modifier = models.CriterionModifierNotEquals verifyScenesRating(t, ratingCriterion) ratingCriterion.Modifier = models.CriterionModifierGreaterThan verifyScenesRating(t, ratingCriterion) ratingCriterion.Modifier = models.CriterionModifierLessThan verifyScenesRating(t, ratingCriterion) ratingCriterion.Modifier = models.CriterionModifierIsNull verifyScenesRating(t, ratingCriterion) ratingCriterion.Modifier = models.CriterionModifierNotNull verifyScenesRating(t, ratingCriterion) } func verifyScenesRating(t *testing.T, ratingCriterion models.IntCriterionInput) { withTxn(func(r models.Repository) error { sqb := r.Scene() sceneFilter := models.SceneFilterType{ Rating: &ratingCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) for _, scene := range scenes { verifyInt64(t, scene.Rating, ratingCriterion) } return nil }) } func verifyInt64(t *testing.T, value sql.NullInt64, criterion models.IntCriterionInput) { t.Helper() assert := assert.New(t) if criterion.Modifier == models.CriterionModifierIsNull { assert.False(value.Valid, "expect is null values to be null") } if criterion.Modifier == models.CriterionModifierNotNull { assert.True(value.Valid, "expect is null values to be null") } if criterion.Modifier == models.CriterionModifierEquals { assert.Equal(int64(criterion.Value), value.Int64) } if criterion.Modifier == models.CriterionModifierNotEquals { assert.NotEqual(int64(criterion.Value), value.Int64) } if criterion.Modifier == models.CriterionModifierGreaterThan { assert.True(value.Int64 > int64(criterion.Value)) } if criterion.Modifier == models.CriterionModifierLessThan { assert.True(value.Int64 < int64(criterion.Value)) } } func TestSceneQueryOCounter(t *testing.T) { const oCounter = 1 oCounterCriterion := models.IntCriterionInput{ Value: oCounter, Modifier: models.CriterionModifierEquals, } verifyScenesOCounter(t, oCounterCriterion) oCounterCriterion.Modifier = models.CriterionModifierNotEquals verifyScenesOCounter(t, oCounterCriterion) oCounterCriterion.Modifier = models.CriterionModifierGreaterThan verifyScenesOCounter(t, oCounterCriterion) oCounterCriterion.Modifier = models.CriterionModifierLessThan verifyScenesOCounter(t, oCounterCriterion) } func verifyScenesOCounter(t *testing.T, oCounterCriterion models.IntCriterionInput) { withTxn(func(r models.Repository) error { sqb := r.Scene() sceneFilter := models.SceneFilterType{ OCounter: &oCounterCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) for _, scene := range scenes { verifyInt(t, scene.OCounter, oCounterCriterion) } return nil }) } func verifyInt(t *testing.T, value int, criterion models.IntCriterionInput) { t.Helper() assert := assert.New(t) if criterion.Modifier == models.CriterionModifierEquals { assert.Equal(criterion.Value, value) } if criterion.Modifier == models.CriterionModifierNotEquals { assert.NotEqual(criterion.Value, value) } if criterion.Modifier == models.CriterionModifierGreaterThan { assert.Greater(value, criterion.Value) } if criterion.Modifier == models.CriterionModifierLessThan { assert.Less(value, criterion.Value) } } func TestSceneQueryDuration(t *testing.T) { duration := 200.432 durationCriterion := models.IntCriterionInput{ Value: int(duration), Modifier: models.CriterionModifierEquals, } verifyScenesDuration(t, durationCriterion) durationCriterion.Modifier = models.CriterionModifierNotEquals verifyScenesDuration(t, durationCriterion) durationCriterion.Modifier = models.CriterionModifierGreaterThan verifyScenesDuration(t, durationCriterion) durationCriterion.Modifier = models.CriterionModifierLessThan verifyScenesDuration(t, durationCriterion) durationCriterion.Modifier = models.CriterionModifierIsNull verifyScenesDuration(t, durationCriterion) durationCriterion.Modifier = models.CriterionModifierNotNull verifyScenesDuration(t, durationCriterion) } func verifyScenesDuration(t *testing.T, durationCriterion models.IntCriterionInput) { withTxn(func(r models.Repository) error { sqb := r.Scene() sceneFilter := models.SceneFilterType{ Duration: &durationCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) for _, scene := range scenes { if durationCriterion.Modifier == models.CriterionModifierEquals { assert.True(t, scene.Duration.Float64 >= float64(durationCriterion.Value) && scene.Duration.Float64 < float64(durationCriterion.Value+1)) } else if durationCriterion.Modifier == models.CriterionModifierNotEquals { assert.True(t, scene.Duration.Float64 < float64(durationCriterion.Value) || scene.Duration.Float64 >= float64(durationCriterion.Value+1)) } else { verifyFloat64(t, scene.Duration, durationCriterion) } } return nil }) } func verifyFloat64(t *testing.T, value sql.NullFloat64, criterion models.IntCriterionInput) { assert := assert.New(t) if criterion.Modifier == models.CriterionModifierIsNull { assert.False(value.Valid, "expect is null values to be null") } if criterion.Modifier == models.CriterionModifierNotNull { assert.True(value.Valid, "expect is null values to be null") } if criterion.Modifier == models.CriterionModifierEquals { assert.Equal(float64(criterion.Value), value.Float64) } if criterion.Modifier == models.CriterionModifierNotEquals { assert.NotEqual(float64(criterion.Value), value.Float64) } if criterion.Modifier == models.CriterionModifierGreaterThan { assert.True(value.Float64 > float64(criterion.Value)) } if criterion.Modifier == models.CriterionModifierLessThan { assert.True(value.Float64 < float64(criterion.Value)) } } func TestSceneQueryResolution(t *testing.T) { verifyScenesResolution(t, models.ResolutionEnumLow) verifyScenesResolution(t, models.ResolutionEnumStandard) verifyScenesResolution(t, models.ResolutionEnumStandardHd) verifyScenesResolution(t, models.ResolutionEnumFullHd) verifyScenesResolution(t, models.ResolutionEnumFourK) verifyScenesResolution(t, models.ResolutionEnum("unknown")) } func verifyScenesResolution(t *testing.T, resolution models.ResolutionEnum) { withTxn(func(r models.Repository) error { sqb := r.Scene() sceneFilter := models.SceneFilterType{ Resolution: &models.ResolutionCriterionInput{ Value: resolution, Modifier: models.CriterionModifierEquals, }, } scenes := queryScene(t, sqb, &sceneFilter, nil) for _, scene := range scenes { verifySceneResolution(t, scene.Height, resolution) } return nil }) } func verifySceneResolution(t *testing.T, height sql.NullInt64, resolution models.ResolutionEnum) { assert := assert.New(t) h := height.Int64 switch resolution { case models.ResolutionEnumLow: assert.True(h < 480) case models.ResolutionEnumStandard: assert.True(h >= 480 && h < 720) case models.ResolutionEnumStandardHd: assert.True(h >= 720 && h < 1080) case models.ResolutionEnumFullHd: assert.True(h >= 1080 && h < 2160) case models.ResolutionEnumFourK: assert.True(h >= 2160) } } func TestAllResolutionsHaveResolutionRange(t *testing.T) { for _, resolution := range models.AllResolutionEnum { assert.NotZero(t, resolution.GetMinResolution(), "Define resolution range for %s in extension_resolution.go", resolution) assert.NotZero(t, resolution.GetMaxResolution(), "Define resolution range for %s in extension_resolution.go", resolution) } } func TestSceneQueryResolutionModifiers(t *testing.T) { if err := withRollbackTxn(func(r models.Repository) error { qb := r.Scene() sceneNoResolution, _ := createScene(qb, 0, 0) firstScene540P, _ := createScene(qb, 960, 540) secondScene540P, _ := createScene(qb, 1280, 719) firstScene720P, _ := createScene(qb, 1280, 720) secondScene720P, _ := createScene(qb, 1280, 721) thirdScene720P, _ := createScene(qb, 1920, 1079) scene1080P, _ := createScene(qb, 1920, 1080) scenesEqualTo720P := queryScenes(t, qb, models.ResolutionEnumStandardHd, models.CriterionModifierEquals) scenesNotEqualTo720P := queryScenes(t, qb, models.ResolutionEnumStandardHd, models.CriterionModifierNotEquals) scenesGreaterThan720P := queryScenes(t, qb, models.ResolutionEnumStandardHd, models.CriterionModifierGreaterThan) scenesLessThan720P := queryScenes(t, qb, models.ResolutionEnumStandardHd, models.CriterionModifierLessThan) assert.Subset(t, scenesEqualTo720P, []*models.Scene{firstScene720P, secondScene720P, thirdScene720P}) assert.NotSubset(t, scenesEqualTo720P, []*models.Scene{sceneNoResolution, firstScene540P, secondScene540P, scene1080P}) assert.Subset(t, scenesNotEqualTo720P, []*models.Scene{sceneNoResolution, firstScene540P, secondScene540P, scene1080P}) assert.NotSubset(t, scenesNotEqualTo720P, []*models.Scene{firstScene720P, secondScene720P, thirdScene720P}) assert.Subset(t, scenesGreaterThan720P, []*models.Scene{scene1080P}) assert.NotSubset(t, scenesGreaterThan720P, []*models.Scene{sceneNoResolution, firstScene540P, secondScene540P, firstScene720P, secondScene720P, thirdScene720P}) assert.Subset(t, scenesLessThan720P, []*models.Scene{sceneNoResolution, firstScene540P, secondScene540P}) assert.NotSubset(t, scenesLessThan720P, []*models.Scene{scene1080P, firstScene720P, secondScene720P, thirdScene720P}) return nil }); err != nil { t.Error(err.Error()) } } func queryScenes(t *testing.T, queryBuilder models.SceneReaderWriter, resolution models.ResolutionEnum, modifier models.CriterionModifier) []*models.Scene { sceneFilter := models.SceneFilterType{ Resolution: &models.ResolutionCriterionInput{ Value: resolution, Modifier: modifier, }, } return queryScene(t, queryBuilder, &sceneFilter, nil) } func createScene(queryBuilder models.SceneReaderWriter, width int64, height int64) (*models.Scene, error) { name := fmt.Sprintf("TestSceneQueryResolutionModifiers %d %d", width, height) scene := models.Scene{ Path: name, Width: sql.NullInt64{ Int64: width, Valid: true, }, Height: sql.NullInt64{ Int64: height, Valid: true, }, Checksum: sql.NullString{String: md5.FromString(name), Valid: true}, } return queryBuilder.Create(scene) } func TestSceneQueryHasMarkers(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() hasMarkers := "true" sceneFilter := models.SceneFilterType{ HasMarkers: &hasMarkers, } q := getSceneStringValue(sceneIdxWithMarkers, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes := queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 1) assert.Equal(t, sceneIDs[sceneIdxWithMarkers], scenes[0].ID) hasMarkers = "false" scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) findFilter.Q = nil scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.NotEqual(t, 0, len(scenes)) // ensure non of the ids equal the one with gallery for _, scene := range scenes { assert.NotEqual(t, sceneIDs[sceneIdxWithMarkers], scene.ID) } return nil }) } func TestSceneQueryIsMissingGallery(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() isMissing := "galleries" sceneFilter := models.SceneFilterType{ IsMissing: &isMissing, } q := getSceneStringValue(sceneIdxWithGallery, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes := queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) findFilter.Q = nil scenes = queryScene(t, sqb, &sceneFilter, &findFilter) // ensure non of the ids equal the one with gallery for _, scene := range scenes { assert.NotEqual(t, sceneIDs[sceneIdxWithGallery], scene.ID) } return nil }) } func TestSceneQueryIsMissingStudio(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() isMissing := "studio" sceneFilter := models.SceneFilterType{ IsMissing: &isMissing, } q := getSceneStringValue(sceneIdxWithStudio, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes := queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) findFilter.Q = nil scenes = queryScene(t, sqb, &sceneFilter, &findFilter) // ensure non of the ids equal the one with studio for _, scene := range scenes { assert.NotEqual(t, sceneIDs[sceneIdxWithStudio], scene.ID) } return nil }) } func TestSceneQueryIsMissingMovies(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() isMissing := "movie" sceneFilter := models.SceneFilterType{ IsMissing: &isMissing, } q := getSceneStringValue(sceneIdxWithMovie, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes := queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) findFilter.Q = nil scenes = queryScene(t, sqb, &sceneFilter, &findFilter) // ensure non of the ids equal the one with movies for _, scene := range scenes { assert.NotEqual(t, sceneIDs[sceneIdxWithMovie], scene.ID) } return nil }) } func TestSceneQueryIsMissingPerformers(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() isMissing := "performers" sceneFilter := models.SceneFilterType{ IsMissing: &isMissing, } q := getSceneStringValue(sceneIdxWithPerformer, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes := queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) findFilter.Q = nil scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.True(t, len(scenes) > 0) // ensure non of the ids equal the one with movies for _, scene := range scenes { assert.NotEqual(t, sceneIDs[sceneIdxWithPerformer], scene.ID) } return nil }) } func TestSceneQueryIsMissingDate(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() isMissing := "date" sceneFilter := models.SceneFilterType{ IsMissing: &isMissing, } scenes := queryScene(t, sqb, &sceneFilter, nil) // three in four scenes have no date assert.Len(t, scenes, int(math.Ceil(float64(totalScenes)/4*3))) // ensure date is null, empty or "0001-01-01" for _, scene := range scenes { assert.True(t, !scene.Date.Valid || scene.Date.String == "" || scene.Date.String == "0001-01-01") } return nil }) } func TestSceneQueryIsMissingTags(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() isMissing := "tags" sceneFilter := models.SceneFilterType{ IsMissing: &isMissing, } q := getSceneStringValue(sceneIdxWithTwoTags, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes := queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) findFilter.Q = nil scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.True(t, len(scenes) > 0) return nil }) } func TestSceneQueryIsMissingRating(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() isMissing := "rating" sceneFilter := models.SceneFilterType{ IsMissing: &isMissing, } scenes := queryScene(t, sqb, &sceneFilter, nil) assert.True(t, len(scenes) > 0) // ensure date is null, empty or "0001-01-01" for _, scene := range scenes { assert.True(t, !scene.Rating.Valid) } return nil }) } func TestSceneQueryPerformers(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() performerCriterion := models.MultiCriterionInput{ Value: []string{ strconv.Itoa(performerIDs[performerIdxWithScene]), strconv.Itoa(performerIDs[performerIdx1WithScene]), }, Modifier: models.CriterionModifierIncludes, } sceneFilter := models.SceneFilterType{ Performers: &performerCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 2) // ensure ids are correct for _, scene := range scenes { assert.True(t, scene.ID == sceneIDs[sceneIdxWithPerformer] || scene.ID == sceneIDs[sceneIdxWithTwoPerformers]) } performerCriterion = models.MultiCriterionInput{ Value: []string{ strconv.Itoa(performerIDs[performerIdx1WithScene]), strconv.Itoa(performerIDs[performerIdx2WithScene]), }, Modifier: models.CriterionModifierIncludesAll, } scenes = queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 1) assert.Equal(t, sceneIDs[sceneIdxWithTwoPerformers], scenes[0].ID) performerCriterion = models.MultiCriterionInput{ Value: []string{ strconv.Itoa(performerIDs[performerIdx1WithScene]), }, Modifier: models.CriterionModifierExcludes, } q := getSceneStringValue(sceneIdxWithTwoPerformers, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) return nil }) } func TestSceneQueryTags(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() tagCriterion := models.HierarchicalMultiCriterionInput{ Value: []string{ strconv.Itoa(tagIDs[tagIdxWithScene]), strconv.Itoa(tagIDs[tagIdx1WithScene]), }, Modifier: models.CriterionModifierIncludes, } sceneFilter := models.SceneFilterType{ Tags: &tagCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 2) // ensure ids are correct for _, scene := range scenes { assert.True(t, scene.ID == sceneIDs[sceneIdxWithTag] || scene.ID == sceneIDs[sceneIdxWithTwoTags]) } tagCriterion = models.HierarchicalMultiCriterionInput{ Value: []string{ strconv.Itoa(tagIDs[tagIdx1WithScene]), strconv.Itoa(tagIDs[tagIdx2WithScene]), }, Modifier: models.CriterionModifierIncludesAll, } scenes = queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 1) assert.Equal(t, sceneIDs[sceneIdxWithTwoTags], scenes[0].ID) tagCriterion = models.HierarchicalMultiCriterionInput{ Value: []string{ strconv.Itoa(tagIDs[tagIdx1WithScene]), }, Modifier: models.CriterionModifierExcludes, } q := getSceneStringValue(sceneIdxWithTwoTags, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) return nil }) } func TestSceneQueryPerformerTags(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() tagCriterion := models.HierarchicalMultiCriterionInput{ Value: []string{ strconv.Itoa(tagIDs[tagIdxWithPerformer]), strconv.Itoa(tagIDs[tagIdx1WithPerformer]), }, Modifier: models.CriterionModifierIncludes, } sceneFilter := models.SceneFilterType{ PerformerTags: &tagCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 2) // ensure ids are correct for _, scene := range scenes { assert.True(t, scene.ID == sceneIDs[sceneIdxWithPerformerTag] || scene.ID == sceneIDs[sceneIdxWithPerformerTwoTags]) } tagCriterion = models.HierarchicalMultiCriterionInput{ Value: []string{ strconv.Itoa(tagIDs[tagIdx1WithPerformer]), strconv.Itoa(tagIDs[tagIdx2WithPerformer]), }, Modifier: models.CriterionModifierIncludesAll, } scenes = queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 1) assert.Equal(t, sceneIDs[sceneIdxWithPerformerTwoTags], scenes[0].ID) tagCriterion = models.HierarchicalMultiCriterionInput{ Value: []string{ strconv.Itoa(tagIDs[tagIdx1WithPerformer]), }, Modifier: models.CriterionModifierExcludes, } q := getSceneStringValue(sceneIdxWithPerformerTwoTags, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) tagCriterion = models.HierarchicalMultiCriterionInput{ Modifier: models.CriterionModifierIsNull, } q = getSceneStringValue(sceneIdx1WithPerformer, titleField) scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 1) assert.Equal(t, sceneIDs[sceneIdx1WithPerformer], scenes[0].ID) q = getSceneStringValue(sceneIdxWithPerformerTag, titleField) scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) tagCriterion.Modifier = models.CriterionModifierNotNull scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 1) assert.Equal(t, sceneIDs[sceneIdxWithPerformerTag], scenes[0].ID) q = getSceneStringValue(sceneIdx1WithPerformer, titleField) scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) return nil }) } func TestSceneQueryStudio(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() studioCriterion := models.HierarchicalMultiCriterionInput{ Value: []string{ strconv.Itoa(studioIDs[studioIdxWithScene]), }, Modifier: models.CriterionModifierIncludes, } sceneFilter := models.SceneFilterType{ Studios: &studioCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 1) // ensure id is correct assert.Equal(t, sceneIDs[sceneIdxWithStudio], scenes[0].ID) studioCriterion = models.HierarchicalMultiCriterionInput{ Value: []string{ strconv.Itoa(studioIDs[studioIdxWithScene]), }, Modifier: models.CriterionModifierExcludes, } q := getSceneStringValue(sceneIdxWithStudio, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) return nil }) } func TestSceneQueryStudioDepth(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() depth := 2 studioCriterion := models.HierarchicalMultiCriterionInput{ Value: []string{ strconv.Itoa(studioIDs[studioIdxWithGrandChild]), }, Modifier: models.CriterionModifierIncludes, Depth: &depth, } sceneFilter := models.SceneFilterType{ Studios: &studioCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 1) depth = 1 scenes = queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 0) studioCriterion.Value = []string{strconv.Itoa(studioIDs[studioIdxWithParentAndChild])} scenes = queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 1) // ensure id is correct assert.Equal(t, sceneIDs[sceneIdxWithGrandChildStudio], scenes[0].ID) depth = 2 studioCriterion = models.HierarchicalMultiCriterionInput{ Value: []string{ strconv.Itoa(studioIDs[studioIdxWithGrandChild]), }, Modifier: models.CriterionModifierExcludes, Depth: &depth, } q := getSceneStringValue(sceneIdxWithGrandChildStudio, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) depth = 1 scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 1) studioCriterion.Value = []string{strconv.Itoa(studioIDs[studioIdxWithParentAndChild])} scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) return nil }) } func TestSceneQueryMovies(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() movieCriterion := models.MultiCriterionInput{ Value: []string{ strconv.Itoa(movieIDs[movieIdxWithScene]), }, Modifier: models.CriterionModifierIncludes, } sceneFilter := models.SceneFilterType{ Movies: &movieCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) assert.Len(t, scenes, 1) // ensure id is correct assert.Equal(t, sceneIDs[sceneIdxWithMovie], scenes[0].ID) movieCriterion = models.MultiCriterionInput{ Value: []string{ strconv.Itoa(movieIDs[movieIdxWithScene]), }, Modifier: models.CriterionModifierExcludes, } q := getSceneStringValue(sceneIdxWithMovie, titleField) findFilter := models.FindFilterType{ Q: &q, } scenes = queryScene(t, sqb, &sceneFilter, &findFilter) assert.Len(t, scenes, 0) return nil }) } func TestSceneQuerySorting(t *testing.T) { sort := titleField direction := models.SortDirectionEnumAsc findFilter := models.FindFilterType{ Sort: &sort, Direction: &direction, } withTxn(func(r models.Repository) error { sqb := r.Scene() scenes := queryScene(t, sqb, nil, &findFilter) // scenes should be in same order as indexes firstScene := scenes[0] lastScene := scenes[len(scenes)-1] assert.Equal(t, sceneIDs[0], firstScene.ID) assert.Equal(t, sceneIDs[sceneIdxWithSpacedName], lastScene.ID) // sort in descending order direction = models.SortDirectionEnumDesc scenes = queryScene(t, sqb, nil, &findFilter) firstScene = scenes[0] lastScene = scenes[len(scenes)-1] assert.Equal(t, sceneIDs[sceneIdxWithSpacedName], firstScene.ID) assert.Equal(t, sceneIDs[0], lastScene.ID) return nil }) } func TestSceneQueryPagination(t *testing.T) { perPage := 1 findFilter := models.FindFilterType{ PerPage: &perPage, } withTxn(func(r models.Repository) error { sqb := r.Scene() scenes := queryScene(t, sqb, nil, &findFilter) assert.Len(t, scenes, 1) firstID := scenes[0].ID page := 2 findFilter.Page = &page scenes = queryScene(t, sqb, nil, &findFilter) assert.Len(t, scenes, 1) secondID := scenes[0].ID assert.NotEqual(t, firstID, secondID) perPage = 2 page = 1 scenes = queryScene(t, sqb, nil, &findFilter) assert.Len(t, scenes, 2) assert.Equal(t, firstID, scenes[0].ID) assert.Equal(t, secondID, scenes[1].ID) return nil }) } func TestSceneQueryTagCount(t *testing.T) { const tagCount = 1 tagCountCriterion := models.IntCriterionInput{ Value: tagCount, Modifier: models.CriterionModifierEquals, } verifyScenesTagCount(t, tagCountCriterion) tagCountCriterion.Modifier = models.CriterionModifierNotEquals verifyScenesTagCount(t, tagCountCriterion) tagCountCriterion.Modifier = models.CriterionModifierGreaterThan verifyScenesTagCount(t, tagCountCriterion) tagCountCriterion.Modifier = models.CriterionModifierLessThan verifyScenesTagCount(t, tagCountCriterion) } func verifyScenesTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) { withTxn(func(r models.Repository) error { sqb := r.Scene() sceneFilter := models.SceneFilterType{ TagCount: &tagCountCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) assert.Greater(t, len(scenes), 0) for _, scene := range scenes { ids, err := sqb.GetTagIDs(scene.ID) if err != nil { return err } verifyInt(t, len(ids), tagCountCriterion) } return nil }) } func TestSceneQueryPerformerCount(t *testing.T) { const performerCount = 1 performerCountCriterion := models.IntCriterionInput{ Value: performerCount, Modifier: models.CriterionModifierEquals, } verifyScenesPerformerCount(t, performerCountCriterion) performerCountCriterion.Modifier = models.CriterionModifierNotEquals verifyScenesPerformerCount(t, performerCountCriterion) performerCountCriterion.Modifier = models.CriterionModifierGreaterThan verifyScenesPerformerCount(t, performerCountCriterion) performerCountCriterion.Modifier = models.CriterionModifierLessThan verifyScenesPerformerCount(t, performerCountCriterion) } func verifyScenesPerformerCount(t *testing.T, performerCountCriterion models.IntCriterionInput) { withTxn(func(r models.Repository) error { sqb := r.Scene() sceneFilter := models.SceneFilterType{ PerformerCount: &performerCountCriterion, } scenes := queryScene(t, sqb, &sceneFilter, nil) assert.Greater(t, len(scenes), 0) for _, scene := range scenes { ids, err := sqb.GetPerformerIDs(scene.ID) if err != nil { return err } verifyInt(t, len(ids), performerCountCriterion) } return nil }) } func TestSceneCountByTagID(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() sceneCount, err := sqb.CountByTagID(tagIDs[tagIdxWithScene]) if err != nil { t.Errorf("error calling CountByTagID: %s", err.Error()) } assert.Equal(t, 1, sceneCount) sceneCount, err = sqb.CountByTagID(0) if err != nil { t.Errorf("error calling CountByTagID: %s", err.Error()) } assert.Equal(t, 0, sceneCount) return nil }) } func TestSceneCountByMovieID(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() sceneCount, err := sqb.CountByMovieID(movieIDs[movieIdxWithScene]) if err != nil { t.Errorf("error calling CountByMovieID: %s", err.Error()) } assert.Equal(t, 1, sceneCount) sceneCount, err = sqb.CountByMovieID(0) if err != nil { t.Errorf("error calling CountByMovieID: %s", err.Error()) } assert.Equal(t, 0, sceneCount) return nil }) } func TestSceneCountByStudioID(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() sceneCount, err := sqb.CountByStudioID(studioIDs[studioIdxWithScene]) if err != nil { t.Errorf("error calling CountByStudioID: %s", err.Error()) } assert.Equal(t, 1, sceneCount) sceneCount, err = sqb.CountByStudioID(0) if err != nil { t.Errorf("error calling CountByStudioID: %s", err.Error()) } assert.Equal(t, 0, sceneCount) return nil }) } func TestFindByMovieID(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() scenes, err := sqb.FindByMovieID(movieIDs[movieIdxWithScene]) if err != nil { t.Errorf("error calling FindByMovieID: %s", err.Error()) } assert.Len(t, scenes, 1) assert.Equal(t, sceneIDs[sceneIdxWithMovie], scenes[0].ID) scenes, err = sqb.FindByMovieID(0) if err != nil { t.Errorf("error calling FindByMovieID: %s", err.Error()) } assert.Len(t, scenes, 0) return nil }) } func TestFindByPerformerID(t *testing.T) { withTxn(func(r models.Repository) error { sqb := r.Scene() scenes, err := sqb.FindByPerformerID(performerIDs[performerIdxWithScene]) if err != nil { t.Errorf("error calling FindByPerformerID: %s", err.Error()) } assert.Len(t, scenes, 1) assert.Equal(t, sceneIDs[sceneIdxWithPerformer], scenes[0].ID) scenes, err = sqb.FindByPerformerID(0) if err != nil { t.Errorf("error calling FindByPerformerID: %s", err.Error()) } assert.Len(t, scenes, 0) return nil }) } func TestSceneUpdateSceneCover(t *testing.T) { if err := withTxn(func(r models.Repository) error { qb := r.Scene() // create performer to test against const name = "TestSceneUpdateSceneCover" scene := models.Scene{ Path: name, Checksum: sql.NullString{String: md5.FromString(name), Valid: true}, } created, err := qb.Create(scene) if err != nil { return fmt.Errorf("Error creating scene: %s", err.Error()) } image := []byte("image") err = qb.UpdateCover(created.ID, image) if err != nil { return fmt.Errorf("Error updating scene cover: %s", err.Error()) } // ensure image set storedImage, err := qb.GetCover(created.ID) if err != nil { return fmt.Errorf("Error getting image: %s", err.Error()) } assert.Equal(t, storedImage, image) // set nil image err = qb.UpdateCover(created.ID, nil) if err == nil { return fmt.Errorf("Expected error setting nil image") } return nil }); err != nil { t.Error(err.Error()) } } func TestSceneDestroySceneCover(t *testing.T) { if err := withTxn(func(r models.Repository) error { qb := r.Scene() // create performer to test against const name = "TestSceneDestroySceneCover" scene := models.Scene{ Path: name, Checksum: sql.NullString{String: md5.FromString(name), Valid: true}, } created, err := qb.Create(scene) if err != nil { return fmt.Errorf("Error creating scene: %s", err.Error()) } image := []byte("image") err = qb.UpdateCover(created.ID, image) if err != nil { return fmt.Errorf("Error updating scene image: %s", err.Error()) } err = qb.DestroyCover(created.ID) if err != nil { return fmt.Errorf("Error destroying scene cover: %s", err.Error()) } // image should be nil storedImage, err := qb.GetCover(created.ID) if err != nil { return fmt.Errorf("Error getting image: %s", err.Error()) } assert.Nil(t, storedImage) return nil }); err != nil { t.Error(err.Error()) } } func TestSceneStashIDs(t *testing.T) { if err := withTxn(func(r models.Repository) error { qb := r.Scene() // create scene to test against const name = "TestSceneStashIDs" scene := models.Scene{ Path: name, Checksum: sql.NullString{String: md5.FromString(name), Valid: true}, } created, err := qb.Create(scene) if err != nil { return fmt.Errorf("Error creating scene: %s", err.Error()) } testStashIDReaderWriter(t, qb, created.ID) return nil }); err != nil { t.Error(err.Error()) } } func TestSceneQueryQTrim(t *testing.T) { if err := withTxn(func(r models.Repository) error { qb := r.Scene() expectedID := sceneIDs[sceneIdxWithSpacedName] type test struct { query string id int count int } tests := []test{ {query: " zzz yyy ", id: expectedID, count: 1}, {query: " \"zzz yyy xxx\" ", id: expectedID, count: 1}, {query: "zzz", id: expectedID, count: 1}, {query: "\" zzz yyy \"", count: 0}, {query: "\"zzz yyy\"", count: 0}, {query: "\" zzz yyy\"", count: 0}, {query: "\"zzz yyy \"", count: 0}, } for _, tst := range tests { f := models.FindFilterType{ Q: &tst.query, } scenes := queryScene(t, qb, nil, &f) assert.Len(t, scenes, tst.count) if len(scenes) > 0 { assert.Equal(t, tst.id, scenes[0].ID) } } findFilter := models.FindFilterType{} scenes := queryScene(t, qb, nil, &findFilter) assert.NotEqual(t, 0, len(scenes)) return nil }); err != nil { t.Error(err.Error()) } } // TODO Update // TODO IncrementOCounter // TODO DecrementOCounter // TODO ResetOCounter // TODO Destroy // TODO FindByChecksum // TODO Count // TODO SizeCount // TODO All