package email import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" channelpkg "github.com/gochat/gochat/internal/channel" "github.com/gochat/gochat/internal/model" ) // safeCallCov10 runs fn and recovers from panics (nil-dep panics etc). func safeCallCov10(fn func()) { defer func() { _ = recover() }() fn() } // ============================================================ // provider.go — ValidateConfig edge cases // ============================================================ func TestValidateConfig_FloatPort_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) err := p.ValidateConfig(context.Background(), channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_port": float64(993), "imap_email": "user@test.com", "imap_password": "pass", "smtp_address": "smtp.test.com", "smtp_port": float64(587), "smtp_email": "user@test.com", "smtp_password": "pass", }) require.NoError(t, err) } func TestValidateConfig_Int64Port_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) err := p.ValidateConfig(context.Background(), channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_port": int64(993), "imap_email": "user@test.com", "imap_password": "pass", "smtp_address": "smtp.test.com", "smtp_port": int64(587), "smtp_email": "user@test.com", "smtp_password": "pass", }) require.NoError(t, err) } func TestValidateConfig_StringPort_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) err := p.ValidateConfig(context.Background(), channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_port": "993", "imap_email": "user@test.com", "imap_password": "pass", "smtp_address": "smtp.test.com", "smtp_port": "587", "smtp_email": "user@test.com", "smtp_password": "pass", }) require.NoError(t, err) } func TestValidateConfig_InvalidStringPort_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) err := p.ValidateConfig(context.Background(), channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_port": "abc", "imap_email": "user@test.com", "imap_password": "pass", "smtp_address": "smtp.test.com", "smtp_port": "587", "smtp_email": "user@test.com", "smtp_password": "pass", }) require.Error(t, err) assert.Contains(t, err.Error(), "imap_port") } func TestValidateConfig_BoolPort_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) err := p.ValidateConfig(context.Background(), channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_port": true, "imap_email": "user@test.com", "imap_password": "pass", "smtp_address": "smtp.test.com", "smtp_port": 587, "smtp_email": "user@test.com", "smtp_password": "pass", }) require.Error(t, err) assert.Contains(t, err.Error(), "imap_port") } func TestValidateConfig_DefaultPort_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) // Missing imap_port entirely → default 0 → error err := p.ValidateConfig(context.Background(), channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_email": "user@test.com", "imap_password": "pass", "smtp_address": "smtp.test.com", "smtp_port": 587, "smtp_email": "user@test.com", "smtp_password": "pass", }) require.Error(t, err) assert.Contains(t, err.Error(), "imap_port") } // ============================================================ // provider.go — OnCreate forward_to_address path // ============================================================ func TestOnCreate_ForwardToAddressSet_Cov10(t *testing.T) { // OnCreate with a Service that has nil imap/smtp → will panic on nil dep. // Use safeCall to handle that. p := NewEmailProvider(&Service{}, nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}} safeCallCov10(func() { cfg, err := p.OnCreate(context.Background(), inbox, channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_port": 993, "imap_email": "user@test.com", "imap_password": "pass", "smtp_address": "smtp.test.com", "smtp_port": 587, "smtp_email": "user@test.com", "smtp_password": "pass", }) // Will error on IMAP validation, but the smtp_email is set in config _ = cfg _ = err }) } func TestOnCreate_NilService_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}} safeCallCov10(func() { _, _ = p.OnCreate(context.Background(), inbox, channelpkg.ChannelConfig{}) }) } func TestOnCreate_SMTPFail_Cov10(t *testing.T) { // Use a Service with nil imapListener → IMAP validation panics p := &EmailProvider{ service: &Service{}, } inbox := &model.Inbox{Base: model.Base{ID: 1}} safeCallCov10(func() { _, _ = p.OnCreate(context.Background(), inbox, channelpkg.ChannelConfig{ "imap_address": "127.0.0.1", "imap_port": 1, "imap_email": "user@test.com", "imap_password": "pass", "smtp_address": "smtp.test.com", "smtp_port": 587, "smtp_email": "user@test.com", "smtp_password": "pass", }) }) } // ============================================================ // provider.go — ProcessIncoming with various EmailMessage fields // ============================================================ func TestProcessIncoming_WithHTMLContent_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} emailMsg := EmailMessage{ MessageID: "msg-html", FromAddress: "sender@test.com", FromName: "Sender", Subject: "HTML Test", HTMLContent: "

Hello

", TextContent: "Hello", ToAddresses: []string{"to@test.com"}, CcAddresses: []string{"cc@test.com"}, ReplyToAddress: "reply@test.com", References: "", } payload, _ := json.Marshal(emailMsg) msg, err := p.ProcessIncoming(context.Background(), inbox, payload) require.NoError(t, err) require.NotNil(t, msg) assert.Equal(t, "msg-html", msg.SourceID) assert.Equal(t, "Hello", msg.Content) assert.Equal(t, "HTML Test", msg.Extra["subject"]) assert.Equal(t, "

Hello

