stash/pkg/sqlite/scene_test.go

4788 lines
110 KiB
Go

//go:build integration
// +build integration
package sqlite_test
import (
"context"
"fmt"
"math"
"path/filepath"
"reflect"
"regexp"
"strconv"
"testing"
"time"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
"github.com/stashapp/stash/pkg/sliceutil/intslice"
"github.com/stretchr/testify/assert"
)
func loadSceneRelationships(ctx context.Context, expected models.Scene, actual *models.Scene) error {
if expected.URLs.Loaded() {
if err := actual.LoadURLs(ctx, db.Scene); err != nil {
return err
}
}
if expected.GalleryIDs.Loaded() {
if err := actual.LoadGalleryIDs(ctx, db.Scene); err != nil {
return err
}
}
if expected.TagIDs.Loaded() {
if err := actual.LoadTagIDs(ctx, db.Scene); err != nil {
return err
}
}
if expected.PerformerIDs.Loaded() {
if err := actual.LoadPerformerIDs(ctx, db.Scene); err != nil {
return err
}
}
if expected.Groups.Loaded() {
if err := actual.LoadGroups(ctx, db.Scene); err != nil {
return err
}
}
if expected.StashIDs.Loaded() {
if err := actual.LoadStashIDs(ctx, db.Scene); err != nil {
return err
}
}
if expected.Files.Loaded() {
if err := actual.LoadFiles(ctx, db.Scene); err != nil {
return err
}
}
// clear Path, Checksum, PrimaryFileID
if expected.Path == "" {
actual.Path = ""
}
if expected.Checksum == "" {
actual.Checksum = ""
}
if expected.OSHash == "" {
actual.OSHash = ""
}
if expected.PrimaryFileID == nil {
actual.PrimaryFileID = nil
}
return nil
}
func Test_sceneQueryBuilder_Create(t *testing.T) {
var (
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 60
resumeTime = 10.0
playDuration = 34.0
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
sceneIndex = 123
sceneIndex2 = 234
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
stashID1 = "stashid1"
stashID2 = "stashid2"
date, _ = models.ParseDate("2003-02-01")
videoFile = makeFileWithID(fileIdxStartVideoFiles)
)
tests := []struct {
name string
newObject models.Scene
wantErr bool
}{
{
"full",
models.Scene{
Title: title,
Code: code,
Details: details,
Director: director,
URLs: models.NewRelatedStrings([]string{url}),
Date: &date,
Rating: &rating,
Organized: true,
StudioID: &studioIDs[studioIdxWithScene],
CreatedAt: createdAt,
UpdatedAt: updatedAt,
GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}),
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithScene]}),
PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]}),
Groups: models.NewRelatedGroups([]models.GroupsScenes{
{
GroupID: groupIDs[groupIdxWithScene],
SceneIndex: &sceneIndex,
},
{
GroupID: groupIDs[groupIdxWithStudio],
SceneIndex: &sceneIndex2,
},
}),
StashIDs: models.NewRelatedStashIDs([]models.StashID{
{
StashID: stashID1,
Endpoint: endpoint1,
},
{
StashID: stashID2,
Endpoint: endpoint2,
},
}),
ResumeTime: float64(resumeTime),
PlayDuration: playDuration,
},
false,
},
{
"with file",
models.Scene{
Title: title,
Code: code,
Details: details,
Director: director,
URLs: models.NewRelatedStrings([]string{url}),
Date: &date,
Rating: &rating,
Organized: true,
StudioID: &studioIDs[studioIdxWithScene],
Files: models.NewRelatedVideoFiles([]*models.VideoFile{
videoFile.(*models.VideoFile),
}),
CreatedAt: createdAt,
UpdatedAt: updatedAt,
GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}),
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithScene]}),
PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]}),
Groups: models.NewRelatedGroups([]models.GroupsScenes{
{
GroupID: groupIDs[groupIdxWithScene],
SceneIndex: &sceneIndex,
},
{
GroupID: groupIDs[groupIdxWithStudio],
SceneIndex: &sceneIndex2,
},
}),
StashIDs: models.NewRelatedStashIDs([]models.StashID{
{
StashID: stashID1,
Endpoint: endpoint1,
},
{
StashID: stashID2,
Endpoint: endpoint2,
},
}),
ResumeTime: resumeTime,
PlayDuration: playDuration,
},
false,
},
{
"invalid studio id",
models.Scene{
StudioID: &invalidID,
},
true,
},
{
"invalid gallery id",
models.Scene{
GalleryIDs: models.NewRelatedIDs([]int{invalidID}),
},
true,
},
{
"invalid tag id",
models.Scene{
TagIDs: models.NewRelatedIDs([]int{invalidID}),
},
true,
},
{
"invalid performer id",
models.Scene{
PerformerIDs: models.NewRelatedIDs([]int{invalidID}),
},
true,
},
{
"invalid group id",
models.Scene{
Groups: models.NewRelatedGroups([]models.GroupsScenes{
{
GroupID: invalidID,
SceneIndex: &sceneIndex,
},
}),
},
true,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
var fileIDs []models.FileID
if tt.newObject.Files.Loaded() {
for _, f := range tt.newObject.Files.List() {
fileIDs = append(fileIDs, f.ID)
}
}
s := tt.newObject
if err := qb.Create(ctx, &s, fileIDs); (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.Create() error = %v, wantErr = %v", err, tt.wantErr)
}
if tt.wantErr {
assert.Zero(s.ID)
return
}
assert.NotZero(s.ID)
copy := tt.newObject
copy.ID = s.ID
// load relationships
if err := loadSceneRelationships(ctx, copy, &s); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
assert.Equal(copy, s)
// ensure can find the scene
found, err := qb.Find(ctx, s.ID)
if err != nil {
t.Errorf("sceneQueryBuilder.Find() error = %v", err)
}
if !assert.NotNil(found) {
return
}
// load relationships
if err := loadSceneRelationships(ctx, copy, found); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
assert.Equal(copy, *found)
return
})
}
}
func clearSceneFileIDs(scene *models.Scene) {
if scene.Files.Loaded() {
for _, f := range scene.Files.List() {
f.Base().ID = 0
}
}
}
func makeSceneFileWithID(i int) *models.VideoFile {
ret := makeSceneFile(i)
ret.ID = sceneFileIDs[i]
return ret
}
func Test_sceneQueryBuilder_Update(t *testing.T) {
var (
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 60
resumeTime = 10.0
playDuration = 34.0
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
sceneIndex = 123
sceneIndex2 = 234
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
stashID1 = "stashid1"
stashID2 = "stashid2"
date, _ = models.ParseDate("2003-02-01")
)
tests := []struct {
name string
updatedObject *models.Scene
wantErr bool
}{
{
"full",
&models.Scene{
ID: sceneIDs[sceneIdxWithGallery],
Title: title,
Code: code,
Details: details,
Director: director,
URLs: models.NewRelatedStrings([]string{url}),
Date: &date,
Rating: &rating,
Organized: true,
StudioID: &studioIDs[studioIdxWithScene],
CreatedAt: createdAt,
UpdatedAt: updatedAt,
GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}),
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithScene]}),
PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]}),
Groups: models.NewRelatedGroups([]models.GroupsScenes{
{
GroupID: groupIDs[groupIdxWithScene],
SceneIndex: &sceneIndex,
},
{
GroupID: groupIDs[groupIdxWithStudio],
SceneIndex: &sceneIndex2,
},
}),
StashIDs: models.NewRelatedStashIDs([]models.StashID{
{
StashID: stashID1,
Endpoint: endpoint1,
},
{
StashID: stashID2,
Endpoint: endpoint2,
},
}),
ResumeTime: resumeTime,
PlayDuration: playDuration,
},
false,
},
{
"clear nullables",
&models.Scene{
ID: sceneIDs[sceneIdxWithSpacedName],
GalleryIDs: models.NewRelatedIDs([]int{}),
TagIDs: models.NewRelatedIDs([]int{}),
PerformerIDs: models.NewRelatedIDs([]int{}),
Groups: models.NewRelatedGroups([]models.GroupsScenes{}),
StashIDs: models.NewRelatedStashIDs([]models.StashID{}),
},
false,
},
{
"clear gallery ids",
&models.Scene{
ID: sceneIDs[sceneIdxWithGallery],
GalleryIDs: models.NewRelatedIDs([]int{}),
},
false,
},
{
"clear tag ids",
&models.Scene{
ID: sceneIDs[sceneIdxWithTag],
TagIDs: models.NewRelatedIDs([]int{}),
},
false,
},
{
"clear performer ids",
&models.Scene{
ID: sceneIDs[sceneIdxWithPerformer],
PerformerIDs: models.NewRelatedIDs([]int{}),
},
false,
},
{
"clear groups",
&models.Scene{
ID: sceneIDs[sceneIdxWithGroup],
Groups: models.NewRelatedGroups([]models.GroupsScenes{}),
},
false,
},
{
"invalid studio id",
&models.Scene{
ID: sceneIDs[sceneIdxWithGallery],
StudioID: &invalidID,
},
true,
},
{
"invalid gallery id",
&models.Scene{
ID: sceneIDs[sceneIdxWithGallery],
GalleryIDs: models.NewRelatedIDs([]int{invalidID}),
},
true,
},
{
"invalid tag id",
&models.Scene{
ID: sceneIDs[sceneIdxWithGallery],
TagIDs: models.NewRelatedIDs([]int{invalidID}),
},
true,
},
{
"invalid performer id",
&models.Scene{
ID: sceneIDs[sceneIdxWithGallery],
PerformerIDs: models.NewRelatedIDs([]int{invalidID}),
},
true,
},
{
"invalid group id",
&models.Scene{
ID: sceneIDs[sceneIdxWithSpacedName],
Groups: models.NewRelatedGroups([]models.GroupsScenes{
{
GroupID: invalidID,
SceneIndex: &sceneIndex,
},
}),
},
true,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
copy := *tt.updatedObject
if err := qb.Update(ctx, tt.updatedObject); (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.Update() error = %v, wantErr %v", err, tt.wantErr)
}
if tt.wantErr {
return
}
s, err := qb.Find(ctx, tt.updatedObject.ID)
if err != nil {
t.Errorf("sceneQueryBuilder.Find() error = %v", err)
}
// load relationships
if err := loadSceneRelationships(ctx, copy, s); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
assert.Equal(copy, *s)
})
}
}
func clearScenePartial() models.ScenePartial {
// leave mandatory fields
return models.ScenePartial{
Title: models.OptionalString{Set: true, Null: true},
Code: models.OptionalString{Set: true, Null: true},
Details: models.OptionalString{Set: true, Null: true},
Director: models.OptionalString{Set: true, Null: true},
URLs: &models.UpdateStrings{Mode: models.RelationshipUpdateModeSet},
Date: models.OptionalDate{Set: true, Null: true},
Rating: models.OptionalInt{Set: true, Null: true},
StudioID: models.OptionalInt{Set: true, Null: true},
GalleryIDs: &models.UpdateIDs{Mode: models.RelationshipUpdateModeSet},
TagIDs: &models.UpdateIDs{Mode: models.RelationshipUpdateModeSet},
PerformerIDs: &models.UpdateIDs{Mode: models.RelationshipUpdateModeSet},
StashIDs: &models.UpdateStashIDs{Mode: models.RelationshipUpdateModeSet},
}
}
func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) {
var (
title = "title"
code = "1337"
details = "details"
director = "director"
url = "url"
rating = 60
resumeTime = 10.0
playDuration = 34.0
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
sceneIndex = 123
sceneIndex2 = 234
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
stashID1 = "stashid1"
stashID2 = "stashid2"
date, _ = models.ParseDate("2003-02-01")
)
tests := []struct {
name string
id int
partial models.ScenePartial
want models.Scene
wantErr bool
}{
{
"full",
sceneIDs[sceneIdxWithSpacedName],
models.ScenePartial{
Title: models.NewOptionalString(title),
Code: models.NewOptionalString(code),
Details: models.NewOptionalString(details),
Director: models.NewOptionalString(director),
URLs: &models.UpdateStrings{
Values: []string{url},
Mode: models.RelationshipUpdateModeSet,
},
Date: models.NewOptionalDate(date),
Rating: models.NewOptionalInt(rating),
Organized: models.NewOptionalBool(true),
StudioID: models.NewOptionalInt(studioIDs[studioIdxWithScene]),
CreatedAt: models.NewOptionalTime(createdAt),
UpdatedAt: models.NewOptionalTime(updatedAt),
GalleryIDs: &models.UpdateIDs{
IDs: []int{galleryIDs[galleryIdxWithScene]},
Mode: models.RelationshipUpdateModeSet,
},
TagIDs: &models.UpdateIDs{
IDs: []int{tagIDs[tagIdx1WithScene], tagIDs[tagIdx1WithDupName]},
Mode: models.RelationshipUpdateModeSet,
},
PerformerIDs: &models.UpdateIDs{
IDs: []int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]},
Mode: models.RelationshipUpdateModeSet,
},
GroupIDs: &models.UpdateGroupIDs{
Groups: []models.GroupsScenes{
{
GroupID: groupIDs[groupIdxWithScene],
SceneIndex: &sceneIndex,
},
{
GroupID: groupIDs[groupIdxWithStudio],
SceneIndex: &sceneIndex2,
},
},
Mode: models.RelationshipUpdateModeSet,
},
StashIDs: &models.UpdateStashIDs{
StashIDs: []models.StashID{
{
StashID: stashID1,
Endpoint: endpoint1,
},
{
StashID: stashID2,
Endpoint: endpoint2,
},
},
Mode: models.RelationshipUpdateModeSet,
},
ResumeTime: models.NewOptionalFloat64(resumeTime),
PlayDuration: models.NewOptionalFloat64(playDuration),
},
models.Scene{
ID: sceneIDs[sceneIdxWithSpacedName],
Files: models.NewRelatedVideoFiles([]*models.VideoFile{
makeSceneFile(sceneIdxWithSpacedName),
}),
Title: title,
Code: code,
Details: details,
Director: director,
URLs: models.NewRelatedStrings([]string{url}),
Date: &date,
Rating: &rating,
Organized: true,
StudioID: &studioIDs[studioIdxWithScene],
CreatedAt: createdAt,
UpdatedAt: updatedAt,
GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}),
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithScene]}),
PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]}),
Groups: models.NewRelatedGroups([]models.GroupsScenes{
{
GroupID: groupIDs[groupIdxWithScene],
SceneIndex: &sceneIndex,
},
{
GroupID: groupIDs[groupIdxWithStudio],
SceneIndex: &sceneIndex2,
},
}),
StashIDs: models.NewRelatedStashIDs([]models.StashID{
{
StashID: stashID1,
Endpoint: endpoint1,
},
{
StashID: stashID2,
Endpoint: endpoint2,
},
}),
ResumeTime: resumeTime,
PlayDuration: playDuration,
},
false,
},
{
"clear all",
sceneIDs[sceneIdxWithSpacedName],
clearScenePartial(),
models.Scene{
ID: sceneIDs[sceneIdxWithSpacedName],
Files: models.NewRelatedVideoFiles([]*models.VideoFile{
makeSceneFile(sceneIdxWithSpacedName),
}),
GalleryIDs: models.NewRelatedIDs([]int{}),
TagIDs: models.NewRelatedIDs([]int{}),
PerformerIDs: models.NewRelatedIDs([]int{}),
Groups: models.NewRelatedGroups([]models.GroupsScenes{}),
StashIDs: models.NewRelatedStashIDs([]models.StashID{}),
PlayDuration: getScenePlayDuration(sceneIdxWithSpacedName),
ResumeTime: getSceneResumeTime(sceneIdxWithSpacedName),
},
false,
},
{
"invalid id",
invalidID,
models.ScenePartial{},
models.Scene{},
true,
},
}
for _, tt := range tests {
qb := db.Scene
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.UpdatePartial(ctx, tt.id, tt.partial)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.UpdatePartial() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr {
return
}
// load relationships
if err := loadSceneRelationships(ctx, tt.want, got); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
// ignore file ids
clearSceneFileIDs(got)
assert.Equal(tt.want, *got)
s, err := qb.Find(ctx, tt.id)
if err != nil {
t.Errorf("sceneQueryBuilder.Find() error = %v", err)
}
// load relationships
if err := loadSceneRelationships(ctx, tt.want, s); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
// ignore file ids
clearSceneFileIDs(s)
assert.Equal(tt.want, *s)
})
}
}
func Test_sceneQueryBuilder_UpdatePartialRelationships(t *testing.T) {
var (
sceneIndex = 123
sceneIndex2 = 234
endpoint1 = "endpoint1"
endpoint2 = "endpoint2"
stashID1 = "stashid1"
stashID2 = "stashid2"
groupScenes = []models.GroupsScenes{
{
GroupID: groupIDs[groupIdxWithDupName],
SceneIndex: &sceneIndex,
},
{
GroupID: groupIDs[groupIdxWithStudio],
SceneIndex: &sceneIndex2,
},
}
stashIDs = []models.StashID{
{
StashID: stashID1,
Endpoint: endpoint1,
},
{
StashID: stashID2,
Endpoint: endpoint2,
},
}
)
tests := []struct {
name string
id int
partial models.ScenePartial
want models.Scene
wantErr bool
}{
{
"add galleries",
sceneIDs[sceneIdxWithGallery],
models.ScenePartial{
GalleryIDs: &models.UpdateIDs{
IDs: []int{galleryIDs[galleryIdx1WithImage], galleryIDs[galleryIdx1WithPerformer]},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
GalleryIDs: models.NewRelatedIDs(append(indexesToIDs(galleryIDs, sceneGalleries[sceneIdxWithGallery]),
galleryIDs[galleryIdx1WithImage],
galleryIDs[galleryIdx1WithPerformer],
)),
},
false,
},
{
"add identical galleries",
sceneIDs[sceneIdxWithGallery],
models.ScenePartial{
GalleryIDs: &models.UpdateIDs{
IDs: []int{galleryIDs[galleryIdx1WithImage], galleryIDs[galleryIdx1WithImage]},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
GalleryIDs: models.NewRelatedIDs(append(indexesToIDs(galleryIDs, sceneGalleries[sceneIdxWithGallery]),
galleryIDs[galleryIdx1WithImage],
)),
},
false,
},
{
"add tags",
sceneIDs[sceneIdxWithTwoTags],
models.ScenePartial{
TagIDs: &models.UpdateIDs{
IDs: []int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithGallery]},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
TagIDs: models.NewRelatedIDs(append(
[]int{
tagIDs[tagIdx1WithGallery],
tagIDs[tagIdx1WithDupName],
},
indexesToIDs(tagIDs, sceneTags[sceneIdxWithTwoTags])...,
)),
},
false,
},
{
"add identical tags",
sceneIDs[sceneIdxWithTwoTags],
models.ScenePartial{
TagIDs: &models.UpdateIDs{
IDs: []int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithDupName]},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
TagIDs: models.NewRelatedIDs(append(
[]int{
tagIDs[tagIdx1WithDupName],
},
indexesToIDs(tagIDs, sceneTags[sceneIdxWithTwoTags])...,
)),
},
false,
},
{
"add performers",
sceneIDs[sceneIdxWithTwoPerformers],
models.ScenePartial{
PerformerIDs: &models.UpdateIDs{
IDs: []int{performerIDs[performerIdx1WithDupName], performerIDs[performerIdx1WithGallery]},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
PerformerIDs: models.NewRelatedIDs(append(indexesToIDs(performerIDs, scenePerformers[sceneIdxWithTwoPerformers]),
performerIDs[performerIdx1WithDupName],
performerIDs[performerIdx1WithGallery],
)),
},
false,
},
{
"add identical performers",
sceneIDs[sceneIdxWithTwoPerformers],
models.ScenePartial{
PerformerIDs: &models.UpdateIDs{
IDs: []int{performerIDs[performerIdx1WithDupName], performerIDs[performerIdx1WithDupName]},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
PerformerIDs: models.NewRelatedIDs(append(indexesToIDs(performerIDs, scenePerformers[sceneIdxWithTwoPerformers]),
performerIDs[performerIdx1WithDupName],
)),
},
false,
},
{
"add groups",
sceneIDs[sceneIdxWithGroup],
models.ScenePartial{
GroupIDs: &models.UpdateGroupIDs{
Groups: groupScenes,
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
Groups: models.NewRelatedGroups(append([]models.GroupsScenes{
{
GroupID: indexesToIDs(groupIDs, sceneGroups[sceneIdxWithGroup])[0],
},
}, groupScenes...)),
},
false,
},
{
"add groups to empty",
sceneIDs[sceneIdx1WithPerformer],
models.ScenePartial{
GroupIDs: &models.UpdateGroupIDs{
Groups: groupScenes,
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
Groups: models.NewRelatedGroups([]models.GroupsScenes{
{
GroupID: groupIDs[groupIdxWithDupName],
SceneIndex: &sceneIndex,
},
{
GroupID: groupIDs[groupIdxWithStudio],
SceneIndex: &sceneIndex2,
},
}),
},
false,
},
{
"add stash ids",
sceneIDs[sceneIdxWithSpacedName],
models.ScenePartial{
StashIDs: &models.UpdateStashIDs{
StashIDs: stashIDs,
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
StashIDs: models.NewRelatedStashIDs(append([]models.StashID{sceneStashID(sceneIdxWithSpacedName)}, stashIDs...)),
},
false,
},
{
"add duplicate galleries",
sceneIDs[sceneIdxWithGallery],
models.ScenePartial{
GalleryIDs: &models.UpdateIDs{
IDs: []int{galleryIDs[galleryIdxWithScene], galleryIDs[galleryIdx1WithPerformer]},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
GalleryIDs: models.NewRelatedIDs(append(indexesToIDs(galleryIDs, sceneGalleries[sceneIdxWithGallery]),
galleryIDs[galleryIdx1WithPerformer],
)),
},
false,
},
{
"add duplicate tags",
sceneIDs[sceneIdxWithTwoTags],
models.ScenePartial{
TagIDs: &models.UpdateIDs{
IDs: []int{tagIDs[tagIdx1WithScene], tagIDs[tagIdx1WithGallery]},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
TagIDs: models.NewRelatedIDs(append(
[]int{tagIDs[tagIdx1WithGallery]},
indexesToIDs(tagIDs, sceneTags[sceneIdxWithTwoTags])...,
)),
},
false,
},
{
"add duplicate performers",
sceneIDs[sceneIdxWithTwoPerformers],
models.ScenePartial{
PerformerIDs: &models.UpdateIDs{
IDs: []int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithGallery]},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
PerformerIDs: models.NewRelatedIDs(append(indexesToIDs(performerIDs, scenePerformers[sceneIdxWithTwoPerformers]),
performerIDs[performerIdx1WithGallery],
)),
},
false,
},
{
"add duplicate groups",
sceneIDs[sceneIdxWithGroup],
models.ScenePartial{
GroupIDs: &models.UpdateGroupIDs{
Groups: append([]models.GroupsScenes{
{
GroupID: groupIDs[groupIdxWithScene],
SceneIndex: &sceneIndex,
},
},
groupScenes...,
),
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
Groups: models.NewRelatedGroups(append([]models.GroupsScenes{
{
GroupID: indexesToIDs(groupIDs, sceneGroups[sceneIdxWithGroup])[0],
},
}, groupScenes...)),
},
false,
},
{
"add duplicate stash ids",
sceneIDs[sceneIdxWithSpacedName],
models.ScenePartial{
StashIDs: &models.UpdateStashIDs{
StashIDs: []models.StashID{
sceneStashID(sceneIdxWithSpacedName),
},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{
StashIDs: models.NewRelatedStashIDs([]models.StashID{sceneStashID(sceneIdxWithSpacedName)}),
},
false,
},
{
"add invalid galleries",
sceneIDs[sceneIdxWithGallery],
models.ScenePartial{
GalleryIDs: &models.UpdateIDs{
IDs: []int{invalidID},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{},
true,
},
{
"add invalid tags",
sceneIDs[sceneIdxWithTwoTags],
models.ScenePartial{
TagIDs: &models.UpdateIDs{
IDs: []int{invalidID},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{},
true,
},
{
"add invalid performers",
sceneIDs[sceneIdxWithTwoPerformers],
models.ScenePartial{
PerformerIDs: &models.UpdateIDs{
IDs: []int{invalidID},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{},
true,
},
{
"add invalid groups",
sceneIDs[sceneIdxWithGroup],
models.ScenePartial{
GroupIDs: &models.UpdateGroupIDs{
Groups: []models.GroupsScenes{
{
GroupID: invalidID,
},
},
Mode: models.RelationshipUpdateModeAdd,
},
},
models.Scene{},
true,
},
{
"remove galleries",
sceneIDs[sceneIdxWithGallery],
models.ScenePartial{
GalleryIDs: &models.UpdateIDs{
IDs: []int{galleryIDs[galleryIdxWithScene]},
Mode: models.RelationshipUpdateModeRemove,
},
},
models.Scene{
GalleryIDs: models.NewRelatedIDs([]int{}),
},
false,
},
{
"remove tags",
sceneIDs[sceneIdxWithTwoTags],
models.ScenePartial{
TagIDs: &models.UpdateIDs{
IDs: []int{tagIDs[tagIdx1WithScene]},
Mode: models.RelationshipUpdateModeRemove,
},
},
models.Scene{
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx2WithScene]}),
},
false,
},
{
"remove performers",
sceneIDs[sceneIdxWithTwoPerformers],
models.ScenePartial{
PerformerIDs: &models.UpdateIDs{
IDs: []int{performerIDs[performerIdx1WithScene]},
Mode: models.RelationshipUpdateModeRemove,
},
},
models.Scene{
PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx2WithScene]}),
},
false,
},
{
"remove groups",
sceneIDs[sceneIdxWithGroup],
models.ScenePartial{
GroupIDs: &models.UpdateGroupIDs{
Groups: []models.GroupsScenes{
{
GroupID: groupIDs[groupIdxWithScene],
},
},
Mode: models.RelationshipUpdateModeRemove,
},
},
models.Scene{
Groups: models.NewRelatedGroups([]models.GroupsScenes{}),
},
false,
},
{
"remove stash ids",
sceneIDs[sceneIdxWithSpacedName],
models.ScenePartial{
StashIDs: &models.UpdateStashIDs{
StashIDs: []models.StashID{sceneStashID(sceneIdxWithSpacedName)},
Mode: models.RelationshipUpdateModeRemove,
},
},
models.Scene{
StashIDs: models.NewRelatedStashIDs([]models.StashID{}),
},
false,
},
{
"remove unrelated galleries",
sceneIDs[sceneIdxWithGallery],
models.ScenePartial{
GalleryIDs: &models.UpdateIDs{
IDs: []int{galleryIDs[galleryIdx1WithImage]},
Mode: models.RelationshipUpdateModeRemove,
},
},
models.Scene{
GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}),
},
false,
},
{
"remove unrelated tags",
sceneIDs[sceneIdxWithTwoTags],
models.ScenePartial{
TagIDs: &models.UpdateIDs{
IDs: []int{tagIDs[tagIdx1WithPerformer]},
Mode: models.RelationshipUpdateModeRemove,
},
},
models.Scene{
TagIDs: models.NewRelatedIDs(indexesToIDs(tagIDs, sceneTags[sceneIdxWithTwoTags])),
},
false,
},
{
"remove unrelated performers",
sceneIDs[sceneIdxWithTwoPerformers],
models.ScenePartial{
PerformerIDs: &models.UpdateIDs{
IDs: []int{performerIDs[performerIdx1WithDupName]},
Mode: models.RelationshipUpdateModeRemove,
},
},
models.Scene{
PerformerIDs: models.NewRelatedIDs(indexesToIDs(performerIDs, scenePerformers[sceneIdxWithTwoPerformers])),
},
false,
},
{
"remove unrelated groups",
sceneIDs[sceneIdxWithGroup],
models.ScenePartial{
GroupIDs: &models.UpdateGroupIDs{
Groups: []models.GroupsScenes{
{
GroupID: groupIDs[groupIdxWithDupName],
},
},
Mode: models.RelationshipUpdateModeRemove,
},
},
models.Scene{
Groups: models.NewRelatedGroups([]models.GroupsScenes{
{
GroupID: indexesToIDs(groupIDs, sceneGroups[sceneIdxWithGroup])[0],
},
}),
},
false,
},
{
"remove unrelated stash ids",
sceneIDs[sceneIdxWithGallery],
models.ScenePartial{
StashIDs: &models.UpdateStashIDs{
StashIDs: stashIDs,
Mode: models.RelationshipUpdateModeRemove,
},
},
models.Scene{
StashIDs: models.NewRelatedStashIDs([]models.StashID{sceneStashID(sceneIdxWithGallery)}),
},
false,
},
}
for _, tt := range tests {
qb := db.Scene
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.UpdatePartial(ctx, tt.id, tt.partial)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.UpdatePartial() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr {
return
}
s, err := qb.Find(ctx, tt.id)
if err != nil {
t.Errorf("sceneQueryBuilder.Find() error = %v", err)
}
// load relationships
if err := loadSceneRelationships(ctx, tt.want, got); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
if err := loadSceneRelationships(ctx, tt.want, s); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
// only compare fields that were in the partial
if tt.partial.PerformerIDs != nil {
assert.ElementsMatch(tt.want.PerformerIDs.List(), got.PerformerIDs.List())
assert.ElementsMatch(tt.want.PerformerIDs.List(), s.PerformerIDs.List())
}
if tt.partial.TagIDs != nil {
assert.ElementsMatch(tt.want.TagIDs.List(), got.TagIDs.List())
assert.ElementsMatch(tt.want.TagIDs.List(), s.TagIDs.List())
}
if tt.partial.GalleryIDs != nil {
assert.ElementsMatch(tt.want.GalleryIDs.List(), got.GalleryIDs.List())
assert.ElementsMatch(tt.want.GalleryIDs.List(), s.GalleryIDs.List())
}
if tt.partial.GroupIDs != nil {
assert.ElementsMatch(tt.want.Groups.List(), got.Groups.List())
assert.ElementsMatch(tt.want.Groups.List(), s.Groups.List())
}
if tt.partial.StashIDs != nil {
assert.ElementsMatch(tt.want.StashIDs.List(), got.StashIDs.List())
assert.ElementsMatch(tt.want.StashIDs.List(), s.StashIDs.List())
}
})
}
}
func Test_sceneQueryBuilder_AddO(t *testing.T) {
tests := []struct {
name string
id int
want int
wantErr bool
}{
{
"increment",
sceneIDs[1],
1,
false,
},
{
"invalid",
invalidID,
0,
true,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
got, err := qb.AddO(ctx, tt.id, nil)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.AddO() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got) != tt.want {
t.Errorf("sceneQueryBuilder.AddO() = %v, want %v", got, tt.want)
}
})
}
}
func Test_sceneQueryBuilder_DeleteO(t *testing.T) {
tests := []struct {
name string
id int
want int
wantErr bool
}{
{
"decrement",
sceneIDs[2],
0,
false,
},
{
"zero",
sceneIDs[0],
0,
false,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
got, err := qb.DeleteO(ctx, tt.id, nil)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.DeleteO() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got) != tt.want {
t.Errorf("sceneQueryBuilder.DeleteO() = %v, want %v", got, tt.want)
}
})
}
}
func Test_sceneQueryBuilder_ResetO(t *testing.T) {
tests := []struct {
name string
id int
want int
wantErr bool
}{
{
"decrement",
sceneIDs[2],
0,
false,
},
{
"zero",
sceneIDs[0],
0,
false,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
got, err := qb.ResetO(ctx, tt.id)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.ResetO() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("sceneQueryBuilder.ResetOCounter() = %v, want %v", got, tt.want)
}
})
}
}
func Test_sceneQueryBuilder_ResetWatchCount(t *testing.T) {
return
}
func Test_sceneQueryBuilder_Destroy(t *testing.T) {
tests := []struct {
name string
id int
wantErr bool
}{
{
"valid",
sceneIDs[sceneIdxWithGallery],
false,
},
{
"invalid",
invalidID,
true,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
if err := qb.Destroy(ctx, tt.id); (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.Destroy() error = %v, wantErr %v", err, tt.wantErr)
}
// ensure cannot be found
i, err := qb.Find(ctx, tt.id)
assert.Nil(err)
assert.Nil(i)
})
}
}
func makeSceneWithID(index int) *models.Scene {
ret := makeScene(index)
ret.ID = sceneIDs[index]
ret.Files = models.NewRelatedVideoFiles([]*models.VideoFile{makeSceneFile(index)})
return ret
}
func Test_sceneQueryBuilder_Find(t *testing.T) {
tests := []struct {
name string
id int
want *models.Scene
wantErr bool
}{
{
"valid",
sceneIDs[sceneIdxWithSpacedName],
makeSceneWithID(sceneIdxWithSpacedName),
false,
},
{
"invalid",
invalidID,
nil,
false,
},
{
"with galleries",
sceneIDs[sceneIdxWithGallery],
makeSceneWithID(sceneIdxWithGallery),
false,
},
{
"with performers",
sceneIDs[sceneIdxWithTwoPerformers],
makeSceneWithID(sceneIdxWithTwoPerformers),
false,
},
{
"with tags",
sceneIDs[sceneIdxWithTwoTags],
makeSceneWithID(sceneIdxWithTwoTags),
false,
},
{
"with groups",
sceneIDs[sceneIdxWithGroup],
makeSceneWithID(sceneIdxWithGroup),
false,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.Find(ctx, tt.id)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.Find() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != nil {
// load relationships
if err := loadSceneRelationships(ctx, *tt.want, got); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
clearSceneFileIDs(got)
}
assert.Equal(tt.want, got)
})
}
}
func postFindScenes(ctx context.Context, want []*models.Scene, got []*models.Scene) error {
for i, s := range got {
// load relationships
if i < len(want) {
if err := loadSceneRelationships(ctx, *want[i], s); err != nil {
return err
}
}
clearSceneFileIDs(s)
}
return nil
}
func Test_sceneQueryBuilder_FindMany(t *testing.T) {
tests := []struct {
name string
ids []int
want []*models.Scene
wantErr bool
}{
{
"valid with relationships",
[]int{
sceneIDs[sceneIdxWithGallery],
sceneIDs[sceneIdxWithTwoPerformers],
sceneIDs[sceneIdxWithTwoTags],
sceneIDs[sceneIdxWithGroup],
},
[]*models.Scene{
makeSceneWithID(sceneIdxWithGallery),
makeSceneWithID(sceneIdxWithTwoPerformers),
makeSceneWithID(sceneIdxWithTwoTags),
makeSceneWithID(sceneIdxWithGroup),
},
false,
},
{
"invalid",
[]int{sceneIDs[sceneIdxWithGallery], sceneIDs[sceneIdxWithTwoPerformers], invalidID},
nil,
true,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.FindMany(ctx, tt.ids)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.FindMany() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err := postFindScenes(ctx, tt.want, got); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
assert.Equal(tt.want, got)
})
}
}
func Test_sceneQueryBuilder_FindByChecksum(t *testing.T) {
getChecksum := func(index int) string {
return getSceneStringValue(index, checksumField)
}
tests := []struct {
name string
checksum string
want []*models.Scene
wantErr bool
}{
{
"valid",
getChecksum(sceneIdxWithSpacedName),
[]*models.Scene{makeSceneWithID(sceneIdxWithSpacedName)},
false,
},
{
"invalid",
"invalid checksum",
nil,
false,
},
{
"with galleries",
getChecksum(sceneIdxWithGallery),
[]*models.Scene{makeSceneWithID(sceneIdxWithGallery)},
false,
},
{
"with performers",
getChecksum(sceneIdxWithTwoPerformers),
[]*models.Scene{makeSceneWithID(sceneIdxWithTwoPerformers)},
false,
},
{
"with tags",
getChecksum(sceneIdxWithTwoTags),
[]*models.Scene{makeSceneWithID(sceneIdxWithTwoTags)},
false,
},
{
"with groups",
getChecksum(sceneIdxWithGroup),
[]*models.Scene{makeSceneWithID(sceneIdxWithGroup)},
false,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.FindByChecksum(ctx, tt.checksum)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.FindByChecksum() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err := postFindScenes(ctx, tt.want, got); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
assert.Equal(tt.want, got)
})
}
}
func Test_sceneQueryBuilder_FindByOSHash(t *testing.T) {
getOSHash := func(index int) string {
return getSceneStringValue(index, "oshash")
}
tests := []struct {
name string
oshash string
want []*models.Scene
wantErr bool
}{
{
"valid",
getOSHash(sceneIdxWithSpacedName),
[]*models.Scene{makeSceneWithID(sceneIdxWithSpacedName)},
false,
},
{
"invalid",
"invalid oshash",
nil,
false,
},
{
"with galleries",
getOSHash(sceneIdxWithGallery),
[]*models.Scene{makeSceneWithID(sceneIdxWithGallery)},
false,
},
{
"with performers",
getOSHash(sceneIdxWithTwoPerformers),
[]*models.Scene{makeSceneWithID(sceneIdxWithTwoPerformers)},
false,
},
{
"with tags",
getOSHash(sceneIdxWithTwoTags),
[]*models.Scene{makeSceneWithID(sceneIdxWithTwoTags)},
false,
},
{
"with groups",
getOSHash(sceneIdxWithGroup),
[]*models.Scene{makeSceneWithID(sceneIdxWithGroup)},
false,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
got, err := qb.FindByOSHash(ctx, tt.oshash)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.FindByOSHash() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err := postFindScenes(ctx, tt.want, got); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("sceneQueryBuilder.FindByOSHash() = %v, want %v", got, tt.want)
}
})
}
}
func Test_sceneQueryBuilder_FindByPath(t *testing.T) {
getPath := func(index int) string {
return getFilePath(folderIdxWithSceneFiles, getSceneBasename(index))
}
tests := []struct {
name string
path string
want []*models.Scene
wantErr bool
}{
{
"valid",
getPath(sceneIdxWithSpacedName),
[]*models.Scene{makeSceneWithID(sceneIdxWithSpacedName)},
false,
},
{
"invalid",
"invalid path",
nil,
false,
},
{
"with galleries",
getPath(sceneIdxWithGallery),
[]*models.Scene{makeSceneWithID(sceneIdxWithGallery)},
false,
},
{
"with performers",
getPath(sceneIdxWithTwoPerformers),
[]*models.Scene{makeSceneWithID(sceneIdxWithTwoPerformers)},
false,
},
{
"with tags",
getPath(sceneIdxWithTwoTags),
[]*models.Scene{makeSceneWithID(sceneIdxWithTwoTags)},
false,
},
{
"with groups",
getPath(sceneIdxWithGroup),
[]*models.Scene{makeSceneWithID(sceneIdxWithGroup)},
false,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.FindByPath(ctx, tt.path)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.FindByPath() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err := postFindScenes(ctx, tt.want, got); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
assert.Equal(tt.want, got)
})
}
}
func Test_sceneQueryBuilder_FindByGalleryID(t *testing.T) {
tests := []struct {
name string
galleryID int
want []*models.Scene
wantErr bool
}{
{
"valid",
galleryIDs[galleryIdxWithScene],
[]*models.Scene{makeSceneWithID(sceneIdxWithGallery)},
false,
},
{
"none",
galleryIDs[galleryIdx1WithPerformer],
nil,
false,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.FindByGalleryID(ctx, tt.galleryID)
if (err != nil) != tt.wantErr {
t.Errorf("sceneQueryBuilder.FindByGalleryID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err := postFindScenes(ctx, tt.want, got); err != nil {
t.Errorf("loadSceneRelationships() error = %v", err)
return
}
assert.Equal(tt.want, got)
return
})
}
}
func TestSceneCountByPerformerID(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
count, err := sqb.CountByPerformerID(ctx, performerIDs[performerIdxWithScene])
if err != nil {
t.Errorf("Error counting scenes: %s", err.Error())
}
assert.Equal(t, 1, count)
count, err = sqb.CountByPerformerID(ctx, 0)
if err != nil {
t.Errorf("Error counting scenes: %s", err.Error())
}
assert.Equal(t, 0, count)
return nil
})
}
func scenesToIDs(i []*models.Scene) []int {
ret := make([]int, len(i))
for i, v := range i {
ret[i] = v.ID
}
return ret
}
func Test_sceneStore_FindByFileID(t *testing.T) {
tests := []struct {
name string
fileID models.FileID
include []int
exclude []int
}{
{
"valid",
sceneFileIDs[sceneIdx1WithPerformer],
[]int{sceneIdx1WithPerformer},
nil,
},
{
"invalid",
invalidFileID,
nil,
[]int{sceneIdx1WithPerformer},
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.FindByFileID(ctx, tt.fileID)
if err != nil {
t.Errorf("SceneStore.FindByFileID() error = %v", err)
return
}
for _, f := range got {
clearSceneFileIDs(f)
}
ids := scenesToIDs(got)
include := indexesToIDs(galleryIDs, tt.include)
exclude := indexesToIDs(galleryIDs, tt.exclude)
for _, i := range include {
assert.Contains(ids, i)
}
for _, e := range exclude {
assert.NotContains(ids, e)
}
})
}
}
func Test_sceneStore_CountByFileID(t *testing.T) {
tests := []struct {
name string
fileID models.FileID
want int
}{
{
"valid",
sceneFileIDs[sceneIdxWithTwoPerformers],
1,
},
{
"invalid",
invalidFileID,
0,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.CountByFileID(ctx, tt.fileID)
if err != nil {
t.Errorf("SceneStore.CountByFileID() error = %v", err)
return
}
assert.Equal(tt.want, got)
})
}
}
func Test_sceneStore_CountMissingChecksum(t *testing.T) {
tests := []struct {
name string
want int
}{
{
"valid",
0,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.CountMissingChecksum(ctx)
if err != nil {
t.Errorf("SceneStore.CountMissingChecksum() error = %v", err)
return
}
assert.Equal(tt.want, got)
})
}
}
func Test_sceneStore_CountMissingOshash(t *testing.T) {
tests := []struct {
name string
want int
}{
{
"valid",
0,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.CountMissingOSHash(ctx)
if err != nil {
t.Errorf("SceneStore.CountMissingOSHash() error = %v", err)
return
}
assert.Equal(tt.want, got)
})
}
}
func TestSceneWall(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
const sceneIdx = 2
wallQuery := getSceneStringValue(sceneIdx, "Details")
scenes, err := sqb.Wall(ctx, &wallQuery)
if err != nil {
t.Errorf("Error finding scenes: %s", err.Error())
return nil
}
assert.Len(t, scenes, 1)
scene := scenes[0]
assert.Equal(t, sceneIDs[sceneIdx], scene.ID)
scenePath := getFilePath(folderIdxWithSceneFiles, getSceneBasename(sceneIdx))
assert.Equal(t, scenePath, scene.Path)
wallQuery = "not exist"
scenes, err = sqb.Wall(ctx, &wallQuery)
if err != nil {
t.Errorf("Error finding scene: %s", err.Error())
return nil
}
assert.Len(t, scenes, 0)
return nil
})
}
func TestSceneQueryQ(t *testing.T) {
const sceneIdx = 2
q := getSceneStringValue(sceneIdx, titleField)
withTxn(func(ctx context.Context) error {
sqb := db.Scene
sceneQueryQ(ctx, t, sqb, q, sceneIdx)
return nil
})
}
func queryScene(ctx context.Context, t *testing.T, sqb models.SceneReader, sceneFilter *models.SceneFilterType, findFilter *models.FindFilterType) []*models.Scene {
t.Helper()
result, err := sqb.Query(ctx, models.SceneQueryOptions{
QueryOptions: models.QueryOptions{
FindFilter: findFilter,
Count: true,
},
SceneFilter: sceneFilter,
TotalDuration: true,
TotalSize: true,
})
if err != nil {
t.Errorf("Error querying scene: %v", err)
return nil
}
scenes, err := result.Resolve(ctx)
if err != nil {
t.Errorf("Error resolving scenes: %v", err)
}
return scenes
}
func sceneQueryQ(ctx context.Context, t *testing.T, sqb models.SceneReader, q string, expectedSceneIdx int) {
filter := models.FindFilterType{
Q: &q,
}
scenes := queryScene(ctx, t, sqb, nil, &filter)
if !assert.Len(t, scenes, 1) {
return
}
scene := scenes[0]
assert.Equal(t, sceneIDs[expectedSceneIdx], scene.ID)
// no Q should return all results
filter.Q = nil
pp := totalScenes
filter.PerPage = &pp
scenes = queryScene(ctx, t, sqb, nil, &filter)
assert.Len(t, scenes, totalScenes)
}
func TestSceneQuery(t *testing.T) {
var (
endpoint = sceneStashID(sceneIdxWithGallery).Endpoint
stashID = sceneStashID(sceneIdxWithGallery).StashID
depth = -1
)
tests := []struct {
name string
findFilter *models.FindFilterType
filter *models.SceneFilterType
includeIdxs []int
excludeIdxs []int
wantErr bool
}{
{
"specific resume time",
nil,
&models.SceneFilterType{
ResumeTime: &models.IntCriterionInput{
Modifier: models.CriterionModifierEquals,
Value: int(getSceneResumeTime(sceneIdxWithGallery)),
},
},
[]int{sceneIdxWithGallery},
[]int{sceneIdxWithGroup},
false,
},
{
"specific play duration",
nil,
&models.SceneFilterType{
PlayDuration: &models.IntCriterionInput{
Modifier: models.CriterionModifierEquals,
Value: int(getScenePlayDuration(sceneIdxWithGallery)),
},
},
[]int{sceneIdxWithGallery},
[]int{sceneIdxWithGroup},
false,
},
// {
// "specific play count",
// nil,
// &models.SceneFilterType{
// PlayCount: &models.IntCriterionInput{
// Modifier: models.CriterionModifierEquals,
// Value: getScenePlayCount(sceneIdxWithGallery),
// },
// },
// []int{sceneIdxWithGallery},
// []int{sceneIdxWithGroup},
// false,
// },
{
"stash id with endpoint",
nil,
&models.SceneFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
StashID: &stashID,
Modifier: models.CriterionModifierEquals,
},
},
[]int{sceneIdxWithGallery},
nil,
false,
},
{
"exclude stash id with endpoint",
nil,
&models.SceneFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
StashID: &stashID,
Modifier: models.CriterionModifierNotEquals,
},
},
nil,
[]int{sceneIdxWithGallery},
false,
},
{
"null stash id with endpoint",
nil,
&models.SceneFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
Modifier: models.CriterionModifierIsNull,
},
},
nil,
[]int{sceneIdxWithGallery},
false,
},
{
"not null stash id with endpoint",
nil,
&models.SceneFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
Modifier: models.CriterionModifierNotNull,
},
},
[]int{sceneIdxWithGallery},
nil,
false,
},
{
"with studio id 0 including child studios",
nil,
&models.SceneFilterType{
Studios: &models.HierarchicalMultiCriterionInput{
Value: []string{"0"},
Modifier: models.CriterionModifierIncludes,
Depth: &depth,
},
},
nil,
nil,
false,
},
}
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
results, err := db.Scene.Query(ctx, models.SceneQueryOptions{
SceneFilter: tt.filter,
QueryOptions: models.QueryOptions{
FindFilter: tt.findFilter,
},
})
if (err != nil) != tt.wantErr {
t.Errorf("SceneStore.Query() error = %v, wantErr %v", err, tt.wantErr)
return
}
include := indexesToIDs(sceneIDs, tt.includeIdxs)
exclude := indexesToIDs(sceneIDs, tt.excludeIdxs)
for _, i := range include {
assert.Contains(results.IDs, i)
}
for _, e := range exclude {
assert.NotContains(results.IDs, e)
}
})
}
}
func TestSceneQueryPath(t *testing.T) {
const (
sceneIdx = 1
otherSceneIdx = 2
)
folder := folderPaths[folderIdxWithSceneFiles]
basename := getSceneBasename(sceneIdx)
scenePath := getFilePath(folderIdxWithSceneFiles, getSceneBasename(sceneIdx))
tests := []struct {
name string
input models.StringCriterionInput
mustInclude []int
mustExclude []int
}{
{
"equals full path",
models.StringCriterionInput{
Value: scenePath,
Modifier: models.CriterionModifierEquals,
},
[]int{sceneIdx},
[]int{otherSceneIdx},
},
{
"equals full path wildcard",
models.StringCriterionInput{
Value: filepath.Join(folder, "scene_0001_%"),
Modifier: models.CriterionModifierEquals,
},
[]int{sceneIdx},
[]int{otherSceneIdx},
},
{
"not equals full path",
models.StringCriterionInput{
Value: scenePath,
Modifier: models.CriterionModifierNotEquals,
},
[]int{otherSceneIdx},
[]int{sceneIdx},
},
{
"includes folder name",
models.StringCriterionInput{
Value: folder,
Modifier: models.CriterionModifierIncludes,
},
[]int{sceneIdx},
nil,
},
{
"includes base name",
models.StringCriterionInput{
Value: basename,
Modifier: models.CriterionModifierIncludes,
},
[]int{sceneIdx},
nil,
},
{
"includes full path",
models.StringCriterionInput{
Value: scenePath,
Modifier: models.CriterionModifierIncludes,
},
[]int{sceneIdx},
[]int{otherSceneIdx},
},
{
"matches regex",
models.StringCriterionInput{
Value: "scene_.*1_Path",
Modifier: models.CriterionModifierMatchesRegex,
},
[]int{sceneIdx},
nil,
},
{
"not matches regex",
models.StringCriterionInput{
Value: "scene_.*1_Path",
Modifier: models.CriterionModifierNotMatchesRegex,
},
nil,
[]int{sceneIdx},
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
got, err := qb.Query(ctx, models.SceneQueryOptions{
SceneFilter: &models.SceneFilterType{
Path: &tt.input,
},
})
if err != nil {
t.Errorf("sceneQueryBuilder.TestSceneQueryPath() error = %v", err)
return
}
mustInclude := indexesToIDs(sceneIDs, tt.mustInclude)
mustExclude := indexesToIDs(sceneIDs, tt.mustExclude)
missing := sliceutil.Exclude(mustInclude, got.IDs)
if len(missing) > 0 {
t.Errorf("SceneStore.TestSceneQueryPath() missing expected IDs: %v", missing)
}
notExcluded := sliceutil.Intersect(mustExclude, got.IDs)
if len(notExcluded) > 0 {
t.Errorf("SceneStore.TestSceneQueryPath() expected IDs to be excluded: %v", notExcluded)
}
})
}
}
func TestSceneQueryURL(t *testing.T) {
const sceneIdx = 1
sceneURL := getSceneStringValue(sceneIdx, urlField)
urlCriterion := models.StringCriterionInput{
Value: sceneURL,
Modifier: models.CriterionModifierEquals,
}
filter := models.SceneFilterType{
URL: &urlCriterion,
}
verifyFn := func(s *models.Scene) {
t.Helper()
urls := s.URLs.List()
var url string
if len(urls) > 0 {
url = urls[0]
}
verifyString(t, 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 := getFilePath(folderIdxWithSceneFiles, getSceneBasename(scene1Idx))
scene2Path := getFilePath(folderIdxWithSceneFiles, getSceneBasename(scene2Idx))
sceneFilter := models.SceneFilterType{
Path: &models.StringCriterionInput{
Value: scene1Path,
Modifier: models.CriterionModifierEquals,
},
OperatorFilter: models.OperatorFilter[models.SceneFilterType]{
Or: &models.SceneFilterType{
Path: &models.StringCriterionInput{
Value: scene2Path,
Modifier: models.CriterionModifierEquals,
},
},
},
}
withTxn(func(ctx context.Context) error {
sqb := db.Scene
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
if !assert.Len(t, scenes, 2) {
return nil
}
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 := getFilePath(folderIdxWithSceneFiles, getSceneBasename(sceneIdx))
sceneRating := int(getRating(sceneIdx).Int64)
sceneFilter := models.SceneFilterType{
Path: &models.StringCriterionInput{
Value: scenePath,
Modifier: models.CriterionModifierEquals,
},
OperatorFilter: models.OperatorFilter[models.SceneFilterType]{
And: &models.SceneFilterType{
Rating100: &models.IntCriterionInput{
Value: sceneRating,
Modifier: models.CriterionModifierEquals,
},
},
},
}
withTxn(func(ctx context.Context) error {
sqb := db.Scene
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
if !assert.Len(t, scenes, 1) {
return nil
}
assert.Equal(t, scenePath, scenes[0].Path)
assert.Equal(t, sceneRating, *scenes[0].Rating)
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,
OperatorFilter: models.OperatorFilter[models.SceneFilterType]{
Not: &models.SceneFilterType{
Rating100: &ratingCriterion,
},
},
}
withTxn(func(ctx context.Context) error {
sqb := db.Scene
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
for _, scene := range scenes {
verifyString(t, scene.Path, pathCriterion)
ratingCriterion.Modifier = models.CriterionModifierNotEquals
verifyIntPtr(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{
OperatorFilter: models.OperatorFilter[models.SceneFilterType]{
And: &subFilter,
Or: &subFilter,
},
}
withTxn(func(ctx context.Context) error {
sqb := db.Scene
queryOptions := models.SceneQueryOptions{
SceneFilter: sceneFilter,
}
_, err := sqb.Query(ctx, queryOptions)
assert.NotNil(err)
sceneFilter.Or = nil
sceneFilter.Not = &subFilter
_, err = sqb.Query(ctx, queryOptions)
assert.NotNil(err)
sceneFilter.And = nil
sceneFilter.Or = &subFilter
_, err = sqb.Query(ctx, queryOptions)
assert.NotNil(err)
return nil
})
}
func verifySceneQuery(t *testing.T, filter models.SceneFilterType, verifyFn func(s *models.Scene)) {
t.Helper()
withTxn(func(ctx context.Context) error {
t.Helper()
sqb := db.Scene
scenes := queryScene(ctx, t, sqb, &filter, nil)
for _, scene := range scenes {
if err := scene.LoadRelationships(ctx, sqb); err != nil {
t.Errorf("Error loading scene relationships: %v", err)
}
}
// 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(ctx context.Context) error {
sqb := db.Scene
sceneFilter := models.SceneFilterType{
Path: &pathCriterion,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
for _, scene := range scenes {
verifyString(t, scene.Path, pathCriterion)
}
return nil
})
}
func verifyStringPtr(t *testing.T, value *string, criterion models.StringCriterionInput) {
t.Helper()
assert := assert.New(t)
if criterion.Modifier == models.CriterionModifierIsNull {
if value != nil && *value == "" {
// correct
return
}
assert.Nil(value, "expect is null values to be null")
}
if criterion.Modifier == models.CriterionModifierNotNull {
assert.NotNil(value, "expect is null values to be null")
assert.Greater(len(*value), 0)
}
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.NotNil(value)
assert.Regexp(regexp.MustCompile(criterion.Value), *value)
}
if criterion.Modifier == models.CriterionModifierNotMatchesRegex {
if value == nil {
// 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)
switch criterion.Modifier {
case models.CriterionModifierEquals:
assert.Equal(criterion.Value, value)
case models.CriterionModifierNotEquals:
assert.NotEqual(criterion.Value, value)
case models.CriterionModifierMatchesRegex:
assert.Regexp(regexp.MustCompile(criterion.Value), value)
case models.CriterionModifierNotMatchesRegex:
assert.NotRegexp(regexp.MustCompile(criterion.Value), value)
case models.CriterionModifierIsNull:
assert.Equal("", value)
case models.CriterionModifierNotNull:
assert.NotEqual("", value)
}
}
func TestSceneQueryRating100(t *testing.T) {
const rating = 60
ratingCriterion := models.IntCriterionInput{
Value: rating,
Modifier: models.CriterionModifierEquals,
}
verifyScenesRating100(t, ratingCriterion)
ratingCriterion.Modifier = models.CriterionModifierNotEquals
verifyScenesRating100(t, ratingCriterion)
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
verifyScenesRating100(t, ratingCriterion)
ratingCriterion.Modifier = models.CriterionModifierLessThan
verifyScenesRating100(t, ratingCriterion)
ratingCriterion.Modifier = models.CriterionModifierIsNull
verifyScenesRating100(t, ratingCriterion)
ratingCriterion.Modifier = models.CriterionModifierNotNull
verifyScenesRating100(t, ratingCriterion)
}
func verifyScenesRating100(t *testing.T, ratingCriterion models.IntCriterionInput) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
sceneFilter := models.SceneFilterType{
Rating100: &ratingCriterion,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
for _, scene := range scenes {
verifyIntPtr(t, scene.Rating, ratingCriterion)
}
return nil
})
}
func verifyIntPtr(t *testing.T, value *int, criterion models.IntCriterionInput) {
t.Helper()
assert := assert.New(t)
if criterion.Modifier == models.CriterionModifierIsNull {
assert.Nil(value, "expect is null values to be null")
}
if criterion.Modifier == models.CriterionModifierNotNull {
assert.NotNil(value, "expect is null values to be null")
}
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.True(*value > criterion.Value)
}
if criterion.Modifier == models.CriterionModifierLessThan {
assert.True(*value < 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(ctx context.Context) error {
sqb := db.Scene
sceneFilter := models.SceneFilterType{
OCounter: &oCounterCriterion,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
for _, scene := range scenes {
count, err := sqb.GetOCount(ctx, scene.ID)
if err != nil {
t.Errorf("Error getting ocounter: %v", err)
}
verifyInt(t, count, oCounterCriterion)
}
return nil
})
}
func verifyInt(t *testing.T, value int, criterion models.IntCriterionInput) bool {
t.Helper()
assert := assert.New(t)
if criterion.Modifier == models.CriterionModifierEquals {
return assert.Equal(criterion.Value, value)
}
if criterion.Modifier == models.CriterionModifierNotEquals {
return assert.NotEqual(criterion.Value, value)
}
if criterion.Modifier == models.CriterionModifierGreaterThan {
return assert.Greater(value, criterion.Value)
}
if criterion.Modifier == models.CriterionModifierLessThan {
return assert.Less(value, criterion.Value)
}
return true
}
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(ctx context.Context) error {
sqb := db.Scene
sceneFilter := models.SceneFilterType{
Duration: &durationCriterion,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
for _, scene := range scenes {
if err := scene.LoadPrimaryFile(ctx, db.File); err != nil {
t.Errorf("Error querying scene files: %v", err)
return nil
}
duration := scene.Files.Primary().Duration
if durationCriterion.Modifier == models.CriterionModifierEquals {
assert.True(t, duration >= float64(durationCriterion.Value) && duration < float64(durationCriterion.Value+1))
} else if durationCriterion.Modifier == models.CriterionModifierNotEquals {
assert.True(t, duration < float64(durationCriterion.Value) || duration >= float64(durationCriterion.Value+1))
} else {
verifyFloat64(t, duration, durationCriterion)
}
}
return nil
})
}
func verifyFloat64(t *testing.T, value float64, criterion models.IntCriterionInput) {
assert := assert.New(t)
if criterion.Modifier == models.CriterionModifierEquals {
assert.Equal(float64(criterion.Value), value)
}
if criterion.Modifier == models.CriterionModifierNotEquals {
assert.NotEqual(float64(criterion.Value), value)
}
if criterion.Modifier == models.CriterionModifierGreaterThan {
assert.True(value > float64(criterion.Value))
}
if criterion.Modifier == models.CriterionModifierLessThan {
assert.True(value < float64(criterion.Value))
}
}
func verifyFloat64Ptr(t *testing.T, value *float64, criterion models.IntCriterionInput) {
assert := assert.New(t)
switch criterion.Modifier {
case models.CriterionModifierIsNull:
assert.Nil(value, "expect is null values to be null")
case models.CriterionModifierNotNull:
assert.NotNil(value, "expect is not null values to not be null")
case models.CriterionModifierEquals:
assert.EqualValues(float64(criterion.Value), value)
case models.CriterionModifierNotEquals:
assert.NotEqualValues(float64(criterion.Value), value)
case models.CriterionModifierGreaterThan:
assert.True(value != nil && *value > float64(criterion.Value))
case models.CriterionModifierLessThan:
assert.True(value != nil && *value < 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(ctx context.Context) error {
sqb := db.Scene
sceneFilter := models.SceneFilterType{
Resolution: &models.ResolutionCriterionInput{
Value: resolution,
Modifier: models.CriterionModifierEquals,
},
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
for _, scene := range scenes {
if err := scene.LoadPrimaryFile(ctx, db.File); err != nil {
t.Errorf("Error querying scene files: %v", err)
return nil
}
f := scene.Files.Primary()
height := 0
if f != nil {
height = f.Height
}
verifySceneResolution(t, &height, resolution)
}
return nil
})
}
func verifySceneResolution(t *testing.T, height *int, resolution models.ResolutionEnum) {
if !resolution.IsValid() {
return
}
assert := assert.New(t)
assert.NotNil(height)
if t.Failed() {
return
}
h := *height
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(ctx context.Context) error {
qb := db.Scene
sceneNoResolution, _ := createScene(ctx, 0, 0)
firstScene540P, _ := createScene(ctx, 960, 540)
secondScene540P, _ := createScene(ctx, 1280, 719)
firstScene720P, _ := createScene(ctx, 1280, 720)
secondScene720P, _ := createScene(ctx, 1280, 721)
thirdScene720P, _ := createScene(ctx, 1920, 1079)
scene1080P, _ := createScene(ctx, 1920, 1080)
scenesEqualTo720P := queryScenes(ctx, t, qb, models.ResolutionEnumStandardHd, models.CriterionModifierEquals)
scenesNotEqualTo720P := queryScenes(ctx, t, qb, models.ResolutionEnumStandardHd, models.CriterionModifierNotEquals)
scenesGreaterThan720P := queryScenes(ctx, t, qb, models.ResolutionEnumStandardHd, models.CriterionModifierGreaterThan)
scenesLessThan720P := queryScenes(ctx, 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(ctx context.Context, t *testing.T, queryBuilder models.SceneReaderWriter, resolution models.ResolutionEnum, modifier models.CriterionModifier) []*models.Scene {
sceneFilter := models.SceneFilterType{
Resolution: &models.ResolutionCriterionInput{
Value: resolution,
Modifier: modifier,
},
}
// needed so that we don't hit the default limit of 25 scenes
pp := 1000
findFilter := &models.FindFilterType{
PerPage: &pp,
}
return queryScene(ctx, t, queryBuilder, &sceneFilter, findFilter)
}
func createScene(ctx context.Context, width int, height int) (*models.Scene, error) {
name := fmt.Sprintf("TestSceneQueryResolutionModifiers %d %d", width, height)
sceneFile := &models.VideoFile{
BaseFile: &models.BaseFile{
Basename: name,
ParentFolderID: folderIDs[folderIdxWithSceneFiles],
},
Width: width,
Height: height,
}
if err := db.File.Create(ctx, sceneFile); err != nil {
return nil, err
}
scene := &models.Scene{}
if err := db.Scene.Create(ctx, scene, []models.FileID{sceneFile.ID}); err != nil {
return nil, err
}
return scene, nil
}
func TestSceneQueryHasMarkers(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
hasMarkers := "true"
sceneFilter := models.SceneFilterType{
HasMarkers: &hasMarkers,
}
q := getSceneStringValue(sceneIdxWithMarkers, titleField)
findFilter := models.FindFilterType{
Q: &q,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 1)
assert.Equal(t, sceneIDs[sceneIdxWithMarkers], scenes[0].ID)
hasMarkers = "false"
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 0)
findFilter.Q = nil
scenes = queryScene(ctx, 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(ctx context.Context) error {
sqb := db.Scene
isMissing := "galleries"
sceneFilter := models.SceneFilterType{
IsMissing: &isMissing,
}
q := getSceneStringValue(sceneIdxWithGallery, titleField)
findFilter := models.FindFilterType{
Q: &q,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 0)
findFilter.Q = nil
scenes = queryScene(ctx, 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(ctx context.Context) error {
sqb := db.Scene
isMissing := "studio"
sceneFilter := models.SceneFilterType{
IsMissing: &isMissing,
}
q := getSceneStringValue(sceneIdxWithStudio, titleField)
findFilter := models.FindFilterType{
Q: &q,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 0)
findFilter.Q = nil
scenes = queryScene(ctx, 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(ctx context.Context) error {
sqb := db.Scene
isMissing := "movie"
sceneFilter := models.SceneFilterType{
IsMissing: &isMissing,
}
q := getSceneStringValue(sceneIdxWithGroup, titleField)
findFilter := models.FindFilterType{
Q: &q,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 0)
findFilter.Q = nil
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
// ensure non of the ids equal the one with movies
for _, scene := range scenes {
assert.NotEqual(t, sceneIDs[sceneIdxWithGroup], scene.ID)
}
return nil
})
}
func TestSceneQueryIsMissingPerformers(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
isMissing := "performers"
sceneFilter := models.SceneFilterType{
IsMissing: &isMissing,
}
q := getSceneStringValue(sceneIdxWithPerformer, titleField)
findFilter := models.FindFilterType{
Q: &q,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 0)
findFilter.Q = nil
scenes = queryScene(ctx, 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(ctx context.Context) error {
sqb := db.Scene
isMissing := "date"
sceneFilter := models.SceneFilterType{
IsMissing: &isMissing,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
// one in four scenes have no date
assert.Len(t, scenes, int(math.Ceil(float64(totalScenes)/4)))
// ensure date is null
for _, scene := range scenes {
assert.Nil(t, scene.Date)
}
return nil
})
}
func TestSceneQueryIsMissingTags(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
isMissing := "tags"
sceneFilter := models.SceneFilterType{
IsMissing: &isMissing,
}
q := getSceneStringValue(sceneIdxWithTwoTags, titleField)
findFilter := models.FindFilterType{
Q: &q,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 0)
findFilter.Q = nil
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.True(t, len(scenes) > 0)
return nil
})
}
func TestSceneQueryIsMissingRating(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
isMissing := "rating"
sceneFilter := models.SceneFilterType{
IsMissing: &isMissing,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
assert.True(t, len(scenes) > 0)
// ensure rating is null
for _, scene := range scenes {
assert.Nil(t, scene.Rating)
}
return nil
})
}
func TestSceneQueryIsMissingPhash(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
isMissing := "phash"
sceneFilter := models.SceneFilterType{
IsMissing: &isMissing,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
if !assert.Len(t, scenes, 1) {
return nil
}
assert.Equal(t, sceneIDs[sceneIdxMissingPhash], scenes[0].ID)
return nil
})
}
func TestSceneQueryPerformers(t *testing.T) {
tests := []struct {
name string
filter models.MultiCriterionInput
includeIdxs []int
excludeIdxs []int
wantErr bool
}{
{
"includes",
models.MultiCriterionInput{
Value: []string{
strconv.Itoa(performerIDs[performerIdxWithScene]),
strconv.Itoa(performerIDs[performerIdx1WithScene]),
},
Modifier: models.CriterionModifierIncludes,
},
[]int{
sceneIdxWithPerformer,
sceneIdxWithTwoPerformers,
},
[]int{
sceneIdxWithGallery,
},
false,
},
{
"includes all",
models.MultiCriterionInput{
Value: []string{
strconv.Itoa(performerIDs[performerIdx1WithScene]),
strconv.Itoa(performerIDs[performerIdx2WithScene]),
},
Modifier: models.CriterionModifierIncludesAll,
},
[]int{
sceneIdxWithTwoPerformers,
},
[]int{
sceneIdxWithPerformer,
},
false,
},
{
"excludes",
models.MultiCriterionInput{
Modifier: models.CriterionModifierExcludes,
Value: []string{strconv.Itoa(tagIDs[performerIdx1WithScene])},
},
nil,
[]int{sceneIdxWithTwoPerformers},
false,
},
{
"is null",
models.MultiCriterionInput{
Modifier: models.CriterionModifierIsNull,
},
[]int{sceneIdxWithTag},
[]int{
sceneIdxWithPerformer,
sceneIdxWithTwoPerformers,
sceneIdxWithPerformerTwoTags,
},
false,
},
{
"not null",
models.MultiCriterionInput{
Modifier: models.CriterionModifierNotNull,
},
[]int{
sceneIdxWithPerformer,
sceneIdxWithTwoPerformers,
sceneIdxWithPerformerTwoTags,
},
[]int{sceneIdxWithTag},
false,
},
{
"equals",
models.MultiCriterionInput{
Modifier: models.CriterionModifierEquals,
Value: []string{
strconv.Itoa(tagIDs[performerIdx1WithScene]),
strconv.Itoa(tagIDs[performerIdx2WithScene]),
},
},
[]int{sceneIdxWithTwoPerformers},
[]int{
sceneIdxWithThreePerformers,
},
false,
},
{
"not equals",
models.MultiCriterionInput{
Modifier: models.CriterionModifierNotEquals,
Value: []string{
strconv.Itoa(tagIDs[performerIdx1WithScene]),
strconv.Itoa(tagIDs[performerIdx2WithScene]),
},
},
nil,
nil,
true,
},
}
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
results, err := db.Scene.Query(ctx, models.SceneQueryOptions{
SceneFilter: &models.SceneFilterType{
Performers: &tt.filter,
},
})
if (err != nil) != tt.wantErr {
t.Errorf("SceneStore.Query() error = %v, wantErr %v", err, tt.wantErr)
return
}
include := indexesToIDs(sceneIDs, tt.includeIdxs)
exclude := indexesToIDs(sceneIDs, tt.excludeIdxs)
for _, i := range include {
assert.Contains(results.IDs, i)
}
for _, e := range exclude {
assert.NotContains(results.IDs, e)
}
})
}
}
func TestSceneQueryTags(t *testing.T) {
tests := []struct {
name string
filter models.HierarchicalMultiCriterionInput
includeIdxs []int
excludeIdxs []int
wantErr bool
}{
{
"includes",
models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(tagIDs[tagIdxWithScene]),
strconv.Itoa(tagIDs[tagIdx1WithScene]),
},
Modifier: models.CriterionModifierIncludes,
},
[]int{
sceneIdxWithTag,
sceneIdxWithTwoTags,
},
[]int{
sceneIdxWithGallery,
},
false,
},
{
"includes all",
models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(tagIDs[tagIdx1WithScene]),
strconv.Itoa(tagIDs[tagIdx2WithScene]),
},
Modifier: models.CriterionModifierIncludesAll,
},
[]int{
sceneIdxWithTwoTags,
},
[]int{
sceneIdxWithTag,
},
false,
},
{
"excludes",
models.HierarchicalMultiCriterionInput{
Modifier: models.CriterionModifierExcludes,
Value: []string{strconv.Itoa(tagIDs[tagIdx1WithScene])},
},
nil,
[]int{sceneIdxWithTwoTags},
false,
},
{
"is null",
models.HierarchicalMultiCriterionInput{
Modifier: models.CriterionModifierIsNull,
},
[]int{sceneIdx1WithPerformer},
[]int{
sceneIdxWithTag,
sceneIdxWithTwoTags,
sceneIdxWithMarkerAndTag,
},
false,
},
{
"not null",
models.HierarchicalMultiCriterionInput{
Modifier: models.CriterionModifierNotNull,
},
[]int{
sceneIdxWithTag,
sceneIdxWithTwoTags,
sceneIdxWithMarkerAndTag,
},
[]int{sceneIdx1WithPerformer},
false,
},
{
"equals",
models.HierarchicalMultiCriterionInput{
Modifier: models.CriterionModifierEquals,
Value: []string{
strconv.Itoa(tagIDs[tagIdx1WithScene]),
strconv.Itoa(tagIDs[tagIdx2WithScene]),
},
},
[]int{sceneIdxWithTwoTags},
[]int{
sceneIdxWithThreeTags,
},
false,
},
{
"not equals",
models.HierarchicalMultiCriterionInput{
Modifier: models.CriterionModifierNotEquals,
Value: []string{
strconv.Itoa(tagIDs[tagIdx1WithScene]),
strconv.Itoa(tagIDs[tagIdx2WithScene]),
},
},
nil,
nil,
true,
},
}
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
results, err := db.Scene.Query(ctx, models.SceneQueryOptions{
SceneFilter: &models.SceneFilterType{
Tags: &tt.filter,
},
})
if (err != nil) != tt.wantErr {
t.Errorf("SceneStore.Query() error = %v, wantErr %v", err, tt.wantErr)
return
}
include := indexesToIDs(sceneIDs, tt.includeIdxs)
exclude := indexesToIDs(sceneIDs, tt.excludeIdxs)
for _, i := range include {
assert.Contains(results.IDs, i)
}
for _, e := range exclude {
assert.NotContains(results.IDs, e)
}
})
}
}
func TestSceneQueryPerformerTags(t *testing.T) {
allDepth := -1
tests := []struct {
name string
findFilter *models.FindFilterType
filter *models.SceneFilterType
includeIdxs []int
excludeIdxs []int
wantErr bool
}{
{
"includes",
nil,
&models.SceneFilterType{
PerformerTags: &models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(tagIDs[tagIdxWithPerformer]),
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
},
Modifier: models.CriterionModifierIncludes,
},
},
[]int{
sceneIdxWithPerformerTag,
sceneIdxWithPerformerTwoTags,
sceneIdxWithTwoPerformerTag,
},
[]int{
sceneIdxWithPerformer,
},
false,
},
{
"includes sub-tags",
nil,
&models.SceneFilterType{
PerformerTags: &models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(tagIDs[tagIdxWithParentAndChild]),
},
Depth: &allDepth,
Modifier: models.CriterionModifierIncludes,
},
},
[]int{
sceneIdxWithPerformerParentTag,
},
[]int{
sceneIdxWithPerformer,
sceneIdxWithPerformerTag,
sceneIdxWithPerformerTwoTags,
sceneIdxWithTwoPerformerTag,
},
false,
},
{
"includes all",
nil,
&models.SceneFilterType{
PerformerTags: &models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
},
Modifier: models.CriterionModifierIncludesAll,
},
},
[]int{
sceneIdxWithPerformerTwoTags,
},
[]int{
sceneIdxWithPerformer,
sceneIdxWithPerformerTag,
sceneIdxWithTwoPerformerTag,
},
false,
},
{
"excludes performer tag tagIdx2WithPerformer",
nil,
&models.SceneFilterType{
PerformerTags: &models.HierarchicalMultiCriterionInput{
Modifier: models.CriterionModifierExcludes,
Value: []string{strconv.Itoa(tagIDs[tagIdx2WithPerformer])},
},
},
nil,
[]int{sceneIdxWithTwoPerformerTag},
false,
},
{
"excludes sub-tags",
nil,
&models.SceneFilterType{
PerformerTags: &models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(tagIDs[tagIdxWithParentAndChild]),
},
Depth: &allDepth,
Modifier: models.CriterionModifierExcludes,
},
},
[]int{
sceneIdxWithPerformer,
sceneIdxWithPerformerTag,
sceneIdxWithPerformerTwoTags,
sceneIdxWithTwoPerformerTag,
},
[]int{
sceneIdxWithPerformerParentTag,
},
false,
},
{
"is null",
nil,
&models.SceneFilterType{
PerformerTags: &models.HierarchicalMultiCriterionInput{
Modifier: models.CriterionModifierIsNull,
},
},
[]int{sceneIdx1WithPerformer},
[]int{sceneIdxWithPerformerTag},
false,
},
{
"not null",
nil,
&models.SceneFilterType{
PerformerTags: &models.HierarchicalMultiCriterionInput{
Modifier: models.CriterionModifierNotNull,
},
},
[]int{sceneIdxWithPerformerTag},
[]int{sceneIdx1WithPerformer},
false,
},
{
"equals",
nil,
&models.SceneFilterType{
PerformerTags: &models.HierarchicalMultiCriterionInput{
Modifier: models.CriterionModifierEquals,
Value: []string{
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
},
},
},
nil,
nil,
true,
},
{
"not equals",
nil,
&models.SceneFilterType{
PerformerTags: &models.HierarchicalMultiCriterionInput{
Modifier: models.CriterionModifierNotEquals,
Value: []string{
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
},
},
},
nil,
nil,
true,
},
}
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
results, err := db.Scene.Query(ctx, models.SceneQueryOptions{
SceneFilter: tt.filter,
QueryOptions: models.QueryOptions{
FindFilter: tt.findFilter,
},
})
if (err != nil) != tt.wantErr {
t.Errorf("SceneStore.Query() error = %v, wantErr %v", err, tt.wantErr)
return
}
include := indexesToIDs(sceneIDs, tt.includeIdxs)
exclude := indexesToIDs(sceneIDs, tt.excludeIdxs)
for _, i := range include {
assert.Contains(results.IDs, i)
}
for _, e := range exclude {
assert.NotContains(results.IDs, e)
}
})
}
}
func TestSceneQueryStudio(t *testing.T) {
tests := []struct {
name string
q string
studioCriterion models.HierarchicalMultiCriterionInput
expectedIDs []int
wantErr bool
}{
{
"includes",
"",
models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(studioIDs[studioIdxWithScene]),
},
Modifier: models.CriterionModifierIncludes,
},
[]int{sceneIDs[sceneIdxWithStudio]},
false,
},
{
"excludes",
getSceneStringValue(sceneIdxWithStudio, titleField),
models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(studioIDs[studioIdxWithScene]),
},
Modifier: models.CriterionModifierExcludes,
},
[]int{},
false,
},
{
"excludes includes null",
getSceneStringValue(sceneIdxWithGallery, titleField),
models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(studioIDs[studioIdxWithScene]),
},
Modifier: models.CriterionModifierExcludes,
},
[]int{sceneIDs[sceneIdxWithGallery]},
false,
},
{
"equals",
"",
models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(studioIDs[studioIdxWithScene]),
},
Modifier: models.CriterionModifierEquals,
},
[]int{sceneIDs[sceneIdxWithStudio]},
false,
},
{
"not equals",
getSceneStringValue(sceneIdxWithStudio, titleField),
models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(studioIDs[studioIdxWithScene]),
},
Modifier: models.CriterionModifierNotEquals,
},
[]int{},
false,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
studioCriterion := tt.studioCriterion
sceneFilter := models.SceneFilterType{
Studios: &studioCriterion,
}
var findFilter *models.FindFilterType
if tt.q != "" {
findFilter = &models.FindFilterType{
Q: &tt.q,
}
}
scenes := queryScene(ctx, t, qb, &sceneFilter, findFilter)
assert.ElementsMatch(t, scenesToIDs(scenes), tt.expectedIDs)
})
}
}
func TestSceneQueryStudioDepth(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
depth := 2
studioCriterion := models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(studioIDs[studioIdxWithGrandChild]),
},
Modifier: models.CriterionModifierIncludes,
Depth: &depth,
}
sceneFilter := models.SceneFilterType{
Studios: &studioCriterion,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
assert.Len(t, scenes, 1)
depth = 1
scenes = queryScene(ctx, t, sqb, &sceneFilter, nil)
assert.Len(t, scenes, 0)
studioCriterion.Value = []string{strconv.Itoa(studioIDs[studioIdxWithParentAndChild])}
scenes = queryScene(ctx, 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(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 0)
depth = 1
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 1)
studioCriterion.Value = []string{strconv.Itoa(studioIDs[studioIdxWithParentAndChild])}
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 0)
return nil
})
}
func TestSceneGroups(t *testing.T) {
type criterion struct {
valueIdxs []int
modifier models.CriterionModifier
depth int
}
tests := []struct {
name string
c criterion
q string
includeIdxs []int
excludeIdxs []int
}{
{
"includes",
criterion{
[]int{groupIdxWithScene},
models.CriterionModifierIncludes,
0,
},
"",
[]int{sceneIdxWithGroup},
nil,
},
{
"excludes",
criterion{
[]int{groupIdxWithScene},
models.CriterionModifierExcludes,
0,
},
getSceneStringValue(sceneIdxWithGroup, titleField),
nil,
[]int{sceneIdxWithGroup},
},
{
"includes (depth = 1)",
criterion{
[]int{groupIdxWithChildWithScene},
models.CriterionModifierIncludes,
1,
},
"",
[]int{sceneIdxWithGroupWithParent},
nil,
},
}
for _, tt := range tests {
valueIDs := indexesToIDs(groupIDs, tt.c.valueIdxs)
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
sceneFilter := &models.SceneFilterType{
Groups: &models.HierarchicalMultiCriterionInput{
Value: intslice.IntSliceToStringSlice(valueIDs),
Modifier: tt.c.modifier,
},
}
if tt.c.depth != 0 {
sceneFilter.Groups.Depth = &tt.c.depth
}
findFilter := &models.FindFilterType{}
if tt.q != "" {
findFilter.Q = &tt.q
}
results, err := db.Scene.Query(ctx, models.SceneQueryOptions{
SceneFilter: sceneFilter,
QueryOptions: models.QueryOptions{
FindFilter: findFilter,
},
})
if err != nil {
t.Errorf("SceneStore.Query() error = %v", err)
return
}
include := indexesToIDs(sceneIDs, tt.includeIdxs)
exclude := indexesToIDs(sceneIDs, tt.excludeIdxs)
assert.Subset(results.IDs, include)
for _, e := range exclude {
assert.NotContains(results.IDs, e)
}
})
}
}
func TestSceneQueryMovies(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
movieCriterion := models.MultiCriterionInput{
Value: []string{
strconv.Itoa(groupIDs[groupIdxWithScene]),
},
Modifier: models.CriterionModifierIncludes,
}
sceneFilter := models.SceneFilterType{
Movies: &movieCriterion,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
assert.Len(t, scenes, 1)
// ensure id is correct
assert.Equal(t, sceneIDs[sceneIdxWithGroup], scenes[0].ID)
movieCriterion = models.MultiCriterionInput{
Value: []string{
strconv.Itoa(groupIDs[groupIdxWithScene]),
},
Modifier: models.CriterionModifierExcludes,
}
q := getSceneStringValue(sceneIdxWithGroup, titleField)
findFilter := models.FindFilterType{
Q: &q,
}
scenes = queryScene(ctx, t, sqb, &sceneFilter, &findFilter)
assert.Len(t, scenes, 0)
return nil
})
}
func TestSceneQueryPhashDuplicated(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
duplicated := true
phashCriterion := models.PHashDuplicationCriterionInput{
Duplicated: &duplicated,
}
sceneFilter := models.SceneFilterType{
Duplicated: &phashCriterion,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
assert.Len(t, scenes, dupeScenePhashes*2)
duplicated = false
scenes = queryScene(ctx, t, sqb, &sceneFilter, nil)
// -1 for missing phash
assert.Len(t, scenes, totalScenes-(dupeScenePhashes*2)-1)
return nil
})
}
func TestSceneQuerySorting(t *testing.T) {
tests := []struct {
name string
sortBy string
dir models.SortDirectionEnum
firstSceneIdx int // -1 to ignore
lastSceneIdx int
}{
{
"bitrate",
"bitrate",
models.SortDirectionEnumAsc,
-1,
-1,
},
{
"duration",
"duration",
models.SortDirectionEnumDesc,
-1,
-1,
},
{
"file mod time",
"file_mod_time",
models.SortDirectionEnumDesc,
-1,
-1,
},
{
"file size",
"filesize",
models.SortDirectionEnumDesc,
-1,
-1,
},
{
"frame rate",
"framerate",
models.SortDirectionEnumDesc,
-1,
-1,
},
{
"path",
"path",
models.SortDirectionEnumDesc,
-1,
-1,
},
{
"perceptual_similarity",
"perceptual_similarity",
models.SortDirectionEnumDesc,
-1,
-1,
},
{
"play_count",
"play_count",
models.SortDirectionEnumDesc,
-1,
-1,
},
{
"last_played_at",
"last_played_at",
models.SortDirectionEnumDesc,
-1,
-1,
},
{
"resume_time",
"resume_time",
models.SortDirectionEnumDesc,
sceneIDs[sceneIdx1WithPerformer],
-1,
},
{
"play_duration",
"play_duration",
models.SortDirectionEnumDesc,
sceneIDs[sceneIdx1WithPerformer],
-1,
},
}
qb := db.Scene
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
got, err := qb.Query(ctx, models.SceneQueryOptions{
QueryOptions: models.QueryOptions{
FindFilter: &models.FindFilterType{
Sort: &tt.sortBy,
Direction: &tt.dir,
},
},
})
if err != nil {
t.Errorf("sceneQueryBuilder.TestSceneQuerySorting() error = %v", err)
return
}
scenes, err := got.Resolve(ctx)
if err != nil {
t.Errorf("sceneQueryBuilder.TestSceneQuerySorting() error = %v", err)
return
}
if !assert.Greater(len(scenes), 0) {
return
}
// scenes should be in same order as indexes
firstScene := scenes[0]
lastScene := scenes[len(scenes)-1]
if tt.firstSceneIdx != -1 {
firstSceneID := sceneIDs[tt.firstSceneIdx]
assert.Equal(firstSceneID, firstScene.ID)
}
if tt.lastSceneIdx != -1 {
lastSceneID := sceneIDs[tt.lastSceneIdx]
assert.Equal(lastSceneID, lastScene.ID)
}
})
}
}
func TestSceneQueryPagination(t *testing.T) {
perPage := 1
findFilter := models.FindFilterType{
PerPage: &perPage,
}
withTxn(func(ctx context.Context) error {
sqb := db.Scene
scenes := queryScene(ctx, t, sqb, nil, &findFilter)
assert.Len(t, scenes, 1)
firstID := scenes[0].ID
page := 2
findFilter.Page = &page
scenes = queryScene(ctx, t, sqb, nil, &findFilter)
assert.Len(t, scenes, 1)
secondID := scenes[0].ID
assert.NotEqual(t, firstID, secondID)
perPage = 2
page = 1
scenes = queryScene(ctx, 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(ctx context.Context) error {
sqb := db.Scene
sceneFilter := models.SceneFilterType{
TagCount: &tagCountCriterion,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
assert.Greater(t, len(scenes), 0)
for _, scene := range scenes {
if err := scene.LoadTagIDs(ctx, sqb); err != nil {
t.Errorf("scene.LoadTagIDs() error = %v", err)
return nil
}
verifyInt(t, len(scene.TagIDs.List()), 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(ctx context.Context) error {
sqb := db.Scene
sceneFilter := models.SceneFilterType{
PerformerCount: &performerCountCriterion,
}
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
assert.Greater(t, len(scenes), 0)
for _, scene := range scenes {
if err := scene.LoadPerformerIDs(ctx, sqb); err != nil {
t.Errorf("scene.LoadPerformerIDs() error = %v", err)
return nil
}
verifyInt(t, len(scene.PerformerIDs.List()), performerCountCriterion)
}
return nil
})
}
func TestFindByMovieID(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Scene
scenes, err := sqb.FindByGroupID(ctx, groupIDs[groupIdxWithScene])
if err != nil {
t.Errorf("error calling FindByMovieID: %s", err.Error())
}
assert.Len(t, scenes, 1)
assert.Equal(t, sceneIDs[sceneIdxWithGroup], scenes[0].ID)
scenes, err = sqb.FindByGroupID(ctx, 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(ctx context.Context) error {
sqb := db.Scene
scenes, err := sqb.FindByPerformerID(ctx, 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(ctx, 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(ctx context.Context) error {
qb := db.Scene
sceneID := sceneIDs[sceneIdxWithGallery]
return testUpdateImage(t, ctx, sceneID, qb.UpdateCover, qb.GetCover)
}); err != nil {
t.Error(err.Error())
}
}
func TestSceneStashIDs(t *testing.T) {
if err := withTxn(func(ctx context.Context) error {
qb := db.Scene
// create scene to test against
const name = "TestSceneStashIDs"
scene := &models.Scene{
Title: name,
}
if err := qb.Create(ctx, scene, nil); err != nil {
return fmt.Errorf("Error creating scene: %s", err.Error())
}
if err := scene.LoadStashIDs(ctx, qb); err != nil {
return err
}
testSceneStashIDs(ctx, t, scene)
return nil
}); err != nil {
t.Error(err.Error())
}
}
func testSceneStashIDs(ctx context.Context, t *testing.T, s *models.Scene) {
// ensure no stash IDs to begin with
assert.Len(t, s.StashIDs.List(), 0)
// add stash ids
const stashIDStr = "stashID"
const endpoint = "endpoint"
stashID := models.StashID{
StashID: stashIDStr,
Endpoint: endpoint,
}
qb := db.Scene
// update stash ids and ensure was updated
var err error
s, err = qb.UpdatePartial(ctx, s.ID, models.ScenePartial{
StashIDs: &models.UpdateStashIDs{
StashIDs: []models.StashID{stashID},
Mode: models.RelationshipUpdateModeSet,
},
})
if err != nil {
t.Error(err.Error())
}
if err := s.LoadStashIDs(ctx, qb); err != nil {
t.Error(err.Error())
return
}
assert.Equal(t, []models.StashID{stashID}, s.StashIDs.List())
// remove stash ids and ensure was updated
s, err = qb.UpdatePartial(ctx, s.ID, models.ScenePartial{
StashIDs: &models.UpdateStashIDs{
StashIDs: []models.StashID{stashID},
Mode: models.RelationshipUpdateModeRemove,
},
})
if err != nil {
t.Error(err.Error())
}
if err := s.LoadStashIDs(ctx, qb); err != nil {
t.Error(err.Error())
return
}
assert.Len(t, s.StashIDs.List(), 0)
}
func TestSceneQueryQTrim(t *testing.T) {
if err := withTxn(func(ctx context.Context) error {
qb := db.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(ctx, 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(ctx, t, qb, nil, &findFilter)
assert.NotEqual(t, 0, len(scenes))
return nil
}); err != nil {
t.Error(err.Error())
}
}
func TestSceneStore_All(t *testing.T) {
qb := db.Scene
withRollbackTxn(func(ctx context.Context) error {
got, err := qb.All(ctx)
if err != nil {
t.Errorf("SceneStore.All() error = %v", err)
return nil
}
// it's possible that other tests have created scenes
assert.GreaterOrEqual(t, len(got), len(sceneIDs))
return nil
})
}
func TestSceneStore_FindDuplicates(t *testing.T) {
qb := db.Scene
withRollbackTxn(func(ctx context.Context) error {
distance := 0
durationDiff := -1.
got, err := qb.FindDuplicates(ctx, distance, durationDiff)
if err != nil {
t.Errorf("SceneStore.FindDuplicates() error = %v", err)
return nil
}
assert.Len(t, got, dupeScenePhashes)
distance = 1
durationDiff = -1.
got, err = qb.FindDuplicates(ctx, distance, durationDiff)
if err != nil {
t.Errorf("SceneStore.FindDuplicates() error = %v", err)
return nil
}
assert.Len(t, got, dupeScenePhashes)
return nil
})
}
func TestSceneStore_AssignFiles(t *testing.T) {
tests := []struct {
name string
sceneID int
fileID models.FileID
wantErr bool
}{
{
"valid",
sceneIDs[sceneIdx1WithPerformer],
sceneFileIDs[sceneIdx1WithStudio],
false,
},
{
"invalid file id",
sceneIDs[sceneIdx1WithPerformer],
invalidFileID,
true,
},
{
"invalid scene id",
invalidID,
sceneFileIDs[sceneIdx1WithStudio],
true,
},
}
qb := db.Scene
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
withRollbackTxn(func(ctx context.Context) error {
if err := qb.AssignFiles(ctx, tt.sceneID, []models.FileID{tt.fileID}); (err != nil) != tt.wantErr {
t.Errorf("SceneStore.AssignFiles() error = %v, wantErr %v", err, tt.wantErr)
}
return nil
})
})
}
}
func TestSceneStore_AddView(t *testing.T) {
tests := []struct {
name string
sceneID int
expectedCount int
wantErr bool
}{
{
"valid",
sceneIDs[sceneIdx1WithPerformer],
1, //getScenePlayCount(sceneIdx1WithPerformer) + 1,
false,
},
{
"invalid scene id",
invalidID,
0,
true,
},
}
qb := db.Scene
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
withRollbackTxn(func(ctx context.Context) error {
views, err := qb.AddViews(ctx, tt.sceneID, nil)
if (err != nil) != tt.wantErr {
t.Errorf("SceneStore.AddView() error = %v, wantErr %v", err, tt.wantErr)
}
if err != nil {
return nil
}
assert := assert.New(t)
assert.Equal(tt.expectedCount, len(views))
// find the scene and check the count
count, err := qb.CountViews(ctx, tt.sceneID)
if err != nil {
t.Errorf("SceneStore.CountViews() error = %v", err)
}
lastView, err := qb.LastView(ctx, tt.sceneID)
if err != nil {
t.Errorf("SceneStore.LastView() error = %v", err)
}
assert.Equal(tt.expectedCount, count)
assert.True(lastView.After(time.Now().Add(-1 * time.Minute)))
return nil
})
})
}
}
func TestSceneStore_DecrementWatchCount(t *testing.T) {
return
}
func TestSceneStore_SaveActivity(t *testing.T) {
var (
resumeTime = 111.2
playDuration = 98.7
)
tests := []struct {
name string
sceneIdx int
resumeTime *float64
playDuration *float64
wantErr bool
}{
{
"both",
sceneIdx1WithPerformer,
&resumeTime,
&playDuration,
false,
},
{
"resumeTime only",
sceneIdx1WithPerformer,
&resumeTime,
nil,
false,
},
{
"playDuration only",
sceneIdx1WithPerformer,
nil,
&playDuration,
false,
},
{
"none",
sceneIdx1WithPerformer,
nil,
nil,
false,
},
{
"invalid scene id",
-1,
&resumeTime,
&playDuration,
true,
},
}
qb := db.Scene
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
withRollbackTxn(func(ctx context.Context) error {
id := -1
if tt.sceneIdx != -1 {
id = sceneIDs[tt.sceneIdx]
}
_, err := qb.SaveActivity(ctx, id, tt.resumeTime, tt.playDuration)
if (err != nil) != tt.wantErr {
t.Errorf("SceneStore.SaveActivity() error = %v, wantErr %v", err, tt.wantErr)
}
if err != nil {
return nil
}
assert := assert.New(t)
// find the scene and check the values
scene, err := qb.Find(ctx, id)
if err != nil {
t.Errorf("SceneStore.Find() error = %v", err)
}
expectedResumeTime := getSceneResumeTime(tt.sceneIdx)
expectedPlayDuration := getScenePlayDuration(tt.sceneIdx)
if tt.resumeTime != nil {
expectedResumeTime = *tt.resumeTime
}
if tt.playDuration != nil {
expectedPlayDuration += *tt.playDuration
}
assert.Equal(expectedResumeTime, scene.ResumeTime)
assert.Equal(expectedPlayDuration, scene.PlayDuration)
return nil
})
})
}
}
// TODO Count
// TODO SizeCount
// TODO - this should be in history_test and generalised
func TestSceneStore_CountAllViews(t *testing.T) {
withRollbackTxn(func(ctx context.Context) error {
qb := db.Scene
sceneID := sceneIDs[sceneIdx1WithPerformer]
// get the current play count
currentCount, err := qb.CountAllViews(ctx)
if err != nil {
t.Errorf("SceneStore.CountAllViews() error = %v", err)
return nil
}
// add a view
_, err = qb.AddViews(ctx, sceneID, nil)
if err != nil {
t.Errorf("SceneStore.AddViews() error = %v", err)
return nil
}
// get the new play count
newCount, err := qb.CountAllViews(ctx)
if err != nil {
t.Errorf("SceneStore.CountAllViews() error = %v", err)
return nil
}
assert.Equal(t, currentCount+1, newCount)
return nil
})
}
func TestSceneStore_CountUniqueViews(t *testing.T) {
withRollbackTxn(func(ctx context.Context) error {
qb := db.Scene
sceneID := sceneIDs[sceneIdx1WithPerformer]
// get the current play count
currentCount, err := qb.CountUniqueViews(ctx)
if err != nil {
t.Errorf("SceneStore.CountUniqueViews() error = %v", err)
return nil
}
// add a view
_, err = qb.AddViews(ctx, sceneID, nil)
if err != nil {
t.Errorf("SceneStore.AddViews() error = %v", err)
return nil
}
// add a second view
_, err = qb.AddViews(ctx, sceneID, nil)
if err != nil {
t.Errorf("SceneStore.AddViews() error = %v", err)
return nil
}
// get the new play count
newCount, err := qb.CountUniqueViews(ctx)
if err != nil {
t.Errorf("SceneStore.CountUniqueViews() error = %v", err)
return nil
}
assert.Equal(t, currentCount+1, newCount)
return nil
})
}