mirror of https://github.com/stashapp/stash.git
3024 lines
70 KiB
Go
3024 lines
70 KiB
Go
//go:build integration
|
|
// +build integration
|
|
|
|
package sqlite_test
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stashapp/stash/pkg/models"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func loadImageRelationships(ctx context.Context, expected models.Image, actual *models.Image) error {
|
|
if expected.URLs.Loaded() {
|
|
if err := actual.LoadURLs(ctx, db.Image); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if expected.GalleryIDs.Loaded() {
|
|
if err := actual.LoadGalleryIDs(ctx, db.Image); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if expected.TagIDs.Loaded() {
|
|
if err := actual.LoadTagIDs(ctx, db.Image); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if expected.PerformerIDs.Loaded() {
|
|
if err := actual.LoadPerformerIDs(ctx, db.Image); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if expected.Files.Loaded() {
|
|
if err := actual.LoadFiles(ctx, db.Image); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// clear Path, Checksum, PrimaryFileID
|
|
if expected.Path == "" {
|
|
actual.Path = ""
|
|
}
|
|
if expected.Checksum == "" {
|
|
actual.Checksum = ""
|
|
}
|
|
if expected.PrimaryFileID == nil {
|
|
actual.PrimaryFileID = nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func Test_imageQueryBuilder_Create(t *testing.T) {
|
|
var (
|
|
title = "title"
|
|
code = "code"
|
|
rating = 60
|
|
details = "details"
|
|
photographer = "photographer"
|
|
ocounter = 5
|
|
url = "url"
|
|
date, _ = models.ParseDate("2003-02-01")
|
|
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
imageFile = makeFileWithID(fileIdxStartImageFiles)
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
newObject models.Image
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"full",
|
|
models.Image{
|
|
Title: title,
|
|
Code: code,
|
|
Rating: &rating,
|
|
Date: &date,
|
|
Details: details,
|
|
Photographer: photographer,
|
|
URLs: models.NewRelatedStrings([]string{url}),
|
|
Organized: true,
|
|
OCounter: ocounter,
|
|
StudioID: &studioIDs[studioIdxWithImage],
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithImage]}),
|
|
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithImage]}),
|
|
PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithImage], performerIDs[performerIdx1WithDupName]}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"with file",
|
|
models.Image{
|
|
Title: title,
|
|
Code: code,
|
|
Rating: &rating,
|
|
Date: &date,
|
|
Details: details,
|
|
Photographer: photographer,
|
|
URLs: models.NewRelatedStrings([]string{url}),
|
|
Organized: true,
|
|
OCounter: ocounter,
|
|
StudioID: &studioIDs[studioIdxWithImage],
|
|
Files: models.NewRelatedFiles([]models.File{
|
|
imageFile.(*models.ImageFile),
|
|
}),
|
|
PrimaryFileID: &imageFile.Base().ID,
|
|
Path: imageFile.Base().Path,
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithImage]}),
|
|
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithImage]}),
|
|
PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithImage], performerIDs[performerIdx1WithDupName]}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"invalid studio id",
|
|
models.Image{
|
|
StudioID: &invalidID,
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid gallery id",
|
|
models.Image{
|
|
GalleryIDs: models.NewRelatedIDs([]int{invalidID}),
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid tag id",
|
|
models.Image{
|
|
TagIDs: models.NewRelatedIDs([]int{invalidID}),
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid performer id",
|
|
models.Image{
|
|
PerformerIDs: models.NewRelatedIDs([]int{invalidID}),
|
|
},
|
|
true,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
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.Base().ID)
|
|
}
|
|
}
|
|
s := tt.newObject
|
|
if err := qb.Create(ctx, &s, fileIDs); (err != nil) != tt.wantErr {
|
|
t.Errorf("imageQueryBuilder.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 := loadImageRelationships(ctx, copy, &s); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
assert.Equal(copy, s)
|
|
|
|
// ensure can find the image
|
|
found, err := qb.Find(ctx, s.ID)
|
|
if err != nil {
|
|
t.Errorf("imageQueryBuilder.Find() error = %v", err)
|
|
}
|
|
|
|
// load relationships
|
|
if err := loadImageRelationships(ctx, copy, found); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
assert.Equal(copy, *found)
|
|
|
|
return
|
|
})
|
|
}
|
|
}
|
|
|
|
func clearImageFileIDs(image *models.Image) {
|
|
if image.Files.Loaded() {
|
|
for _, f := range image.Files.List() {
|
|
f.Base().ID = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
func makeImageFileWithID(i int) *models.ImageFile {
|
|
ret := makeImageFile(i)
|
|
ret.ID = imageFileIDs[i]
|
|
return ret
|
|
}
|
|
|
|
func Test_imageQueryBuilder_Update(t *testing.T) {
|
|
var (
|
|
title = "title"
|
|
code = "code"
|
|
rating = 60
|
|
url = "url"
|
|
details = "details"
|
|
photographer = "photographer"
|
|
date, _ = models.ParseDate("2003-02-01")
|
|
ocounter = 5
|
|
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
updatedObject *models.Image
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"full",
|
|
&models.Image{
|
|
ID: imageIDs[imageIdxWithGallery],
|
|
Title: title,
|
|
Code: code,
|
|
Rating: &rating,
|
|
URLs: models.NewRelatedStrings([]string{url}),
|
|
Date: &date,
|
|
Details: details,
|
|
Photographer: photographer,
|
|
Organized: true,
|
|
OCounter: ocounter,
|
|
StudioID: &studioIDs[studioIdxWithImage],
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithImage]}),
|
|
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithImage]}),
|
|
PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithImage], performerIDs[performerIdx1WithDupName]}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"clear nullables",
|
|
&models.Image{
|
|
ID: imageIDs[imageIdxWithGallery],
|
|
GalleryIDs: models.NewRelatedIDs([]int{}),
|
|
TagIDs: models.NewRelatedIDs([]int{}),
|
|
PerformerIDs: models.NewRelatedIDs([]int{}),
|
|
Organized: true,
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"clear gallery ids",
|
|
&models.Image{
|
|
ID: imageIDs[imageIdxWithGallery],
|
|
GalleryIDs: models.NewRelatedIDs([]int{}),
|
|
TagIDs: models.NewRelatedIDs([]int{}),
|
|
PerformerIDs: models.NewRelatedIDs([]int{}),
|
|
Organized: true,
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"clear tag ids",
|
|
&models.Image{
|
|
ID: imageIDs[imageIdxWithTag],
|
|
GalleryIDs: models.NewRelatedIDs([]int{}),
|
|
TagIDs: models.NewRelatedIDs([]int{}),
|
|
PerformerIDs: models.NewRelatedIDs([]int{}),
|
|
Organized: true,
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"clear performer ids",
|
|
&models.Image{
|
|
ID: imageIDs[imageIdxWithPerformer],
|
|
GalleryIDs: models.NewRelatedIDs([]int{}),
|
|
TagIDs: models.NewRelatedIDs([]int{}),
|
|
PerformerIDs: models.NewRelatedIDs([]int{}),
|
|
Organized: true,
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"invalid studio id",
|
|
&models.Image{
|
|
ID: imageIDs[imageIdxWithGallery],
|
|
Organized: true,
|
|
StudioID: &invalidID,
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid gallery id",
|
|
&models.Image{
|
|
ID: imageIDs[imageIdxWithGallery],
|
|
Organized: true,
|
|
GalleryIDs: models.NewRelatedIDs([]int{invalidID}),
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid tag id",
|
|
&models.Image{
|
|
ID: imageIDs[imageIdxWithGallery],
|
|
Organized: true,
|
|
TagIDs: models.NewRelatedIDs([]int{invalidID}),
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid performer id",
|
|
&models.Image{
|
|
ID: imageIDs[imageIdxWithGallery],
|
|
Organized: true,
|
|
PerformerIDs: models.NewRelatedIDs([]int{invalidID}),
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
},
|
|
true,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
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("imageQueryBuilder.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("imageQueryBuilder.Find() error = %v", err)
|
|
}
|
|
|
|
// load relationships
|
|
if err := loadImageRelationships(ctx, copy, s); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
assert.Equal(copy, *s)
|
|
|
|
return
|
|
})
|
|
}
|
|
}
|
|
|
|
func clearImagePartial() models.ImagePartial {
|
|
// leave mandatory fields
|
|
return models.ImagePartial{
|
|
Title: models.OptionalString{Set: true, Null: true},
|
|
Code: models.OptionalString{Set: true, Null: true},
|
|
Details: models.OptionalString{Set: true, Null: true},
|
|
Photographer: models.OptionalString{Set: true, Null: true},
|
|
Rating: models.OptionalInt{Set: true, Null: true},
|
|
URLs: &models.UpdateStrings{Mode: models.RelationshipUpdateModeSet},
|
|
Date: models.OptionalDate{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},
|
|
}
|
|
}
|
|
|
|
func Test_imageQueryBuilder_UpdatePartial(t *testing.T) {
|
|
var (
|
|
title = "title"
|
|
code = "code"
|
|
details = "details"
|
|
photographer = "photographer"
|
|
rating = 60
|
|
url = "url"
|
|
date, _ = models.ParseDate("2003-02-01")
|
|
ocounter = 5
|
|
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
id int
|
|
partial models.ImagePartial
|
|
want models.Image
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"full",
|
|
imageIDs[imageIdx1WithGallery],
|
|
models.ImagePartial{
|
|
Title: models.NewOptionalString(title),
|
|
Code: models.NewOptionalString(code),
|
|
Details: models.NewOptionalString(details),
|
|
Photographer: models.NewOptionalString(photographer),
|
|
Rating: models.NewOptionalInt(rating),
|
|
URLs: &models.UpdateStrings{
|
|
Values: []string{url},
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
},
|
|
Date: models.NewOptionalDate(date),
|
|
Organized: models.NewOptionalBool(true),
|
|
OCounter: models.NewOptionalInt(ocounter),
|
|
StudioID: models.NewOptionalInt(studioIDs[studioIdxWithImage]),
|
|
CreatedAt: models.NewOptionalTime(createdAt),
|
|
UpdatedAt: models.NewOptionalTime(updatedAt),
|
|
GalleryIDs: &models.UpdateIDs{
|
|
IDs: []int{galleryIDs[galleryIdxWithImage]},
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
},
|
|
TagIDs: &models.UpdateIDs{
|
|
IDs: []int{tagIDs[tagIdx1WithImage], tagIDs[tagIdx1WithDupName]},
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
},
|
|
PerformerIDs: &models.UpdateIDs{
|
|
IDs: []int{performerIDs[performerIdx1WithImage], performerIDs[performerIdx1WithDupName]},
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
},
|
|
},
|
|
models.Image{
|
|
ID: imageIDs[imageIdx1WithGallery],
|
|
Title: title,
|
|
Code: code,
|
|
Details: details,
|
|
Photographer: photographer,
|
|
Rating: &rating,
|
|
URLs: models.NewRelatedStrings([]string{url}),
|
|
Date: &date,
|
|
Organized: true,
|
|
OCounter: ocounter,
|
|
StudioID: &studioIDs[studioIdxWithImage],
|
|
Files: models.NewRelatedFiles([]models.File{
|
|
makeImageFile(imageIdx1WithGallery),
|
|
}),
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithImage]}),
|
|
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithImage]}),
|
|
PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithImage], performerIDs[performerIdx1WithDupName]}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"clear all",
|
|
imageIDs[imageIdx1WithGallery],
|
|
clearImagePartial(),
|
|
models.Image{
|
|
ID: imageIDs[imageIdx1WithGallery],
|
|
OCounter: getOCounter(imageIdx1WithGallery),
|
|
Files: models.NewRelatedFiles([]models.File{
|
|
makeImageFile(imageIdx1WithGallery),
|
|
}),
|
|
GalleryIDs: models.NewRelatedIDs([]int{}),
|
|
TagIDs: models.NewRelatedIDs([]int{}),
|
|
PerformerIDs: models.NewRelatedIDs([]int{}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"invalid id",
|
|
invalidID,
|
|
models.ImagePartial{},
|
|
models.Image{},
|
|
true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
qb := db.Image
|
|
|
|
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("imageQueryBuilder.UpdatePartial() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if tt.wantErr {
|
|
return
|
|
}
|
|
|
|
// load relationships
|
|
if err := loadImageRelationships(ctx, tt.want, got); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
clearImageFileIDs(got)
|
|
|
|
assert.Equal(tt.want, *got)
|
|
|
|
s, err := qb.Find(ctx, tt.id)
|
|
if err != nil {
|
|
t.Errorf("imageQueryBuilder.Find() error = %v", err)
|
|
}
|
|
|
|
// load relationships
|
|
if err := loadImageRelationships(ctx, tt.want, s); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
clearImageFileIDs(s)
|
|
assert.Equal(tt.want, *s)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageQueryBuilder_UpdatePartialRelationships(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
id int
|
|
partial models.ImagePartial
|
|
want models.Image
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"add galleries",
|
|
imageIDs[imageIdxWithGallery],
|
|
models.ImagePartial{
|
|
GalleryIDs: &models.UpdateIDs{
|
|
IDs: []int{galleryIDs[galleryIdx1WithImage], galleryIDs[galleryIdx1WithPerformer]},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Image{
|
|
GalleryIDs: models.NewRelatedIDs(append(indexesToIDs(galleryIDs, imageGalleries[imageIdxWithGallery]),
|
|
galleryIDs[galleryIdx1WithImage],
|
|
galleryIDs[galleryIdx1WithPerformer],
|
|
)),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"add tags",
|
|
imageIDs[imageIdxWithTwoTags],
|
|
models.ImagePartial{
|
|
TagIDs: &models.UpdateIDs{
|
|
IDs: []int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithGallery]},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Image{
|
|
TagIDs: models.NewRelatedIDs(append(
|
|
[]int{
|
|
tagIDs[tagIdx1WithGallery],
|
|
tagIDs[tagIdx1WithDupName],
|
|
},
|
|
indexesToIDs(tagIDs, imageTags[imageIdxWithTwoTags])...,
|
|
)),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"add performers",
|
|
imageIDs[imageIdxWithTwoPerformers],
|
|
models.ImagePartial{
|
|
PerformerIDs: &models.UpdateIDs{
|
|
IDs: []int{performerIDs[performerIdx1WithDupName], performerIDs[performerIdx1WithGallery]},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Image{
|
|
PerformerIDs: models.NewRelatedIDs(append(indexesToIDs(performerIDs, imagePerformers[imageIdxWithTwoPerformers]),
|
|
performerIDs[performerIdx1WithDupName],
|
|
performerIDs[performerIdx1WithGallery],
|
|
)),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"add duplicate galleries",
|
|
imageIDs[imageIdxWithGallery],
|
|
models.ImagePartial{
|
|
GalleryIDs: &models.UpdateIDs{
|
|
IDs: []int{galleryIDs[galleryIdxWithImage], galleryIDs[galleryIdx1WithPerformer]},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Image{
|
|
GalleryIDs: models.NewRelatedIDs(append(indexesToIDs(galleryIDs, imageGalleries[imageIdxWithGallery]),
|
|
galleryIDs[galleryIdx1WithPerformer],
|
|
)),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"add duplicate tags",
|
|
imageIDs[imageIdxWithTwoTags],
|
|
models.ImagePartial{
|
|
TagIDs: &models.UpdateIDs{
|
|
IDs: []int{tagIDs[tagIdx1WithImage], tagIDs[tagIdx1WithGallery]},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Image{
|
|
TagIDs: models.NewRelatedIDs(append(
|
|
[]int{tagIDs[tagIdx1WithGallery]},
|
|
indexesToIDs(tagIDs, imageTags[imageIdxWithTwoTags])...,
|
|
)),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"add duplicate performers",
|
|
imageIDs[imageIdxWithTwoPerformers],
|
|
models.ImagePartial{
|
|
PerformerIDs: &models.UpdateIDs{
|
|
IDs: []int{performerIDs[performerIdx1WithImage], performerIDs[performerIdx1WithGallery]},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Image{
|
|
PerformerIDs: models.NewRelatedIDs(append(indexesToIDs(performerIDs, imagePerformers[imageIdxWithTwoPerformers]),
|
|
performerIDs[performerIdx1WithGallery],
|
|
)),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"add invalid galleries",
|
|
imageIDs[imageIdxWithGallery],
|
|
models.ImagePartial{
|
|
GalleryIDs: &models.UpdateIDs{
|
|
IDs: []int{invalidID},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Image{},
|
|
true,
|
|
},
|
|
{
|
|
"add invalid tags",
|
|
imageIDs[imageIdxWithTwoTags],
|
|
models.ImagePartial{
|
|
TagIDs: &models.UpdateIDs{
|
|
IDs: []int{invalidID},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Image{},
|
|
true,
|
|
},
|
|
{
|
|
"add invalid performers",
|
|
imageIDs[imageIdxWithTwoPerformers],
|
|
models.ImagePartial{
|
|
PerformerIDs: &models.UpdateIDs{
|
|
IDs: []int{invalidID},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Image{},
|
|
true,
|
|
},
|
|
{
|
|
"remove galleries",
|
|
imageIDs[imageIdxWithGallery],
|
|
models.ImagePartial{
|
|
GalleryIDs: &models.UpdateIDs{
|
|
IDs: []int{galleryIDs[galleryIdxWithImage]},
|
|
Mode: models.RelationshipUpdateModeRemove,
|
|
},
|
|
},
|
|
models.Image{
|
|
GalleryIDs: models.NewRelatedIDs([]int{}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"remove tags",
|
|
imageIDs[imageIdxWithTwoTags],
|
|
models.ImagePartial{
|
|
TagIDs: &models.UpdateIDs{
|
|
IDs: []int{tagIDs[tagIdx1WithImage]},
|
|
Mode: models.RelationshipUpdateModeRemove,
|
|
},
|
|
},
|
|
models.Image{
|
|
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx2WithImage]}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"remove performers",
|
|
imageIDs[imageIdxWithTwoPerformers],
|
|
models.ImagePartial{
|
|
PerformerIDs: &models.UpdateIDs{
|
|
IDs: []int{performerIDs[performerIdx1WithImage]},
|
|
Mode: models.RelationshipUpdateModeRemove,
|
|
},
|
|
},
|
|
models.Image{
|
|
PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx2WithImage]}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"remove unrelated galleries",
|
|
imageIDs[imageIdxWithGallery],
|
|
models.ImagePartial{
|
|
GalleryIDs: &models.UpdateIDs{
|
|
IDs: []int{galleryIDs[galleryIdx1WithImage]},
|
|
Mode: models.RelationshipUpdateModeRemove,
|
|
},
|
|
},
|
|
models.Image{
|
|
GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithImage]}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"remove unrelated tags",
|
|
imageIDs[imageIdxWithTwoTags],
|
|
models.ImagePartial{
|
|
TagIDs: &models.UpdateIDs{
|
|
IDs: []int{tagIDs[tagIdx1WithPerformer]},
|
|
Mode: models.RelationshipUpdateModeRemove,
|
|
},
|
|
},
|
|
models.Image{
|
|
TagIDs: models.NewRelatedIDs(indexesToIDs(tagIDs, imageTags[imageIdxWithTwoTags])),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"remove unrelated performers",
|
|
imageIDs[imageIdxWithTwoPerformers],
|
|
models.ImagePartial{
|
|
PerformerIDs: &models.UpdateIDs{
|
|
IDs: []int{performerIDs[performerIdx1WithDupName]},
|
|
Mode: models.RelationshipUpdateModeRemove,
|
|
},
|
|
},
|
|
models.Image{
|
|
PerformerIDs: models.NewRelatedIDs(indexesToIDs(performerIDs, imagePerformers[imageIdxWithTwoPerformers])),
|
|
},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
qb := db.Image
|
|
|
|
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("imageQueryBuilder.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("imageQueryBuilder.Find() error = %v", err)
|
|
}
|
|
|
|
// load relationships
|
|
if err := loadImageRelationships(ctx, tt.want, got); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
if err := loadImageRelationships(ctx, tt.want, s); err != nil {
|
|
t.Errorf("loadImageRelationships() 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())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageQueryBuilder_IncrementOCounter(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
id int
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"increment",
|
|
imageIDs[1],
|
|
2,
|
|
false,
|
|
},
|
|
{
|
|
"invalid",
|
|
invalidID,
|
|
0,
|
|
true,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
got, err := qb.IncrementOCounter(ctx, tt.id)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("imageQueryBuilder.IncrementOCounter() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("imageQueryBuilder.IncrementOCounter() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageQueryBuilder_DecrementOCounter(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
id int
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"decrement",
|
|
imageIDs[2],
|
|
1,
|
|
false,
|
|
},
|
|
{
|
|
"zero",
|
|
imageIDs[0],
|
|
0,
|
|
false,
|
|
},
|
|
{
|
|
"invalid",
|
|
invalidID,
|
|
0,
|
|
true,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
got, err := qb.DecrementOCounter(ctx, tt.id)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("imageQueryBuilder.DecrementOCounter() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("imageQueryBuilder.DecrementOCounter() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageQueryBuilder_ResetOCounter(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
id int
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"decrement",
|
|
imageIDs[2],
|
|
0,
|
|
false,
|
|
},
|
|
{
|
|
"zero",
|
|
imageIDs[0],
|
|
0,
|
|
false,
|
|
},
|
|
{
|
|
"invalid",
|
|
invalidID,
|
|
0,
|
|
true,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
got, err := qb.ResetOCounter(ctx, tt.id)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("imageQueryBuilder.ResetOCounter() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("imageQueryBuilder.ResetOCounter() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageQueryBuilder_Destroy(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
id int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"valid",
|
|
imageIDs[imageIdxWithGallery],
|
|
false,
|
|
},
|
|
{
|
|
"invalid",
|
|
invalidID,
|
|
true,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
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("imageQueryBuilder.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 makeImageWithID(index int) *models.Image {
|
|
const fromDB = true
|
|
ret := makeImage(index)
|
|
ret.ID = imageIDs[index]
|
|
|
|
ret.Files = models.NewRelatedFiles([]models.File{makeImageFile(index)})
|
|
|
|
return ret
|
|
}
|
|
|
|
func Test_imageQueryBuilder_Find(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
id int
|
|
want *models.Image
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"valid",
|
|
imageIDs[imageIdxWithGallery],
|
|
makeImageWithID(imageIdxWithGallery),
|
|
false,
|
|
},
|
|
{
|
|
"invalid",
|
|
invalidID,
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
"with performers",
|
|
imageIDs[imageIdxWithTwoPerformers],
|
|
makeImageWithID(imageIdxWithTwoPerformers),
|
|
false,
|
|
},
|
|
{
|
|
"with tags",
|
|
imageIDs[imageIdxWithTwoTags],
|
|
makeImageWithID(imageIdxWithTwoTags),
|
|
false,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
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("imageQueryBuilder.Find() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if got != nil {
|
|
// load relationships
|
|
if err := loadImageRelationships(ctx, *tt.want, got); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
clearImageFileIDs(got)
|
|
}
|
|
assert.Equal(tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func postFindImages(ctx context.Context, want []*models.Image, got []*models.Image) error {
|
|
for i, s := range got {
|
|
// load relationships
|
|
if i < len(want) {
|
|
if err := loadImageRelationships(ctx, *want[i], s); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
clearImageFileIDs(s)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func Test_imageQueryBuilder_FindMany(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ids []int
|
|
want []*models.Image
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"valid with relationships",
|
|
[]int{imageIDs[imageIdxWithGallery], imageIDs[imageIdxWithTwoPerformers], imageIDs[imageIdxWithTwoTags]},
|
|
[]*models.Image{
|
|
makeImageWithID(imageIdxWithGallery),
|
|
makeImageWithID(imageIdxWithTwoPerformers),
|
|
makeImageWithID(imageIdxWithTwoTags),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"invalid",
|
|
[]int{imageIDs[imageIdxWithGallery], imageIDs[imageIdxWithTwoPerformers], invalidID},
|
|
nil,
|
|
true,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
got, err := qb.FindMany(ctx, tt.ids)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("imageQueryBuilder.FindMany() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if err := postFindImages(ctx, tt.want, got); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("imageQueryBuilder.FindMany() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageQueryBuilder_FindByChecksum(t *testing.T) {
|
|
getChecksum := func(index int) string {
|
|
return getImageStringValue(index, checksumField)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
checksum string
|
|
want []*models.Image
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"valid",
|
|
getChecksum(imageIdxWithGallery),
|
|
[]*models.Image{makeImageWithID(imageIdxWithGallery)},
|
|
false,
|
|
},
|
|
{
|
|
"invalid",
|
|
"invalid checksum",
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
"with performers",
|
|
getChecksum(imageIdxWithTwoPerformers),
|
|
[]*models.Image{makeImageWithID(imageIdxWithTwoPerformers)},
|
|
false,
|
|
},
|
|
{
|
|
"with tags",
|
|
getChecksum(imageIdxWithTwoTags),
|
|
[]*models.Image{makeImageWithID(imageIdxWithTwoTags)},
|
|
false,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
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("imageQueryBuilder.FindByChecksum() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if err := postFindImages(ctx, tt.want, got); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
assert.Equal(tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageQueryBuilder_FindByFingerprints(t *testing.T) {
|
|
getChecksum := func(index int) string {
|
|
return getImageStringValue(index, checksumField)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
fingerprints []models.Fingerprint
|
|
want []*models.Image
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"valid",
|
|
[]models.Fingerprint{
|
|
{
|
|
Type: models.FingerprintTypeMD5,
|
|
Fingerprint: getChecksum(imageIdxWithGallery),
|
|
},
|
|
},
|
|
[]*models.Image{makeImageWithID(imageIdxWithGallery)},
|
|
false,
|
|
},
|
|
{
|
|
"invalid",
|
|
[]models.Fingerprint{
|
|
{
|
|
Type: models.FingerprintTypeMD5,
|
|
Fingerprint: "invalid checksum",
|
|
},
|
|
},
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
"with performers",
|
|
[]models.Fingerprint{
|
|
{
|
|
Type: models.FingerprintTypeMD5,
|
|
Fingerprint: getChecksum(imageIdxWithTwoPerformers),
|
|
},
|
|
},
|
|
[]*models.Image{makeImageWithID(imageIdxWithTwoPerformers)},
|
|
false,
|
|
},
|
|
{
|
|
"with tags",
|
|
[]models.Fingerprint{
|
|
{
|
|
Type: models.FingerprintTypeMD5,
|
|
Fingerprint: getChecksum(imageIdxWithTwoTags),
|
|
},
|
|
},
|
|
[]*models.Image{makeImageWithID(imageIdxWithTwoTags)},
|
|
false,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
assert := assert.New(t)
|
|
got, err := qb.FindByFingerprints(ctx, tt.fingerprints)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("imageQueryBuilder.FindByChecksum() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if err := postFindImages(ctx, tt.want, got); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
assert.Equal(tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageQueryBuilder_FindByGalleryID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
galleryID int
|
|
want []*models.Image
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"valid",
|
|
galleryIDs[galleryIdxWithTwoImages],
|
|
[]*models.Image{makeImageWithID(imageIdx1WithGallery), makeImageWithID(imageIdx2WithGallery)},
|
|
false,
|
|
},
|
|
{
|
|
"none",
|
|
galleryIDs[galleryIdx1WithPerformer],
|
|
nil,
|
|
false,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
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("imageQueryBuilder.FindByGalleryID() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if err := postFindImages(ctx, tt.want, got); err != nil {
|
|
t.Errorf("loadImageRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
assert.Equal(tt.want, got)
|
|
return
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageQueryBuilder_CountByGalleryID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
galleryID int
|
|
want int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"valid",
|
|
galleryIDs[galleryIdxWithTwoImages],
|
|
2,
|
|
false,
|
|
},
|
|
{
|
|
"none",
|
|
galleryIDs[galleryIdx1WithPerformer],
|
|
0,
|
|
false,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
got, err := qb.CountByGalleryID(ctx, tt.galleryID)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("imageQueryBuilder.CountByGalleryID() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("imageQueryBuilder.CountByGalleryID() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func imagesToIDs(i []*models.Image) []int {
|
|
var ret []int
|
|
for _, ii := range i {
|
|
ret = append(ret, ii.ID)
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func Test_imageStore_FindByFileID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
fileID models.FileID
|
|
include []int
|
|
exclude []int
|
|
}{
|
|
{
|
|
"valid",
|
|
imageFileIDs[imageIdxWithGallery],
|
|
[]int{imageIdxWithGallery},
|
|
nil,
|
|
},
|
|
{
|
|
"invalid",
|
|
invalidFileID,
|
|
nil,
|
|
[]int{imageIdxWithGallery},
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
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("ImageStore.FindByFileID() error = %v", err)
|
|
return
|
|
}
|
|
for _, f := range got {
|
|
clearImageFileIDs(f)
|
|
}
|
|
|
|
ids := imagesToIDs(got)
|
|
include := indexesToIDs(imageIDs, tt.include)
|
|
exclude := indexesToIDs(imageIDs, tt.exclude)
|
|
|
|
for _, i := range include {
|
|
assert.Contains(ids, i)
|
|
}
|
|
for _, e := range exclude {
|
|
assert.NotContains(ids, e)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageStore_FindByFolderID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
folderID models.FolderID
|
|
include []int
|
|
exclude []int
|
|
}{
|
|
{
|
|
"valid",
|
|
folderIDs[folderIdxWithImageFiles],
|
|
[]int{imageIdxWithGallery},
|
|
nil,
|
|
},
|
|
{
|
|
"invalid",
|
|
invalidFolderID,
|
|
nil,
|
|
[]int{imageIdxWithGallery},
|
|
},
|
|
{
|
|
"parent folder",
|
|
folderIDs[folderIdxForObjectFiles],
|
|
nil,
|
|
[]int{imageIdxWithGallery},
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
assert := assert.New(t)
|
|
got, err := qb.FindByFolderID(ctx, tt.folderID)
|
|
if err != nil {
|
|
t.Errorf("ImageStore.FindByFolderID() error = %v", err)
|
|
return
|
|
}
|
|
for _, f := range got {
|
|
clearImageFileIDs(f)
|
|
}
|
|
|
|
ids := imagesToIDs(got)
|
|
include := indexesToIDs(imageIDs, tt.include)
|
|
exclude := indexesToIDs(imageIDs, tt.exclude)
|
|
|
|
for _, i := range include {
|
|
assert.Contains(ids, i)
|
|
}
|
|
for _, e := range exclude {
|
|
assert.NotContains(ids, e)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_imageStore_FindByZipFileID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
zipFileID models.FileID
|
|
include []int
|
|
exclude []int
|
|
}{
|
|
{
|
|
"valid",
|
|
fileIDs[fileIdxZip],
|
|
[]int{imageIdxInZip},
|
|
nil,
|
|
},
|
|
{
|
|
"invalid",
|
|
invalidFileID,
|
|
nil,
|
|
[]int{imageIdxInZip},
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
assert := assert.New(t)
|
|
got, err := qb.FindByZipFileID(ctx, tt.zipFileID)
|
|
if err != nil {
|
|
t.Errorf("ImageStore.FindByZipFileID() error = %v", err)
|
|
return
|
|
}
|
|
for _, f := range got {
|
|
clearImageFileIDs(f)
|
|
}
|
|
|
|
ids := imagesToIDs(got)
|
|
include := indexesToIDs(imageIDs, tt.include)
|
|
exclude := indexesToIDs(imageIDs, tt.exclude)
|
|
|
|
for _, i := range include {
|
|
assert.Contains(ids, i)
|
|
}
|
|
for _, e := range exclude {
|
|
assert.NotContains(ids, e)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestImageQueryQ(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
const imageIdx = 2
|
|
|
|
q := getImageStringValue(imageIdx, titleField)
|
|
|
|
sqb := db.Image
|
|
|
|
imageQueryQ(ctx, t, sqb, q, imageIdx)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func queryImagesWithCount(ctx context.Context, sqb models.ImageReader, imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) ([]*models.Image, int, error) {
|
|
result, err := sqb.Query(ctx, models.ImageQueryOptions{
|
|
QueryOptions: models.QueryOptions{
|
|
FindFilter: findFilter,
|
|
Count: true,
|
|
},
|
|
ImageFilter: imageFilter,
|
|
})
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
images, err := result.Resolve(ctx)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return images, result.Count, nil
|
|
}
|
|
|
|
func imageQueryQ(ctx context.Context, t *testing.T, sqb models.ImageReader, q string, expectedImageIdx int) {
|
|
filter := models.FindFilterType{
|
|
Q: &q,
|
|
}
|
|
images := queryImages(ctx, t, sqb, nil, &filter)
|
|
|
|
assert.Len(t, images, 1)
|
|
image := images[0]
|
|
assert.Equal(t, imageIDs[expectedImageIdx], image.ID)
|
|
|
|
count, err := sqb.QueryCount(ctx, nil, &filter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
assert.Equal(t, len(images), count)
|
|
|
|
// no Q should return all results
|
|
filter.Q = nil
|
|
images = queryImages(ctx, t, sqb, nil, &filter)
|
|
|
|
assert.Len(t, images, totalImages)
|
|
}
|
|
|
|
func verifyImageQuery(t *testing.T, filter models.ImageFilterType, verifyFn func(ctx context.Context, s *models.Image)) {
|
|
t.Helper()
|
|
withTxn(func(ctx context.Context) error {
|
|
t.Helper()
|
|
sqb := db.Image
|
|
|
|
images := queryImages(ctx, t, sqb, &filter, nil)
|
|
|
|
// assume it should find at least one
|
|
assert.Greater(t, len(images), 0)
|
|
|
|
for _, image := range images {
|
|
verifyFn(ctx, image)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryURL(t *testing.T) {
|
|
const imageIdx = 1
|
|
imageURL := getImageStringValue(imageIdx, urlField)
|
|
urlCriterion := models.StringCriterionInput{
|
|
Value: imageURL,
|
|
Modifier: models.CriterionModifierEquals,
|
|
}
|
|
filter := models.ImageFilterType{
|
|
URL: &urlCriterion,
|
|
}
|
|
|
|
verifyFn := func(ctx context.Context, o *models.Image) {
|
|
t.Helper()
|
|
|
|
if err := o.LoadURLs(ctx, db.Image); err != nil {
|
|
t.Errorf("Error loading scene URLs: %v", err)
|
|
}
|
|
|
|
urls := o.URLs.List()
|
|
var url string
|
|
if len(urls) > 0 {
|
|
url = urls[0]
|
|
}
|
|
|
|
verifyString(t, url, urlCriterion)
|
|
}
|
|
|
|
verifyImageQuery(t, filter, verifyFn)
|
|
urlCriterion.Modifier = models.CriterionModifierNotEquals
|
|
verifyImageQuery(t, filter, verifyFn)
|
|
urlCriterion.Modifier = models.CriterionModifierMatchesRegex
|
|
urlCriterion.Value = "image_.*1_URL"
|
|
verifyImageQuery(t, filter, verifyFn)
|
|
urlCriterion.Modifier = models.CriterionModifierNotMatchesRegex
|
|
verifyImageQuery(t, filter, verifyFn)
|
|
urlCriterion.Modifier = models.CriterionModifierIsNull
|
|
urlCriterion.Value = ""
|
|
verifyImageQuery(t, filter, verifyFn)
|
|
urlCriterion.Modifier = models.CriterionModifierNotNull
|
|
verifyImageQuery(t, filter, verifyFn)
|
|
}
|
|
|
|
func TestImageQueryPath(t *testing.T) {
|
|
const imageIdx = 1
|
|
imagePath := getFilePath(folderIdxWithImageFiles, getImageBasename(imageIdx))
|
|
|
|
pathCriterion := models.StringCriterionInput{
|
|
Value: imagePath,
|
|
Modifier: models.CriterionModifierEquals,
|
|
}
|
|
|
|
verifyImagePath(t, pathCriterion, 1)
|
|
|
|
pathCriterion.Modifier = models.CriterionModifierNotEquals
|
|
verifyImagePath(t, pathCriterion, totalImages-1)
|
|
|
|
pathCriterion.Modifier = models.CriterionModifierMatchesRegex
|
|
pathCriterion.Value = "image_.*01_Path"
|
|
verifyImagePath(t, pathCriterion, 1) // TODO - 2 if zip path is included
|
|
|
|
pathCriterion.Modifier = models.CriterionModifierNotMatchesRegex
|
|
verifyImagePath(t, pathCriterion, totalImages-1) // TODO - -2 if zip path is included
|
|
}
|
|
|
|
func verifyImagePath(t *testing.T, pathCriterion models.StringCriterionInput, expected int) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
imageFilter := models.ImageFilterType{
|
|
Path: &pathCriterion,
|
|
}
|
|
|
|
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
|
|
assert.Equal(t, expected, len(images), "number of returned images")
|
|
|
|
for _, image := range images {
|
|
verifyString(t, image.Path, pathCriterion)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryPathOr(t *testing.T) {
|
|
const image1Idx = 1
|
|
const image2Idx = 2
|
|
|
|
image1Path := getFilePath(folderIdxWithImageFiles, getImageBasename(image1Idx))
|
|
image2Path := getFilePath(folderIdxWithImageFiles, getImageBasename(image2Idx))
|
|
|
|
imageFilter := models.ImageFilterType{
|
|
Path: &models.StringCriterionInput{
|
|
Value: image1Path,
|
|
Modifier: models.CriterionModifierEquals,
|
|
},
|
|
OperatorFilter: models.OperatorFilter[models.ImageFilterType]{
|
|
Or: &models.ImageFilterType{
|
|
Path: &models.StringCriterionInput{
|
|
Value: image2Path,
|
|
Modifier: models.CriterionModifierEquals,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
|
|
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
|
|
if !assert.Len(t, images, 2) {
|
|
return nil
|
|
}
|
|
|
|
assert.Equal(t, image1Path, images[0].Path)
|
|
assert.Equal(t, image2Path, images[1].Path)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryPathAndRating(t *testing.T) {
|
|
const imageIdx = 1
|
|
imagePath := getFilePath(folderIdxWithImageFiles, getImageBasename(imageIdx))
|
|
imageRating := getRating(imageIdx)
|
|
|
|
imageFilter := models.ImageFilterType{
|
|
Path: &models.StringCriterionInput{
|
|
Value: imagePath,
|
|
Modifier: models.CriterionModifierEquals,
|
|
},
|
|
OperatorFilter: models.OperatorFilter[models.ImageFilterType]{
|
|
And: &models.ImageFilterType{
|
|
Rating100: &models.IntCriterionInput{
|
|
Value: int(imageRating.Int64),
|
|
Modifier: models.CriterionModifierEquals,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
|
|
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
|
|
if !assert.Len(t, images, 1) {
|
|
return nil
|
|
}
|
|
|
|
assert.Equal(t, imagePath, images[0].Path)
|
|
assert.Equal(t, int(imageRating.Int64), *images[0].Rating)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryPathNotRating(t *testing.T) {
|
|
const imageIdx = 1
|
|
|
|
imageRating := getRating(imageIdx)
|
|
|
|
pathCriterion := models.StringCriterionInput{
|
|
Value: "image_.*1_Path",
|
|
Modifier: models.CriterionModifierMatchesRegex,
|
|
}
|
|
|
|
ratingCriterion := models.IntCriterionInput{
|
|
Value: int(imageRating.Int64),
|
|
Modifier: models.CriterionModifierEquals,
|
|
}
|
|
|
|
imageFilter := models.ImageFilterType{
|
|
Path: &pathCriterion,
|
|
OperatorFilter: models.OperatorFilter[models.ImageFilterType]{
|
|
Not: &models.ImageFilterType{
|
|
Rating100: &ratingCriterion,
|
|
},
|
|
},
|
|
}
|
|
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
|
|
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
|
|
for _, image := range images {
|
|
verifyString(t, image.Path, pathCriterion)
|
|
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
|
verifyIntPtr(t, image.Rating, ratingCriterion)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageIllegalQuery(t *testing.T) {
|
|
assert := assert.New(t)
|
|
|
|
const imageIdx = 1
|
|
subFilter := models.ImageFilterType{
|
|
Path: &models.StringCriterionInput{
|
|
Value: getImageStringValue(imageIdx, "Path"),
|
|
Modifier: models.CriterionModifierEquals,
|
|
},
|
|
}
|
|
|
|
imageFilter := &models.ImageFilterType{
|
|
OperatorFilter: models.OperatorFilter[models.ImageFilterType]{
|
|
And: &subFilter,
|
|
Or: &subFilter,
|
|
},
|
|
}
|
|
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
|
|
_, _, err := queryImagesWithCount(ctx, sqb, imageFilter, nil)
|
|
assert.NotNil(err)
|
|
|
|
imageFilter.Or = nil
|
|
imageFilter.Not = &subFilter
|
|
_, _, err = queryImagesWithCount(ctx, sqb, imageFilter, nil)
|
|
assert.NotNil(err)
|
|
|
|
imageFilter.And = nil
|
|
imageFilter.Or = &subFilter
|
|
_, _, err = queryImagesWithCount(ctx, sqb, imageFilter, nil)
|
|
assert.NotNil(err)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryRating100(t *testing.T) {
|
|
const rating = 60
|
|
ratingCriterion := models.IntCriterionInput{
|
|
Value: rating,
|
|
Modifier: models.CriterionModifierEquals,
|
|
}
|
|
|
|
verifyImagesRating100(t, ratingCriterion)
|
|
|
|
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
|
verifyImagesRating100(t, ratingCriterion)
|
|
|
|
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
|
|
verifyImagesRating100(t, ratingCriterion)
|
|
|
|
ratingCriterion.Modifier = models.CriterionModifierLessThan
|
|
verifyImagesRating100(t, ratingCriterion)
|
|
|
|
ratingCriterion.Modifier = models.CriterionModifierIsNull
|
|
verifyImagesRating100(t, ratingCriterion)
|
|
|
|
ratingCriterion.Modifier = models.CriterionModifierNotNull
|
|
verifyImagesRating100(t, ratingCriterion)
|
|
}
|
|
|
|
func verifyImagesRating100(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
imageFilter := models.ImageFilterType{
|
|
Rating100: &ratingCriterion,
|
|
}
|
|
|
|
images, _, err := queryImagesWithCount(ctx, sqb, &imageFilter, nil)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
for _, image := range images {
|
|
verifyIntPtr(t, image.Rating, ratingCriterion)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryOCounter(t *testing.T) {
|
|
const oCounter = 1
|
|
oCounterCriterion := models.IntCriterionInput{
|
|
Value: oCounter,
|
|
Modifier: models.CriterionModifierEquals,
|
|
}
|
|
|
|
verifyImagesOCounter(t, oCounterCriterion)
|
|
|
|
oCounterCriterion.Modifier = models.CriterionModifierNotEquals
|
|
verifyImagesOCounter(t, oCounterCriterion)
|
|
|
|
oCounterCriterion.Modifier = models.CriterionModifierGreaterThan
|
|
verifyImagesOCounter(t, oCounterCriterion)
|
|
|
|
oCounterCriterion.Modifier = models.CriterionModifierLessThan
|
|
verifyImagesOCounter(t, oCounterCriterion)
|
|
}
|
|
|
|
func verifyImagesOCounter(t *testing.T, oCounterCriterion models.IntCriterionInput) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
imageFilter := models.ImageFilterType{
|
|
OCounter: &oCounterCriterion,
|
|
}
|
|
|
|
images, _, err := queryImagesWithCount(ctx, sqb, &imageFilter, nil)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
for _, image := range images {
|
|
verifyInt(t, image.OCounter, oCounterCriterion)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryResolution(t *testing.T) {
|
|
verifyImagesResolution(t, models.ResolutionEnumLow)
|
|
verifyImagesResolution(t, models.ResolutionEnumStandard)
|
|
verifyImagesResolution(t, models.ResolutionEnumStandardHd)
|
|
verifyImagesResolution(t, models.ResolutionEnumFullHd)
|
|
verifyImagesResolution(t, models.ResolutionEnumFourK)
|
|
verifyImagesResolution(t, models.ResolutionEnum("unknown"))
|
|
}
|
|
|
|
func verifyImagesResolution(t *testing.T, resolution models.ResolutionEnum) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
imageFilter := models.ImageFilterType{
|
|
Resolution: &models.ResolutionCriterionInput{
|
|
Value: resolution,
|
|
Modifier: models.CriterionModifierEquals,
|
|
},
|
|
}
|
|
|
|
images, _, err := queryImagesWithCount(ctx, sqb, &imageFilter, nil)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
for _, image := range images {
|
|
if err := image.LoadPrimaryFile(ctx, db.File); err != nil {
|
|
t.Errorf("Error loading primary file: %s", err.Error())
|
|
return nil
|
|
}
|
|
f := image.Files.Primary()
|
|
vf, ok := f.(models.VisualFile)
|
|
if !ok {
|
|
t.Errorf("Error: image primary file is not a visual file (is type %T)", f)
|
|
}
|
|
verifyImageResolution(t, vf.GetHeight(), resolution)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func verifyImageResolution(t *testing.T, height int, resolution models.ResolutionEnum) {
|
|
if !resolution.IsValid() {
|
|
return
|
|
}
|
|
|
|
assert := assert.New(t)
|
|
|
|
switch resolution {
|
|
case models.ResolutionEnumLow:
|
|
assert.True(height < 480)
|
|
case models.ResolutionEnumStandard:
|
|
assert.True(height >= 480 && height < 720)
|
|
case models.ResolutionEnumStandardHd:
|
|
assert.True(height >= 720 && height < 1080)
|
|
case models.ResolutionEnumFullHd:
|
|
assert.True(height >= 1080 && height < 2160)
|
|
case models.ResolutionEnumFourK:
|
|
assert.True(height >= 2160)
|
|
}
|
|
}
|
|
|
|
func TestImageQueryIsMissingGalleries(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
isMissing := "galleries"
|
|
imageFilter := models.ImageFilterType{
|
|
IsMissing: &isMissing,
|
|
}
|
|
|
|
q := getImageStringValue(imageIdxWithGallery, titleField)
|
|
findFilter := models.FindFilterType{
|
|
Q: &q,
|
|
}
|
|
|
|
images, _, err := queryImagesWithCount(ctx, sqb, &imageFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
assert.Len(t, images, 0)
|
|
|
|
findFilter.Q = nil
|
|
images, _, err = queryImagesWithCount(ctx, sqb, &imageFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
assert.Greater(t, len(images), 0)
|
|
|
|
// ensure non of the ids equal the one with gallery
|
|
for _, image := range images {
|
|
assert.NotEqual(t, imageIDs[imageIdxWithGallery], image.ID)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryIsMissingStudio(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
isMissing := "studio"
|
|
imageFilter := models.ImageFilterType{
|
|
IsMissing: &isMissing,
|
|
}
|
|
|
|
q := getImageStringValue(imageIdxWithStudio, titleField)
|
|
findFilter := models.FindFilterType{
|
|
Q: &q,
|
|
}
|
|
|
|
images, _, err := queryImagesWithCount(ctx, sqb, &imageFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
assert.Len(t, images, 0)
|
|
|
|
findFilter.Q = nil
|
|
images, _, err = queryImagesWithCount(ctx, sqb, &imageFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
// ensure non of the ids equal the one with studio
|
|
for _, image := range images {
|
|
assert.NotEqual(t, imageIDs[imageIdxWithStudio], image.ID)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryIsMissingPerformers(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
isMissing := "performers"
|
|
imageFilter := models.ImageFilterType{
|
|
IsMissing: &isMissing,
|
|
}
|
|
|
|
q := getImageStringValue(imageIdxWithPerformer, titleField)
|
|
findFilter := models.FindFilterType{
|
|
Q: &q,
|
|
}
|
|
|
|
images, _, err := queryImagesWithCount(ctx, sqb, &imageFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
assert.Len(t, images, 0)
|
|
|
|
findFilter.Q = nil
|
|
images, _, err = queryImagesWithCount(ctx, sqb, &imageFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
assert.True(t, len(images) > 0)
|
|
|
|
// ensure non of the ids equal the one with performers
|
|
for _, image := range images {
|
|
assert.NotEqual(t, imageIDs[imageIdxWithPerformer], image.ID)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryIsMissingTags(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
isMissing := "tags"
|
|
imageFilter := models.ImageFilterType{
|
|
IsMissing: &isMissing,
|
|
}
|
|
|
|
q := getImageStringValue(imageIdxWithTwoTags, titleField)
|
|
findFilter := models.FindFilterType{
|
|
Q: &q,
|
|
}
|
|
|
|
images, _, err := queryImagesWithCount(ctx, sqb, &imageFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
assert.Len(t, images, 0)
|
|
|
|
findFilter.Q = nil
|
|
images, _, err = queryImagesWithCount(ctx, sqb, &imageFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
assert.True(t, len(images) > 0)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryIsMissingRating(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
isMissing := "rating"
|
|
imageFilter := models.ImageFilterType{
|
|
IsMissing: &isMissing,
|
|
}
|
|
|
|
images, _, err := queryImagesWithCount(ctx, sqb, &imageFilter, nil)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
assert.True(t, len(images) > 0)
|
|
|
|
// ensure rating is null
|
|
for _, image := range images {
|
|
assert.Nil(t, image.Rating)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryGallery(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
galleryCriterion := models.MultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(galleryIDs[galleryIdxWithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludes,
|
|
}
|
|
|
|
imageFilter := models.ImageFilterType{
|
|
Galleries: &galleryCriterion,
|
|
}
|
|
|
|
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
assert.Len(t, images, 1)
|
|
|
|
// ensure ids are correct
|
|
for _, image := range images {
|
|
assert.True(t, image.ID == imageIDs[imageIdxWithGallery])
|
|
}
|
|
|
|
galleryCriterion = models.MultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(galleryIDs[galleryIdx1WithImage]),
|
|
strconv.Itoa(galleryIDs[galleryIdx2WithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludesAll,
|
|
}
|
|
|
|
images = queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
|
|
assert.Len(t, images, 1)
|
|
assert.Equal(t, imageIDs[imageIdxWithTwoGalleries], images[0].ID)
|
|
|
|
galleryCriterion = models.MultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(performerIDs[galleryIdx1WithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierExcludes,
|
|
}
|
|
|
|
q := getImageStringValue(imageIdxWithTwoGalleries, titleField)
|
|
findFilter := models.FindFilterType{
|
|
Q: &q,
|
|
}
|
|
|
|
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
|
assert.Len(t, images, 0)
|
|
|
|
q = getImageStringValue(imageIdxWithPerformer, titleField)
|
|
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
|
assert.Len(t, images, 1)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryPerformers(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
filter models.MultiCriterionInput
|
|
includeIdxs []int
|
|
excludeIdxs []int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"includes",
|
|
models.MultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(performerIDs[performerIdxWithImage]),
|
|
strconv.Itoa(performerIDs[performerIdx1WithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludes,
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformer,
|
|
imageIdxWithTwoPerformers,
|
|
},
|
|
[]int{
|
|
imageIdxWithGallery,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"includes all",
|
|
models.MultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(performerIDs[performerIdx1WithImage]),
|
|
strconv.Itoa(performerIDs[performerIdx2WithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludesAll,
|
|
},
|
|
[]int{
|
|
imageIdxWithTwoPerformers,
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformer,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"excludes",
|
|
models.MultiCriterionInput{
|
|
Modifier: models.CriterionModifierExcludes,
|
|
Value: []string{strconv.Itoa(tagIDs[performerIdx1WithImage])},
|
|
},
|
|
nil,
|
|
[]int{imageIdxWithTwoPerformers},
|
|
false,
|
|
},
|
|
{
|
|
"is null",
|
|
models.MultiCriterionInput{
|
|
Modifier: models.CriterionModifierIsNull,
|
|
},
|
|
[]int{imageIdxWithTag},
|
|
[]int{
|
|
imageIdxWithPerformer,
|
|
imageIdxWithTwoPerformers,
|
|
imageIdxWithPerformerTwoTags,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"not null",
|
|
models.MultiCriterionInput{
|
|
Modifier: models.CriterionModifierNotNull,
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformer,
|
|
imageIdxWithTwoPerformers,
|
|
imageIdxWithPerformerTwoTags,
|
|
},
|
|
[]int{imageIdxWithTag},
|
|
false,
|
|
},
|
|
{
|
|
"equals",
|
|
models.MultiCriterionInput{
|
|
Modifier: models.CriterionModifierEquals,
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[performerIdx1WithImage]),
|
|
strconv.Itoa(tagIDs[performerIdx2WithImage]),
|
|
},
|
|
},
|
|
[]int{imageIdxWithTwoPerformers},
|
|
[]int{
|
|
imageIdxWithThreePerformers,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"not equals",
|
|
models.MultiCriterionInput{
|
|
Modifier: models.CriterionModifierNotEquals,
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[performerIdx1WithImage]),
|
|
strconv.Itoa(tagIDs[performerIdx2WithImage]),
|
|
},
|
|
},
|
|
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.Image.Query(ctx, models.ImageQueryOptions{
|
|
ImageFilter: &models.ImageFilterType{
|
|
Performers: &tt.filter,
|
|
},
|
|
})
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("ImageStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
include := indexesToIDs(imageIDs, tt.includeIdxs)
|
|
exclude := indexesToIDs(imageIDs, tt.excludeIdxs)
|
|
|
|
for _, i := range include {
|
|
assert.Contains(results.IDs, i)
|
|
}
|
|
for _, e := range exclude {
|
|
assert.NotContains(results.IDs, e)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestImageQueryTags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
filter models.HierarchicalMultiCriterionInput
|
|
includeIdxs []int
|
|
excludeIdxs []int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"includes",
|
|
models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdxWithImage]),
|
|
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludes,
|
|
},
|
|
[]int{
|
|
imageIdxWithTag,
|
|
imageIdxWithTwoTags,
|
|
},
|
|
[]int{
|
|
imageIdxWithGallery,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"includes all",
|
|
models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
|
strconv.Itoa(tagIDs[tagIdx2WithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludesAll,
|
|
},
|
|
[]int{
|
|
imageIdxWithTwoTags,
|
|
},
|
|
[]int{
|
|
imageIdxWithTag,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"excludes",
|
|
models.HierarchicalMultiCriterionInput{
|
|
Modifier: models.CriterionModifierExcludes,
|
|
Value: []string{strconv.Itoa(tagIDs[tagIdx1WithImage])},
|
|
},
|
|
nil,
|
|
[]int{imageIdxWithTwoTags},
|
|
false,
|
|
},
|
|
{
|
|
"is null",
|
|
models.HierarchicalMultiCriterionInput{
|
|
Modifier: models.CriterionModifierIsNull,
|
|
},
|
|
[]int{imageIdx1WithPerformer},
|
|
[]int{
|
|
imageIdxWithTag,
|
|
imageIdxWithTwoTags,
|
|
imageIdxWithThreeTags,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"not null",
|
|
models.HierarchicalMultiCriterionInput{
|
|
Modifier: models.CriterionModifierNotNull,
|
|
},
|
|
[]int{
|
|
imageIdxWithTag,
|
|
imageIdxWithTwoTags,
|
|
imageIdxWithThreeTags,
|
|
},
|
|
[]int{imageIdx1WithPerformer},
|
|
false,
|
|
},
|
|
{
|
|
"equals",
|
|
models.HierarchicalMultiCriterionInput{
|
|
Modifier: models.CriterionModifierEquals,
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
|
strconv.Itoa(tagIDs[tagIdx2WithImage]),
|
|
},
|
|
},
|
|
[]int{imageIdxWithTwoTags},
|
|
[]int{
|
|
imageIdxWithThreeTags,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"not equals",
|
|
models.HierarchicalMultiCriterionInput{
|
|
Modifier: models.CriterionModifierNotEquals,
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdx1WithImage]),
|
|
strconv.Itoa(tagIDs[tagIdx2WithImage]),
|
|
},
|
|
},
|
|
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.Image.Query(ctx, models.ImageQueryOptions{
|
|
ImageFilter: &models.ImageFilterType{
|
|
Tags: &tt.filter,
|
|
},
|
|
})
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("ImageStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
include := indexesToIDs(imageIDs, tt.includeIdxs)
|
|
exclude := indexesToIDs(imageIDs, tt.excludeIdxs)
|
|
|
|
for _, i := range include {
|
|
assert.Contains(results.IDs, i)
|
|
}
|
|
for _, e := range exclude {
|
|
assert.NotContains(results.IDs, e)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestImageQueryStudio(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
q string
|
|
studioCriterion models.HierarchicalMultiCriterionInput
|
|
expectedIDs []int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"includes",
|
|
"",
|
|
models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludes,
|
|
},
|
|
[]int{imageIDs[imageIdxWithStudio]},
|
|
false,
|
|
},
|
|
{
|
|
"excludes",
|
|
getImageStringValue(imageIdxWithStudio, titleField),
|
|
models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierExcludes,
|
|
},
|
|
[]int{},
|
|
false,
|
|
},
|
|
{
|
|
"excludes includes null",
|
|
getImageStringValue(imageIdxWithGallery, titleField),
|
|
models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierExcludes,
|
|
},
|
|
[]int{imageIDs[imageIdxWithGallery]},
|
|
false,
|
|
},
|
|
{
|
|
"equals",
|
|
"",
|
|
models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierEquals,
|
|
},
|
|
[]int{imageIDs[imageIdxWithStudio]},
|
|
false,
|
|
},
|
|
{
|
|
"not equals",
|
|
getImageStringValue(imageIdxWithStudio, titleField),
|
|
models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(studioIDs[studioIdxWithImage]),
|
|
},
|
|
Modifier: models.CriterionModifierNotEquals,
|
|
},
|
|
[]int{},
|
|
false,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
studioCriterion := tt.studioCriterion
|
|
|
|
imageFilter := models.ImageFilterType{
|
|
Studios: &studioCriterion,
|
|
}
|
|
|
|
var findFilter *models.FindFilterType
|
|
if tt.q != "" {
|
|
findFilter = &models.FindFilterType{
|
|
Q: &tt.q,
|
|
}
|
|
}
|
|
|
|
images := queryImages(ctx, t, qb, &imageFilter, findFilter)
|
|
|
|
assert.ElementsMatch(t, imagesToIDs(images), tt.expectedIDs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestImageQueryStudioDepth(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
depth := 2
|
|
studioCriterion := models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(studioIDs[studioIdxWithGrandChild]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludes,
|
|
Depth: &depth,
|
|
}
|
|
|
|
imageFilter := models.ImageFilterType{
|
|
Studios: &studioCriterion,
|
|
}
|
|
|
|
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
assert.Len(t, images, 1)
|
|
|
|
depth = 1
|
|
|
|
images = queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
assert.Len(t, images, 0)
|
|
|
|
studioCriterion.Value = []string{strconv.Itoa(studioIDs[studioIdxWithParentAndChild])}
|
|
images = queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
assert.Len(t, images, 1)
|
|
|
|
// ensure id is correct
|
|
assert.Equal(t, imageIDs[imageIdxWithGrandChildStudio], images[0].ID)
|
|
|
|
depth = 2
|
|
|
|
studioCriterion = models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(studioIDs[studioIdxWithGrandChild]),
|
|
},
|
|
Modifier: models.CriterionModifierExcludes,
|
|
Depth: &depth,
|
|
}
|
|
|
|
q := getImageStringValue(imageIdxWithGrandChildStudio, titleField)
|
|
findFilter := models.FindFilterType{
|
|
Q: &q,
|
|
}
|
|
|
|
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
|
assert.Len(t, images, 0)
|
|
|
|
depth = 1
|
|
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
|
assert.Len(t, images, 1)
|
|
|
|
studioCriterion.Value = []string{strconv.Itoa(studioIDs[studioIdxWithParentAndChild])}
|
|
images = queryImages(ctx, t, sqb, &imageFilter, &findFilter)
|
|
assert.Len(t, images, 0)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func queryImages(ctx context.Context, t *testing.T, sqb models.ImageReader, imageFilter *models.ImageFilterType, findFilter *models.FindFilterType) []*models.Image {
|
|
images, _, err := queryImagesWithCount(ctx, sqb, imageFilter, findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying images: %s", err.Error())
|
|
}
|
|
|
|
return images
|
|
}
|
|
|
|
func TestImageQueryPerformerTags(t *testing.T) {
|
|
allDepth := -1
|
|
|
|
tests := []struct {
|
|
name string
|
|
findFilter *models.FindFilterType
|
|
filter *models.ImageFilterType
|
|
includeIdxs []int
|
|
excludeIdxs []int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"includes",
|
|
nil,
|
|
&models.ImageFilterType{
|
|
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdxWithPerformer]),
|
|
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludes,
|
|
},
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformerTag,
|
|
imageIdxWithPerformerTwoTags,
|
|
imageIdxWithTwoPerformerTag,
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformer,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"includes sub-tags",
|
|
nil,
|
|
&models.ImageFilterType{
|
|
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdxWithParentAndChild]),
|
|
},
|
|
Depth: &allDepth,
|
|
Modifier: models.CriterionModifierIncludes,
|
|
},
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformerParentTag,
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformer,
|
|
imageIdxWithPerformerTag,
|
|
imageIdxWithPerformerTwoTags,
|
|
imageIdxWithTwoPerformerTag,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"includes all",
|
|
nil,
|
|
&models.ImageFilterType{
|
|
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdx1WithPerformer]),
|
|
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludesAll,
|
|
},
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformerTwoTags,
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformer,
|
|
imageIdxWithPerformerTag,
|
|
imageIdxWithTwoPerformerTag,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"excludes performer tag tagIdx2WithPerformer",
|
|
nil,
|
|
&models.ImageFilterType{
|
|
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
|
Modifier: models.CriterionModifierExcludes,
|
|
Value: []string{strconv.Itoa(tagIDs[tagIdx2WithPerformer])},
|
|
},
|
|
},
|
|
nil,
|
|
[]int{imageIdxWithTwoPerformerTag},
|
|
false,
|
|
},
|
|
{
|
|
"excludes sub-tags",
|
|
nil,
|
|
&models.ImageFilterType{
|
|
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdxWithParentAndChild]),
|
|
},
|
|
Depth: &allDepth,
|
|
Modifier: models.CriterionModifierExcludes,
|
|
},
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformer,
|
|
imageIdxWithPerformerTag,
|
|
imageIdxWithPerformerTwoTags,
|
|
imageIdxWithTwoPerformerTag,
|
|
},
|
|
[]int{
|
|
imageIdxWithPerformerParentTag,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"is null",
|
|
nil,
|
|
&models.ImageFilterType{
|
|
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
|
Modifier: models.CriterionModifierIsNull,
|
|
},
|
|
},
|
|
[]int{imageIdxWithGallery},
|
|
[]int{imageIdxWithPerformerTag},
|
|
false,
|
|
},
|
|
{
|
|
"not null",
|
|
nil,
|
|
&models.ImageFilterType{
|
|
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
|
Modifier: models.CriterionModifierNotNull,
|
|
},
|
|
},
|
|
[]int{imageIdxWithPerformerTag},
|
|
[]int{imageIdxWithGallery},
|
|
false,
|
|
},
|
|
{
|
|
"equals",
|
|
nil,
|
|
&models.ImageFilterType{
|
|
PerformerTags: &models.HierarchicalMultiCriterionInput{
|
|
Modifier: models.CriterionModifierEquals,
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdx2WithPerformer]),
|
|
},
|
|
},
|
|
},
|
|
nil,
|
|
nil,
|
|
true,
|
|
},
|
|
{
|
|
"not equals",
|
|
nil,
|
|
&models.ImageFilterType{
|
|
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.Image.Query(ctx, models.ImageQueryOptions{
|
|
ImageFilter: tt.filter,
|
|
QueryOptions: models.QueryOptions{
|
|
FindFilter: tt.findFilter,
|
|
},
|
|
})
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("ImageStore.Query() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
include := indexesToIDs(imageIDs, tt.includeIdxs)
|
|
exclude := indexesToIDs(imageIDs, tt.excludeIdxs)
|
|
|
|
for _, i := range include {
|
|
assert.Contains(results.IDs, i)
|
|
}
|
|
for _, e := range exclude {
|
|
assert.NotContains(results.IDs, e)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestImageQueryTagCount(t *testing.T) {
|
|
const tagCount = 1
|
|
tagCountCriterion := models.IntCriterionInput{
|
|
Value: tagCount,
|
|
Modifier: models.CriterionModifierEquals,
|
|
}
|
|
|
|
verifyImagesTagCount(t, tagCountCriterion)
|
|
|
|
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
|
|
verifyImagesTagCount(t, tagCountCriterion)
|
|
|
|
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
|
verifyImagesTagCount(t, tagCountCriterion)
|
|
|
|
tagCountCriterion.Modifier = models.CriterionModifierLessThan
|
|
verifyImagesTagCount(t, tagCountCriterion)
|
|
}
|
|
|
|
func verifyImagesTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
imageFilter := models.ImageFilterType{
|
|
TagCount: &tagCountCriterion,
|
|
}
|
|
|
|
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
assert.Greater(t, len(images), 0)
|
|
|
|
for _, image := range images {
|
|
ids, err := sqb.GetTagIDs(ctx, image.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
verifyInt(t, len(ids), tagCountCriterion)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQueryPerformerCount(t *testing.T) {
|
|
const performerCount = 1
|
|
performerCountCriterion := models.IntCriterionInput{
|
|
Value: performerCount,
|
|
Modifier: models.CriterionModifierEquals,
|
|
}
|
|
|
|
verifyImagesPerformerCount(t, performerCountCriterion)
|
|
|
|
performerCountCriterion.Modifier = models.CriterionModifierNotEquals
|
|
verifyImagesPerformerCount(t, performerCountCriterion)
|
|
|
|
performerCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
|
verifyImagesPerformerCount(t, performerCountCriterion)
|
|
|
|
performerCountCriterion.Modifier = models.CriterionModifierLessThan
|
|
verifyImagesPerformerCount(t, performerCountCriterion)
|
|
}
|
|
|
|
func verifyImagesPerformerCount(t *testing.T, performerCountCriterion models.IntCriterionInput) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Image
|
|
imageFilter := models.ImageFilterType{
|
|
PerformerCount: &performerCountCriterion,
|
|
}
|
|
|
|
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
|
assert.Greater(t, len(images), 0)
|
|
|
|
for _, image := range images {
|
|
ids, err := sqb.GetPerformerIDs(ctx, image.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
verifyInt(t, len(ids), performerCountCriterion)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestImageQuerySorting(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
sortBy string
|
|
dir models.SortDirectionEnum
|
|
firstIdx int // -1 to ignore
|
|
lastIdx int
|
|
}{
|
|
{
|
|
"file mod time",
|
|
"file_mod_time",
|
|
models.SortDirectionEnumDesc,
|
|
-1,
|
|
-1,
|
|
},
|
|
{
|
|
"file size",
|
|
"filesize",
|
|
models.SortDirectionEnumDesc,
|
|
-1,
|
|
-1,
|
|
},
|
|
{
|
|
"path",
|
|
"path",
|
|
models.SortDirectionEnumDesc,
|
|
-1,
|
|
-1,
|
|
},
|
|
{
|
|
"date",
|
|
"date",
|
|
models.SortDirectionEnumDesc,
|
|
imageIdxWithTwoGalleries,
|
|
imageIdxWithGrandChildStudio,
|
|
},
|
|
}
|
|
|
|
qb := db.Image
|
|
|
|
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.ImageQueryOptions{
|
|
QueryOptions: models.QueryOptions{
|
|
FindFilter: &models.FindFilterType{
|
|
Sort: &tt.sortBy,
|
|
Direction: &tt.dir,
|
|
},
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
t.Errorf("ImageStore.TestImageQuerySorting() error = %v", err)
|
|
return
|
|
}
|
|
|
|
images, err := got.Resolve(ctx)
|
|
if err != nil {
|
|
t.Errorf("ImageStore.TestImageQuerySorting() error = %v", err)
|
|
return
|
|
}
|
|
|
|
if !assert.Greater(len(images), 0) {
|
|
return
|
|
}
|
|
|
|
// image should be in same order as indexes
|
|
first := images[0]
|
|
last := images[len(images)-1]
|
|
|
|
if tt.firstIdx != -1 {
|
|
firstID := sceneIDs[tt.firstIdx]
|
|
assert.Equal(firstID, first.ID)
|
|
}
|
|
if tt.lastIdx != -1 {
|
|
lastID := sceneIDs[tt.lastIdx]
|
|
assert.Equal(lastID, last.ID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestImageQueryPagination(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
perPage := 1
|
|
findFilter := models.FindFilterType{
|
|
PerPage: &perPage,
|
|
}
|
|
|
|
sqb := db.Image
|
|
images, _, err := queryImagesWithCount(ctx, sqb, nil, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
assert.Len(t, images, 1)
|
|
|
|
firstID := images[0].ID
|
|
|
|
page := 2
|
|
findFilter.Page = &page
|
|
images, _, err = queryImagesWithCount(ctx, sqb, nil, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
|
|
assert.Len(t, images, 1)
|
|
secondID := images[0].ID
|
|
assert.NotEqual(t, firstID, secondID)
|
|
|
|
perPage = 2
|
|
page = 1
|
|
|
|
images, _, err = queryImagesWithCount(ctx, sqb, nil, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying image: %s", err.Error())
|
|
}
|
|
assert.Len(t, images, 2)
|
|
assert.Equal(t, firstID, images[0].ID)
|
|
assert.Equal(t, secondID, images[1].ID)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// TODO Count
|
|
// TODO SizeCount
|
|
// TODO All
|