Movie/Group tags (#4969)

* Combine common tag control code into hook
* Combine common scraped tag row code into hook
This commit is contained in:
WithoutPants
2024-06-18 11:24:15 +10:00
committed by GitHub
parent f9a624b803
commit fda4776d30
63 changed files with 1586 additions and 450 deletions

View File

@@ -9,6 +9,7 @@ import (
"strconv"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
@@ -17,7 +18,12 @@ import (
func loadMovieRelationships(ctx context.Context, expected models.Movie, actual *models.Movie) error {
if expected.URLs.Loaded() {
if err := actual.LoadURLs(ctx, db.Gallery); err != nil {
if err := actual.LoadURLs(ctx, db.Movie); err != nil {
return err
}
}
if expected.TagIDs.Loaded() {
if err := actual.LoadTagIDs(ctx, db.Movie); err != nil {
return err
}
}
@@ -25,6 +31,337 @@ func loadMovieRelationships(ctx context.Context, expected models.Movie, actual *
return nil
}
func Test_MovieStore_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")
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.Movie
wantErr bool
}{
{
"full",
models.Movie{
Name: name,
Duration: &duration,
Date: &date,
Rating: &rating,
StudioID: &studioIDs[studioIdxWithMovie],
Director: director,
Synopsis: synopsis,
URLs: models.NewRelatedStrings([]string{url}),
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithMovie]}),
Aliases: aliases,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
},
false,
},
{
"invalid tag id",
models.Movie{
Name: name,
TagIDs: models.NewRelatedIDs([]int{invalidID}),
},
true,
},
}
qb := db.Movie
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("MovieStore.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 := loadMovieRelationships(ctx, copy, &p); err != nil {
t.Errorf("loadMovieRelationships() error = %v", err)
return
}
assert.Equal(copy, p)
// ensure can find the movie
found, err := qb.Find(ctx, p.ID)
if err != nil {
t.Errorf("MovieStore.Find() error = %v", err)
}
if !assert.NotNil(found) {
return
}
// load relationships
if err := loadMovieRelationships(ctx, copy, found); err != nil {
t.Errorf("loadMovieRelationships() error = %v", err)
return
}
assert.Equal(copy, *found)
return
})
}
}
func Test_movieQueryBuilder_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")
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.Movie
wantErr bool
}{
{
"full",
&models.Movie{
ID: movieIDs[movieIdxWithTag],
Name: name,
Duration: &duration,
Date: &date,
Rating: &rating,
StudioID: &studioIDs[studioIdxWithMovie],
Director: director,
Synopsis: synopsis,
URLs: models.NewRelatedStrings([]string{url}),
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithMovie]}),
Aliases: aliases,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
},
false,
},
{
"clear tag ids",
&models.Movie{
ID: movieIDs[movieIdxWithTag],
Name: name,
TagIDs: models.NewRelatedIDs([]int{}),
},
false,
},
{
"invalid studio id",
&models.Movie{
ID: movieIDs[movieIdxWithScene],
Name: name,
StudioID: &invalidID,
},
true,
},
{
"invalid tag id",
&models.Movie{
ID: movieIDs[movieIdxWithScene],
Name: name,
TagIDs: models.NewRelatedIDs([]int{invalidID}),
},
true,
},
}
qb := db.Movie
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("movieQueryBuilder.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("movieQueryBuilder.Find() error = %v", err)
}
// load relationships
if err := loadMovieRelationships(ctx, copy, s); err != nil {
t.Errorf("loadMovieRelationships() error = %v", err)
return
}
assert.Equal(copy, *s)
})
}
}
func clearMoviePartial() models.MoviePartial {
// leave mandatory fields
return models.MoviePartial{
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},
}
}
func Test_movieQueryBuilder_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")
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.MoviePartial
want models.Movie
wantErr bool
}{
{
"full",
movieIDs[movieIdxWithScene],
models.MoviePartial{
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[studioIdxWithMovie]),
CreatedAt: models.NewOptionalTime(createdAt),
UpdatedAt: models.NewOptionalTime(updatedAt),
TagIDs: &models.UpdateIDs{
IDs: []int{tagIDs[tagIdx1WithMovie], tagIDs[tagIdx1WithDupName]},
Mode: models.RelationshipUpdateModeSet,
},
},
models.Movie{
ID: movieIDs[movieIdxWithScene],
Name: name,
Director: director,
Synopsis: synopsis,
Aliases: aliases,
URLs: models.NewRelatedStrings([]string{url}),
Date: &date,
Duration: &duration,
Rating: &rating,
StudioID: &studioIDs[studioIdxWithMovie],
CreatedAt: createdAt,
UpdatedAt: updatedAt,
TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithMovie]}),
},
false,
},
{
"clear all",
movieIDs[movieIdxWithScene],
clearMoviePartial(),
models.Movie{
ID: movieIDs[movieIdxWithScene],
Name: movieNames[movieIdxWithScene],
TagIDs: models.NewRelatedIDs([]int{}),
},
false,
},
{
"invalid id",
invalidID,
models.MoviePartial{},
models.Movie{},
true,
},
}
for _, tt := range tests {
qb := db.Movie
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("movieQueryBuilder.UpdatePartial() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr {
return
}
// load relationships
if err := loadMovieRelationships(ctx, tt.want, got); err != nil {
t.Errorf("loadMovieRelationships() error = %v", err)
return
}
assert.Equal(tt.want, *got)
s, err := qb.Find(ctx, tt.id)
if err != nil {
t.Errorf("movieQueryBuilder.Find() error = %v", err)
}
// load relationships
if err := loadMovieRelationships(ctx, tt.want, s); err != nil {
t.Errorf("loadMovieRelationships() error = %v", err)
return
}
assert.Equal(tt.want, *s)
})
}
}
func TestMovieFindByName(t *testing.T) {
withTxn(func(ctx context.Context) error {
mqb := db.Movie
@@ -280,12 +617,12 @@ func TestMovieQueryURLExcludes(t *testing.T) {
Name: &nameCriterion,
}
movies := queryMovie(ctx, t, mqb, &filter, nil)
movies := queryMovies(ctx, t, &filter, nil)
assert.Len(t, movies, 0, "Expected no movies to be found")
// query for movies that exclude the URL "ccc"
urlCriterion.Value = "ccc"
movies = queryMovie(ctx, t, mqb, &filter, nil)
movies = queryMovies(ctx, t, &filter, nil)
if assert.Len(t, movies, 1, "Expected one movie to be found") {
assert.Equal(t, movie.Name, movies[0].Name)
@@ -300,7 +637,7 @@ func verifyMovieQuery(t *testing.T, filter models.MovieFilterType, verifyFn func
t.Helper()
sqb := db.Movie
movies := queryMovie(ctx, t, sqb, &filter, nil)
movies := queryMovies(ctx, t, &filter, nil)
for _, movie := range movies {
if err := movie.LoadURLs(ctx, sqb); err != nil {
@@ -319,7 +656,8 @@ func verifyMovieQuery(t *testing.T, filter models.MovieFilterType, verifyFn func
})
}
func queryMovie(ctx context.Context, t *testing.T, sqb models.MovieReader, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) []*models.Movie {
func queryMovies(ctx context.Context, t *testing.T, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) []*models.Movie {
sqb := db.Movie
movies, _, err := sqb.Query(ctx, movieFilter, findFilter)
if err != nil {
t.Errorf("Error querying movie: %s", err.Error())
@@ -328,6 +666,102 @@ func queryMovie(ctx context.Context, t *testing.T, sqb models.MovieReader, movie
return movies
}
func TestMovieQueryTags(t *testing.T) {
withTxn(func(ctx context.Context) error {
tagCriterion := models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(tagIDs[tagIdxWithMovie]),
strconv.Itoa(tagIDs[tagIdx1WithMovie]),
},
Modifier: models.CriterionModifierIncludes,
}
movieFilter := models.MovieFilterType{
Tags: &tagCriterion,
}
// ensure ids are correct
movies := queryMovies(ctx, t, &movieFilter, nil)
assert.Len(t, movies, 3)
for _, movie := range movies {
assert.True(t, movie.ID == movieIDs[movieIdxWithTag] || movie.ID == movieIDs[movieIdxWithTwoTags] || movie.ID == movieIDs[movieIdxWithThreeTags])
}
tagCriterion = models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(tagIDs[tagIdx1WithMovie]),
strconv.Itoa(tagIDs[tagIdx2WithMovie]),
},
Modifier: models.CriterionModifierIncludesAll,
}
movies = queryMovies(ctx, t, &movieFilter, nil)
if assert.Len(t, movies, 2) {
assert.Equal(t, sceneIDs[movieIdxWithTwoTags], movies[0].ID)
assert.Equal(t, sceneIDs[movieIdxWithThreeTags], movies[1].ID)
}
tagCriterion = models.HierarchicalMultiCriterionInput{
Value: []string{
strconv.Itoa(tagIDs[tagIdx1WithMovie]),
},
Modifier: models.CriterionModifierExcludes,
}
q := getSceneStringValue(movieIdxWithTwoTags, titleField)
findFilter := models.FindFilterType{
Q: &q,
}
movies = queryMovies(ctx, t, &movieFilter, &findFilter)
assert.Len(t, movies, 0)
return nil
})
}
func TestMovieQueryTagCount(t *testing.T) {
const tagCount = 1
tagCountCriterion := models.IntCriterionInput{
Value: tagCount,
Modifier: models.CriterionModifierEquals,
}
verifyMoviesTagCount(t, tagCountCriterion)
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
verifyMoviesTagCount(t, tagCountCriterion)
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
verifyMoviesTagCount(t, tagCountCriterion)
tagCountCriterion.Modifier = models.CriterionModifierLessThan
verifyMoviesTagCount(t, tagCountCriterion)
}
func verifyMoviesTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
withTxn(func(ctx context.Context) error {
sqb := db.Movie
movieFilter := models.MovieFilterType{
TagCount: &tagCountCriterion,
}
movies := queryMovies(ctx, t, &movieFilter, nil)
assert.Greater(t, len(movies), 0)
for _, movie := range movies {
ids, err := sqb.GetTagIDs(ctx, movie.ID)
if err != nil {
return err
}
verifyInt(t, len(ids), tagCountCriterion)
}
return nil
})
}
func TestMovieQuerySorting(t *testing.T) {
sort := "scenes_count"
direction := models.SortDirectionEnumDesc
@@ -337,8 +771,7 @@ func TestMovieQuerySorting(t *testing.T) {
}
withTxn(func(ctx context.Context) error {
sqb := db.Movie
movies := queryMovie(ctx, t, sqb, nil, &findFilter)
movies := queryMovies(ctx, t, nil, &findFilter)
// scenes should be in same order as indexes
firstMovie := movies[0]
@@ -348,7 +781,7 @@ func TestMovieQuerySorting(t *testing.T) {
// sort in descending order
direction = models.SortDirectionEnumAsc
movies = queryMovie(ctx, t, sqb, nil, &findFilter)
movies = queryMovies(ctx, t, nil, &findFilter)
lastMovie := movies[len(movies)-1]
assert.Equal(t, movieIDs[movieIdxWithScene], lastMovie.ID)