", msg.Extra["html_content"]) assert.Equal(t, "reply@test.com", msg.Extra["reply_to_address"]) } func TestProcessIncoming_WithUID_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} emailMsg := EmailMessage{ MessageID: "msg-uid", UID: 42, } payload, _ := json.Marshal(emailMsg) msg, err := p.ProcessIncoming(context.Background(), inbox, payload) require.NoError(t, err) require.NotNil(t, msg) // UID should be in Extra uidVal, ok := msg.Extra["imap_uid"] assert.True(t, ok) _ = uidVal } func TestProcessIncoming_NoInReplyTo_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} emailMsg := EmailMessage{ MessageID: "msg-no-reply", FromAddress: "sender@test.com", TextContent: "hello", } payload, _ := json.Marshal(emailMsg) msg, err := p.ProcessIncoming(context.Background(), inbox, payload) require.NoError(t, err) require.NotNil(t, msg) assert.Empty(t, msg.ReplyToID) } func TestProcessIncoming_WithInReplyTo_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} emailMsg := EmailMessage{ MessageID: "msg-reply", InReplyTo: "parent-msg-id", } payload, _ := json.Marshal(emailMsg) msg, err := p.ProcessIncoming(context.Background(), inbox, payload) require.NoError(t, err) require.NotNil(t, msg) assert.Equal(t, "parent-msg-id", msg.ReplyToID) assert.Equal(t, "parent-msg-id", msg.ConversationID) } func TestProcessIncoming_EmptyPayload_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}} _, err := p.ProcessIncoming(context.Background(), inbox, []byte{}) require.Error(t, err) } func TestProcessIncoming_NilPayload_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}} _, err := p.ProcessIncoming(context.Background(), inbox, nil) require.Error(t, err) } func TestProcessIncoming_MultipleAttachments_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} emailMsg := EmailMessage{ MessageID: "msg-multi-att", FromAddress: "sender@test.com", TextContent: "see attachments", Attachments: []EmailAttachment{ {Filename: "file1.pdf", ContentType: "application/pdf", URL: "https://example.com/f1", FileSize: 1024}, {Filename: "file2.png", ContentType: "image/png", URL: "https://example.com/f2", FileSize: 2048}, {Filename: "file3.txt", ContentType: "text/plain", URL: "https://example.com/f3"}, }, } payload, _ := json.Marshal(emailMsg) msg, err := p.ProcessIncoming(context.Background(), inbox, payload) require.NoError(t, err) require.NotNil(t, msg) assert.Len(t, msg.Attachments, 3) assert.Equal(t, "file1.pdf", msg.Attachments[0].Filename) assert.Equal(t, int64(1024), msg.Attachments[0].FileSize) assert.Equal(t, "file3.txt", msg.Attachments[2].Filename) } // ============================================================ // provider.go — PollMessages with messages // ============================================================ func TestPollMessages_WithMessages_Cov10(t *testing.T) { // PollMessages with a nil service → will panic, use safeCall p := &EmailProvider{} inbox := &model.Inbox{Base: model.Base{ID: 1}} safeCallCov10(func() { _, _ = p.PollMessages(context.Background(), inbox) }) } func TestPollMessages_WithValidConfig_Cov10(t *testing.T) { p := NewEmailProvider(NewService(), nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}} inbox.ChannelConfig = `{"imap_address":"127.0.0.1","imap_port":1,"imap_login":"user","imap_password":"pass","imap_ssl_mode":"none"}` // Will fail on connection but exercise the config parsing path _, _ = p.PollMessages(context.Background(), inbox) } func TestPollMessages_NilInbox_Cov10(t *testing.T) { p := &EmailProvider{} safeCallCov10(func() { _, _ = p.PollMessages(context.Background(), nil) }) } // ============================================================ // provider.go — SendMessage edge cases // ============================================================ func TestSendMessage_EmptyContact_Cov10(t *testing.T) { p := NewEmailProvider(NewService(), nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}} inbox.ChannelConfig = `{"smtp_email":"from@test.com"}` contact := &model.Contact{} _, err := p.SendMessage(context.Background(), inbox, &model.Message{Content: "test"}, contact) require.Error(t, err) assert.Contains(t, err.Error(), "source_id") } func TestSendMessage_NilPipeline_Cov10(t *testing.T) { p := NewEmailProvider(NewService(), nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}} inbox.ChannelConfig = `{"smtp_email":"from@test.com"}` contact := &model.Contact{SourceID: "to@test.com"} // This will attempt SMTP send and fail with network error _, err := p.SendMessage(context.Background(), inbox, &model.Message{Content: "test"}, contact) require.Error(t, err) } func TestSendMessage_SuccessResult_Cov10(t *testing.T) { // Test the result structure when SMTP succeeds (unlikely without a server) // But we can verify the error path p := NewEmailProvider(NewService(), nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 5}} inbox.ChannelConfig = `{"smtp_email":"from@test.com","smtp_address":"127.0.0.1","smtp_port":1,"smtp_login":"user","smtp_password":"pass","smtp_ssl_mode":"none"}` contact := &model.Contact{SourceID: "to@test.com"} msg := &model.Message{Content: "hello world"} _, err := p.SendMessage(context.Background(), inbox, msg, contact) require.Error(t, err) assert.Contains(t, err.Error(), "SMTP") } // ============================================================ // provider.go — GetContactProfile // ============================================================ func TestGetContactProfile_EmptySource_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{} profile, err := p.GetContactProfile(context.Background(), inbox, "") require.NoError(t, err) require.NotNil(t, profile) assert.Equal(t, "", profile.Name) assert.Equal(t, "", profile.Extra["email"]) } func TestGetContactProfile_WithDomain_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{} profile, err := p.GetContactProfile(context.Background(), inbox, "user@example.com") require.NoError(t, err) require.NotNil(t, profile) assert.Equal(t, "user@example.com", profile.Name) assert.Equal(t, "user@example.com", profile.Extra["email"]) } // ============================================================ // provider.go — ConfigSchema properties // ============================================================ func TestConfigSchema_AllProperties_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) schema := p.ConfigSchema() require.NotNil(t, schema) expectedProps := []string{ "imap_address", "imap_port", "imap_email", "imap_password", "imap_ssl", "smtp_address", "smtp_port", "smtp_email", "smtp_password", "smtp_ssl", } for _, prop := range expectedProps { assert.Contains(t, schema.Properties, prop, "missing property: %s", prop) } // Check imap_port default assert.Equal(t, 993, schema.Properties["imap_port"].Default) assert.Equal(t, 587, schema.Properties["smtp_port"].Default) // Check required fields for _, req := range expectedProps { if req == "imap_ssl" || req == "smtp_ssl" { continue // These are not required } assert.Contains(t, schema.Required, req, "missing required: %s", req) } // Check secret fields assert.True(t, schema.Properties["imap_password"].Secret) assert.True(t, schema.Properties["smtp_password"].Secret) // Check email format assert.Equal(t, "email", schema.Properties["imap_email"].Format) assert.Equal(t, "email", schema.Properties["smtp_email"].Format) } func TestDefaultConfig_Values_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) cfg := p.DefaultConfig() assert.Equal(t, 993, cfg["imap_port"]) assert.Equal(t, 587, cfg["smtp_port"]) assert.True(t, cfg["imap_ssl"].(bool)) assert.True(t, cfg["smtp_ssl"].(bool)) } // ============================================================ // provider.go — Capabilities // ============================================================ func TestCapabilities_FullCheck_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) caps := p.Capabilities() assert.True(t, caps.SupportsAttachments) assert.False(t, caps.SupportsLocation) assert.False(t, caps.SupportsTypingIndicator) assert.True(t, caps.SupportsDeliveryStatus) assert.True(t, caps.SupportsReplies) assert.False(t, caps.SupportsEmojiReactions) assert.False(t, caps.SupportsVoiceMessages) assert.False(t, caps.SupportsVideoCalls) assert.False(t, caps.SupportsCustomCards) assert.False(t, caps.SupportsTemplates) assert.True(t, caps.SupportsEmailHeaders) assert.Equal(t, int64(25*1024*1024), caps.MaxAttachmentSize) assert.Equal(t, 0, caps.MaxTextLength) } // ============================================================ // smtp_sender.go — Send with various configs // ============================================================ func TestSMTPSend_DefaultSSLMode_Cov10(t *testing.T) { s := NewSMTPSender() _, err := s.Send(channelpkg.ChannelConfig{ "smtp_address": "127.0.0.1", "smtp_port": 1, "smtp_login": "user", "smtp_password": "pass", }, "from@test.com", "to@test.com", "subj", "body") require.Error(t, err) } func TestSMTPSend_EmptyFrom_Cov10(t *testing.T) { s := NewSMTPSender() _, err := s.Send(channelpkg.ChannelConfig{ "smtp_address": "127.0.0.1", "smtp_port": 1, "smtp_login": "user", "smtp_password": "pass", "smtp_ssl_mode": "none", "email": "default@test.com", }, "", "to@test.com", "subj", "body") require.Error(t, err) } func TestSMTPSend_WithMailboxName_Cov10(t *testing.T) { s := NewSMTPSender() _, err := s.Send(channelpkg.ChannelConfig{ "smtp_address": "127.0.0.1", "smtp_port": 1, "smtp_login": "user", "smtp_password": "pass", "smtp_ssl_mode": "none", "mailbox_name": "Support Team", }, "from@test.com", "to@test.com", "subj", "body") require.Error(t, err) } func TestSMTPSend_SSLMode_Cov10(t *testing.T) { s := NewSMTPSender() _, err := s.Send(channelpkg.ChannelConfig{ "smtp_address": "127.0.0.1", "smtp_port": 1, "smtp_login": "user", "smtp_password": "pass", "smtp_ssl_mode": "ssl", }, "from@test.com", "to@test.com", "subj", "body") require.Error(t, err) assert.Contains(t, err.Error(), "TLS") } func TestSMTPSend_STARTTLSMode_Cov10(t *testing.T) { s := NewSMTPSender() _, err := s.Send(channelpkg.ChannelConfig{ "smtp_address": "127.0.0.1", "smtp_port": 1, "smtp_login": "user", "smtp_password": "pass", "smtp_ssl_mode": "starttls", }, "from@test.com", "to@test.com", "subj", "body") require.Error(t, err) } func TestSMTPSend_NoPassword_Cov10(t *testing.T) { s := NewSMTPSender() _, err := s.Send(channelpkg.ChannelConfig{ "smtp_address": "smtp.test.com", "smtp_login": "user", }, "from@test.com", "to@test.com", "subj", "body") // address and login are set but password is missing → "address and login required" only checks those two // So it proceeds to connection attempt require.Error(t, err) } func TestSMTPValidateConnection_DefaultMode_Cov10(t *testing.T) { s := NewSMTPSender() err := s.ValidateConnection(channelpkg.ChannelConfig{ "smtp_address": "127.0.0.1", "smtp_port": 1, "smtp_login": "user", "smtp_password": "pass", }) require.Error(t, err) } func TestSMTPValidateConnection_SSLMode_Cov10(t *testing.T) { s := NewSMTPSender() err := s.ValidateConnection(channelpkg.ChannelConfig{ "smtp_address": "127.0.0.1", "smtp_port": 1, "smtp_login": "user", "smtp_password": "pass", "smtp_ssl_mode": "ssl", }) require.Error(t, err) } func TestSMTPValidateConnection_STARTTLSMode_Cov10(t *testing.T) { s := NewSMTPSender() err := s.ValidateConnection(channelpkg.ChannelConfig{ "smtp_address": "127.0.0.1", "smtp_port": 1, "smtp_login": "user", "smtp_password": "pass", "smtp_ssl_mode": "starttls", }) require.Error(t, err) } // ============================================================ // smtp_sender.go — composeSMTPMessage edge cases // ============================================================ func TestComposeSMTPMessage_EmptyBody_Cov10(t *testing.T) { msg := composeSMTPMessage("from@test.com", "to@test.com", "Test", "", "") assert.Contains(t, msg, "From: from@test.com") assert.Contains(t, msg, "To: to@test.com") assert.Contains(t, msg, "Subject: Test") assert.Contains(t, msg, "Message-Id: ") assert.Contains(t, msg, "MIME-Version: 1.0") assert.Contains(t, msg, "Content-Type: text/plain; charset=UTF-8") assert.Contains(t, msg, "Content-Transfer-Encoding: 8bit") } func TestComposeSMTPMessage_HTMLBody_Cov10(t *testing.T) { body := "

