Files
Rogeeandrogee 2b182f9956 H-300: wire Captain Skills into Web runtime (#48)
* H-300: wire Captain Skills into Web runtime

* H-300: enforce effective model and conservative skill budget

* H-300: fix CI gosec step

* ci: extend golangci-lint timeout

* fix lint findings across backend

* fix(push): resolve delivery protocol blockers

* test(repository): close SQLite test databases

* test(repository): reuse SQLite schema per package

* H-307: restore backend Go cache in CI

* H-307: prefetch modules before cold lint

* H-307: resolve govulncheck security gate

* H-307: build lint with patched Go toolchain

* H-307: clear remaining security scan findings

---------

Co-authored-by: Rogee <rogee@ipao.vip>
2026-08-19 07:08:14 +08:00

297 lines
10 KiB
Go

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"
)
func TestNewWebhookHandler_Cov4(t *testing.T) {
h := NewWebhookHandler()
assert.NotNil(t, h)
}
func TestWebhookHandler_HandleWebhookRequest_EmptyBody_Cov4(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 TestWebhookHandler_HandleWebhookRequest_ReadError_Cov4(t *testing.T) {
h := NewWebhookHandler()
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/webhook", &errReaderCov4{})
h.HandleWebhookRequest(w, r)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestWebhookHandler_HandleWebhookRequest_ValidMailgun_Cov4(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 TestWebhookHandler_HandleWebhookRequest_ValidSendGrid_Cov4(t *testing.T) {
h := NewWebhookHandler()
w := httptest.NewRecorder()
body := `{"envelope":{"from":"sender@example.com","to":["recipient@example.com"]},"subject":"Test","text":"Hello","html":"<p>Hello</p>","messageId":"msg-2"}`
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 TestWebhookHandler_HandleWebhookRequest_GenericJSON_Cov4(t *testing.T) {
h := NewWebhookHandler()
w := httptest.NewRecorder()
body := `{"from":"sender@example.com","subject":"Test","text":"Hello","to":"recipient@example.com","message_id":"msg-3"}`
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 TestWebhookHandler_HandleWebhookRequest_RawEmail_Cov4(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 TestWebhookHandler_HandleWebhookRequest_InvalidMailgun_Cov4(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 TestWebhookHandler_ParseWebhookBody_Cov4(t *testing.T) {
h := NewWebhookHandler()
t.Run("mailgun", func(t *testing.T) {
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)
})
t.Run("sendgrid", func(t *testing.T) {
body := `{"envelope":{"from":"sender@example.com","to":["recipient@example.com"]},"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, "sender@example.com", msg.FromAddress)
assert.Equal(t, "Test", msg.Subject)
})
t.Run("generic json", func(t *testing.T) {
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)
})
t.Run("raw email", func(t *testing.T) {
body := "From: sender@example.com\r\nSubject: Test\r\n\r\nHello"
msg, err := h.ParseWebhookBody([]byte(body), http.Header{})
require.NoError(t, err)
assert.NotEmpty(t, msg.TextContent)
})
}
func TestIsMailgunPayload_Cov4(t *testing.T) {
headers := http.Header{}
assert.False(t, isMailgunPayload(headers))
headers.Set("X-Mailgun-Sid", "12345")
assert.True(t, isMailgunPayload(headers))
}
func TestIsSendGridPayload_Cov4(t *testing.T) {
headers := http.Header{}
assert.False(t, isSendGridPayload(headers))
headers.Set("X-Sg-Envelope-Id", "env-123")
assert.True(t, isSendGridPayload(headers))
}
func TestParseMailgunPayload_Cov4(t *testing.T) {
t.Run("valid", func(t *testing.T) {
body := `{"from":"sender@example.com","subject":"Test","body-plain":"Hello","to":"recipient@example.com","Message-Id":"msg-1","In-Reply-To":"parent","References":"<parent>","Date":"2024-01-01","body-html":"<p>Hello</p>"}`
msg, err := parseMailgunPayload([]byte(body))
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, "<p>Hello</p>", msg.HTMLContent)
assert.Equal(t, "msg-1", msg.MessageID)
})
t.Run("invalid json", func(t *testing.T) {
_, err := parseMailgunPayload([]byte("invalid"))
assert.Error(t, err)
})
}
func TestParseSendGridPayload_Cov4(t *testing.T) {
t.Run("valid", func(t *testing.T) {
body := `{"envelope":{"from":"sender@example.com","to":["recipient@example.com"]},"subject":"Test","text":"Hello","html":"<p>Hello</p>","messageId":"msg-2","in_reply_to":"parent","date":"2024-01-01"}`
msg, err := parseSendGridPayload([]byte(body))
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, "<p>Hello</p>", msg.HTMLContent)
assert.Equal(t, "msg-2", msg.MessageID)
assert.Equal(t, "parent", msg.InReplyTo)
})
t.Run("invalid json", func(t *testing.T) {
_, err := parseSendGridPayload([]byte("invalid"))
assert.Error(t, err)
})
}
func TestParseGenericJSONPayload_Cov4(t *testing.T) {
t.Run("valid", func(t *testing.T) {
data := map[string]interface{}{
"from": "sender@example.com",
"subject": "Test",
"text": "Hello",
"html": "<p>Hello</p>",
"message_id": "msg-3",
"in_reply_to": "parent",
"date": "2024-01-01",
"to": "recipient@example.com",
}
msg, err := parseGenericJSONPayload(data)
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, "<p>Hello</p>", msg.HTMLContent)
assert.Equal(t, "msg-3", msg.MessageID)
assert.Equal(t, "parent", msg.InReplyTo)
assert.Equal(t, []string{"recipient@example.com"}, msg.ToAddresses)
})
}
func TestParseRawEmail_Cov4(t *testing.T) {
body := "From: sender@example.com\r\nSubject: Test\r\n\r\nHello world"
msg, err := parseRawEmail([]byte(body))
require.NoError(t, err)
assert.NotEmpty(t, msg.TextContent)
assert.Contains(t, msg.TextContent, "Hello world")
}
func TestEmailProvider_SendMessage_WithSMTPConfig_Cov4(t *testing.T) {
provider := NewEmailProvider(NewService(), &Repository{}, NewIncomingProcessor(nil))
ctx := context.Background()
inbox := &model.Inbox{
Base: model.Base{ID: 1},
AccountID: 1,
ChannelConfig: `{"smtp_email":"support@example.com","smtp_address":"smtp.example.com","smtp_port":587,"smtp_password":"pass","imap_address":"imap.example.com","imap_port":993,"imap_email":"support@example.com","imap_password":"pass"}`,
}
contact := &model.Contact{SourceID: "recipient@example.com"}
msg := &model.Message{Content: "test"}
// This will try to connect to smtp.example.com — should fail with network error
_, err := provider.SendMessage(ctx, inbox, msg, contact)
assert.Error(t, err)
}
func TestEmailProvider_PollInterval_Cov4(t *testing.T) {
provider := NewEmailProvider(NewService(), &Repository{}, NewIncomingProcessor(nil))
interval := provider.PollInterval()
assert.NotZero(t, interval)
}
func TestEmailProvider_PollMessages_Cov4(t *testing.T) {
provider := NewEmailProvider(NewService(), &Repository{}, NewIncomingProcessor(nil))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
// With empty config, should fail
_, err := provider.PollMessages(context.Background(), inbox)
assert.Error(t, err)
}
func TestEmailMessage_JSON_Cov4(t *testing.T) {
msg := EmailMessage{
MessageID: "msg-1",
FromAddress: "sender@example.com",
Subject: "Test",
TextContent: "Hello",
}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "msg-1")
var decoded EmailMessage
err = json.Unmarshal(data, &decoded)
require.NoError(t, err)
assert.Equal(t, msg.MessageID, decoded.MessageID)
}
func TestEmailAttachment_JSON_Cov4(t *testing.T) {
att := EmailAttachment{
Filename: "doc.pdf",
ContentType: "application/pdf",
FileSize: 1024,
}
data, err := json.Marshal(att)
require.NoError(t, err)
assert.Contains(t, string(data), "doc.pdf")
var decoded EmailAttachment
err = json.Unmarshal(data, &decoded)
require.NoError(t, err)
assert.Equal(t, att.Filename, decoded.Filename)
}
// errReaderCov4 is a reader that always returns an error
type errReaderCov4 struct{}
func (e *errReaderCov4) Read(p []byte) (n int, err error) {
return 0, assertErrorCov4("read error")
}
type errStringCov4 string
func (e errStringCov4) Error() string { return string(e) }
func assertErrorCov4(msg string) error {
return errStringCov4(msg)
}
func TestChannelpkgConstant_Cov4(t *testing.T) {
// Ensure channelpkg import is used
assert.Equal(t, "email", string(channelpkg.ChannelEmail))
}