/
githubmirror
/
photoprism
Обзор
Документация
Войти
/
githubmirror
/
photoprism
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
internal/entity/query/faces_test.go
345 строк
74 KB
Michael Mayer
Config: Centralize SQL driver identifiers in pkg/dsn #5588
17 май 2026, 17:47
17 май 2026, 17:47
c8390a2
Код
Авторство
О чём код?
package query import ( "errors" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/photoprism/photoprism/internal/ai/face" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/pkg/dsn" "github.com/photoprism/photoprism/pkg/rnd" ) func TestFaces(t *testing.T) { t.Run("Known", func(t *testing.T) { results, err := Faces(true, false, false, false) if err != nil { t.Fatal(err) } assert.GreaterOrEqual(t, len(results), 1) for _, val := range results { assert.IsType(t, entity.Face{}, val) } }) t.Run("Hidden", func(t *testing.T) { results, err := Faces(false, false, true, false) if err != nil { t.Fatal(err) } assert.GreaterOrEqual(t, len(results), 1) }) t.Run("Ignored", func(t *testing.T) { results, err := Faces(false, false, true, true) if err != nil { t.Fatal(err) } assert.GreaterOrEqual(t, len(results), 1) }) t.Run("Unmatched", func(t *testing.T) { results, err := Faces(false, true, false, false) if err != nil { t.Fatal(err) } assert.GreaterOrEqual(t, len(results), 1) for _, val := range results { assert.IsType(t, entity.Face{}, val) } }) } func TestManuallyAddedFaces(t *testing.T) { t.Run("Ok", func(t *testing.T) { results, err := ManuallyAddedFaces(false, false, "") if err != nil { t.Fatal(err) } assert.GreaterOrEqual(t, len(results), 1) for _, val := range results { assert.IsType(t, entity.Face{}, val) } }) t.Run("Hidden", func(t *testing.T) { results, err := ManuallyAddedFaces(true, false, "") if err != nil { t.Fatal(err) } assert.Empty(t, results) }) t.Run("SpecificSubject", func(t *testing.T) { results, err := ManuallyAddedFaces(false, false, "foobar") if err != nil { t.Fatal(err) } assert.Empty(t, results) }) } func TestMatchFaceMarkers(t *testing.T) { const faceFixtureId = "ms6sg6b1wowuy444" if m, err := MarkerByUID(faceFixtureId); err != nil { t.Fatal(err) } else if m == nil { t.Fatal("marker is nil") } else { assert.Empty(t, m.SubjUID) } // Reset subj_uid. if err := Db().Model(&entity.Marker{}). Where("subj_src = ?", entity.SrcAuto). Where("subj_uid = ?", "js6sg6b1qekk9jx8"). UpdateColumn("subj_uid", "").Error; err != nil { t.Fatal(err) } affected, err := MatchFaceMarkers() if err != nil { t.Fatal(err) } assert.Equal(t, int64(2), affected) if m, err := MarkerByUID(faceFixtureId); err != nil { t.Fatal(err) } else if m == nil { t.Fatal("marker is nil") } else { assert.Equal(t, "js6sg6b1qekk9jx8", m.SubjUID) } } func TestMatchFaceMarkers_ReturnsUpdateError(t *testing.T) { originalDb := entity.Db() require.NotNil(t, originalDb) tempConn := &entity.DbConn{ Driver: dsn.DriverSQLite3, Dsn: filepath.Join(t.TempDir(), "match-face-markers-error.db"), } tempDb := tempConn.Db() require.NotNil(t, tempDb) require.NoError(t, tempDb.AutoMigrate(&entity.Face{}).Error) require.NoError(t, tempDb.Create(&entity.Face{ ID: "FACE-MATCH-ERR-1", FaceSrc: entity.SrcManual, FaceKind: int(face.RegularFace), FaceHidden: false, SubjUID: rnd.GenerateUID('j'), Samples: 1, }).Error) entity.SetDbProvider(tempConn) t.Cleanup(func() { entity.SetDbProvider(staticDbProvider{db: originalDb}) tempConn.Close() }) affected, err := MatchFaceMarkers() require.Error(t, err) assert.Equal(t, int64(0), affected) assert.Contains(t, err.Error(), "no such table") assert.Contains(t, err.Error(), entity.Marker{}.TableName()) } func TestRemoveAnonymousFaceClusters(t *testing.T) { removed, err := RemoveAnonymousFaceClusters() if err != nil { t.Fatal(err) } assert.Equal(t, 2, removed) } func TestCountNewFaceMarkers(t *testing.T) { t.Run("All", func(t *testing.T) { assert.GreaterOrEqual(t, CountNewFaceMarkers(0, 0), 1) }) t.Run("ScoreTen", func(t *testing.T) { assert.GreaterOrEqual(t, CountNewFaceMarkers(0, 10), 1) }) t.Run("SizeNum160", func(t *testing.T) { assert.GreaterOrEqual(t, CountNewFaceMarkers(160, 0), 1) }) t.Run("ScoreNum50AndSizeNum160", func(t *testing.T) { assert.GreaterOrEqual(t, CountNewFaceMarkers(160, 50), 1) }) } func TestMergeFaces(t *testing.T) { t.Run("SameSubjects", func(t *testing.T) { face1 := entity.NewFace( "jqynvsf28rhn6b0c", entity.SrcManual, face.Embeddings{ face.Embedding{0.10730543085474682, -0.007740289179353713, 0.04013410115400314, 0.01458170011165962, -0.033333988977870946, 0.06636234022813034, -0.00010941258007316575, 0.0266348918046072, -0.050 … [Строка слишком длинная. Вы можете скачать файл] face.Embedding{0.20730543085474682, -0.006640289179353713, 0.03013410115400314, 0.01258170011165962, -0.023333988977870946, 0.05536234022813034, -0.00010941258007316575, 0.0266348918046072, -0.050 … [Строка слишком длинная. Вы можете скачать файл] }, ) face2 := entity.NewFace( "jqynvsf28rhn6b0c", entity.SrcManual, face.Embeddings{ face.Embedding{0.10230543085474682, -0.007840289179353713, 0.03013410115400314, 0.02458170011165962, -0.033333988977870946, 0.06636234022813034, -0.00010941258007316575, 0.0266348918046072, -0.050 … [Строка слишком длинная. Вы можете скачать файл] }, ) if err := face1.Create(); err != nil { t.Fatal(err) } if err := face2.Create(); err != nil { t.Fatal(err) } faces := entity.Faces{*face1, *face2} result, err := MergeFaces(faces, false) if err != nil { t.Fatal(err) } assert.Equal(t, "4FD6YTOMWTDU5JKD3SS2MTRUTKZRZT7O", result.ID) assert.Equal(t, entity.SrcManual, result.FaceSrc) assert.Equal(t, "jqynvsf28rhn6b0c", result.SubjUID) assert.Equal(t, 2, result.Samples) assert.InDelta(t, 0.040200777224183845, result.SampleRadius, 1e-9) assert.Equal(t, 0, result.Collisions) assert.Equal(t, float64(0), result.CollisionRadius) // t.Logf("merged: %#v", result) }) t.Run("AmbiguousSubjects", func(t *testing.T) { face1 := entity.NewFace( "jqynvsf28rhn6b0c", entity.SrcManual, face.Embeddings{ face.Embedding{0.10730543085474682, -0.007740289179353713, 0.04013410115400314, 0.01458170011165962, -0.033333988977870946, 0.06636234022813034, -0.00010941258007316575, 0.0266348918046072, -0.050 … [Строка слишком длинная. Вы можете скачать файл] face.Embedding{0.20730543085474682, -0.006640289179353713, 0.03013410115400314, 0.01258170011165962, -0.023333988977870946, 0.05536234022813034, -0.00010941258007316575, 0.0266348918046072, -0.050 … [Строка слишком длинная. Вы можете скачать файл] }, ) face2 := entity.NewFace( "jqynvt925h8c1asv", entity.SrcManual, face.Embeddings{ face.Embedding{0.10230543085474682, -0.007840289179353713, 0.03013410115400314, 0.02458170011165962, -0.033333988977870946, 0.06636234022813034, -0.00010941258007316575, 0.0266348918046072, -0.050 … [Строка слишком длинная. Вы можете скачать файл] }, ) faces := entity.Faces{*face1, *face2} result, err := MergeFaces(faces, false) assert.EqualError(t, err, "faces: cannot merge clusters with conflicting subjects jqynvsf28rhn6b0c <> jqynvt925h8c1asv") assert.Nil(t, result) }) t.Run("OneSubject", func(t *testing.T) { result, err := MergeFaces(entity.Faces{entity.Face{ID: "4FD6YTOMWTDU5JKD3SS2MTRUTKZRZT7O"}}, false) assert.EqualError(t, err, "faces: two or more clusters required for merging") assert.Nil(t, result) }) } func TestMergeFacesRetainedClusters(t *testing.T) { subjUID := rnd.GenerateUID('j') embeddingA := face.RandomEmbeddings(1, face.RegularFace) embeddingB := face.RandomEmbeddings(1, face.RegularFace) faceA := entity.NewFace(subjUID, entity.SrcManual, embeddingA) require.NoError(t, faceA.Create()) faceB := entity.NewFace(subjUID, entity.SrcManual, embeddingB) require.NoError(t, faceB.Create()) // Create markers that deliberately fail to match the merged embedding. neutralEmbedding := face.Embeddings{face.NullEmbedding} neutralJSON := neutralEmbedding.JSON() markers := []*entity.Marker{ { FileUID: rnd.GenerateUID('f'), MarkerType: entity.MarkerFace, MarkerSrc: entity.SrcManual, FaceID: faceA.ID, EmbeddingsJSON: neutralJSON, }, { FileUID: rnd.GenerateUID('f'), MarkerType: entity.MarkerFace, MarkerSrc: entity.SrcManual, FaceID: faceB.ID, EmbeddingsJSON: neutralJSON, }, } for _, marker := range markers { require.NoError(t, entity.Db().Create(marker).Error) } _, err := MergeFaces(entity.Faces{*faceA, *faceB}, false) require.Error(t, err) require.True(t, errors.Is(err, ErrRetainedManualClusters)) var updated entity.Face require.NoError(t, entity.Db().Where("id = ?", faceA.ID).First(&updated).Error) require.NotZero(t, updated.MergeRetry) require.NotEmpty(t, updated.MergeNotes) resetCount, err := ResetFaceMergeRetry(subjUID) require.NoError(t, err) require.GreaterOrEqual(t, resetCount, 1) require.NoError(t, entity.Db().Where("id = ?", faceA.ID).First(&updated).Error) require.Zero(t, updated.MergeRetry) require.Empty(t, updated.MergeNotes) } func TestResolveFaceCollisions(t *testing.T) { c, r, err := ResolveFaceCollisions() if err != nil { t.Fatal(err) } assert.LessOrEqual(t, 1, c) assert.LessOrEqual(t, 1, r) } func TestRemoveAutoFaceClusters(t *testing.T) { removed, err := RemoveAutoFaceClusters() if err != nil { t.Fatal(err) } assert.LessOrEqual(t, 3, removed) }