Hello

" msg := composeSMTPMessage("from@test.com", "to@test.com", "HTML Test", body, "") assert.Contains(t, msg, "Content-Type: text/html; charset=UTF-8") assert.Contains(t, msg, body) } func TestComposeSMTPMessage_DivBody_Cov10(t *testing.T) { body := "
Content
" msg := composeSMTPMessage("from@test.com", "to@test.com", "Div Test", body, "") assert.Contains(t, msg, "Content-Type: text/html; charset=UTF-8") } func TestComposeSMTPMessage_PTagBody_Cov10(t *testing.T) { body := "

Paragraph

" msg := composeSMTPMessage("from@test.com", "to@test.com", "P Test", body, "") assert.Contains(t, msg, "Content-Type: text/html; charset=UTF-8") } func TestComposeSMTPMessage_BrTagBody_Cov10(t *testing.T) { body := "Line 1
Line 2" msg := composeSMTPMessage("from@test.com", "to@test.com", "BR Test", body, "") assert.Contains(t, msg, "Content-Type: text/html; charset=UTF-8") } func TestComposeSMTPMessage_BoldTagBody_Cov10(t *testing.T) { body := "Bold text" msg := composeSMTPMessage("from@test.com", "to@test.com", "Bold Test", body, "") assert.Contains(t, msg, "Content-Type: text/html; charset=UTF-8") } func TestComposeSMTPMessage_ItalicTagBody_Cov10(t *testing.T) { body := "Italic text" msg := composeSMTPMessage("from@test.com", "to@test.com", "Italic Test", body, "") assert.Contains(t, msg, "Content-Type: text/html; charset=UTF-8") } func TestComposeSMTPMessage_PlainTextBody_Cov10(t *testing.T) { body := "This is plain text" msg := composeSMTPMessage("from@test.com", "to@test.com", "Plain Test", body, "") assert.Contains(t, msg, "Content-Type: text/plain; charset=UTF-8") } func TestComposeSMTPMessage_SpecialChars_Cov10(t *testing.T) { msg := composeSMTPMessage("from@test.com", "to@test.com", "Subjéct wîth spéciål", "Bödý", "") assert.Contains(t, msg, "Subjéct wîth spéciål") } // ============================================================ // smtp_sender.go — extractMessageIDFromRaw edge cases // ============================================================ func TestExtractMessageIDFromRaw_CapitalID_Cov10(t *testing.T) { msg := []byte("From: test@test.com\r\nMessage-ID: \r\n\r\nbody") id := extractMessageIDFromRaw(msg) assert.Equal(t, "", id) } func TestExtractMessageIDFromRaw_MixedCase_Cov10(t *testing.T) { msg := []byte("From: test@test.com\r\nMessage-Id: \r\n\r\nbody") id := extractMessageIDFromRaw(msg) assert.Equal(t, "", id) } func TestExtractMessageIDFromRaw_NoMessageID_Cov10(t *testing.T) { msg := []byte("From: test@test.com\r\nSubject: Test\r\n\r\nbody") id := extractMessageIDFromRaw(msg) assert.Equal(t, "", id) } func TestExtractMessageIDFromRaw_Empty_Cov10(t *testing.T) { id := extractMessageIDFromRaw([]byte{}) assert.Equal(t, "", id) } func TestExtractMessageIDFromRaw_OnlyNewlines_Cov10(t *testing.T) { id := extractMessageIDFromRaw([]byte("\r\n\r\n")) assert.Equal(t, "", id) } // ============================================================ // imap_listener.go — connectIMAP with various SSL modes // ============================================================ func TestConnectIMAP_SSLMode_Cov10(t *testing.T) { l := &IMAPListener{} _, err := l.connectIMAP("127.0.0.1:1", "ssl") require.Error(t, err) assert.Contains(t, err.Error(), "TLS") } func TestConnectIMAP_StartTLSMode_Cov10(t *testing.T) { l := &IMAPListener{} _, err := l.connectIMAP("127.0.0.1:1", "starttls") require.Error(t, err) } func TestConnectIMAP_NoneMode_Cov10(t *testing.T) { l := &IMAPListener{} _, err := l.connectIMAP("127.0.0.1:1", "none") require.Error(t, err) } func TestConnectIMAP_DefaultMode_Cov10(t *testing.T) { l := &IMAPListener{} _, err := l.connectIMAP("127.0.0.1:1", "") require.Error(t, err) assert.Contains(t, err.Error(), "TLS") } func TestConnectIMAP_UnknownMode_Cov10(t *testing.T) { l := &IMAPListener{} _, err := l.connectIMAP("127.0.0.1:1", "unknown") require.Error(t, err) // Default mode is SSL assert.Contains(t, err.Error(), "TLS") } // ============================================================ // imap_listener.go — Fetch with various configs // ============================================================ func TestFetch_MissingLogin_Cov10(t *testing.T) { l := NewIMAPListener() inbox := &model.Inbox{Base: model.Base{ID: 1}} _, err := l.Fetch(context.Background(), inbox, channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_port": 993, }) require.Error(t, err) assert.Contains(t, err.Error(), "configuration incomplete") } func TestFetch_MissingPassword_Cov10(t *testing.T) { l := NewIMAPListener() inbox := &model.Inbox{Base: model.Base{ID: 1}} _, err := l.Fetch(context.Background(), inbox, channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_login": "user", }) require.Error(t, err) assert.Contains(t, err.Error(), "configuration incomplete") } func TestFetch_WithMailboxName_Cov10(t *testing.T) { l := NewIMAPListener() inbox := &model.Inbox{Base: model.Base{ID: 1}} _, err := l.Fetch(context.Background(), inbox, channelpkg.ChannelConfig{ "imap_address": "127.0.0.1", "imap_port": 1, "imap_login": "user", "imap_password": "pass", "imap_ssl_mode": "none", "mailbox_name": "CustomBox", }) require.Error(t, err) } func TestFetch_DefaultMailbox_Cov10(t *testing.T) { l := NewIMAPListener() inbox := &model.Inbox{Base: model.Base{ID: 1}} _, err := l.Fetch(context.Background(), inbox, channelpkg.ChannelConfig{ "imap_address": "127.0.0.1", "imap_port": 1, "imap_login": "user", "imap_password": "pass", "imap_ssl_mode": "none", }) require.Error(t, err) } func TestFetch_NilInbox_Cov10(t *testing.T) { l := NewIMAPListener() safeCallCov10(func() { _, _ = l.Fetch(context.Background(), nil, channelpkg.ChannelConfig{ "imap_address": "127.0.0.1", "imap_port": 1, "imap_login": "user", "imap_password": "pass", "imap_ssl_mode": "none", }) }) } // ============================================================ // imap_listener.go — ValidateConnection with various configs // ============================================================ func TestValidateConnection_WithMailboxName_Cov10(t *testing.T) { l := NewIMAPListener() err := l.ValidateConnection(channelpkg.ChannelConfig{ "imap_address": "127.0.0.1", "imap_port": 1, "imap_login": "user", "imap_password": "pass", "imap_ssl_mode": "none", "mailbox_name": "Sent", }) require.Error(t, err) } func TestValidateConnection_DefaultMailbox_Cov10(t *testing.T) { l := NewIMAPListener() err := l.ValidateConnection(channelpkg.ChannelConfig{ "imap_address": "127.0.0.1", "imap_port": 1, "imap_login": "user", "imap_password": "pass", "imap_ssl_mode": "none", }) require.Error(t, err) } func TestValidateConnection_SSLMode_Cov10(t *testing.T) { l := NewIMAPListener() err := l.ValidateConnection(channelpkg.ChannelConfig{ "imap_address": "127.0.0.1", "imap_port": 1, "imap_login": "user", "imap_password": "pass", "imap_ssl_mode": "ssl", }) require.Error(t, err) } func TestValidateConnection_StartTLSMode_Cov10(t *testing.T) { l := NewIMAPListener() err := l.ValidateConnection(channelpkg.ChannelConfig{ "imap_address": "127.0.0.1", "imap_port": 1, "imap_login": "user", "imap_password": "pass", "imap_ssl_mode": "starttls", }) require.Error(t, err) } func TestValidateConnection_DefaultMode_Cov10(t *testing.T) { l := NewIMAPListener() err := l.ValidateConnection(channelpkg.ChannelConfig{ "imap_address": "127.0.0.1", "imap_port": 1, "imap_login": "user", "imap_password": "pass", }) require.Error(t, err) } func TestValidateConnection_MissingAddress_Cov10(t *testing.T) { l := NewIMAPListener() err := l.ValidateConnection(channelpkg.ChannelConfig{ "imap_login": "user", "imap_password": "pass", }) require.Error(t, err) assert.Contains(t, err.Error(), "required") } func TestValidateConnection_MissingLogin_Cov10(t *testing.T) { l := NewIMAPListener() err := l.ValidateConnection(channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_password": "pass", }) require.Error(t, err) assert.Contains(t, err.Error(), "required") } func TestValidateConnection_MissingPassword_Cov10(t *testing.T) { l := NewIMAPListener() err := l.ValidateConnection(channelpkg.ChannelConfig{ "imap_address": "imap.test.com", "imap_login": "user", }) require.Error(t, err) assert.Contains(t, err.Error(), "required") } // ============================================================ // imap_listener.go — parseMessage with nil // ============================================================ func TestParseMessage_NilClient_Cov10(t *testing.T) { l := &IMAPListener{} safeCallCov10(func() { _, _ = l.parseMessage(context.Background(), nil, nil, "", &model.Inbox{}) }) } func TestParseMessage_NilInbox_Cov10(t *testing.T) { l := &IMAPListener{} safeCallCov10(func() { _, _ = l.parseMessage(context.Background(), nil, nil, "", nil) }) } // ============================================================ // webhook_handler.go — HandleWebhookRequest edge cases // ============================================================ func TestHandleWebhookRequest_EmptyBody_Cov10(t *testing.T) { h := NewWebhookHandler() w := httptest.NewRecorder() r := httptest.NewRequest("POST", "/webhook", nil) h.HandleWebhookRequest(w, r) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestHandleWebhookRequest_ValidMailgun_Cov10(t *testing.T) { h := NewWebhookHandler() w := httptest.NewRecorder() body := `{"from":"sender@example.com","subject":"Test","body-plain":"Hello","to":"recipient@example.com","Message-Id":"msg-1"}` r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body)) r.Header.Set("X-Mailgun-Sid", "12345") r.Header.Set("Content-Type", "application/json") h.HandleWebhookRequest(w, r) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "accepted") } func TestHandleWebhookRequest_ValidSendGrid_Cov10(t *testing.T) { h := NewWebhookHandler() w := httptest.NewRecorder() body := `{"envelope":{"from":"sender@example.com","to":["recipient@example.com"]},"subject":"Test","text":"Hello"}` r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body)) r.Header.Set("X-Sg-Envelope-Id", "env-123") r.Header.Set("Content-Type", "application/json") h.HandleWebhookRequest(w, r) assert.Equal(t, http.StatusOK, w.Code) } func TestHandleWebhookRequest_GenericJSON_Cov10(t *testing.T) { h := NewWebhookHandler() w := httptest.NewRecorder() body := `{"from":"sender@example.com","subject":"Test","text":"Hello","to":"recipient@example.com"}` r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body)) r.Header.Set("Content-Type", "application/json") h.HandleWebhookRequest(w, r) assert.Equal(t, http.StatusOK, w.Code) } func TestHandleWebhookRequest_RawEmail_Cov10(t *testing.T) { h := NewWebhookHandler() w := httptest.NewRecorder() body := "From: sender@example.com\r\nSubject: Test\r\n\r\nHello world" r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body)) h.HandleWebhookRequest(w, r) assert.Equal(t, http.StatusOK, w.Code) } func TestHandleWebhookRequest_InvalidMailgunJSON_Cov10(t *testing.T) { h := NewWebhookHandler() w := httptest.NewRecorder() body := "invalid json" r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body)) r.Header.Set("X-Mailgun-Sid", "12345") h.HandleWebhookRequest(w, r) // Should still return 200 to prevent retries assert.Equal(t, http.StatusOK, w.Code) } func TestHandleWebhookRequest_InvalidSendGridJSON_Cov10(t *testing.T) { h := NewWebhookHandler() w := httptest.NewRecorder() body := "invalid json" r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body)) r.Header.Set("X-Sg-Envelope-Id", "env-123") h.HandleWebhookRequest(w, r) assert.Equal(t, http.StatusOK, w.Code) } func TestHandleWebhookRequest_InvalidGenericJSON_Cov10(t *testing.T) { h := NewWebhookHandler() w := httptest.NewRecorder() body := "not json at all" r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body)) r.Header.Set("Content-Type", "application/json") h.HandleWebhookRequest(w, r) // Falls through to raw email parsing assert.Equal(t, http.StatusOK, w.Code) } func TestHandleWebhookRequest_NoContentType_Cov10(t *testing.T) { h := NewWebhookHandler() w := httptest.NewRecorder() body := "From: sender@example.com\r\nSubject: Test\r\n\r\nHello" r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body)) h.HandleWebhookRequest(w, r) assert.Equal(t, http.StatusOK, w.Code) } // ============================================================ // webhook_handler.go — ParseWebhookBody // ============================================================ func TestParseWebhookBody_Mailgun_Cov10(t *testing.T) { h := NewWebhookHandler() body := `{"from":"sender@example.com","subject":"Test","body-plain":"Hello","to":"recipient@example.com","Message-Id":"msg-1"}` headers := http.Header{} headers.Set("X-Mailgun-Sid", "12345") msg, err := h.ParseWebhookBody([]byte(body), headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) assert.Equal(t, "Test", msg.Subject) assert.Equal(t, "Hello", msg.TextContent) assert.Equal(t, "msg-1", msg.MessageID) assert.Equal(t, []string{"recipient@example.com"}, msg.ToAddresses) } func TestParseWebhookBody_SendGrid_Cov10(t *testing.T) { h := NewWebhookHandler() body := `{"envelope":{"from":"sender@example.com","to":["recipient@example.com"]},"subject":"Test","text":"Hello","html":"

