Files
agent/backend/internal/logic/search/service_test.go

170 lines
6.9 KiB
Go

package search
import (
"fmt"
"strings"
"testing"
"time"
"github.com/glebarez/sqlite"
"github.com/stretchr/testify/require"
"gorm.io/gorm"
"senlinai-agent/backend/internal/models"
)
func TestSearchFindsDocumentBody(t *testing.T) {
database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, models.AutoMigrate(database))
models.DBService = database
require.NoError(t, database.Create(&models.SaProject{ID: 1, OwnerID: 7, Name: "支付项目"}).Error)
createSearchDocument(t, database, 1, 7, "接口方案.md", "二维码支付回调设计", "", time.Time{})
service := NewService()
results, err := service.Search(7, "回调")
require.NoError(t, err)
require.Len(t, results, 1)
require.Equal(t, "document", results[0].Type)
}
func TestSearchAppliesStableFairGlobalLimitAcrossResultTypes(t *testing.T) {
database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, models.AutoMigrate(database))
models.DBService = database
baseTime := time.Date(2026, time.July, 21, 12, 0, 0, 0, time.UTC)
for index := 0; index < 60; index++ {
project := models.SaProject{
Identity: fmt.Sprintf("00000000-0000-7001-8000-%012d", index),
OwnerID: 7,
Name: fmt.Sprintf("稳定排序项目 %02d", index),
Identifier: fmt.Sprintf("SORT-%02d", index),
UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
}
require.NoError(t, database.Create(&project).Error)
require.NoError(t, database.Create(&models.SaTask{
Identity: fmt.Sprintf("00000000-0000-7002-8000-%012d", index),
ProjectID: project.ID,
CreatedBy: 7,
Title: fmt.Sprintf("稳定排序任务 %02d", index),
Status: "open",
UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
}).Error)
document := models.SaDocumentTree{
Identity: fmt.Sprintf("00000000-0000-7003-8000-%012d", index),
ProjectID: project.ID,
OwnerID: 7,
Kind: models.DocumentKindFile,
Name: fmt.Sprintf("稳定排序文档 %02d.md", index),
NormalizedName: fmt.Sprintf("稳定排序文档 %02d.md", index),
Extension: ".md",
Revision: 1,
UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
}
require.NoError(t, database.Create(&document).Error)
}
service := NewService(database)
results, err := service.Search(7, "稳定排序")
require.NoError(t, err)
require.Len(t, results, maxSearchResults)
counts := map[string]int{}
for _, result := range results {
counts[result.Type]++
}
require.Equal(t, map[string]int{"project": 17, "task": 17, "document": 16}, counts)
require.Equal(t,
[]string{"project", "task", "document", "project", "task", "document", "project", "task", "document"},
resultTypes(results[:9]),
)
require.Equal(t, "00000000-0000-7001-8000-000000000059", results[0].ID)
require.Equal(t, "00000000-0000-7002-8000-000000000059", results[1].ID)
require.Equal(t, "00000000-0000-7003-8000-000000000059", results[2].ID)
again, err := service.Search(7, "稳定排序")
require.NoError(t, err)
require.Equal(t, results, again)
}
func TestSearchBoundsChineseSnippetsByRune(t *testing.T) {
database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, models.AutoMigrate(database))
models.DBService = database
project := models.SaProject{OwnerID: 7, Name: "摘要项目", Identifier: "SNIPPET"}
require.NoError(t, database.Create(&project).Error)
createSearchDocument(t, database, project.ID, 7, "中文摘要.md", strings.Repeat("森", 300)+"尾", "", time.Time{})
results, err := NewService(database).Search(7, "森")
require.NoError(t, err)
require.Len(t, results, 1)
require.Len(t, []rune(results[0].Snippet), 240)
require.NotContains(t, results[0].Snippet, "尾")
}
func resultTypes(results []SearchResultDTO) []string {
types := make([]string, 0, len(results))
for _, result := range results {
types = append(types, result.Type)
}
return types
}
func TestSearchOnlyReturnsNavigableProjectTaskAndDocumentResults(t *testing.T) {
database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, models.AutoMigrate(database))
models.DBService = database
require.NoError(t, database.Create(&models.SaProject{ID: 1, OwnerID: 7, Name: "webhook 支付项目"}).Error)
require.NoError(t, database.Create(&models.SaTask{ProjectID: 1, CreatedBy: 7, Title: "回调任务", Description: "检查 webhook"}).Error)
createSearchDocument(t, database, 1, 7, "回调文档.md", "webhook 流程", "", time.Time{})
require.NoError(t, database.Create(&models.SaInboxItem{ProjectID: 1, CreatedBy: 7, SourceType: "text", Title: "收集项", Body: "webhook 待整理"}).Error)
require.NoError(t, database.Create(&models.SaAISession{ProjectID: 1, CreatedBy: 7, Title: "AI 分析", Context: "webhook 问答"}).Error)
service := NewService()
results, err := service.Search(7, "webhook")
require.NoError(t, err)
types := make(map[string]bool)
for _, result := range results {
types[result.Type] = true
}
require.Equal(t, map[string]bool{"project": true, "task": true, "document": true}, types)
require.Len(t, results, 3)
}
func createSearchDocument(t *testing.T, database *gorm.DB, projectID, userID uint, name, markdown, identity string, updatedAt time.Time) models.SaDocumentTree {
t.Helper()
document := models.SaDocumentTree{
Identity: identity, ProjectID: projectID, OwnerID: userID, Kind: models.DocumentKindFile,
Name: name, NormalizedName: strings.ToLower(name), Extension: ".md",
MimeType: "text/markdown", Size: int64(len([]byte(markdown))), Revision: 1,
UpdatedAt: updatedAt,
}
require.NoError(t, database.Create(&document).Error)
require.NoError(t, database.Create(&models.SaDocumentContent{DocumentID: document.ID, Markdown: markdown}).Error)
return document
}
func TestSearchTreatsLikeWildcardsAsLiteralKeywords(t *testing.T) {
database, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, models.AutoMigrate(database))
models.DBService = database
require.NoError(t, database.Create(&models.SaProject{ID: 1, OwnerID: 7, Name: "100%_完成", Identifier: "SPECIAL"}).Error)
require.NoError(t, database.Create(&models.SaProject{ID: 2, OwnerID: 7, Name: "普通项目", Identifier: "NORMAL"}).Error)
percentResults, err := NewService().Search(7, "%")
require.NoError(t, err)
require.Len(t, percentResults, 1)
require.Equal(t, "100%_完成", percentResults[0].Title)
underscoreResults, err := NewService().Search(7, "_")
require.NoError(t, err)
require.Len(t, underscoreResults, 1)
require.Equal(t, "100%_完成", underscoreResults[0].Title)
}