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 }