Hello

"}` headers := http.Header{} headers.Set("X-Sg-Envelope-Id", "env-123") msg, err := h.ParseWebhookBody([]byte(body), headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) assert.Equal(t, "Test", msg.Subject) assert.Equal(t, "Hello", msg.TextContent) assert.Equal(t, "

Hello

", msg.HTMLContent) assert.Equal(t, []string{"recipient@example.com"}, msg.ToAddresses) } func TestParseWebhookBody_GenericJSON_Cov10(t *testing.T) { h := NewWebhookHandler() body := `{"from":"sender@example.com","subject":"Test","text":"Hello","to":"recipient@example.com"}` headers := http.Header{} headers.Set("Content-Type", "application/json") msg, err := h.ParseWebhookBody([]byte(body), headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) assert.Equal(t, "Test", msg.Subject) assert.Equal(t, []string{"recipient@example.com"}, msg.ToAddresses) } func TestParseWebhookBody_RawEmail_Cov10(t *testing.T) { h := NewWebhookHandler() body := "From: sender@example.com\r\nSubject: Test\r\n\r\nHello world" msg, err := h.ParseWebhookBody([]byte(body), http.Header{}) require.NoError(t, err) assert.NotEmpty(t, msg.TextContent) assert.Contains(t, msg.TextContent, "Hello world") } func TestParseWebhookBody_MailgunWithAllFields_Cov10(t *testing.T) { h := NewWebhookHandler() body := `{"from":"sender@example.com","subject":"Re: Test","body-plain":"Hello","body-html":"

