474 lines
15 KiB
Go
474 lines
15 KiB
Go
package search
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
)
|
|
|
|
// --- ParseSearchMode ---
|
|
|
|
func TestParseSearchMode_Trigram_Cov3(t *testing.T) {
|
|
assert.Equal(t, SearchModeTrigram, ParseSearchMode("trigram"))
|
|
assert.Equal(t, SearchModeTrigram, ParseSearchMode("TRIGRAM"))
|
|
}
|
|
|
|
func TestParseSearchMode_Default_Cov3(t *testing.T) {
|
|
assert.Equal(t, SearchModeILike, ParseSearchMode(""))
|
|
assert.Equal(t, SearchModeILike, ParseSearchMode("unknown"))
|
|
}
|
|
|
|
// --- SearchEngineDB ---
|
|
|
|
func TestSearchEngineDB_Search_Cov3(t *testing.T) {
|
|
repo := &mockSearchRepo{
|
|
conversations: []model.Conversation{
|
|
{Base: model.Base{ID: 1}, AccountID: 1, Status: "open"},
|
|
},
|
|
convCount: 1,
|
|
}
|
|
engine := NewSearchEngineDB(repo)
|
|
resp, err := engine.Search(context.Background(), 1, "test", &SearchFilter{
|
|
Page: 1,
|
|
PerPage: 25,
|
|
SortBy: "created_at",
|
|
SortOrder: "desc",
|
|
})
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
assert.NotEmpty(t, resp.Results)
|
|
}
|
|
|
|
func TestSearchEngineDB_IndexDocument_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
err := engine.IndexDocument(context.Background(), SearchDocument{ID: 1, Type: ResultTypeConversation})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchEngineDB_IndexBatch_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
err := engine.IndexBatch(context.Background(), []SearchDocument{{ID: 1}, {ID: 2}})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchEngineDB_DeleteDocument_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
err := engine.DeleteDocument(context.Background(), ResultTypeConversation, 1, 1)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchEngineDB_Bootstrap_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
err := engine.Bootstrap(context.Background())
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchEngineDB_Close_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
err := engine.Close()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// --- CompanyDocument ---
|
|
|
|
func TestCompanyDocument_Cov3(t *testing.T) {
|
|
company := model.Company{
|
|
ID: 1,
|
|
AccountID: 1,
|
|
Name: "Acme Corp",
|
|
Description: "A company",
|
|
Domain: "acme.com",
|
|
WebsiteURL: "https://acme.com",
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
doc := CompanyDocument(company)
|
|
assert.Equal(t, "Acme Corp", doc.Title)
|
|
assert.Equal(t, ResultTypeCompany, doc.Type)
|
|
assert.Contains(t, doc.Content, "A company")
|
|
assert.Contains(t, doc.Content, "acme.com")
|
|
assert.NotEmpty(t, doc.UID)
|
|
}
|
|
|
|
// --- messageConversationDisplayID ---
|
|
|
|
func TestMessageConversationDisplayID_WithDisplayID_Cov3(t *testing.T) {
|
|
displayID := uint(200)
|
|
conv := &model.Conversation{Base: model.Base{ID: 1}, DisplayID: &displayID}
|
|
assert.Equal(t, uint(200), messageConversationDisplayID(conv))
|
|
}
|
|
|
|
func TestMessageConversationDisplayID_NilConversation_Cov3(t *testing.T) {
|
|
assert.Equal(t, uint(0), messageConversationDisplayID(nil))
|
|
}
|
|
|
|
func TestMessageConversationDisplayID_NoDisplayID_Cov3(t *testing.T) {
|
|
conv := &model.Conversation{Base: model.Base{ID: 5}}
|
|
assert.Equal(t, uint(5), messageConversationDisplayID(conv))
|
|
}
|
|
|
|
func TestMessageConversationDisplayID_ZeroDisplayID_Cov3(t *testing.T) {
|
|
displayID := uint(0)
|
|
conv := &model.Conversation{Base: model.Base{ID: 5}, DisplayID: &displayID}
|
|
assert.Equal(t, uint(5), messageConversationDisplayID(conv))
|
|
}
|
|
|
|
// --- jsonObject ---
|
|
|
|
func TestJsonObject_Empty_Cov3(t *testing.T) {
|
|
result := jsonObject(nil)
|
|
assert.Empty(t, result)
|
|
}
|
|
|
|
func TestJsonObject_NullString_Cov3(t *testing.T) {
|
|
result := jsonObject([]byte("null"))
|
|
assert.Empty(t, result)
|
|
}
|
|
|
|
func TestJsonObject_Valid_Cov3(t *testing.T) {
|
|
result := jsonObject([]byte(`{"key":"value"}`))
|
|
assert.Equal(t, "value", result["key"])
|
|
}
|
|
|
|
func TestJsonObject_Invalid_Cov3(t *testing.T) {
|
|
result := jsonObject([]byte(`{invalid`))
|
|
assert.Empty(t, result)
|
|
}
|
|
|
|
// --- metadataValue ---
|
|
|
|
func TestMetadataValue_Empty_Cov3(t *testing.T) {
|
|
assert.Equal(t, "", metadataValue("", "key"))
|
|
}
|
|
|
|
func TestMetadataValue_InvalidJSON_Cov3(t *testing.T) {
|
|
assert.Equal(t, "", metadataValue("{invalid", "key"))
|
|
}
|
|
|
|
func TestMetadataValue_StringValue_Cov3(t *testing.T) {
|
|
assert.Equal(t, "hello", metadataValue(`{"key":"hello"}`, "key"))
|
|
}
|
|
|
|
func TestMetadataValue_NonStringValue_Cov3(t *testing.T) {
|
|
assert.Equal(t, "", metadataValue(`{"key":123}`, "key"))
|
|
}
|
|
|
|
func TestMetadataValue_MissingKey_Cov3(t *testing.T) {
|
|
assert.Equal(t, "", metadataValue(`{"other":"value"}`, "key"))
|
|
}
|
|
|
|
// --- firstNonEmpty ---
|
|
|
|
func TestFirstNonEmpty_Cov3(t *testing.T) {
|
|
assert.Equal(t, "first", firstNonEmpty("first", "second"))
|
|
assert.Equal(t, "second", firstNonEmpty("", "second"))
|
|
assert.Equal(t, "third", firstNonEmpty(" ", "", "third"))
|
|
assert.Equal(t, "", firstNonEmpty("", " ", ""))
|
|
}
|
|
|
|
// --- conversationSearchTitle ---
|
|
|
|
func TestConversationSearchTitle_WithDisplayID_Cov3(t *testing.T) {
|
|
displayID := uint(200)
|
|
conv := model.Conversation{Base: model.Base{ID: 1}, DisplayID: &displayID}
|
|
title := conversationSearchTitle(conv)
|
|
assert.Contains(t, title, "200")
|
|
}
|
|
|
|
func TestConversationSearchTitle_WithoutDisplayID_Cov3(t *testing.T) {
|
|
conv := model.Conversation{Base: model.Base{ID: 42}}
|
|
title := conversationSearchTitle(conv)
|
|
assert.Contains(t, title, "42")
|
|
}
|
|
|
|
// --- SearchService additional tests ---
|
|
|
|
func TestNewSearchServiceWithEngine_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
assert.NotNil(t, svc)
|
|
assert.NotNil(t, svc.engine)
|
|
}
|
|
|
|
func TestSearchService_SearchResolvedContacts_Cov3(t *testing.T) {
|
|
repo := &mockSearchRepo{
|
|
contacts: []model.Contact{
|
|
{Base: model.Base{ID: 1}, AccountID: 1, Name: "Alice"},
|
|
},
|
|
contactCount: 1,
|
|
}
|
|
svc := NewSearchService(repo)
|
|
results, total, err := svc.SearchResolvedContacts(context.Background(), 1, "alice", &SearchFilter{
|
|
Page: 1, PerPage: 25, SortBy: "created_at", SortOrder: "desc",
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Len(t, results, 1)
|
|
}
|
|
|
|
func TestSearchService_SearchCompanies_Cov3(t *testing.T) {
|
|
repo := &mockSearchRepo{
|
|
companies: []model.Company{
|
|
{ID: 1, AccountID: 1, Name: "Acme"},
|
|
},
|
|
companyCount: 1,
|
|
}
|
|
svc := NewSearchService(repo)
|
|
results, total, err := svc.SearchCompanies(context.Background(), 1, "acme", &SearchFilter{
|
|
Page: 1, PerPage: 25,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Len(t, results, 1)
|
|
}
|
|
|
|
func TestSearchService_SearchCompanies_Error_Cov3(t *testing.T) {
|
|
repo := &mockSearchRepo{
|
|
companyErr: context.DeadlineExceeded,
|
|
}
|
|
svc := NewSearchService(repo)
|
|
_, _, err := svc.SearchCompanies(context.Background(), 1, "acme", &SearchFilter{
|
|
Page: 1, PerPage: 25,
|
|
})
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestSearchService_SearchCompanies_WithEngine_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{
|
|
companies: []model.Company{
|
|
{ID: 1, AccountID: 1, Name: "Acme"},
|
|
},
|
|
companyCount: 1,
|
|
})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
results, total, err := svc.SearchCompanies(context.Background(), 1, "acme", &SearchFilter{
|
|
Page: 1, PerPage: 25,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Len(t, results, 1)
|
|
}
|
|
|
|
func TestSearchService_SearchArticles_WithEngine_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{
|
|
articles: []model.Article{
|
|
{Base: model.Base{ID: 1}, AccountID: 1, Title: "How to"},
|
|
},
|
|
articleCount: 1,
|
|
})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
results, total, err := svc.SearchArticles(context.Background(), 1, "how", &SearchFilter{
|
|
Page: 1, PerPage: 25,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Len(t, results, 1)
|
|
}
|
|
|
|
func TestSearchService_GlobalSearch_WithEngine_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{
|
|
conversations: []model.Conversation{
|
|
{Base: model.Base{ID: 1}, AccountID: 1},
|
|
},
|
|
convCount: 1,
|
|
})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
resp, err := svc.GlobalSearch(context.Background(), 1, "test", &SearchFilter{
|
|
Page: 1, PerPage: 25, SortBy: "created_at", SortOrder: "desc",
|
|
})
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, resp)
|
|
}
|
|
|
|
func TestSearchService_GlobalSearch_WithEngine_Error_Cov3(t *testing.T) {
|
|
engine := &mockEngine_Cov3{err: context.DeadlineExceeded}
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
_, err := svc.GlobalSearch(context.Background(), 1, "test", &SearchFilter{
|
|
Page: 1, PerPage: 25, SortBy: "created_at", SortOrder: "desc",
|
|
})
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestSearchService_IndexDocument_NoEngine_Cov3(t *testing.T) {
|
|
svc := NewSearchService(&mockSearchRepo{})
|
|
err := svc.IndexDocument(context.Background(), SearchDocument{})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_IndexDocument_WithEngine_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
err := svc.IndexDocument(context.Background(), SearchDocument{ID: 1})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_IndexConversation_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
err := svc.IndexConversation(context.Background(), &model.Conversation{Base: model.Base{ID: 1}})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_IndexConversation_Nil_Cov3(t *testing.T) {
|
|
svc := NewSearchService(&mockSearchRepo{})
|
|
err := svc.IndexConversation(context.Background(), nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_DeleteConversation_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
err := svc.DeleteConversation(context.Background(), 1, 1)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_IndexMessage_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
err := svc.IndexMessage(context.Background(), &model.Message{Base: model.Base{ID: 1}})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_IndexMessage_Nil_Cov3(t *testing.T) {
|
|
svc := NewSearchService(&mockSearchRepo{})
|
|
err := svc.IndexMessage(context.Background(), nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_DeleteMessage_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
err := svc.DeleteMessage(context.Background(), 1, 1)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_IndexContact_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
err := svc.IndexContact(context.Background(), &model.Contact{Base: model.Base{ID: 1}})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_IndexContact_Nil_Cov3(t *testing.T) {
|
|
svc := NewSearchService(&mockSearchRepo{})
|
|
err := svc.IndexContact(context.Background(), nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_DeleteContact_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
err := svc.DeleteContact(context.Background(), 1, 1)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestSearchService_IndexCompany_Cov3(t *testing.T) {
|
|
engine := NewSearchEngineDB(&mockSearchRepo{})
|
|
svc := NewSearchServiceWithEngine(engine, &mockSearchRepo{})
|
|
err := svc.IndexCompany(context.Background(), &model.Company{ID: 1})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// --- cloneSearchFilter ---
|
|
|
|
func TestCloneSearchFilter_Nil_Cov3(t *testing.T) {
|
|
clone := cloneSearchFilter(nil)
|
|
assert.NotNil(t, clone)
|
|
assert.Equal(t, 1, clone.Page)
|
|
}
|
|
|
|
func TestCloneSearchFilter_Full_Cov3(t *testing.T) {
|
|
original := &SearchFilter{
|
|
Page: 2,
|
|
PerPage: 50,
|
|
SortBy: "updated_at",
|
|
Types: []SearchResultType{ResultTypeConversation},
|
|
Status: []string{"open"},
|
|
Labels: []string{"vip"},
|
|
AccessibleInboxIDs: []uint{1, 2},
|
|
}
|
|
clone := cloneSearchFilter(original)
|
|
assert.Equal(t, original.Page, clone.Page)
|
|
assert.Equal(t, original.PerPage, clone.PerPage)
|
|
assert.Equal(t, original.Types, clone.Types)
|
|
assert.Equal(t, original.Status, clone.Status)
|
|
assert.Equal(t, original.Labels, clone.Labels)
|
|
assert.Equal(t, original.AccessibleInboxIDs, clone.AccessibleInboxIDs)
|
|
// Verify deep copy
|
|
clone.Types[0] = ResultTypeMessage
|
|
assert.Equal(t, ResultTypeConversation, original.Types[0])
|
|
}
|
|
|
|
// --- withSearchAccess ---
|
|
|
|
func TestSearchService_WithSearchAccess_NoUser_Cov3(t *testing.T) {
|
|
svc := NewSearchService(&mockSearchRepo{})
|
|
filter := &SearchFilter{Page: 1, PerPage: 25}
|
|
result := svc.withSearchAccess(context.Background(), 1, filter)
|
|
assert.False(t, result.EnforceInboxAccess)
|
|
}
|
|
|
|
func TestSearchService_WithSearchAccess_ZeroUser_Cov3(t *testing.T) {
|
|
svc := NewSearchService(&mockSearchRepo{})
|
|
userID := uint(0)
|
|
filter := &SearchFilter{Page: 1, PerPage: 25, CurrentUserID: &userID}
|
|
result := svc.withSearchAccess(context.Background(), 1, filter)
|
|
assert.False(t, result.EnforceInboxAccess)
|
|
}
|
|
|
|
func TestSearchService_WithSearchAccess_WithUser_Cov3(t *testing.T) {
|
|
repo := &mockSearchRepo{
|
|
access: SearchAccess{SkipInboxFiltering: true, InboxIDs: []uint{1, 2}},
|
|
}
|
|
svc := NewSearchService(repo)
|
|
userID := uint(5)
|
|
filter := &SearchFilter{Page: 1, PerPage: 25, CurrentUserID: &userID}
|
|
result := svc.withSearchAccess(context.Background(), 1, filter)
|
|
assert.True(t, result.EnforceInboxAccess == false) // SkipInboxFiltering=true → EnforceInboxAccess=false
|
|
assert.Equal(t, []uint{1, 2}, result.AccessibleInboxIDs)
|
|
}
|
|
|
|
func TestSearchService_WithSearchAccess_AccessError_Cov3(t *testing.T) {
|
|
repo := &mockSearchRepo{
|
|
accessErr: context.DeadlineExceeded,
|
|
}
|
|
svc := NewSearchService(repo)
|
|
userID := uint(5)
|
|
filter := &SearchFilter{Page: 1, PerPage: 25, CurrentUserID: &userID}
|
|
result := svc.withSearchAccess(context.Background(), 1, filter)
|
|
assert.True(t, result.EnforceInboxAccess)
|
|
assert.Nil(t, result.AccessibleInboxIDs)
|
|
}
|
|
|
|
func TestSearchService_WithSearchAccess_NoResolver_Cov3(t *testing.T) {
|
|
svc := &SearchService{searchRepo: &mockSearchRepo{}}
|
|
userID := uint(5)
|
|
filter := &SearchFilter{Page: 1, PerPage: 25, CurrentUserID: &userID}
|
|
result := svc.withSearchAccess(context.Background(), 1, filter)
|
|
assert.False(t, result.EnforceInboxAccess)
|
|
}
|
|
|
|
// --- mockEngine for testing ---
|
|
|
|
type mockEngine_Cov3 struct {
|
|
resp *SearchResponse
|
|
err error
|
|
}
|
|
|
|
func (m *mockEngine_Cov3) Search(ctx context.Context, accountID uint, query string, filter *SearchFilter) (*SearchResponse, error) {
|
|
return m.resp, m.err
|
|
}
|
|
|
|
func (m *mockEngine_Cov3) IndexDocument(ctx context.Context, doc SearchDocument) error { return nil }
|
|
func (m *mockEngine_Cov3) IndexBatch(ctx context.Context, docs []SearchDocument) error { return nil }
|
|
func (m *mockEngine_Cov3) DeleteDocument(ctx context.Context, docType SearchResultType, accountID uint, id uint) error {
|
|
return nil
|
|
}
|
|
func (m *mockEngine_Cov3) Bootstrap(ctx context.Context) error { return nil }
|
|
func (m *mockEngine_Cov3) Close() error { return nil }
|