package scene import ( "errors" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models/json" "github.com/stashapp/stash/pkg/models/jsonschema" "github.com/stashapp/stash/pkg/models/mocks" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "testing" "time" ) const ( sceneID = 1 noImageID = 2 errImageID = 3 studioID = 4 missingStudioID = 5 errStudioID = 6 customFieldsID = 7 noTagsID = 11 errTagsID = 12 noGroupsID = 13 errFindGroupID = 15 noMarkersID = 16 errMarkersID = 17 errFindPrimaryTagID = 18 errFindByMarkerID = 19 errCustomFieldsID = 20 ) var ( url = "url" title = "title" date = "2001-01-01" dateObj, _ = models.ParseDate(date) rating = 5 organized = true details = "details" ) var ( studioName = "studioName" // galleryChecksum = "galleryChecksum" validGroup1 = 1 validGroup2 = 2 invalidGroup = 3 group1Name = "group1Name" group2Name = "group2Name" group1Scene = 1 group2Scene = 2 ) var names = []string{ "name1", "name2", } var imageBytes = []byte("imageBytes") var stashID = models.StashID{ StashID: "StashID", Endpoint: "Endpoint", } const ( path = "path" imageBase64 = "aW1hZ2VCeXRlcw==" ) var ( createTime = time.Date(2001, 01, 01, 0, 0, 0, 0, time.UTC) updateTime = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC) ) var ( emptyCustomFields = make(map[string]interface{}) customFields = map[string]interface{}{ "customField1": "customValue1", } ) func createFullScene(id int) models.Scene { return models.Scene{ ID: id, Title: title, Date: &dateObj, Details: details, Rating: &rating, Organized: organized, URLs: models.NewRelatedStrings([]string{url}), Files: models.NewRelatedVideoFiles([]*models.VideoFile{ { BaseFile: &models.BaseFile{ Path: path, }, }, }), StashIDs: models.NewRelatedStashIDs([]models.StashID{ stashID, }), CreatedAt: createTime, UpdatedAt: updateTime, } } func createEmptyScene(id int) models.Scene { return models.Scene{ ID: id, Files: models.NewRelatedVideoFiles([]*models.VideoFile{ { BaseFile: &models.BaseFile{ Path: path, }, }, }), URLs: models.NewRelatedStrings([]string{}), StashIDs: models.NewRelatedStashIDs([]models.StashID{}), CreatedAt: createTime, UpdatedAt: updateTime, } } func createFullJSONScene(image string, customFields map[string]interface{}) *jsonschema.Scene { return &jsonschema.Scene{ Title: title, Files: []string{path}, Date: date, Details: details, Rating: rating, Organized: organized, URLs: []string{url}, CreatedAt: json.JSONTime{ Time: createTime, }, UpdatedAt: json.JSONTime{ Time: updateTime, }, Cover: image, StashIDs: []models.StashID{ stashID, }, CustomFields: customFields, } } func createEmptyJSONScene() *jsonschema.Scene { return &jsonschema.Scene{ URLs: []string{}, Files: []string{path}, CreatedAt: json.JSONTime{ Time: createTime, }, UpdatedAt: json.JSONTime{ Time: updateTime, }, CustomFields: emptyCustomFields, } } type basicTestScenario struct { input models.Scene customFields map[string]interface{} expected *jsonschema.Scene err bool } var scenarios = []basicTestScenario{ { createFullScene(sceneID), emptyCustomFields, createFullJSONScene(imageBase64, emptyCustomFields), false, }, { createFullScene(customFieldsID), customFields, createFullJSONScene("", customFields), false, }, { createEmptyScene(noImageID), emptyCustomFields, createEmptyJSONScene(), false, }, { createFullScene(errImageID), emptyCustomFields, createFullJSONScene("", emptyCustomFields), // failure to get image should not cause an error false, }, { createFullScene(errCustomFieldsID), customFields, createFullJSONScene("", customFields), true, }, } func TestToJSON(t *testing.T) { db := mocks.NewDatabase() imageErr := errors.New("error getting image") db.Scene.On("GetCover", testCtx, sceneID).Return(imageBytes, nil).Once() db.Scene.On("GetCover", testCtx, noImageID).Return(nil, nil).Once() db.Scene.On("GetCover", testCtx, errImageID).Return(nil, imageErr).Once() db.Scene.On("GetCover", testCtx, mock.Anything).Return(nil, nil) db.Scene.On("GetViewDates", testCtx, mock.Anything).Return(nil, nil) db.Scene.On("GetODates", testCtx, mock.Anything).Return(nil, nil) db.Scene.On("GetCustomFields", testCtx, customFieldsID).Return(customFields, nil).Once() db.Scene.On("GetCustomFields", testCtx, errCustomFieldsID).Return(nil, errors.New("error getting custom fields")).Once() db.Scene.On("GetCustomFields", testCtx, mock.Anything).Return(emptyCustomFields, nil) for i, s := range scenarios { scene := s.input json, err := ToBasicJSON(testCtx, db.Scene, &scene) switch { case !s.err && err != nil: t.Errorf("[%d] unexpected error: %s", i, err.Error()) case s.err && err == nil: t.Errorf("[%d] expected error not returned", i) case err != nil: // error case already handled, no need for assertion default: assert.Equal(t, s.expected, json, "[%d]", i) } } db.AssertExpectations(t) } func createStudioScene(studioID int) models.Scene { return models.Scene{ StudioID: &studioID, } } type stringTestScenario struct { input models.Scene expected string err bool } var getStudioScenarios = []stringTestScenario{ { createStudioScene(studioID), studioName, false, }, { createStudioScene(missingStudioID), "", false, }, { createStudioScene(errStudioID), "", true, }, } func TestGetStudioName(t *testing.T) { db := mocks.NewDatabase() studioErr := errors.New("error getting image") db.Studio.On("Find", testCtx, studioID).Return(&models.Studio{ Name: studioName, }, nil).Once() db.Studio.On("Find", testCtx, missingStudioID).Return(nil, nil).Once() db.Studio.On("Find", testCtx, errStudioID).Return(nil, studioErr).Once() for i, s := range getStudioScenarios { scene := s.input json, err := GetStudioName(testCtx, db.Studio, &scene) switch { case !s.err && err != nil: t.Errorf("[%d] unexpected error: %s", i, err.Error()) case s.err && err == nil: t.Errorf("[%d] expected error not returned", i) default: assert.Equal(t, s.expected, json, "[%d]", i) } } db.AssertExpectations(t) } type stringSliceTestScenario struct { input models.Scene expected []string err bool } var getTagNamesScenarios = []stringSliceTestScenario{ { createEmptyScene(sceneID), names, false, }, { createEmptyScene(noTagsID), nil, false, }, { createEmptyScene(errTagsID), nil, true, }, } func getTags(names []string) []*models.Tag { var ret []*models.Tag for _, n := range names { ret = append(ret, &models.Tag{ Name: n, }) } return ret } func TestGetTagNames(t *testing.T) { db := mocks.NewDatabase() tagErr := errors.New("error getting tag") db.Tag.On("FindBySceneID", testCtx, sceneID).Return(getTags(names), nil).Once() db.Tag.On("FindBySceneID", testCtx, noTagsID).Return(nil, nil).Once() db.Tag.On("FindBySceneID", testCtx, errTagsID).Return(nil, tagErr).Once() for i, s := range getTagNamesScenarios { scene := s.input json, err := GetTagNames(testCtx, db.Tag, &scene) switch { case !s.err && err != nil: t.Errorf("[%d] unexpected error: %s", i, err.Error()) case s.err && err == nil: t.Errorf("[%d] expected error not returned", i) default: assert.Equal(t, s.expected, json, "[%d]", i) } } db.AssertExpectations(t) } type sceneGroupsTestScenario struct { input models.Scene expected []jsonschema.SceneGroup err bool } var validGroups = models.NewRelatedGroups([]models.GroupsScenes{ { GroupID: validGroup1, SceneIndex: &group1Scene, }, { GroupID: validGroup2, SceneIndex: &group2Scene, }, }) var invalidGroups = models.NewRelatedGroups([]models.GroupsScenes{ { GroupID: invalidGroup, SceneIndex: &group1Scene, }, }) var getSceneGroupsJSONScenarios = []sceneGroupsTestScenario{ { models.Scene{ ID: sceneID, Groups: validGroups, }, []jsonschema.SceneGroup{ { GroupName: group1Name, SceneIndex: group1Scene, }, { GroupName: group2Name, SceneIndex: group2Scene, }, }, false, }, { models.Scene{ ID: noGroupsID, Groups: models.NewRelatedGroups([]models.GroupsScenes{}), }, nil, false, }, { models.Scene{ ID: errFindGroupID, Groups: invalidGroups, }, nil, true, }, } func TestGetSceneGroupsJSON(t *testing.T) { db := mocks.NewDatabase() groupErr := errors.New("error getting group") db.Group.On("Find", testCtx, validGroup1).Return(&models.Group{ Name: group1Name, }, nil).Once() db.Group.On("Find", testCtx, validGroup2).Return(&models.Group{ Name: group2Name, }, nil).Once() db.Group.On("Find", testCtx, invalidGroup).Return(nil, groupErr).Once() for i, s := range getSceneGroupsJSONScenarios { scene := s.input json, err := GetSceneGroupsJSON(testCtx, db.Group, &scene) switch { case !s.err && err != nil: t.Errorf("[%d] unexpected error: %s", i, err.Error()) case s.err && err == nil: t.Errorf("[%d] expected error not returned", i) default: assert.Equal(t, s.expected, json, "[%d]", i) } } db.AssertExpectations(t) } const ( validMarkerID1 = 1 validMarkerID2 = 2 invalidMarkerID1 = 3 invalidMarkerID2 = 4 validTagID1 = 1 validTagID2 = 2 validTagName1 = "validTagName1" validTagName2 = "validTagName2" invalidTagID = 3 markerTitle1 = "markerTitle1" markerTitle2 = "markerTitle2" markerSeconds1 = 1.0 markerSeconds2 = 2.3 markerSeconds1Str = "1.0" markerSeconds2Str = "2.3" ) type sceneMarkersTestScenario struct { input models.Scene expected []jsonschema.SceneMarker err bool } var getSceneMarkersJSONScenarios = []sceneMarkersTestScenario{ { createEmptyScene(sceneID), []jsonschema.SceneMarker{ { Title: markerTitle1, PrimaryTag: validTagName1, Seconds: markerSeconds1Str, Tags: []string{ validTagName1, validTagName2, }, CreatedAt: json.JSONTime{ Time: createTime, }, UpdatedAt: json.JSONTime{ Time: updateTime, }, }, { Title: markerTitle2, PrimaryTag: validTagName2, Seconds: markerSeconds2Str, Tags: []string{ validTagName2, }, CreatedAt: json.JSONTime{ Time: createTime, }, UpdatedAt: json.JSONTime{ Time: updateTime, }, }, }, false, }, { createEmptyScene(noMarkersID), nil, false, }, { createEmptyScene(errMarkersID), nil, true, }, { createEmptyScene(errFindPrimaryTagID), nil, true, }, { createEmptyScene(errFindByMarkerID), nil, true, }, } var validMarkers = []*models.SceneMarker{ { ID: validMarkerID1, Title: markerTitle1, PrimaryTagID: validTagID1, Seconds: markerSeconds1, CreatedAt: createTime, UpdatedAt: updateTime, }, { ID: validMarkerID2, Title: markerTitle2, PrimaryTagID: validTagID2, Seconds: markerSeconds2, CreatedAt: createTime, UpdatedAt: updateTime, }, } var invalidMarkers1 = []*models.SceneMarker{ { ID: invalidMarkerID1, PrimaryTagID: invalidTagID, }, } var invalidMarkers2 = []*models.SceneMarker{ { ID: invalidMarkerID2, PrimaryTagID: validTagID1, }, } func TestGetSceneMarkersJSON(t *testing.T) { db := mocks.NewDatabase() markersErr := errors.New("error getting scene markers") tagErr := errors.New("error getting tags") db.SceneMarker.On("FindBySceneID", testCtx, sceneID).Return(validMarkers, nil).Once() db.SceneMarker.On("FindBySceneID", testCtx, noMarkersID).Return(nil, nil).Once() db.SceneMarker.On("FindBySceneID", testCtx, errMarkersID).Return(nil, markersErr).Once() db.SceneMarker.On("FindBySceneID", testCtx, errFindPrimaryTagID).Return(invalidMarkers1, nil).Once() db.SceneMarker.On("FindBySceneID", testCtx, errFindByMarkerID).Return(invalidMarkers2, nil).Once() db.Tag.On("Find", testCtx, validTagID1).Return(&models.Tag{ Name: validTagName1, }, nil) db.Tag.On("Find", testCtx, validTagID2).Return(&models.Tag{ Name: validTagName2, }, nil) db.Tag.On("Find", testCtx, invalidTagID).Return(nil, tagErr) db.Tag.On("FindBySceneMarkerID", testCtx, validMarkerID1).Return([]*models.Tag{ { Name: validTagName1, }, { Name: validTagName2, }, }, nil) db.Tag.On("FindBySceneMarkerID", testCtx, validMarkerID2).Return([]*models.Tag{ { Name: validTagName2, }, }, nil) db.Tag.On("FindBySceneMarkerID", testCtx, invalidMarkerID2).Return(nil, tagErr).Once() for i, s := range getSceneMarkersJSONScenarios { scene := s.input json, err := GetSceneMarkersJSON(testCtx, db.SceneMarker, db.Tag, &scene) switch { case !s.err && err != nil: t.Errorf("[%d] unexpected error: %s", i, err.Error()) case s.err && err == nil: t.Errorf("[%d] expected error not returned", i) default: assert.Equal(t, s.expected, json, "[%d]", i) } } db.AssertExpectations(t) }