Hello

","to":"recipient@example.com","Message-Id":"msg-full","In-Reply-To":"parent","References":"","Date":"2024-01-01"}` headers := http.Header{} headers.Set("X-Mailgun-Sid", "12345") msg, err := h.ParseWebhookBody([]byte(body), headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) assert.Equal(t, "Re: Test", msg.Subject) assert.Equal(t, "Hello", msg.TextContent) assert.Equal(t, "

Hello

", msg.HTMLContent) assert.Equal(t, "msg-full", msg.MessageID) assert.Equal(t, "parent", msg.InReplyTo) assert.Equal(t, "", msg.References) assert.Equal(t, "2024-01-01", msg.Date) } func TestParseWebhookBody_SendGridWithAllFields_Cov10(t *testing.T) { h := NewWebhookHandler() body := `{"envelope":{"from":"sender@example.com","to":["r1@example.com","r2@example.com"]},"subject":"Test","text":"Hello","html":"

Hello

","messageId":"msg-sg","in_reply_to":"parent","date":"2024-01-01"}` headers := http.Header{} headers.Set("X-Sg-Envelope-Id", "env-123") msg, err := h.ParseWebhookBody([]byte(body), headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) assert.Len(t, msg.ToAddresses, 2) assert.Equal(t, "r1@example.com", msg.ToAddresses[0]) assert.Equal(t, "r2@example.com", msg.ToAddresses[1]) assert.Equal(t, "msg-sg", msg.MessageID) assert.Equal(t, "parent", msg.InReplyTo) assert.Equal(t, "2024-01-01", msg.Date) } func TestParseWebhookBody_SendGridNoEnvelope_Cov10(t *testing.T) { h := NewWebhookHandler() body := `{"subject":"Test","text":"Hello"}` headers := http.Header{} headers.Set("X-Sg-Envelope-Id", "env-123") msg, err := h.ParseWebhookBody([]byte(body), headers) require.NoError(t, err) assert.Equal(t, "Test", msg.Subject) assert.Equal(t, "Hello", msg.TextContent) assert.Empty(t, msg.FromAddress) } func TestParseWebhookBody_SendGridEnvelopeNoTo_Cov10(t *testing.T) { h := NewWebhookHandler() body := `{"envelope":{"from":"sender@example.com"},"subject":"Test"}` headers := http.Header{} headers.Set("X-Sg-Envelope-Id", "env-123") msg, err := h.ParseWebhookBody([]byte(body), headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) assert.Nil(t, msg.ToAddresses) } func TestParseWebhookBody_SendGridBadJSON_Cov10(t *testing.T) { h := NewWebhookHandler() body := "invalid json" headers := http.Header{} headers.Set("X-Sg-Envelope-Id", "env-123") _, err := h.ParseWebhookBody([]byte(body), headers) require.Error(t, err) } func TestParseWebhookBody_MailgunBadJSON_Cov10(t *testing.T) { h := NewWebhookHandler() body := "invalid json" headers := http.Header{} headers.Set("X-Mailgun-Sid", "12345") _, err := h.ParseWebhookBody([]byte(body), headers) require.Error(t, err) } func TestParseWebhookBody_MailgunNoTo_Cov10(t *testing.T) { h := NewWebhookHandler() body := `{"from":"sender@example.com","subject":"Test"}` headers := http.Header{} headers.Set("X-Mailgun-Sid", "12345") msg, err := h.ParseWebhookBody([]byte(body), headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) assert.Nil(t, msg.ToAddresses) } func TestParseWebhookBody_GenericJSONNoTo_Cov10(t *testing.T) { h := NewWebhookHandler() body := `{"from":"sender@example.com","subject":"Test"}` headers := http.Header{} headers.Set("Content-Type", "application/json") msg, err := h.ParseWebhookBody([]byte(body), headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) assert.Nil(t, msg.ToAddresses) } func TestParseWebhookBody_GenericJSONWithHTML_Cov10(t *testing.T) { h := NewWebhookHandler() body := `{"from":"sender@example.com","subject":"Test","html":"

