feat(documents): rebuild project document workbench
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -37,8 +38,7 @@ func TestSearchHandlerReturnsCamelCaseIdentityResults(t *testing.T) {
|
||||
require.NoError(t, database.Create(&project).Error)
|
||||
task := models.SaTask{ProjectID: project.ID, CreatedBy: owner.ID, Title: "回调任务", Description: "检查签名", Status: "open"}
|
||||
require.NoError(t, database.Create(&task).Error)
|
||||
note := models.SaNote{ProjectID: project.ID, CreatedBy: owner.ID, Title: "接口说明", Markdown: "回调验签流程"}
|
||||
require.NoError(t, database.Create(¬e).Error)
|
||||
document := createSearchDocument(t, database, project.ID, owner.ID, "接口说明.md", "回调验签流程", "", time.Time{})
|
||||
router := searchHandlerTestRouter(owner.ID)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/search?q=%E5%9B%9E%E8%B0%83", nil)
|
||||
req.Header.Set("Authorization", "Bearer test-token")
|
||||
@@ -65,8 +65,8 @@ func TestSearchHandlerReturnsCamelCaseIdentityResults(t *testing.T) {
|
||||
require.Equal(t, project.Identity, byType["project"]["projectId"])
|
||||
require.Equal(t, task.Identity, byType["task"]["id"])
|
||||
require.Equal(t, project.Identity, byType["task"]["projectId"])
|
||||
require.Equal(t, note.Identity, byType["note"]["id"])
|
||||
require.Equal(t, project.Identity, byType["note"]["projectId"])
|
||||
require.Equal(t, document.Identity, byType["document"]["id"])
|
||||
require.Equal(t, project.Identity, byType["document"]["projectId"])
|
||||
}
|
||||
|
||||
func TestSearchHandlerOnlyReturnsObjectsVisibleToCurrentUser(t *testing.T) {
|
||||
@@ -79,20 +79,17 @@ func TestSearchHandlerOnlyReturnsObjectsVisibleToCurrentUser(t *testing.T) {
|
||||
require.NoError(t, database.Create(&assigned).Error)
|
||||
privateTask := models.SaTask{ProjectID: project.ID, CreatedBy: owner.ID, Title: "检索词私有任务", Status: "open"}
|
||||
require.NoError(t, database.Create(&privateTask).Error)
|
||||
sharedNote := models.SaNote{ProjectID: project.ID, CreatedBy: owner.ID, Title: "检索词共享笔记", Markdown: "已显式分享"}
|
||||
require.NoError(t, database.Create(&sharedNote).Error)
|
||||
privateNote := models.SaNote{ProjectID: project.ID, CreatedBy: owner.ID, Title: "检索词私有笔记", Markdown: "不得泄漏"}
|
||||
require.NoError(t, database.Create(&privateNote).Error)
|
||||
sharedDocument := createSearchDocument(t, database, project.ID, owner.ID, "检索词共享文档.md", "已显式分享", "", time.Time{})
|
||||
privateDocument := createSearchDocument(t, database, project.ID, owner.ID, "检索词私有文档.md", "不得泄漏", "", time.Time{})
|
||||
otherProject := models.SaProject{OwnerID: owner.ID, Name: "其他项目", Identifier: "OTHER"}
|
||||
require.NoError(t, database.Create(&otherProject).Error)
|
||||
crossProjectNote := models.SaNote{ProjectID: otherProject.ID, CreatedBy: owner.ID, Title: "检索词跨项目笔记", Markdown: "伪造分享也不得泄漏"}
|
||||
require.NoError(t, database.Create(&crossProjectNote).Error)
|
||||
crossProjectDocument := createSearchDocument(t, database, otherProject.ID, owner.ID, "检索词跨项目文档.md", "伪造分享也不得泄漏", "", time.Time{})
|
||||
require.NoError(t, database.Create(&models.SaTaskShare{
|
||||
TaskID: assigned.ID, ObjectType: "note", ObjectID: sharedNote.ID,
|
||||
TaskID: assigned.ID, ObjectType: "document", ObjectID: sharedDocument.ID,
|
||||
}).Error)
|
||||
// 即使历史数据绕过服务层写入跨项目分享,搜索也必须在读取边界再次校验项目一致性。
|
||||
require.NoError(t, database.Create(&models.SaTaskShare{
|
||||
TaskID: assigned.ID, ObjectType: "note", ObjectID: crossProjectNote.ID,
|
||||
TaskID: assigned.ID, ObjectType: "document", ObjectID: crossProjectDocument.ID,
|
||||
}).Error)
|
||||
router := searchHandlerTestRouter(viewer.ID)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/search?q=%E6%A3%80%E7%B4%A2%E8%AF%8D", nil)
|
||||
@@ -111,13 +108,13 @@ func TestSearchHandlerOnlyReturnsObjectsVisibleToCurrentUser(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload))
|
||||
require.Len(t, payload.Items, 2)
|
||||
require.ElementsMatch(t, []string{assigned.Identity, sharedNote.Identity}, []string{payload.Items[0].ID, payload.Items[1].ID})
|
||||
require.ElementsMatch(t, []string{assigned.Identity, sharedDocument.Identity}, []string{payload.Items[0].ID, payload.Items[1].ID})
|
||||
for _, item := range payload.Items {
|
||||
require.Equal(t, project.Identity, item.ProjectID)
|
||||
require.Contains(t, []string{"task", "note"}, item.Type)
|
||||
require.Contains(t, []string{"task", "document"}, item.Type)
|
||||
require.NotEqual(t, privateTask.Identity, item.ID)
|
||||
require.NotEqual(t, privateNote.Identity, item.ID)
|
||||
require.NotEqual(t, crossProjectNote.Identity, item.ID)
|
||||
require.NotEqual(t, privateDocument.Identity, item.ID)
|
||||
require.NotEqual(t, crossProjectDocument.Identity, item.ID)
|
||||
require.NotEqual(t, project.Identity, item.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,31 +92,40 @@ func (s *Service) Search(userID uint, query string) ([]SearchResultDTO, error) {
|
||||
})
|
||||
}
|
||||
|
||||
var notes []models.SaNote
|
||||
noteFilter := fmt.Sprintf(`(sa_projects.owner_id = ? OR EXISTS (
|
||||
var documents []models.SaDocument
|
||||
documentFilter := fmt.Sprintf(`(sa_projects.owner_id = ? OR EXISTS (
|
||||
SELECT 1 FROM sa_task_shares
|
||||
JOIN sa_tasks ON sa_tasks.id = sa_task_shares.task_id
|
||||
WHERE sa_task_shares.object_type = 'note'
|
||||
AND sa_task_shares.object_id = sa_notes.id
|
||||
AND sa_tasks.project_id = sa_notes.project_id
|
||||
WHERE sa_task_shares.object_type = 'document'
|
||||
AND sa_task_shares.object_id = sa_documents.id
|
||||
AND sa_tasks.project_id = sa_documents.project_id
|
||||
AND sa_tasks.assignee_id = ?
|
||||
)) AND (sa_notes.title %s ? ESCAPE '!' OR sa_notes.markdown %s ? ESCAPE '!')`, operator, operator)
|
||||
if err := s.database().Select("sa_notes.*").Joins("JOIN sa_projects ON sa_projects.id = sa_notes.project_id").
|
||||
Where(noteFilter, userID, userID, like, like).
|
||||
Order("sa_notes.updated_at DESC").Order("sa_notes.identity ASC").
|
||||
)) AND (sa_documents.name %s ? ESCAPE '!' OR EXISTS (
|
||||
SELECT 1 FROM sa_document_contents
|
||||
WHERE sa_document_contents.document_id = sa_documents.id
|
||||
AND sa_document_contents.markdown %s ? ESCAPE '!'
|
||||
))`, operator, operator)
|
||||
if err := s.database().Select("sa_documents.*").Joins("JOIN sa_projects ON sa_projects.id = sa_documents.project_id").
|
||||
Where(documentFilter, userID, userID, like, like).
|
||||
Order("sa_documents.updated_at DESC").Order("sa_documents.identity ASC").
|
||||
Limit(maxSearchResults).
|
||||
Find(¬es).Error; err != nil {
|
||||
Find(&documents).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
noteResults := make([]rankedSearchResult, 0, len(notes))
|
||||
for _, note := range notes {
|
||||
noteResults = append(noteResults, rankedSearchResult{
|
||||
result: SearchResultDTO{Type: "note", ID: note.Identity, ProjectID: note.ProjectIdentity, Title: note.Title, Snippet: note.Markdown},
|
||||
updatedAt: note.UpdatedAt,
|
||||
documentResults := make([]rankedSearchResult, 0, len(documents))
|
||||
for _, document := range documents {
|
||||
var content models.SaDocumentContent
|
||||
_ = s.database().Where("document_id = ?", document.ID).First(&content).Error
|
||||
documentResults = append(documentResults, rankedSearchResult{
|
||||
result: SearchResultDTO{
|
||||
Type: "document", ID: document.Identity, ProjectID: document.ProjectIdentity,
|
||||
Title: document.Name, Snippet: content.Markdown,
|
||||
},
|
||||
updatedAt: document.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return stableFairLimit(projectResults, taskResults, noteResults), nil
|
||||
return stableFairLimit(projectResults, taskResults, documentResults), nil
|
||||
}
|
||||
|
||||
func stableFairLimit(buckets ...[]rankedSearchResult) []SearchResultDTO {
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestPostgresSearchMatchesChinesePartialKeywords(t *testing.T) {
|
||||
project := models.SaProject{OwnerID: owner.ID, Name: "中文回调平台", Identifier: "PG-CALLBACK"}
|
||||
require.NoError(t, database.Create(&project).Error)
|
||||
require.NoError(t, database.Create(&models.SaTask{ProjectID: project.ID, CreatedBy: owner.ID, Title: "回调任务", Status: "open"}).Error)
|
||||
require.NoError(t, database.Create(&models.SaNote{ProjectID: project.ID, CreatedBy: owner.ID, Title: "说明", Markdown: "回调验签流程"}).Error)
|
||||
createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocument{Name: "说明.md"}, "回调验签流程")
|
||||
|
||||
results, err := NewService(database).Search(owner.ID, "回调")
|
||||
|
||||
@@ -38,16 +38,15 @@ func TestPostgresSearchKeepsAssignmentAndExplicitShareBoundaries(t *testing.T) {
|
||||
assigned := models.SaTask{ProjectID: project.ID, CreatedBy: owner.ID, AssigneeID: &viewer.ID, Title: "边界词已指派", Status: "open"}
|
||||
require.NoError(t, database.Create(&assigned).Error)
|
||||
require.NoError(t, database.Create(&models.SaTask{ProjectID: project.ID, CreatedBy: owner.ID, Title: "边界词私有任务", Status: "open"}).Error)
|
||||
sharedNote := models.SaNote{ProjectID: project.ID, CreatedBy: owner.ID, Title: "边界词共享笔记"}
|
||||
require.NoError(t, database.Create(&sharedNote).Error)
|
||||
require.NoError(t, database.Create(&models.SaNote{ProjectID: project.ID, CreatedBy: owner.ID, Title: "边界词私有笔记"}).Error)
|
||||
require.NoError(t, database.Create(&models.SaTaskShare{TaskID: assigned.ID, ObjectType: "note", ObjectID: sharedNote.ID}).Error)
|
||||
sharedDocument := createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocument{Name: "边界词共享笔记.md"}, "")
|
||||
createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocument{Name: "边界词私有笔记.md"}, "")
|
||||
require.NoError(t, database.Create(&models.SaTaskShare{TaskID: assigned.ID, ObjectType: "document", ObjectID: sharedDocument.ID}).Error)
|
||||
|
||||
results, err := NewService(database).Search(viewer.ID, "边界词")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
require.ElementsMatch(t, []string{assigned.Identity, sharedNote.Identity}, []string{results[0].ID, results[1].ID})
|
||||
require.ElementsMatch(t, []string{assigned.Identity, sharedDocument.Identity}, []string{results[0].ID, results[1].ID})
|
||||
}
|
||||
|
||||
func TestPostgresSearchUsesStableFairLimitAndRuneBoundedSnippets(t *testing.T) {
|
||||
@@ -71,14 +70,11 @@ func TestPostgresSearchUsesStableFairLimitAndRuneBoundedSnippets(t *testing.T) {
|
||||
Status: "open",
|
||||
UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
|
||||
}).Error)
|
||||
require.NoError(t, database.Create(&models.SaNote{
|
||||
createPostgresSearchDocument(t, database, project, owner.ID, models.SaDocument{
|
||||
Identity: fmt.Sprintf("00000000-0000-7003-8000-%012d", index),
|
||||
ProjectID: project.ID,
|
||||
CreatedBy: owner.ID,
|
||||
Title: fmt.Sprintf("稳定排序笔记 %02d", index),
|
||||
Markdown: strings.Repeat("森", 300) + "尾",
|
||||
Name: fmt.Sprintf("稳定排序笔记 %02d.md", index),
|
||||
UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
|
||||
}).Error)
|
||||
}, strings.Repeat("森", 300)+"尾")
|
||||
}
|
||||
|
||||
service := NewService(database)
|
||||
@@ -86,7 +82,7 @@ func TestPostgresSearchUsesStableFairLimitAndRuneBoundedSnippets(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, maxSearchResults)
|
||||
require.Equal(t,
|
||||
[]string{"project", "task", "note", "project", "task", "note", "project", "task", "note"},
|
||||
[]string{"project", "task", "document", "project", "task", "document", "project", "task", "document"},
|
||||
resultTypes(results[:9]),
|
||||
)
|
||||
require.Equal(t, "00000000-0000-7001-8000-000000000059", results[0].ID)
|
||||
@@ -96,17 +92,39 @@ func TestPostgresSearchUsesStableFairLimitAndRuneBoundedSnippets(t *testing.T) {
|
||||
for _, result := range results {
|
||||
counts[result.Type]++
|
||||
}
|
||||
require.Equal(t, map[string]int{"project": 17, "task": 17, "note": 16}, counts)
|
||||
require.Equal(t, map[string]int{"project": 17, "task": 17, "document": 16}, counts)
|
||||
|
||||
again, err := service.Search(owner.ID, "稳定排序")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, results, again)
|
||||
|
||||
noteResults, err := service.Search(owner.ID, "森")
|
||||
documentResults, err := service.Search(owner.ID, "森")
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, noteResults)
|
||||
require.Len(t, []rune(noteResults[0].Snippet), 240)
|
||||
require.NotContains(t, noteResults[0].Snippet, "尾")
|
||||
require.NotEmpty(t, documentResults)
|
||||
require.Len(t, []rune(documentResults[0].Snippet), 240)
|
||||
require.NotContains(t, documentResults[0].Snippet, "尾")
|
||||
}
|
||||
|
||||
func createPostgresSearchDocument(
|
||||
t *testing.T,
|
||||
database *gorm.DB,
|
||||
project models.SaProject,
|
||||
ownerID uint,
|
||||
document models.SaDocument,
|
||||
markdown string,
|
||||
) models.SaDocument {
|
||||
t.Helper()
|
||||
document.ProjectID = project.ID
|
||||
document.ProjectIdentity = project.Identity
|
||||
document.CreatedBy = ownerID
|
||||
document.Kind = models.DocumentKindFile
|
||||
document.Extension = ".md"
|
||||
document.MimeType = "text/markdown; charset=utf-8"
|
||||
document.NormalizedName = strings.ToLower(document.Name)
|
||||
document.Revision = 1
|
||||
require.NoError(t, database.Create(&document).Error)
|
||||
require.NoError(t, database.Create(&models.SaDocumentContent{DocumentID: document.ID, Markdown: markdown}).Error)
|
||||
return document
|
||||
}
|
||||
|
||||
func newPostgresSearchTestDB(t *testing.T) *gorm.DB {
|
||||
|
||||
@@ -12,20 +12,20 @@ import (
|
||||
"senlinai-agent/backend/internal/models"
|
||||
)
|
||||
|
||||
func TestSearchFindsNoteBody(t *testing.T) {
|
||||
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)
|
||||
require.NoError(t, database.Create(&models.SaNote{ProjectID: 1, CreatedBy: 7, Title: "接口方案", Markdown: "二维码支付回调设计"}).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, "note", results[0].Type)
|
||||
require.Equal(t, "document", results[0].Type)
|
||||
}
|
||||
|
||||
func TestSearchAppliesStableFairGlobalLimitAcrossResultTypes(t *testing.T) {
|
||||
@@ -52,13 +52,18 @@ func TestSearchAppliesStableFairGlobalLimitAcrossResultTypes(t *testing.T) {
|
||||
Status: "open",
|
||||
UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
|
||||
}).Error)
|
||||
require.NoError(t, database.Create(&models.SaNote{
|
||||
Identity: fmt.Sprintf("00000000-0000-7003-8000-%012d", index),
|
||||
ProjectID: project.ID,
|
||||
CreatedBy: 7,
|
||||
Title: fmt.Sprintf("稳定排序笔记 %02d", index),
|
||||
UpdatedAt: baseTime.Add(time.Duration(index) * time.Minute),
|
||||
}).Error)
|
||||
document := models.SaDocument{
|
||||
Identity: fmt.Sprintf("00000000-0000-7003-8000-%012d", index),
|
||||
ProjectID: project.ID,
|
||||
CreatedBy: 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)
|
||||
@@ -70,9 +75,9 @@ func TestSearchAppliesStableFairGlobalLimitAcrossResultTypes(t *testing.T) {
|
||||
for _, result := range results {
|
||||
counts[result.Type]++
|
||||
}
|
||||
require.Equal(t, map[string]int{"project": 17, "task": 17, "note": 16}, counts)
|
||||
require.Equal(t, map[string]int{"project": 17, "task": 17, "document": 16}, counts)
|
||||
require.Equal(t,
|
||||
[]string{"project", "task", "note", "project", "task", "note", "project", "task", "note"},
|
||||
[]string{"project", "task", "document", "project", "task", "document", "project", "task", "document"},
|
||||
resultTypes(results[:9]),
|
||||
)
|
||||
require.Equal(t, "00000000-0000-7001-8000-000000000059", results[0].ID)
|
||||
@@ -91,12 +96,7 @@ func TestSearchBoundsChineseSnippetsByRune(t *testing.T) {
|
||||
models.DBService = database
|
||||
project := models.SaProject{OwnerID: 7, Name: "摘要项目", Identifier: "SNIPPET"}
|
||||
require.NoError(t, database.Create(&project).Error)
|
||||
require.NoError(t, database.Create(&models.SaNote{
|
||||
ProjectID: project.ID,
|
||||
CreatedBy: 7,
|
||||
Title: "中文摘要",
|
||||
Markdown: strings.Repeat("森", 300) + "尾",
|
||||
}).Error)
|
||||
createSearchDocument(t, database, project.ID, 7, "中文摘要.md", strings.Repeat("森", 300)+"尾", "", time.Time{})
|
||||
|
||||
results, err := NewService(database).Search(7, "森")
|
||||
require.NoError(t, err)
|
||||
@@ -113,15 +113,14 @@ func resultTypes(results []SearchResultDTO) []string {
|
||||
return types
|
||||
}
|
||||
|
||||
func TestSearchOnlyReturnsNavigableProjectTaskAndNoteResults(t *testing.T) {
|
||||
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)
|
||||
require.NoError(t, database.Create(&models.SaNote{ProjectID: 1, CreatedBy: 7, Title: "回调笔记", Markdown: "webhook 流程"}).Error)
|
||||
require.NoError(t, database.Create(&models.SaSource{ProjectID: 1, CreatedBy: 7, Kind: "link", Title: "支付文档", URL: "https://example.com/pay", ContentText: "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)
|
||||
|
||||
@@ -133,10 +132,23 @@ func TestSearchOnlyReturnsNavigableProjectTaskAndNoteResults(t *testing.T) {
|
||||
for _, result := range results {
|
||||
types[result.Type] = true
|
||||
}
|
||||
require.Equal(t, map[string]bool{"project": true, "task": true, "note": true}, types)
|
||||
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.SaDocument {
|
||||
t.Helper()
|
||||
document := models.SaDocument{
|
||||
Identity: identity, ProjectID: projectID, CreatedBy: 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)
|
||||
|
||||
Reference in New Issue
Block a user