mirror of https://github.com/stashapp/stash.git
1900 lines
45 KiB
Go
1900 lines
45 KiB
Go
//go:build integration
|
|
// +build integration
|
|
|
|
package sqlite_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stashapp/stash/pkg/models"
|
|
"github.com/stashapp/stash/pkg/sliceutil"
|
|
"github.com/stashapp/stash/pkg/sliceutil/intslice"
|
|
)
|
|
|
|
func loadGroupRelationships(ctx context.Context, expected models.Group, actual *models.Group) error {
|
|
if expected.URLs.Loaded() {
|
|
if err := actual.LoadURLs(ctx, db.Group); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if expected.TagIDs.Loaded() {
|
|
if err := actual.LoadTagIDs(ctx, db.Group); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if expected.ContainingGroups.Loaded() {
|
|
if err := actual.LoadContainingGroupIDs(ctx, db.Group); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if expected.SubGroups.Loaded() {
|
|
if err := actual.LoadSubGroupIDs(ctx, db.Group); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func Test_GroupStore_Create(t *testing.T) {
|
|
var (
|
|
name = "name"
|
|
url = "url"
|
|
aliases = "alias1, alias2"
|
|
director = "director"
|
|
rating = 60
|
|
duration = 34
|
|
synopsis = "synopsis"
|
|
date, _ = models.ParseDate("2003-02-01")
|
|
containingGroupDescription = "containingGroupDescription"
|
|
subGroupDescription = "subGroupDescription"
|
|
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
|
|
newObject models.Group
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"full",
|
|
models.Group{
|
|
Name: name,
|
|
Duration: &duration,
|
|
Date: &date,
|
|
Rating: &rating,
|
|
StudioID: &studioIDs[studioIdxWithGroup],
|
|
Director: director,
|
|
Synopsis: synopsis,
|
|
URLs: models.NewRelatedStrings([]string{url}),
|
|
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithGroup]}),
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithScene], Description: containingGroupDescription},
|
|
}),
|
|
SubGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithStudio], Description: subGroupDescription},
|
|
}),
|
|
Aliases: aliases,
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"invalid tag id",
|
|
models.Group{
|
|
Name: name,
|
|
TagIDs: models.NewRelatedIDs([]int{invalidID}),
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid containing group id",
|
|
models.Group{
|
|
Name: name,
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{{GroupID: invalidID}}),
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid sub group id",
|
|
models.Group{
|
|
Name: name,
|
|
SubGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{{GroupID: invalidID}}),
|
|
},
|
|
true,
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
assert := assert.New(t)
|
|
|
|
p := tt.newObject
|
|
if err := qb.Create(ctx, &p); (err != nil) != tt.wantErr {
|
|
t.Errorf("GroupStore.Create() error = %v, wantErr = %v", err, tt.wantErr)
|
|
}
|
|
|
|
if tt.wantErr {
|
|
assert.Zero(p.ID)
|
|
return
|
|
}
|
|
|
|
assert.NotZero(p.ID)
|
|
|
|
copy := tt.newObject
|
|
copy.ID = p.ID
|
|
|
|
// load relationships
|
|
if err := loadGroupRelationships(ctx, copy, &p); err != nil {
|
|
t.Errorf("loadGroupRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
assert.Equal(copy, p)
|
|
|
|
// ensure can find the group
|
|
found, err := qb.Find(ctx, p.ID)
|
|
if err != nil {
|
|
t.Errorf("GroupStore.Find() error = %v", err)
|
|
}
|
|
|
|
if !assert.NotNil(found) {
|
|
return
|
|
}
|
|
|
|
// load relationships
|
|
if err := loadGroupRelationships(ctx, copy, found); err != nil {
|
|
t.Errorf("loadGroupRelationships() error = %v", err)
|
|
return
|
|
}
|
|
assert.Equal(copy, *found)
|
|
|
|
return
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_groupQueryBuilder_Update(t *testing.T) {
|
|
var (
|
|
name = "name"
|
|
url = "url"
|
|
aliases = "alias1, alias2"
|
|
director = "director"
|
|
rating = 60
|
|
duration = 34
|
|
synopsis = "synopsis"
|
|
date, _ = models.ParseDate("2003-02-01")
|
|
containingGroupDescription = "containingGroupDescription"
|
|
subGroupDescription = "subGroupDescription"
|
|
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.Group
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"full",
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithTag],
|
|
Name: name,
|
|
Duration: &duration,
|
|
Date: &date,
|
|
Rating: &rating,
|
|
StudioID: &studioIDs[studioIdxWithGroup],
|
|
Director: director,
|
|
Synopsis: synopsis,
|
|
URLs: models.NewRelatedStrings([]string{url}),
|
|
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithGroup]}),
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithScene], Description: containingGroupDescription},
|
|
}),
|
|
SubGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithStudio], Description: subGroupDescription},
|
|
}),
|
|
Aliases: aliases,
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"clear tag ids",
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithTag],
|
|
Name: name,
|
|
TagIDs: models.NewRelatedIDs([]int{}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"clear containing ids",
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithParent],
|
|
Name: name,
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"clear sub ids",
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithChild],
|
|
Name: name,
|
|
SubGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"invalid studio id",
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithScene],
|
|
Name: name,
|
|
StudioID: &invalidID,
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid tag id",
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithScene],
|
|
Name: name,
|
|
TagIDs: models.NewRelatedIDs([]int{invalidID}),
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid containing group id",
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithScene],
|
|
Name: name,
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{{GroupID: invalidID}}),
|
|
},
|
|
true,
|
|
},
|
|
{
|
|
"invalid sub group id",
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithScene],
|
|
Name: name,
|
|
SubGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{{GroupID: invalidID}}),
|
|
},
|
|
true,
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
assert := assert.New(t)
|
|
|
|
actual := tt.updatedObject
|
|
expected := tt.updatedObject
|
|
|
|
if err := qb.Update(ctx, &actual); (err != nil) != tt.wantErr {
|
|
t.Errorf("groupQueryBuilder.Update() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
|
|
if tt.wantErr {
|
|
return
|
|
}
|
|
|
|
s, err := qb.Find(ctx, actual.ID)
|
|
if err != nil {
|
|
t.Errorf("groupQueryBuilder.Find() error = %v", err)
|
|
}
|
|
|
|
// load relationships
|
|
if err := loadGroupRelationships(ctx, expected, s); err != nil {
|
|
t.Errorf("loadGroupRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
assert.Equal(expected, *s)
|
|
})
|
|
}
|
|
}
|
|
|
|
var clearGroupPartial = models.GroupPartial{
|
|
// leave mandatory fields
|
|
Aliases: models.OptionalString{Set: true, Null: true},
|
|
Synopsis: models.OptionalString{Set: true, Null: true},
|
|
Director: models.OptionalString{Set: true, Null: true},
|
|
Duration: models.OptionalInt{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},
|
|
TagIDs: &models.UpdateIDs{Mode: models.RelationshipUpdateModeSet},
|
|
ContainingGroups: &models.UpdateGroupDescriptions{Mode: models.RelationshipUpdateModeSet},
|
|
SubGroups: &models.UpdateGroupDescriptions{Mode: models.RelationshipUpdateModeSet},
|
|
}
|
|
|
|
func emptyGroup(idx int) models.Group {
|
|
return models.Group{
|
|
ID: groupIDs[idx],
|
|
Name: groupNames[idx],
|
|
TagIDs: models.NewRelatedIDs([]int{}),
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{}),
|
|
SubGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{}),
|
|
}
|
|
}
|
|
|
|
func Test_groupQueryBuilder_UpdatePartial(t *testing.T) {
|
|
var (
|
|
name = "name"
|
|
url = "url"
|
|
aliases = "alias1, alias2"
|
|
director = "director"
|
|
rating = 60
|
|
duration = 34
|
|
synopsis = "synopsis"
|
|
date, _ = models.ParseDate("2003-02-01")
|
|
containingGroupDescription = "containingGroupDescription"
|
|
subGroupDescription = "subGroupDescription"
|
|
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.GroupPartial
|
|
want models.Group
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"full",
|
|
groupIDs[groupIdxWithScene],
|
|
models.GroupPartial{
|
|
Name: models.NewOptionalString(name),
|
|
Director: models.NewOptionalString(director),
|
|
Synopsis: models.NewOptionalString(synopsis),
|
|
Aliases: models.NewOptionalString(aliases),
|
|
URLs: &models.UpdateStrings{
|
|
Values: []string{url},
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
},
|
|
Date: models.NewOptionalDate(date),
|
|
Duration: models.NewOptionalInt(duration),
|
|
Rating: models.NewOptionalInt(rating),
|
|
StudioID: models.NewOptionalInt(studioIDs[studioIdxWithGroup]),
|
|
CreatedAt: models.NewOptionalTime(createdAt),
|
|
UpdatedAt: models.NewOptionalTime(updatedAt),
|
|
TagIDs: &models.UpdateIDs{
|
|
IDs: []int{tagIDs[tagIdx1WithGroup], tagIDs[tagIdx1WithDupName]},
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
},
|
|
ContainingGroups: &models.UpdateGroupDescriptions{
|
|
Groups: []models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithStudio], Description: containingGroupDescription},
|
|
{GroupID: groupIDs[groupIdxWithThreeTags], Description: containingGroupDescription},
|
|
},
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
},
|
|
SubGroups: &models.UpdateGroupDescriptions{
|
|
Groups: []models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithTag], Description: subGroupDescription},
|
|
{GroupID: groupIDs[groupIdxWithDupName], Description: subGroupDescription},
|
|
},
|
|
Mode: models.RelationshipUpdateModeSet,
|
|
},
|
|
},
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithScene],
|
|
Name: name,
|
|
Director: director,
|
|
Synopsis: synopsis,
|
|
Aliases: aliases,
|
|
URLs: models.NewRelatedStrings([]string{url}),
|
|
Date: &date,
|
|
Duration: &duration,
|
|
Rating: &rating,
|
|
StudioID: &studioIDs[studioIdxWithGroup],
|
|
CreatedAt: createdAt,
|
|
UpdatedAt: updatedAt,
|
|
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithGroup]}),
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithStudio], Description: containingGroupDescription},
|
|
{GroupID: groupIDs[groupIdxWithThreeTags], Description: containingGroupDescription},
|
|
}),
|
|
SubGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithTag], Description: subGroupDescription},
|
|
{GroupID: groupIDs[groupIdxWithDupName], Description: subGroupDescription},
|
|
}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"clear all",
|
|
groupIDs[groupIdxWithScene],
|
|
clearGroupPartial,
|
|
emptyGroup(groupIdxWithScene),
|
|
false,
|
|
},
|
|
{
|
|
"clear tag ids",
|
|
groupIDs[groupIdxWithTag],
|
|
clearGroupPartial,
|
|
emptyGroup(groupIdxWithTag),
|
|
false,
|
|
},
|
|
{
|
|
"clear group relationships",
|
|
groupIDs[groupIdxWithParentAndChild],
|
|
clearGroupPartial,
|
|
emptyGroup(groupIdxWithParentAndChild),
|
|
false,
|
|
},
|
|
{
|
|
"add containing group",
|
|
groupIDs[groupIdxWithParent],
|
|
models.GroupPartial{
|
|
ContainingGroups: &models.UpdateGroupDescriptions{
|
|
Groups: []models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithScene], Description: containingGroupDescription},
|
|
},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithParent],
|
|
Name: groupNames[groupIdxWithParent],
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithChild]},
|
|
{GroupID: groupIDs[groupIdxWithScene], Description: containingGroupDescription},
|
|
}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"add sub group",
|
|
groupIDs[groupIdxWithChild],
|
|
models.GroupPartial{
|
|
SubGroups: &models.UpdateGroupDescriptions{
|
|
Groups: []models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithScene], Description: subGroupDescription},
|
|
},
|
|
Mode: models.RelationshipUpdateModeAdd,
|
|
},
|
|
},
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithChild],
|
|
Name: groupNames[groupIdxWithChild],
|
|
SubGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithParent]},
|
|
{GroupID: groupIDs[groupIdxWithScene], Description: subGroupDescription},
|
|
}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"remove containing group",
|
|
groupIDs[groupIdxWithParent],
|
|
models.GroupPartial{
|
|
ContainingGroups: &models.UpdateGroupDescriptions{
|
|
Groups: []models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithChild]},
|
|
},
|
|
Mode: models.RelationshipUpdateModeRemove,
|
|
},
|
|
},
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithParent],
|
|
Name: groupNames[groupIdxWithParent],
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"remove sub group",
|
|
groupIDs[groupIdxWithChild],
|
|
models.GroupPartial{
|
|
SubGroups: &models.UpdateGroupDescriptions{
|
|
Groups: []models.GroupIDDescription{
|
|
{GroupID: groupIDs[groupIdxWithParent]},
|
|
},
|
|
Mode: models.RelationshipUpdateModeRemove,
|
|
},
|
|
},
|
|
models.Group{
|
|
ID: groupIDs[groupIdxWithChild],
|
|
Name: groupNames[groupIdxWithChild],
|
|
SubGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{}),
|
|
},
|
|
false,
|
|
},
|
|
{
|
|
"invalid id",
|
|
invalidID,
|
|
models.GroupPartial{},
|
|
models.Group{},
|
|
true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
qb := db.Group
|
|
|
|
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("groupQueryBuilder.UpdatePartial() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if tt.wantErr {
|
|
return
|
|
}
|
|
|
|
// load relationships
|
|
if err := loadGroupRelationships(ctx, tt.want, got); err != nil {
|
|
t.Errorf("loadGroupRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
assert.Equal(tt.want, *got)
|
|
|
|
s, err := qb.Find(ctx, tt.id)
|
|
if err != nil {
|
|
t.Errorf("groupQueryBuilder.Find() error = %v", err)
|
|
}
|
|
|
|
// load relationships
|
|
if err := loadGroupRelationships(ctx, tt.want, s); err != nil {
|
|
t.Errorf("loadGroupRelationships() error = %v", err)
|
|
return
|
|
}
|
|
|
|
assert.Equal(tt.want, *s)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupFindByName(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
mqb := db.Group
|
|
|
|
name := groupNames[groupIdxWithScene] // find a group by name
|
|
|
|
group, err := mqb.FindByName(ctx, name, false)
|
|
|
|
if err != nil {
|
|
t.Errorf("Error finding groups: %s", err.Error())
|
|
}
|
|
|
|
assert.Equal(t, groupNames[groupIdxWithScene], group.Name)
|
|
|
|
name = groupNames[groupIdxWithDupName] // find a group by name nocase
|
|
|
|
group, err = mqb.FindByName(ctx, name, true)
|
|
|
|
if err != nil {
|
|
t.Errorf("Error finding groups: %s", err.Error())
|
|
}
|
|
// groupIdxWithDupName and groupIdxWithScene should have similar names ( only diff should be Name vs NaMe)
|
|
//group.Name should match with groupIdxWithScene since its ID is before moveIdxWithDupName
|
|
assert.Equal(t, groupNames[groupIdxWithScene], group.Name)
|
|
//group.Name should match with groupIdxWithDupName if the check is not case sensitive
|
|
assert.Equal(t, strings.ToLower(groupNames[groupIdxWithDupName]), strings.ToLower(group.Name))
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestGroupFindByNames(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
var names []string
|
|
|
|
mqb := db.Group
|
|
|
|
names = append(names, groupNames[groupIdxWithScene]) // find groups by names
|
|
|
|
groups, err := mqb.FindByNames(ctx, names, false)
|
|
if err != nil {
|
|
t.Errorf("Error finding groups: %s", err.Error())
|
|
}
|
|
assert.Len(t, groups, 1)
|
|
assert.Equal(t, groupNames[groupIdxWithScene], groups[0].Name)
|
|
|
|
groups, err = mqb.FindByNames(ctx, names, true) // find groups by names nocase
|
|
if err != nil {
|
|
t.Errorf("Error finding groups: %s", err.Error())
|
|
}
|
|
assert.Len(t, groups, 2) // groupIdxWithScene and groupIdxWithDupName
|
|
assert.Equal(t, strings.ToLower(groupNames[groupIdxWithScene]), strings.ToLower(groups[0].Name))
|
|
assert.Equal(t, strings.ToLower(groupNames[groupIdxWithScene]), strings.ToLower(groups[1].Name))
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func groupsToIDs(i []*models.Group) []int {
|
|
ret := make([]int, len(i))
|
|
for i, v := range i {
|
|
ret[i] = v.ID
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func TestGroupQuery(t *testing.T) {
|
|
var (
|
|
frontImage = "front_image"
|
|
backImage = "back_image"
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
findFilter *models.FindFilterType
|
|
filter *models.GroupFilterType
|
|
includeIdxs []int
|
|
excludeIdxs []int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"is missing front image",
|
|
nil,
|
|
&models.GroupFilterType{
|
|
IsMissing: &frontImage,
|
|
},
|
|
// just ensure that it doesn't error
|
|
nil,
|
|
nil,
|
|
false,
|
|
},
|
|
{
|
|
"is missing back image",
|
|
nil,
|
|
&models.GroupFilterType{
|
|
IsMissing: &backImage,
|
|
},
|
|
// just ensure that it doesn't error
|
|
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.Group.Query(ctx, tt.filter, tt.findFilter)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("GroupQueryBuilder.Query() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
ids := groupsToIDs(results)
|
|
include := indexesToIDs(performerIDs, tt.includeIdxs)
|
|
exclude := indexesToIDs(performerIDs, tt.excludeIdxs)
|
|
|
|
for _, i := range include {
|
|
assert.Contains(ids, i)
|
|
}
|
|
for _, e := range exclude {
|
|
assert.NotContains(ids, e)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupQueryStudio(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
mqb := db.Group
|
|
studioCriterion := models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(studioIDs[studioIdxWithGroup]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludes,
|
|
}
|
|
|
|
groupFilter := models.GroupFilterType{
|
|
Studios: &studioCriterion,
|
|
}
|
|
|
|
groups, _, err := mqb.Query(ctx, &groupFilter, nil)
|
|
if err != nil {
|
|
t.Errorf("Error querying group: %s", err.Error())
|
|
}
|
|
|
|
assert.Len(t, groups, 1)
|
|
|
|
// ensure id is correct
|
|
assert.Equal(t, groupIDs[groupIdxWithStudio], groups[0].ID)
|
|
|
|
studioCriterion = models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(studioIDs[studioIdxWithGroup]),
|
|
},
|
|
Modifier: models.CriterionModifierExcludes,
|
|
}
|
|
|
|
q := getGroupStringValue(groupIdxWithStudio, titleField)
|
|
findFilter := models.FindFilterType{
|
|
Q: &q,
|
|
}
|
|
|
|
groups, _, err = mqb.Query(ctx, &groupFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying group: %s", err.Error())
|
|
}
|
|
assert.Len(t, groups, 0)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestGroupQueryURL(t *testing.T) {
|
|
const sceneIdx = 1
|
|
groupURL := getGroupStringValue(sceneIdx, urlField)
|
|
|
|
urlCriterion := models.StringCriterionInput{
|
|
Value: groupURL,
|
|
Modifier: models.CriterionModifierEquals,
|
|
}
|
|
|
|
filter := models.GroupFilterType{
|
|
URL: &urlCriterion,
|
|
}
|
|
|
|
verifyFn := func(n *models.Group) {
|
|
t.Helper()
|
|
|
|
urls := n.URLs.List()
|
|
var url string
|
|
if len(urls) > 0 {
|
|
url = urls[0]
|
|
}
|
|
|
|
verifyString(t, url, urlCriterion)
|
|
}
|
|
|
|
verifyGroupQuery(t, filter, verifyFn)
|
|
|
|
urlCriterion.Modifier = models.CriterionModifierNotEquals
|
|
verifyGroupQuery(t, filter, verifyFn)
|
|
|
|
urlCriterion.Modifier = models.CriterionModifierMatchesRegex
|
|
urlCriterion.Value = "group_.*1_URL"
|
|
verifyGroupQuery(t, filter, verifyFn)
|
|
|
|
urlCriterion.Modifier = models.CriterionModifierNotMatchesRegex
|
|
verifyGroupQuery(t, filter, verifyFn)
|
|
|
|
urlCriterion.Modifier = models.CriterionModifierIsNull
|
|
urlCriterion.Value = ""
|
|
verifyGroupQuery(t, filter, verifyFn)
|
|
|
|
urlCriterion.Modifier = models.CriterionModifierNotNull
|
|
verifyGroupQuery(t, filter, verifyFn)
|
|
}
|
|
|
|
func TestGroupQueryURLExcludes(t *testing.T) {
|
|
withRollbackTxn(func(ctx context.Context) error {
|
|
mqb := db.Group
|
|
|
|
// create group with two URLs
|
|
group := models.Group{
|
|
Name: "TestGroupQueryURLExcludes",
|
|
URLs: models.NewRelatedStrings([]string{
|
|
"aaa",
|
|
"bbb",
|
|
}),
|
|
}
|
|
|
|
err := mqb.Create(ctx, &group)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating group: %w", err)
|
|
}
|
|
|
|
// query for groups that exclude the URL "aaa"
|
|
urlCriterion := models.StringCriterionInput{
|
|
Value: "aaa",
|
|
Modifier: models.CriterionModifierExcludes,
|
|
}
|
|
|
|
nameCriterion := models.StringCriterionInput{
|
|
Value: group.Name,
|
|
Modifier: models.CriterionModifierEquals,
|
|
}
|
|
|
|
filter := models.GroupFilterType{
|
|
URL: &urlCriterion,
|
|
Name: &nameCriterion,
|
|
}
|
|
|
|
groups := queryGroups(ctx, t, &filter, nil)
|
|
assert.Len(t, groups, 0, "Expected no groups to be found")
|
|
|
|
// query for groups that exclude the URL "ccc"
|
|
urlCriterion.Value = "ccc"
|
|
groups = queryGroups(ctx, t, &filter, nil)
|
|
|
|
if assert.Len(t, groups, 1, "Expected one group to be found") {
|
|
assert.Equal(t, group.Name, groups[0].Name)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func verifyGroupQuery(t *testing.T, filter models.GroupFilterType, verifyFn func(s *models.Group)) {
|
|
withTxn(func(ctx context.Context) error {
|
|
t.Helper()
|
|
sqb := db.Group
|
|
|
|
groups := queryGroups(ctx, t, &filter, nil)
|
|
|
|
for _, group := range groups {
|
|
if err := group.LoadURLs(ctx, sqb); err != nil {
|
|
t.Errorf("Error loading group relationships: %v", err)
|
|
}
|
|
}
|
|
|
|
// assume it should find at least one
|
|
assert.Greater(t, len(groups), 0)
|
|
|
|
for _, m := range groups {
|
|
verifyFn(m)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func queryGroups(ctx context.Context, t *testing.T, groupFilter *models.GroupFilterType, findFilter *models.FindFilterType) []*models.Group {
|
|
sqb := db.Group
|
|
groups, _, err := sqb.Query(ctx, groupFilter, findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying group: %s", err.Error())
|
|
}
|
|
|
|
return groups
|
|
}
|
|
|
|
func TestGroupQueryTags(t *testing.T) {
|
|
withTxn(func(ctx context.Context) error {
|
|
tagCriterion := models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdxWithGroup]),
|
|
strconv.Itoa(tagIDs[tagIdx1WithGroup]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludes,
|
|
}
|
|
|
|
groupFilter := models.GroupFilterType{
|
|
Tags: &tagCriterion,
|
|
}
|
|
|
|
// ensure ids are correct
|
|
groups := queryGroups(ctx, t, &groupFilter, nil)
|
|
assert.Len(t, groups, 3)
|
|
for _, group := range groups {
|
|
assert.True(t, group.ID == groupIDs[groupIdxWithTag] || group.ID == groupIDs[groupIdxWithTwoTags] || group.ID == groupIDs[groupIdxWithThreeTags])
|
|
}
|
|
|
|
tagCriterion = models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdx1WithGroup]),
|
|
strconv.Itoa(tagIDs[tagIdx2WithGroup]),
|
|
},
|
|
Modifier: models.CriterionModifierIncludesAll,
|
|
}
|
|
|
|
groups = queryGroups(ctx, t, &groupFilter, nil)
|
|
|
|
if assert.Len(t, groups, 2) {
|
|
assert.Equal(t, sceneIDs[groupIdxWithTwoTags], groups[0].ID)
|
|
assert.Equal(t, sceneIDs[groupIdxWithThreeTags], groups[1].ID)
|
|
}
|
|
|
|
tagCriterion = models.HierarchicalMultiCriterionInput{
|
|
Value: []string{
|
|
strconv.Itoa(tagIDs[tagIdx1WithGroup]),
|
|
},
|
|
Modifier: models.CriterionModifierExcludes,
|
|
}
|
|
|
|
q := getSceneStringValue(groupIdxWithTwoTags, titleField)
|
|
findFilter := models.FindFilterType{
|
|
Q: &q,
|
|
}
|
|
|
|
groups = queryGroups(ctx, t, &groupFilter, &findFilter)
|
|
assert.Len(t, groups, 0)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestGroupQueryTagCount(t *testing.T) {
|
|
const tagCount = 1
|
|
tagCountCriterion := models.IntCriterionInput{
|
|
Value: tagCount,
|
|
Modifier: models.CriterionModifierEquals,
|
|
}
|
|
|
|
verifyGroupsTagCount(t, tagCountCriterion)
|
|
|
|
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
|
|
verifyGroupsTagCount(t, tagCountCriterion)
|
|
|
|
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
|
verifyGroupsTagCount(t, tagCountCriterion)
|
|
|
|
tagCountCriterion.Modifier = models.CriterionModifierLessThan
|
|
verifyGroupsTagCount(t, tagCountCriterion)
|
|
}
|
|
|
|
func verifyGroupsTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
|
|
withTxn(func(ctx context.Context) error {
|
|
sqb := db.Group
|
|
groupFilter := models.GroupFilterType{
|
|
TagCount: &tagCountCriterion,
|
|
}
|
|
|
|
groups := queryGroups(ctx, t, &groupFilter, nil)
|
|
assert.Greater(t, len(groups), 0)
|
|
|
|
for _, group := range groups {
|
|
ids, err := sqb.GetTagIDs(ctx, group.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
verifyInt(t, len(ids), tagCountCriterion)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestGroupQuerySorting(t *testing.T) {
|
|
sort := "scenes_count"
|
|
direction := models.SortDirectionEnumDesc
|
|
findFilter := models.FindFilterType{
|
|
Sort: &sort,
|
|
Direction: &direction,
|
|
}
|
|
|
|
withTxn(func(ctx context.Context) error {
|
|
groups := queryGroups(ctx, t, nil, &findFilter)
|
|
|
|
// scenes should be in same order as indexes
|
|
firstGroup := groups[0]
|
|
|
|
assert.Equal(t, groupIDs[groupIdxWithScene], firstGroup.ID)
|
|
|
|
// sort in descending order
|
|
direction = models.SortDirectionEnumAsc
|
|
|
|
groups = queryGroups(ctx, t, nil, &findFilter)
|
|
lastGroup := groups[len(groups)-1]
|
|
|
|
assert.Equal(t, groupIDs[groupIdxWithParentAndScene], lastGroup.ID)
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestGroupQuerySortOrderIndex(t *testing.T) {
|
|
sort := "sub_group_order"
|
|
direction := models.SortDirectionEnumDesc
|
|
findFilter := models.FindFilterType{
|
|
Sort: &sort,
|
|
Direction: &direction,
|
|
}
|
|
|
|
groupFilter := models.GroupFilterType{
|
|
ContainingGroups: &models.HierarchicalMultiCriterionInput{
|
|
Value: intslice.IntSliceToStringSlice([]int{groupIdxWithChild}),
|
|
Modifier: models.CriterionModifierIncludes,
|
|
},
|
|
}
|
|
|
|
withTxn(func(ctx context.Context) error {
|
|
// just ensure there are no errors
|
|
_, _, err := db.Group.Query(ctx, &groupFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying group: %s", err.Error())
|
|
}
|
|
|
|
_, _, err = db.Group.Query(ctx, nil, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("Error querying group: %s", err.Error())
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func TestGroupUpdateFrontImage(t *testing.T) {
|
|
if err := withRollbackTxn(func(ctx context.Context) error {
|
|
qb := db.Group
|
|
|
|
// create group to test against
|
|
const name = "TestGroupUpdateGroupImages"
|
|
group := models.Group{
|
|
Name: name,
|
|
}
|
|
err := qb.Create(ctx, &group)
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating group: %s", err.Error())
|
|
}
|
|
|
|
return testUpdateImage(t, ctx, group.ID, qb.UpdateFrontImage, qb.GetFrontImage)
|
|
}); err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
}
|
|
|
|
func TestGroupUpdateBackImage(t *testing.T) {
|
|
if err := withRollbackTxn(func(ctx context.Context) error {
|
|
qb := db.Group
|
|
|
|
// create group to test against
|
|
const name = "TestGroupUpdateGroupImages"
|
|
group := models.Group{
|
|
Name: name,
|
|
}
|
|
err := qb.Create(ctx, &group)
|
|
if err != nil {
|
|
return fmt.Errorf("Error creating group: %s", err.Error())
|
|
}
|
|
|
|
return testUpdateImage(t, ctx, group.ID, qb.UpdateBackImage, qb.GetBackImage)
|
|
}); err != nil {
|
|
t.Error(err.Error())
|
|
}
|
|
}
|
|
|
|
func TestGroupQueryContainingGroups(t *testing.T) {
|
|
const nameField = "Name"
|
|
|
|
type criterion struct {
|
|
valueIdxs []int
|
|
modifier models.CriterionModifier
|
|
depth int
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
c criterion
|
|
q string
|
|
includeIdxs []int
|
|
}{
|
|
{
|
|
"includes",
|
|
criterion{
|
|
[]int{groupIdxWithChild},
|
|
models.CriterionModifierIncludes,
|
|
0,
|
|
},
|
|
"",
|
|
[]int{groupIdxWithParent},
|
|
},
|
|
{
|
|
"excludes",
|
|
criterion{
|
|
[]int{groupIdxWithChild},
|
|
models.CriterionModifierExcludes,
|
|
0,
|
|
},
|
|
getGroupStringValue(groupIdxWithParent, nameField),
|
|
nil,
|
|
},
|
|
{
|
|
"includes (all levels)",
|
|
criterion{
|
|
[]int{groupIdxWithGrandChild},
|
|
models.CriterionModifierIncludes,
|
|
-1,
|
|
},
|
|
"",
|
|
[]int{groupIdxWithParentAndChild, groupIdxWithGrandParent},
|
|
},
|
|
{
|
|
"includes (1 level)",
|
|
criterion{
|
|
[]int{groupIdxWithGrandChild},
|
|
models.CriterionModifierIncludes,
|
|
1,
|
|
},
|
|
"",
|
|
[]int{groupIdxWithParentAndChild, groupIdxWithGrandParent},
|
|
},
|
|
{
|
|
"is null",
|
|
criterion{
|
|
nil,
|
|
models.CriterionModifierIsNull,
|
|
0,
|
|
},
|
|
getGroupStringValue(groupIdxWithParent, nameField),
|
|
nil,
|
|
},
|
|
{
|
|
"not null",
|
|
criterion{
|
|
nil,
|
|
models.CriterionModifierNotNull,
|
|
0,
|
|
},
|
|
"",
|
|
[]int{groupIdxWithParentAndChild, groupIdxWithParent, groupIdxWithGrandParent, groupIdxWithParentAndScene},
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
|
|
for _, tt := range tests {
|
|
valueIDs := indexesToIDs(groupIDs, tt.c.valueIdxs)
|
|
expectedIDs := indexesToIDs(groupIDs, tt.includeIdxs)
|
|
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
groupFilter := &models.GroupFilterType{
|
|
ContainingGroups: &models.HierarchicalMultiCriterionInput{
|
|
Value: intslice.IntSliceToStringSlice(valueIDs),
|
|
Modifier: tt.c.modifier,
|
|
},
|
|
}
|
|
|
|
if tt.c.depth != 0 {
|
|
groupFilter.ContainingGroups.Depth = &tt.c.depth
|
|
}
|
|
|
|
findFilter := models.FindFilterType{}
|
|
if tt.q != "" {
|
|
findFilter.Q = &tt.q
|
|
}
|
|
|
|
groups, _, err := qb.Query(ctx, groupFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("GroupStore.Query() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// get ids of groups
|
|
groupIDs := sliceutil.Map(groups, func(g *models.Group) int { return g.ID })
|
|
assert.ElementsMatch(t, expectedIDs, groupIDs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupQuerySubGroups(t *testing.T) {
|
|
const nameField = "Name"
|
|
|
|
type criterion struct {
|
|
valueIdxs []int
|
|
modifier models.CriterionModifier
|
|
depth int
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
c criterion
|
|
q string
|
|
expectedIdxs []int
|
|
}{
|
|
{
|
|
"includes",
|
|
criterion{
|
|
[]int{groupIdxWithParent},
|
|
models.CriterionModifierIncludes,
|
|
0,
|
|
},
|
|
"",
|
|
[]int{groupIdxWithChild},
|
|
},
|
|
{
|
|
"excludes",
|
|
criterion{
|
|
[]int{groupIdxWithParent},
|
|
models.CriterionModifierExcludes,
|
|
0,
|
|
},
|
|
getGroupStringValue(groupIdxWithChild, nameField),
|
|
nil,
|
|
},
|
|
{
|
|
"includes (all levels)",
|
|
criterion{
|
|
[]int{groupIdxWithGrandParent},
|
|
models.CriterionModifierIncludes,
|
|
-1,
|
|
},
|
|
"",
|
|
[]int{groupIdxWithGrandChild, groupIdxWithParentAndChild},
|
|
},
|
|
{
|
|
"includes (1 level)",
|
|
criterion{
|
|
[]int{groupIdxWithGrandParent},
|
|
models.CriterionModifierIncludes,
|
|
1,
|
|
},
|
|
"",
|
|
[]int{groupIdxWithGrandChild, groupIdxWithParentAndChild},
|
|
},
|
|
{
|
|
"is null",
|
|
criterion{
|
|
nil,
|
|
models.CriterionModifierIsNull,
|
|
0,
|
|
},
|
|
getGroupStringValue(groupIdxWithChild, nameField),
|
|
nil,
|
|
},
|
|
{
|
|
"not null",
|
|
criterion{
|
|
nil,
|
|
models.CriterionModifierNotNull,
|
|
0,
|
|
},
|
|
"",
|
|
[]int{groupIdxWithGrandChild, groupIdxWithChild, groupIdxWithParentAndChild, groupIdxWithChildWithScene},
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
|
|
for _, tt := range tests {
|
|
valueIDs := indexesToIDs(groupIDs, tt.c.valueIdxs)
|
|
expectedIDs := indexesToIDs(groupIDs, tt.expectedIdxs)
|
|
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
groupFilter := &models.GroupFilterType{
|
|
SubGroups: &models.HierarchicalMultiCriterionInput{
|
|
Value: intslice.IntSliceToStringSlice(valueIDs),
|
|
Modifier: tt.c.modifier,
|
|
},
|
|
}
|
|
|
|
if tt.c.depth != 0 {
|
|
groupFilter.SubGroups.Depth = &tt.c.depth
|
|
}
|
|
|
|
findFilter := models.FindFilterType{}
|
|
if tt.q != "" {
|
|
findFilter.Q = &tt.q
|
|
}
|
|
|
|
groups, _, err := qb.Query(ctx, groupFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("GroupStore.Query() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// get ids of groups
|
|
groupIDs := sliceutil.Map(groups, func(g *models.Group) int { return g.ID })
|
|
assert.ElementsMatch(t, expectedIDs, groupIDs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupQueryContainingGroupCount(t *testing.T) {
|
|
const nameField = "Name"
|
|
|
|
tests := []struct {
|
|
name string
|
|
value int
|
|
modifier models.CriterionModifier
|
|
q string
|
|
expectedIdxs []int
|
|
}{
|
|
{
|
|
"equals",
|
|
1,
|
|
models.CriterionModifierEquals,
|
|
"",
|
|
[]int{groupIdxWithParent, groupIdxWithGrandParent, groupIdxWithParentAndChild, groupIdxWithParentAndScene},
|
|
},
|
|
{
|
|
"not equals",
|
|
1,
|
|
models.CriterionModifierNotEquals,
|
|
getGroupStringValue(groupIdxWithParent, nameField),
|
|
nil,
|
|
},
|
|
{
|
|
"less than",
|
|
1,
|
|
models.CriterionModifierLessThan,
|
|
getGroupStringValue(groupIdxWithParent, nameField),
|
|
nil,
|
|
},
|
|
{
|
|
"greater than",
|
|
0,
|
|
models.CriterionModifierGreaterThan,
|
|
"",
|
|
[]int{groupIdxWithParent, groupIdxWithGrandParent, groupIdxWithParentAndChild, groupIdxWithParentAndScene},
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
|
|
for _, tt := range tests {
|
|
expectedIDs := indexesToIDs(groupIDs, tt.expectedIdxs)
|
|
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
groupFilter := &models.GroupFilterType{
|
|
ContainingGroupCount: &models.IntCriterionInput{
|
|
Value: tt.value,
|
|
Modifier: tt.modifier,
|
|
},
|
|
}
|
|
|
|
findFilter := models.FindFilterType{}
|
|
if tt.q != "" {
|
|
findFilter.Q = &tt.q
|
|
}
|
|
|
|
groups, _, err := qb.Query(ctx, groupFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("GroupStore.Query() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// get ids of groups
|
|
groupIDs := sliceutil.Map(groups, func(g *models.Group) int { return g.ID })
|
|
assert.ElementsMatch(t, expectedIDs, groupIDs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupQuerySubGroupCount(t *testing.T) {
|
|
const nameField = "Name"
|
|
|
|
tests := []struct {
|
|
name string
|
|
value int
|
|
modifier models.CriterionModifier
|
|
q string
|
|
expectedIdxs []int
|
|
}{
|
|
{
|
|
"equals",
|
|
1,
|
|
models.CriterionModifierEquals,
|
|
"",
|
|
[]int{groupIdxWithChild, groupIdxWithGrandChild, groupIdxWithParentAndChild, groupIdxWithChildWithScene},
|
|
},
|
|
{
|
|
"not equals",
|
|
1,
|
|
models.CriterionModifierNotEquals,
|
|
getGroupStringValue(groupIdxWithChild, nameField),
|
|
nil,
|
|
},
|
|
{
|
|
"less than",
|
|
1,
|
|
models.CriterionModifierLessThan,
|
|
getGroupStringValue(groupIdxWithChild, nameField),
|
|
nil,
|
|
},
|
|
{
|
|
"greater than",
|
|
0,
|
|
models.CriterionModifierGreaterThan,
|
|
"",
|
|
[]int{groupIdxWithChild, groupIdxWithGrandChild, groupIdxWithParentAndChild, groupIdxWithChildWithScene},
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
|
|
for _, tt := range tests {
|
|
expectedIDs := indexesToIDs(groupIDs, tt.expectedIdxs)
|
|
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
groupFilter := &models.GroupFilterType{
|
|
SubGroupCount: &models.IntCriterionInput{
|
|
Value: tt.value,
|
|
Modifier: tt.modifier,
|
|
},
|
|
}
|
|
|
|
findFilter := models.FindFilterType{}
|
|
if tt.q != "" {
|
|
findFilter.Q = &tt.q
|
|
}
|
|
|
|
groups, _, err := qb.Query(ctx, groupFilter, &findFilter)
|
|
if err != nil {
|
|
t.Errorf("GroupStore.Query() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// get ids of groups
|
|
groupIDs := sliceutil.Map(groups, func(g *models.Group) int { return g.ID })
|
|
assert.ElementsMatch(t, expectedIDs, groupIDs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupFindInAncestors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
ancestorIdxs []int
|
|
idxs []int
|
|
expectedIdxs []int
|
|
}{
|
|
{
|
|
"basic",
|
|
[]int{groupIdxWithGrandParent},
|
|
[]int{groupIdxWithGrandChild},
|
|
[]int{groupIdxWithGrandChild},
|
|
},
|
|
{
|
|
"same",
|
|
[]int{groupIdxWithScene},
|
|
[]int{groupIdxWithScene},
|
|
[]int{groupIdxWithScene},
|
|
},
|
|
{
|
|
"no matches",
|
|
[]int{groupIdxWithGrandParent},
|
|
[]int{groupIdxWithScene},
|
|
nil,
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
|
|
for _, tt := range tests {
|
|
ancestorIDs := indexesToIDs(groupIDs, tt.ancestorIdxs)
|
|
ids := indexesToIDs(groupIDs, tt.idxs)
|
|
expectedIDs := indexesToIDs(groupIDs, tt.expectedIdxs)
|
|
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
found, err := qb.FindInAncestors(ctx, ancestorIDs, ids)
|
|
if err != nil {
|
|
t.Errorf("GroupStore.FindInAncestors() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// get ids of groups
|
|
assert.ElementsMatch(t, found, expectedIDs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupReorderSubGroups(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
subGroupLen int
|
|
idxsToMove []int
|
|
insertLoc int
|
|
insertAfter bool
|
|
// order of elements, using original indexes
|
|
expectedIdxs []int
|
|
}{
|
|
{
|
|
"move single back before",
|
|
5,
|
|
[]int{2},
|
|
1,
|
|
false,
|
|
[]int{0, 2, 1, 3, 4},
|
|
},
|
|
{
|
|
"move single forward before",
|
|
5,
|
|
[]int{2},
|
|
4,
|
|
false,
|
|
[]int{0, 1, 3, 2, 4},
|
|
},
|
|
{
|
|
"move multiple back before",
|
|
5,
|
|
[]int{3, 2, 4},
|
|
0,
|
|
false,
|
|
[]int{3, 2, 4, 0, 1},
|
|
},
|
|
{
|
|
"move multiple forward before",
|
|
5,
|
|
[]int{2, 1, 0},
|
|
4,
|
|
false,
|
|
[]int{3, 2, 1, 0, 4},
|
|
},
|
|
{
|
|
"move single back after",
|
|
5,
|
|
[]int{2},
|
|
0,
|
|
true,
|
|
[]int{0, 2, 1, 3, 4},
|
|
},
|
|
{
|
|
"move single forward after",
|
|
5,
|
|
[]int{2},
|
|
4,
|
|
true,
|
|
[]int{0, 1, 3, 4, 2},
|
|
},
|
|
{
|
|
"move multiple back after",
|
|
5,
|
|
[]int{3, 2, 4},
|
|
0,
|
|
false,
|
|
[]int{0, 3, 2, 4, 1},
|
|
},
|
|
{
|
|
"move multiple forward after",
|
|
5,
|
|
[]int{2, 1, 0},
|
|
4,
|
|
false,
|
|
[]int{3, 4, 2, 1, 0},
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
// create the group
|
|
group := models.Group{
|
|
Name: "TestGroupReorderSubGroups",
|
|
}
|
|
|
|
if err := qb.Create(ctx, &group); err != nil {
|
|
t.Errorf("GroupStore.Create() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// and sub-groups
|
|
idxToId := make([]int, tt.subGroupLen)
|
|
|
|
for i := 0; i < tt.subGroupLen; i++ {
|
|
subGroup := models.Group{
|
|
Name: fmt.Sprintf("SubGroup %d", i),
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: group.ID},
|
|
}),
|
|
}
|
|
|
|
if err := qb.Create(ctx, &subGroup); err != nil {
|
|
t.Errorf("GroupStore.Create() error = %v", err)
|
|
return
|
|
}
|
|
|
|
idxToId[i] = subGroup.ID
|
|
}
|
|
|
|
// reorder
|
|
idsToMove := indexesToIDs(idxToId, tt.idxsToMove)
|
|
insertID := idxToId[tt.insertLoc]
|
|
if err := qb.ReorderSubGroups(ctx, group.ID, idsToMove, insertID, tt.insertAfter); err != nil {
|
|
t.Errorf("GroupStore.ReorderSubGroups() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// validate the new order
|
|
gd, err := qb.GetSubGroupDescriptions(ctx, group.ID)
|
|
if err != nil {
|
|
t.Errorf("GroupStore.GetSubGroupDescriptions() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// get ids of groups
|
|
newIDs := sliceutil.Map(gd, func(gd models.GroupIDDescription) int { return gd.GroupID })
|
|
newIdxs := sliceutil.Map(newIDs, func(id int) int { return slices.Index(idxToId, id) })
|
|
|
|
assert.ElementsMatch(t, tt.expectedIdxs, newIdxs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupAddSubGroups(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
existingSubGroupLen int
|
|
insertGroupsLen int
|
|
insertLoc int
|
|
// order of elements, using original indexes
|
|
expectedIdxs []int
|
|
}{
|
|
{
|
|
"append single",
|
|
4,
|
|
1,
|
|
999,
|
|
[]int{0, 1, 2, 3, 4},
|
|
},
|
|
{
|
|
"insert single middle",
|
|
4,
|
|
1,
|
|
2,
|
|
[]int{0, 1, 4, 2, 3},
|
|
},
|
|
{
|
|
"insert single start",
|
|
4,
|
|
1,
|
|
0,
|
|
[]int{4, 0, 1, 2, 3},
|
|
},
|
|
{
|
|
"append multiple",
|
|
4,
|
|
2,
|
|
999,
|
|
[]int{0, 1, 2, 3, 4, 5},
|
|
},
|
|
{
|
|
"insert multiple middle",
|
|
4,
|
|
2,
|
|
2,
|
|
[]int{0, 1, 4, 5, 2, 3},
|
|
},
|
|
{
|
|
"insert multiple start",
|
|
4,
|
|
2,
|
|
0,
|
|
[]int{4, 5, 0, 1, 2, 3},
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
// create the group
|
|
group := models.Group{
|
|
Name: "TestGroupReorderSubGroups",
|
|
}
|
|
|
|
if err := qb.Create(ctx, &group); err != nil {
|
|
t.Errorf("GroupStore.Create() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// and sub-groups
|
|
idxToId := make([]int, tt.existingSubGroupLen+tt.insertGroupsLen)
|
|
|
|
for i := 0; i < tt.existingSubGroupLen; i++ {
|
|
subGroup := models.Group{
|
|
Name: fmt.Sprintf("Existing SubGroup %d", i),
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: group.ID},
|
|
}),
|
|
}
|
|
|
|
if err := qb.Create(ctx, &subGroup); err != nil {
|
|
t.Errorf("GroupStore.Create() error = %v", err)
|
|
return
|
|
}
|
|
|
|
idxToId[i] = subGroup.ID
|
|
}
|
|
|
|
// and sub-groups to insert
|
|
for i := 0; i < tt.insertGroupsLen; i++ {
|
|
subGroup := models.Group{
|
|
Name: fmt.Sprintf("Inserted SubGroup %d", i),
|
|
}
|
|
|
|
if err := qb.Create(ctx, &subGroup); err != nil {
|
|
t.Errorf("GroupStore.Create() error = %v", err)
|
|
return
|
|
}
|
|
|
|
idxToId[i+tt.existingSubGroupLen] = subGroup.ID
|
|
}
|
|
|
|
// convert ids to description
|
|
idDescriptions := make([]models.GroupIDDescription, tt.insertGroupsLen)
|
|
for i, id := range idxToId[tt.existingSubGroupLen:] {
|
|
idDescriptions[i] = models.GroupIDDescription{GroupID: id}
|
|
}
|
|
|
|
// add
|
|
if err := qb.AddSubGroups(ctx, group.ID, idDescriptions, &tt.insertLoc); err != nil {
|
|
t.Errorf("GroupStore.AddSubGroups() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// validate the new order
|
|
gd, err := qb.GetSubGroupDescriptions(ctx, group.ID)
|
|
if err != nil {
|
|
t.Errorf("GroupStore.GetSubGroupDescriptions() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// get ids of groups
|
|
newIDs := sliceutil.Map(gd, func(gd models.GroupIDDescription) int { return gd.GroupID })
|
|
newIdxs := sliceutil.Map(newIDs, func(id int) int { return slices.Index(idxToId, id) })
|
|
|
|
assert.ElementsMatch(t, tt.expectedIdxs, newIdxs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupRemoveSubGroups(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
subGroupLen int
|
|
removeIdxs []int
|
|
// order of elements, using original indexes
|
|
expectedIdxs []int
|
|
}{
|
|
{
|
|
"remove last",
|
|
4,
|
|
[]int{3},
|
|
[]int{0, 1, 2},
|
|
},
|
|
{
|
|
"remove first",
|
|
4,
|
|
[]int{0},
|
|
[]int{1, 2, 3},
|
|
},
|
|
{
|
|
"remove middle",
|
|
4,
|
|
[]int{2},
|
|
[]int{0, 1, 3},
|
|
},
|
|
{
|
|
"remove multiple",
|
|
4,
|
|
[]int{1, 3},
|
|
[]int{0, 2},
|
|
},
|
|
{
|
|
"remove all",
|
|
4,
|
|
[]int{0, 1, 2, 3},
|
|
[]int{},
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
// create the group
|
|
group := models.Group{
|
|
Name: "TestGroupReorderSubGroups",
|
|
}
|
|
|
|
if err := qb.Create(ctx, &group); err != nil {
|
|
t.Errorf("GroupStore.Create() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// and sub-groups
|
|
idxToId := make([]int, tt.subGroupLen)
|
|
|
|
for i := 0; i < tt.subGroupLen; i++ {
|
|
subGroup := models.Group{
|
|
Name: fmt.Sprintf("Existing SubGroup %d", i),
|
|
ContainingGroups: models.NewRelatedGroupDescriptions([]models.GroupIDDescription{
|
|
{GroupID: group.ID},
|
|
}),
|
|
}
|
|
|
|
if err := qb.Create(ctx, &subGroup); err != nil {
|
|
t.Errorf("GroupStore.Create() error = %v", err)
|
|
return
|
|
}
|
|
|
|
idxToId[i] = subGroup.ID
|
|
}
|
|
|
|
idsToRemove := indexesToIDs(idxToId, tt.removeIdxs)
|
|
if err := qb.RemoveSubGroups(ctx, group.ID, idsToRemove); err != nil {
|
|
t.Errorf("GroupStore.RemoveSubGroups() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// validate the new order
|
|
gd, err := qb.GetSubGroupDescriptions(ctx, group.ID)
|
|
if err != nil {
|
|
t.Errorf("GroupStore.GetSubGroupDescriptions() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// get ids of groups
|
|
newIDs := sliceutil.Map(gd, func(gd models.GroupIDDescription) int { return gd.GroupID })
|
|
newIdxs := sliceutil.Map(newIDs, func(id int) int { return slices.Index(idxToId, id) })
|
|
|
|
assert.ElementsMatch(t, tt.expectedIdxs, newIdxs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupFindSubGroupIDs(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
containingGroupIdx int
|
|
subIdxs []int
|
|
expectedIdxs []int
|
|
}{
|
|
{
|
|
"overlap",
|
|
groupIdxWithGrandChild,
|
|
[]int{groupIdxWithParentAndChild, groupIdxWithGrandParent},
|
|
[]int{groupIdxWithParentAndChild},
|
|
},
|
|
{
|
|
"non-overlap",
|
|
groupIdxWithGrandChild,
|
|
[]int{groupIdxWithGrandParent},
|
|
[]int{},
|
|
},
|
|
{
|
|
"none",
|
|
groupIdxWithScene,
|
|
[]int{groupIdxWithDupName},
|
|
[]int{},
|
|
},
|
|
{
|
|
"invalid",
|
|
invalidID,
|
|
[]int{invalidID},
|
|
[]int{},
|
|
},
|
|
}
|
|
|
|
qb := db.Group
|
|
|
|
for _, tt := range tests {
|
|
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
|
|
subIDs := indexesToIDs(groupIDs, tt.subIdxs)
|
|
|
|
id := indexToID(groupIDs, tt.containingGroupIdx)
|
|
|
|
found, err := qb.FindSubGroupIDs(ctx, id, subIDs)
|
|
if err != nil {
|
|
t.Errorf("GroupStore.FindSubGroupIDs() error = %v", err)
|
|
return
|
|
}
|
|
|
|
// get ids of groups
|
|
foundIdxs := sliceutil.Map(found, func(id int) int { return slices.Index(groupIDs, id) })
|
|
|
|
assert.ElementsMatch(t, tt.expectedIdxs, foundIdxs)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TODO Update
|
|
// TODO Destroy - ensure image is destroyed
|
|
// TODO Find
|
|
// TODO Count
|
|
// TODO All
|
|
// TODO Query
|