HTML

","message_id":"msg-1","in_reply_to":"parent","date":"2024-01-01"}` headers := http.Header{} headers.Set("Content-Type", "application/json") msg, err := h.ParseWebhookBody([]byte(body), headers) require.NoError(t, err) assert.Equal(t, "

HTML

", msg.HTMLContent) assert.Equal(t, "msg-1", msg.MessageID) assert.Equal(t, "parent", msg.InReplyTo) assert.Equal(t, "2024-01-01", msg.Date) } // ============================================================ // webhook_handler.go — isMailgunPayload / isSendGridPayload // ============================================================ func TestIsMailgunPayload_True_Cov10(t *testing.T) { headers := http.Header{} headers.Set("X-Mailgun-Sid", "12345") assert.True(t, isMailgunPayload(headers)) } func TestIsMailgunPayload_False_Cov10(t *testing.T) { headers := http.Header{} assert.False(t, isMailgunPayload(headers)) } func TestIsSendGridPayload_True_Cov10(t *testing.T) { headers := http.Header{} headers.Set("X-Sg-Envelope-Id", "env-123") assert.True(t, isSendGridPayload(headers)) } func TestIsSendGridPayload_False_Cov10(t *testing.T) { headers := http.Header{} assert.False(t, isSendGridPayload(headers)) } // ============================================================ // webhook_handler.go — parseRelayPayload direct calls // ============================================================ func TestParseRelayPayload_Mailgun_Cov10(t *testing.T) { body := []byte(`{"from":"sender@example.com","subject":"Test","body-plain":"Hello"}`) headers := http.Header{} headers.Set("X-Mailgun-Sid", "12345") msg, err := parseRelayPayload(body, headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) } func TestParseRelayPayload_SendGrid_Cov10(t *testing.T) { body := []byte(`{"envelope":{"from":"sender@example.com","to":["r@example.com"]},"subject":"Test"}`) headers := http.Header{} headers.Set("X-Sg-Envelope-Id", "env-123") msg, err := parseRelayPayload(body, headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) } func TestParseRelayPayload_GenericJSON_Cov10(t *testing.T) { body := []byte(`{"from":"sender@example.com","subject":"Test","text":"Hello"}`) headers := http.Header{} headers.Set("Content-Type", "application/json") msg, err := parseRelayPayload(body, headers) require.NoError(t, err) assert.Equal(t, "sender@example.com", msg.FromAddress) } func TestParseRelayPayload_RawEmail_Cov10(t *testing.T) { body := []byte("From: sender@example.com\r\nSubject: Test\r\n\r\nHello") msg, err := parseRelayPayload(body, http.Header{}) require.NoError(t, err) assert.NotEmpty(t, msg.TextContent) } func TestParseRelayPayload_InvalidJSONFallsToRaw_Cov10(t *testing.T) { body := []byte("not json at all") headers := http.Header{} headers.Set("Content-Type", "application/json") msg, err := parseRelayPayload(body, headers) // Falls through to raw email parsing require.NoError(t, err) assert.NotEmpty(t, msg.TextContent) } // ============================================================ // service.go — extractEmailAddress / extractDisplayName // ============================================================ func TestExtractEmailAddress_Simple_Cov10(t *testing.T) { assert.Equal(t, "john@example.com", extractEmailAddress("john@example.com")) } func TestExtractEmailAddress_WithName_Cov10(t *testing.T) { assert.Equal(t, "john@example.com", extractEmailAddress("John Doe ")) } func TestExtractEmailAddress_Empty_Cov10(t *testing.T) { assert.Equal(t, "", extractEmailAddress("")) } func TestExtractEmailAddress_BracketsOnly_Cov10(t *testing.T) { assert.Equal(t, "john@example.com", extractEmailAddress("")) } func TestExtractEmailAddress_NoCloseBracket_Cov10(t *testing.T) { result := extractEmailAddress("")) } func TestExtractDisplayName_NoName_Cov10(t *testing.T) { assert.Equal(t, "", extractDisplayName("john@example.com")) } func TestExtractDisplayName_Empty_Cov10(t *testing.T) { assert.Equal(t, "", extractDisplayName("")) } func TestExtractDisplayName_WithQuotes_Cov10(t *testing.T) { result := extractDisplayName(`"John Doe" `) assert.Contains(t, result, "John Doe") } // ============================================================ // service.go — Service methods // ============================================================ func TestService_ValidateIMAPConnection_Empty_Cov10(t *testing.T) { s := NewService() err := s.ValidateIMAPConnection(channelpkg.ChannelConfig{}) require.Error(t, err) } func TestService_ValidateSMTPConnection_Empty_Cov10(t *testing.T) { s := NewService() err := s.ValidateSMTPConnection(channelpkg.ChannelConfig{}) require.Error(t, err) } func TestService_FetchIMAPMessages_Empty_Cov10(t *testing.T) { s := NewService() inbox := &model.Inbox{Base: model.Base{ID: 1}} _, err := s.FetchIMAPMessages(context.Background(), inbox, channelpkg.ChannelConfig{}) require.Error(t, err) } func TestService_SendViaSMTP_Empty_Cov10(t *testing.T) { s := NewService() _, err := s.SendViaSMTP(channelpkg.ChannelConfig{}, "from@test.com", "to@test.com", "subject", "body") require.Error(t, err) } func TestNewService_NotNil_Cov10(t *testing.T) { s := NewService() require.NotNil(t, s) assert.NotNil(t, s.imapListener) assert.NotNil(t, s.smtpSender) } // ============================================================ // pipeline.go — IncomingProcessor / OutgoingProcessor // ============================================================ func TestNewIncomingProcessor_NotNil_Cov10(t *testing.T) { ip := NewIncomingProcessor(nil) require.NotNil(t, ip) } func TestIncomingProcessor_Process_NilHandler_Cov10(t *testing.T) { ip := NewIncomingProcessor(nil) inbox := &model.Inbox{Base: model.Base{ID: 1}} _, err := ip.Process(context.Background(), inbox, &EmailMessage{}) require.Error(t, err) assert.Contains(t, err.Error(), "not initialized") } func TestIncomingProcessor_Process_Success_Cov10(t *testing.T) { ip := NewIncomingProcessor(&mockEmailHandlerCov10{}) inbox := &model.Inbox{Base: model.Base{ID: 1}} _, err := ip.Process(context.Background(), inbox, &EmailMessage{ MessageID: "msg1", FromAddress: "sender@test.com", Subject: "Test", TextContent: "hello", }) require.NoError(t, err) } func TestNewOutgoingProcessor_NotNil_Cov10(t *testing.T) { op := NewOutgoingProcessor(nil) require.NotNil(t, op) } func TestOutgoingProcessor_Process_NilHandler_Cov10(t *testing.T) { op := NewOutgoingProcessor(nil) inbox := &model.Inbox{Base: model.Base{ID: 1}} _, err := op.Process(context.Background(), inbox, &model.Message{}, &model.Contact{}) require.Error(t, err) assert.Contains(t, err.Error(), "not initialized") } func TestOutgoingProcessor_Process_Success_Cov10(t *testing.T) { op := NewOutgoingProcessor(&mockEmailOutgoingHandlerCov10{}) inbox := &model.Inbox{Base: model.Base{ID: 1}} result, err := op.Process(context.Background(), inbox, &model.Message{}, &model.Contact{}) require.NoError(t, err) require.NotNil(t, result) assert.Equal(t, "test_msg_id", result.ExternalID) } // ============================================================ // pipeline.go — serializeEmailMessage // ============================================================ func TestSerializeEmailMessage_Full_Cov10(t *testing.T) { emailMsg := &EmailMessage{ MessageID: "msg1", UID: 42, FromAddress: "sender@test.com", FromName: "Sender", ReplyToAddress: "reply@test.com", ToAddresses: []string{"to@test.com"}, CcAddresses: []string{"cc@test.com"}, InReplyTo: "parent", References: "", Subject: "Test", TextContent: "hello", HTMLContent: "

Hello

", Date: "2024-01-01", Attachments: []EmailAttachment{ {Filename: "file.pdf", ContentType: "application/pdf"}, }, } data, err := serializeEmailMessage(emailMsg) require.NoError(t, err) assert.NotEmpty(t, data) var result map[string]interface{} err = json.Unmarshal(data, &result) require.NoError(t, err) assert.Equal(t, "msg1", result["message_id"]) assert.Equal(t, "sender@test.com", result["from_address"]) assert.Equal(t, "Test", result["subject"]) } func TestSerializeEmailMessage_Empty_Cov10(t *testing.T) { data, err := serializeEmailMessage(&EmailMessage{}) require.NoError(t, err) assert.NotEmpty(t, data) } // ============================================================ // types.go — utility functions // ============================================================ func TestConfigInt_Int_Cov10(t *testing.T) { assert.Equal(t, 993, configInt(channelpkg.ChannelConfig{"port": 993}, "port", 0)) } func TestConfigInt_Int64_Cov10(t *testing.T) { assert.Equal(t, 993, configInt(channelpkg.ChannelConfig{"port": int64(993)}, "port", 0)) } func TestConfigInt_Float64_Cov10(t *testing.T) { assert.Equal(t, 993, configInt(channelpkg.ChannelConfig{"port": float64(993)}, "port", 0)) } func TestConfigInt_String_Cov10(t *testing.T) { assert.Equal(t, 993, configInt(channelpkg.ChannelConfig{"port": "993"}, "port", 0)) } func TestConfigInt_InvalidString_Cov10(t *testing.T) { assert.Equal(t, 0, configInt(channelpkg.ChannelConfig{"port": "abc"}, "port", 0)) } func TestConfigInt_Missing_Cov10(t *testing.T) { assert.Equal(t, 993, configInt(channelpkg.ChannelConfig{}, "port", 993)) } func TestConfigInt_Bool_Cov10(t *testing.T) { assert.Equal(t, 0, configInt(channelpkg.ChannelConfig{"port": true}, "port", 0)) } func TestHTMLToPlainText_Basic_Cov10(t *testing.T) { result := htmlToPlainText("

Hello & goodbye

") assert.Contains(t, result, "Hello & goodbye") } func TestHTMLToPlainText_Nbsp_Cov10(t *testing.T) { result := htmlToPlainText(" hello ") assert.Contains(t, result, "hello") } func TestHTMLToPlainText_Empty_Cov10(t *testing.T) { result := htmlToPlainText("") assert.Equal(t, "", result) } func TestStripHTMLTags_Basic_Cov10(t *testing.T) { assert.Contains(t, stripHTMLTags("

Hello

"), "Hello") } func TestStripHTMLTags_Nested_Cov10(t *testing.T) { result := stripHTMLTags("

Hello

") assert.Contains(t, result, "Hello") } func TestStripHTMLTags_NoTags_Cov10(t *testing.T) { assert.Equal(t, "Hello World", stripHTMLTags("Hello World")) } func TestCollapseWhitespace_MultipleSpaces_Cov10(t *testing.T) { assert.Equal(t, "hello world", collapseWhitespace("hello world")) } func TestCollapseWhitespace_Newlines_Cov10(t *testing.T) { assert.Equal(t, "hello world", collapseWhitespace("hello\n\nworld")) } func TestCollapseWhitespace_Empty_Cov10(t *testing.T) { assert.Equal(t, "", collapseWhitespace("")) } func TestLooksLikeHTML_HtmlTag_Cov10(t *testing.T) { assert.True(t, looksLikeHTML("test")) } func TestLooksLikeHTML_PlainText_Cov10(t *testing.T) { assert.False(t, looksLikeHTML("plain text")) } func TestLooksLikeHTML_Empty_Cov10(t *testing.T) { assert.False(t, looksLikeHTML("")) } func TestStripContentIDBrackets_WithBrackets_Cov10(t *testing.T) { assert.Equal(t, "id123", stripContentIDBrackets("")) } func TestStripContentIDBrackets_NoBrackets_Cov10(t *testing.T) { assert.Equal(t, "id123", stripContentIDBrackets("id123")) } func TestStripContentIDBrackets_Empty_Cov10(t *testing.T) { assert.Equal(t, "", stripContentIDBrackets("")) } func TestExtractDomainFromAddress_Valid_Cov10(t *testing.T) { assert.Equal(t, "example.com", extractDomainFromAddress("user@example.com")) } func TestExtractDomainFromAddress_NoAt_Cov10(t *testing.T) { assert.Equal(t, "", extractDomainFromAddress("notanemail")) } func TestBuildSubjectForReply_Empty_Cov10(t *testing.T) { assert.Equal(t, "Re: Your inquiry", buildSubjectForReply("")) } func TestBuildSubjectForReply_New_Cov10(t *testing.T) { assert.Equal(t, "Re: Test", buildSubjectForReply("Test")) } func TestBuildSubjectForReply_AlreadyRe_Cov10(t *testing.T) { assert.Equal(t, "Re: Test", buildSubjectForReply("Re: Test")) } func TestGenerateMessageID_WithDomain_Cov10(t *testing.T) { id := generateMessageID("test.com") assert.Contains(t, id, "test.com") assert.Contains(t, id, "gochat-") } func TestGenerateMessageID_EmptyDomain_Cov10(t *testing.T) { id := generateMessageID("") assert.Contains(t, id, "gochat.local") } func TestFormatDateNow_NotEmpty_Cov10(t *testing.T) { date := formatDateNow() assert.NotEmpty(t, date) } func TestParseMessageIDList_Empty_Cov10(t *testing.T) { assert.Nil(t, parseMessageIDList("")) } func TestParseMessageIDList_Single_Cov10(t *testing.T) { ids := parseMessageIDList("") assert.Len(t, ids, 1) assert.Equal(t, "msg1", ids[0]) } func TestExtractThreadIDFromReferences_Empty_Cov10(t *testing.T) { assert.Equal(t, "", extractThreadIDFromReferences("")) } func TestExtractThreadIDFromReferences_Single_Cov10(t *testing.T) { assert.Equal(t, "msg1", extractThreadIDFromReferences("")) } // ============================================================ // types.go — convertToIncomingMessage // ============================================================ func TestConvertToIncomingMessage_Basic_Cov10(t *testing.T) { inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 10} emailMsg := &EmailMessage{ MessageID: "msg1", FromAddress: "sender@test.com", FromName: "Sender", Subject: "Test", TextContent: "hello", } msg, err := convertToIncomingMessage(emailMsg, inbox) require.NoError(t, err) require.NotNil(t, msg) assert.Equal(t, "hello", msg.Content) assert.Equal(t, "msg1", msg.SourceID) assert.Equal(t, "sender@test.com", msg.SenderID) assert.Equal(t, "Sender", msg.SenderName) } func TestConvertToIncomingMessage_ReplyTo_Cov10(t *testing.T) { inbox := &model.Inbox{Base: model.Base{ID: 1}} emailMsg := &EmailMessage{ MessageID: "msg1", FromAddress: "sender@test.com", ReplyToAddress: "reply@test.com", TextContent: "hello", } msg, err := convertToIncomingMessage(emailMsg, inbox) require.NoError(t, err) assert.Equal(t, "reply@test.com", msg.SenderID) } func TestConvertToIncomingMessage_HTMLOnly_Cov10(t *testing.T) { inbox := &model.Inbox{Base: model.Base{ID: 1}} emailMsg := &EmailMessage{ MessageID: "msg1", FromAddress: "sender@test.com", HTMLContent: "

HTML content

", } msg, err := convertToIncomingMessage(emailMsg, inbox) require.NoError(t, err) assert.Contains(t, msg.Content, "HTML content") } func TestConvertToIncomingMessage_NoContent_Cov10(t *testing.T) { inbox := &model.Inbox{Base: model.Base{ID: 1}} emailMsg := &EmailMessage{ MessageID: "msg1", FromAddress: "sender@test.com", } msg, err := convertToIncomingMessage(emailMsg, inbox) require.NoError(t, err) assert.Equal(t, "(no content)", msg.Content) } func TestConvertToIncomingMessage_WithAttachments_Cov10(t *testing.T) { inbox := &model.Inbox{Base: model.Base{ID: 1}} emailMsg := &EmailMessage{ MessageID: "msg1", FromAddress: "sender@test.com", TextContent: "see attachment", Attachments: []EmailAttachment{ {Filename: "file.pdf", ContentType: "application/pdf"}, }, } msg, err := convertToIncomingMessage(emailMsg, inbox) require.NoError(t, err) assert.Len(t, msg.Attachments, 1) } func TestConvertToIncomingMessage_WithInReplyTo_Cov10(t *testing.T) { inbox := &model.Inbox{Base: model.Base{ID: 1}} emailMsg := &EmailMessage{ MessageID: "msg1", FromAddress: "sender@test.com", TextContent: "hello", InReplyTo: "parent-msg", } msg, err := convertToIncomingMessage(emailMsg, inbox) require.NoError(t, err) assert.Equal(t, "parent-msg", msg.ReplyToID) } // ============================================================ // provider.go — SetPipeline // ============================================================ func TestSetPipeline_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) ip := NewIncomingProcessor(nil) p.SetPipeline(ip) assert.NotNil(t, p.pipeline) } // ============================================================ // provider.go — OnDestroy // ============================================================ func TestOnDestroy_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{Base: model.Base{ID: 1}} err := p.OnDestroy(context.Background(), inbox, channelpkg.ChannelConfig{}) require.NoError(t, err) } func TestOnDestroy_NilInbox_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) safeCallCov10(func() { _ = p.OnDestroy(context.Background(), nil, channelpkg.ChannelConfig{}) }) } // ============================================================ // provider.go — ValidateWebhookRequest // ============================================================ func TestValidateWebhookRequest_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{} err := p.ValidateWebhookRequest(context.Background(), inbox, &channelpkg.WebhookRequest{}) require.NoError(t, err) } func TestValidateWebhookRequest_NilRequest_Cov10(t *testing.T) { p := NewEmailProvider(nil, nil, nil) inbox := &model.Inbox{} safeCallCov10(func() { _ = p.ValidateWebhookRequest(context.Background(), inbox, nil) }) } // ============================================================ // Repository — PollState table name // ============================================================ func TestPollState_TableName_Cov10(t *testing.T) { assert.Equal(t, "email_poll_states", PollState{}.TableName()) } // ============================================================ // Mock types // ============================================================ type mockEmailHandlerCov10 struct{} func (m *mockEmailHandlerCov10) ProcessIncoming(ctx context.Context, inbox *model.Inbox, rawPayload []byte) (*channelpkg.IncomingMessage, error) { return &channelpkg.IncomingMessage{Content: "test"}, nil } type mockEmailOutgoingHandlerCov10 struct{} func (m *mockEmailOutgoingHandlerCov10) SendMessage(ctx context.Context, inbox *model.Inbox, message *model.Message, contact *model.Contact) (*channelpkg.SendResult, error) { return &channelpkg.SendResult{ExternalID: "test_msg_id"}, nil }