Files
gochat/backend/internal/channel/line/coverage7_test.go
T
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

1170 lines
38 KiB
Go

package line
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"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"
)
// ===========================
// resolveSourceID tests
// ===========================
func TestResolveSourceID_User_Cov7(t *testing.T) {
source := EventSource{Type: "user", UserID: "U123"}
assert.Equal(t, "U123", resolveSourceID(source))
}
func TestResolveSourceID_Group_Cov7(t *testing.T) {
source := EventSource{Type: "group", GroupID: "G123"}
assert.Equal(t, "G123", resolveSourceID(source))
}
func TestResolveSourceID_Room_Cov7(t *testing.T) {
source := EventSource{Type: "room", RoomID: "R123"}
assert.Equal(t, "R123", resolveSourceID(source))
}
func TestResolveSourceID_UserPriority_Cov7(t *testing.T) {
source := EventSource{Type: "user", UserID: "U123", GroupID: "G123"}
assert.Equal(t, "U123", resolveSourceID(source))
}
func TestResolveSourceID_GroupPriority_Cov7(t *testing.T) {
source := EventSource{Type: "group", GroupID: "G123", RoomID: "R123"}
assert.Equal(t, "G123", resolveSourceID(source))
}
func TestResolveSourceID_Empty_Cov7(t *testing.T) {
source := EventSource{}
assert.Equal(t, "", resolveSourceID(source))
}
// ===========================
// parseInboxConfig tests
// ===========================
func TestParseInboxConfig_Empty_Cov7(t *testing.T) {
result := parseInboxConfig("")
assert.Empty(t, result)
}
func TestParseInboxConfig_Valid_Cov7(t *testing.T) {
result := parseInboxConfig(`{"key":"value"}`)
assert.Equal(t, "value", result["key"])
}
func TestParseInboxConfig_Invalid_Cov7(t *testing.T) {
result := parseInboxConfig("invalid json")
assert.Empty(t, result)
}
// ===========================
// configStr tests
// ===========================
func TestConfigStr_Found_Cov7(t *testing.T) {
config := channelpkg.ChannelConfig{"key": "value"}
assert.Equal(t, "value", configStr(config, "key", "default"))
}
func TestConfigStr_NotFound_Cov7(t *testing.T) {
config := channelpkg.ChannelConfig{}
assert.Equal(t, "default", configStr(config, "key", "default"))
}
func TestConfigStr_EmptyValue_Cov7(t *testing.T) {
config := channelpkg.ChannelConfig{"key": ""}
assert.Equal(t, "default", configStr(config, "key", "default"))
}
func TestConfigStr_NonString_Cov7(t *testing.T) {
config := channelpkg.ChannelConfig{"key": 123}
assert.Equal(t, "default", configStr(config, "key", "default"))
}
// ===========================
// LineService.VerifySignature tests
// ===========================
func TestVerifySignature_Valid_Cov7(t *testing.T) {
s := NewLineService(nil)
secret := "test-secret"
body := `{"events":[]}`
// Compute correct signature
mac := hmacSHA256(secret, body)
assert.True(t, s.VerifySignature(secret, body, mac))
}
func TestVerifySignature_Invalid_Cov7(t *testing.T) {
s := NewLineService(nil)
assert.False(t, s.VerifySignature("secret", "body", "invalid-signature"))
}
func TestVerifySignature_EmptyBody_Cov7(t *testing.T) {
s := NewLineService(nil)
mac := hmacSHA256("secret", "")
assert.True(t, s.VerifySignature("secret", "", mac))
}
func TestVerifySignature_WrongSecret_Cov7(t *testing.T) {
s := NewLineService(nil)
body := `{"events":[]}`
mac := hmacSHA256("correct-secret", body)
assert.False(t, s.VerifySignature("wrong-secret", body, mac))
}
func TestVerifySignature_EmptySignature_Cov7(t *testing.T) {
s := NewLineService(nil)
assert.False(t, s.VerifySignature("secret", "body", ""))
}
// ===========================
// WebhookEvent struct tests
// ===========================
func TestWebhookEvent_JSON_Cov7(t *testing.T) {
event := WebhookEvent{
Destination: "dest123",
Events: []EventObject{
{Type: "message", ReplyToken: "token123"},
},
}
data, err := json.Marshal(event)
require.NoError(t, err)
assert.Contains(t, string(data), "dest123")
assert.Contains(t, string(data), "message")
}
func TestWebhookEvent_Unmarshal_Cov7(t *testing.T) {
jsonStr := `{"destination":"dest","events":[{"type":"message","replyToken":"rt"}]}`
var event WebhookEvent
err := json.Unmarshal([]byte(jsonStr), &event)
require.NoError(t, err)
assert.Equal(t, "dest", event.Destination)
assert.Len(t, event.Events, 1)
assert.Equal(t, "message", event.Events[0].Type)
}
func TestWebhookEvent_Empty_Cov7(t *testing.T) {
jsonStr := `{"destination":"","events":[]}`
var event WebhookEvent
err := json.Unmarshal([]byte(jsonStr), &event)
require.NoError(t, err)
assert.Empty(t, event.Events)
}
// ===========================
// EventObject struct tests
// ===========================
func TestEventObject_JSON_Cov7(t *testing.T) {
event := EventObject{
Type: "message",
ReplyToken: "token",
Timestamp: 1234567890,
Mode: "active",
Source: EventSource{Type: "user", UserID: "U123"},
}
data, err := json.Marshal(event)
require.NoError(t, err)
assert.Contains(t, string(data), "message")
assert.Contains(t, string(data), "U123")
}
// ===========================
// EventSource struct tests
// ===========================
func TestEventSource_JSON_Cov7(t *testing.T) {
source := EventSource{Type: "user", UserID: "U123", GroupID: "G456", RoomID: "R789"}
data, err := json.Marshal(source)
require.NoError(t, err)
assert.Contains(t, string(data), "U123")
assert.Contains(t, string(data), "G456")
}
// ===========================
// Message type struct tests
// ===========================
func TestTextMessageContent_JSON_Cov7(t *testing.T) {
msg := TextMessageContent{Type: "text", ID: "msg1", Text: "hello"}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "hello")
}
func TestImageMessageContent_JSON_Cov7(t *testing.T) {
msg := ImageMessageContent{Type: "image", ID: "msg1", OriginalContentURL: "http://img", PreviewImageURL: "http://prev"}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "image")
}
func TestVideoMessageContent_JSON_Cov7(t *testing.T) {
msg := VideoMessageContent{Type: "video", ID: "msg1", OriginalContentURL: "http://vid", PreviewImageURL: "http://prev"}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "video")
}
func TestStickerMessageContent_JSON_Cov7(t *testing.T) {
msg := StickerMessageContent{Type: "sticker", ID: "msg1", PackageID: "pkg1", StickerID: "stk1", StickerResourceType: "STATIC"}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "sticker")
}
func TestLocationMessageContent_JSON_Cov7(t *testing.T) {
msg := LocationMessageContent{Type: "location", ID: "msg1", Title: "T", Address: "A", Latitude: 35.6, Longitude: 139.7}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "location")
}
func TestAudioMessageContent_JSON_Cov7(t *testing.T) {
msg := AudioMessageContent{Type: "audio", ID: "msg1", OriginalContentURL: "http://audio", Duration: 5000}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "audio")
}
func TestFileMessageContent_JSON_Cov7(t *testing.T) {
msg := FileMessageContent{Type: "file", ID: "msg1", FileName: "doc.pdf", FileSize: 1024}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "file")
}
// ===========================
// PostbackData / BeaconData / JoinedMembers / LeftMembers tests
// ===========================
func TestPostbackData_JSON_Cov7(t *testing.T) {
data := PostbackData{Data: "action=buy&id=1"}
d, err := json.Marshal(data)
require.NoError(t, err)
assert.Contains(t, string(d), "action=buy")
}
func TestBeaconData_JSON_Cov7(t *testing.T) {
data := BeaconData{Hwid: "hw1", Type: "enter", DeviceMessage: "msg"}
d, err := json.Marshal(data)
require.NoError(t, err)
assert.Contains(t, string(d), "enter")
}
func TestJoinedMembers_JSON_Cov7(t *testing.T) {
data := JoinedMembers{Members: []EventSource{{Type: "user", UserID: "U1"}}}
d, err := json.Marshal(data)
require.NoError(t, err)
assert.Contains(t, string(d), "U1")
}
func TestLeftMembers_JSON_Cov7(t *testing.T) {
data := LeftMembers{Members: []EventSource{{Type: "user", UserID: "U1"}}}
d, err := json.Marshal(data)
require.NoError(t, err)
assert.Contains(t, string(d), "U1")
}
// ===========================
// Outbound message type tests
// ===========================
func TestReplyMessageRequest_JSON_Cov7(t *testing.T) {
req := ReplyMessageRequest{ReplyToken: "rt", Messages: []OutboundMsg{{Type: "text", Text: "hi"}}}
data, err := json.Marshal(req)
require.NoError(t, err)
assert.Contains(t, string(data), "rt")
assert.Contains(t, string(data), "hi")
}
func TestPushMessageRequest_JSON_Cov7(t *testing.T) {
req := PushMessageRequest{To: "U123", Messages: []OutboundMsg{{Type: "text", Text: "hi"}}}
data, err := json.Marshal(req)
require.NoError(t, err)
assert.Contains(t, string(data), "U123")
}
func TestOutboundMsg_Text_Cov7(t *testing.T) {
msg := OutboundMsg{Type: "text", Text: "hello"}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "hello")
}
func TestOutboundMsg_Image_Cov7(t *testing.T) {
msg := OutboundMsg{Type: "image", OriginalContentURL: "http://img", PreviewImageURL: "http://prev"}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "image")
}
func TestOutboundMsg_Sticker_Cov7(t *testing.T) {
msg := OutboundMsg{Type: "sticker", PackageID: "pkg", StickerID: "stk"}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "sticker")
}
func TestOutboundMsg_Location_Cov7(t *testing.T) {
msg := OutboundMsg{Type: "location", Title: "T", Address: "A", Latitude: 35.6, Longitude: 139.7}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "location")
}
// ===========================
// UserProfile struct tests
// ===========================
func TestUserProfile_JSON_Cov7(t *testing.T) {
profile := UserProfile{
UserID: "U123",
DisplayName: "Test User",
PictureURL: "http://pic",
StatusMessage: "Hello",
Language: "en",
}
data, err := json.Marshal(profile)
require.NoError(t, err)
assert.Contains(t, string(data), "U123")
assert.Contains(t, string(data), "Test User")
}
// ===========================
// PushMessageResponse / ReplyMessageResponse tests
// ===========================
func TestPushMessageResponse_JSON_Cov7(t *testing.T) {
resp := PushMessageResponse{Status: 200, Message: "OK"}
data, err := json.Marshal(resp)
require.NoError(t, err)
assert.Contains(t, string(data), "200")
}
func TestReplyMessageResponse_JSON_Cov7(t *testing.T) {
resp := ReplyMessageResponse{Status: 200, Message: "OK"}
data, err := json.Marshal(resp)
require.NoError(t, err)
assert.Contains(t, string(data), "200")
}
// ===========================
// WebhookHandler tests
// ===========================
func TestNewWebhookHandler_Cov7(t *testing.T) {
h := NewWebhookHandler(nil, nil)
require.NotNil(t, h)
}
func TestWebhookHandler_HandleWebhookVerification_Cov7(t *testing.T) {
h := NewWebhookHandler(nil, nil)
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/webhook/verify", nil)
h.HandleWebhookVerification(w, r)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestWebhookHandler_HandleWebhook_EmptyBody_Cov7(t *testing.T) {
s := NewLineService(nil)
h := NewWebhookHandler(nil, s)
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/webhook", nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
func() {
defer func() { _ = recover() }()
h.HandleWebhook(w, r, inbox)
}()
// nil body causes io.ReadAll to return empty, then JSON parse fails → 400
// Actually nil Body in http.NewRequest is replaced with http.NoBody
}
func TestWebhookHandler_HandleWebhook_InvalidJSON_Cov7(t *testing.T) {
s := NewLineService(nil)
h := NewWebhookHandler(nil, s)
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/webhook", strings.NewReader("invalid json"))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.Contains(t, w.Body.String(), "Invalid JSON")
}
func TestWebhookHandler_HandleWebhook_ValidNoEvents_Cov7(t *testing.T) {
s := NewLineService(nil)
h := NewWebhookHandler(nil, s)
w := httptest.NewRecorder()
body := `{"destination":"dest","events":[]}`
r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestWebhookHandler_HandleWebhook_ValidWithEvents_Cov7(t *testing.T) {
s := NewLineService(nil)
pipeline := NewIncomingProcessor(s)
h := NewWebhookHandler(pipeline, s)
w := httptest.NewRecorder()
body := `{"destination":"dest","events":[{"type":"follow","replyToken":"rt","source":{"type":"user","userId":"U123"}}]}`
r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestWebhookHandler_HandleWebhook_InvalidSignature_Cov7(t *testing.T) {
s := NewLineService(nil)
h := NewWebhookHandler(nil, s)
w := httptest.NewRecorder()
body := `{"destination":"dest","events":[]}`
r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body))
r.Header.Set("X-Line-Signature", "invalid-signature")
inbox := &model.Inbox{
Base: model.Base{ID: 1},
AccountID: 1,
ChannelConfig: `{"channel_secret":"mysecret"}`,
}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusUnauthorized, w.Code)
assert.Contains(t, w.Body.String(), "Invalid signature")
}
func TestWebhookHandler_HandleWebhook_ValidSignature_Cov7(t *testing.T) {
s := NewLineService(nil)
h := NewWebhookHandler(nil, s)
w := httptest.NewRecorder()
body := `{"destination":"dest","events":[]}`
secret := "mysecret"
sig := hmacSHA256(secret, body)
r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body))
r.Header.Set("X-Line-Signature", sig)
inbox := &model.Inbox{
Base: model.Base{ID: 1},
AccountID: 1,
ChannelConfig: `{"channel_secret":"mysecret"}`,
}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestWebhookHandler_HandleWebhook_NoSecret_Cov7(t *testing.T) {
s := NewLineService(nil)
h := NewWebhookHandler(nil, s)
w := httptest.NewRecorder()
body := `{"destination":"dest","events":[]}`
r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body))
r.Header.Set("X-Line-Signature", "sig")
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1, ChannelConfig: `{}`}
// No channel_secret → signature check is skipped, proceeds to parse
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestWebhookHandler_HandleWebhook_ReadError_Cov7(t *testing.T) {
s := NewLineService(nil)
h := NewWebhookHandler(nil, s)
w := httptest.NewRecorder()
r := httptest.NewRequest("POST", "/webhook", errReaderLine{})
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestWebhookHandler_HandleWebhook_MessageEvent_Cov7(t *testing.T) {
s := NewLineService(nil)
pipeline := NewIncomingProcessor(s)
h := NewWebhookHandler(pipeline, s)
w := httptest.NewRecorder()
body := `{"destination":"dest","events":[{"type":"message","replyToken":"rt","source":{"type":"user","userId":"U123"},"message":{"type":"text","id":"msg1","text":"hello"}}]}`
r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestWebhookHandler_HandleWebhook_PostbackEvent_Cov7(t *testing.T) {
s := NewLineService(nil)
pipeline := NewIncomingProcessor(s)
h := NewWebhookHandler(pipeline, s)
w := httptest.NewRecorder()
body := `{"destination":"dest","events":[{"type":"postback","replyToken":"rt","source":{"type":"user","userId":"U123"},"postback":{"data":"action=test"}}]}`
r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestWebhookHandler_HandleWebhook_UnfollowEvent_Cov7(t *testing.T) {
s := NewLineService(nil)
pipeline := NewIncomingProcessor(s)
h := NewWebhookHandler(pipeline, s)
w := httptest.NewRecorder()
body := `{"destination":"dest","events":[{"type":"unfollow","source":{"type":"user","userId":"U123"}}]}`
r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestWebhookHandler_HandleWebhook_UnknownEvent_Cov7(t *testing.T) {
s := NewLineService(nil)
pipeline := NewIncomingProcessor(s)
h := NewWebhookHandler(pipeline, s)
w := httptest.NewRecorder()
body := `{"destination":"dest","events":[{"type":"unknown","source":{"type":"user","userId":"U123"}}]}`
r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestWebhookHandler_HandleWebhook_NoSourceID_Cov7(t *testing.T) {
s := NewLineService(nil)
pipeline := NewIncomingProcessor(s)
h := NewWebhookHandler(pipeline, s)
w := httptest.NewRecorder()
body := `{"destination":"dest","events":[{"type":"message","replyToken":"rt","source":{}}]}`
r := httptest.NewRequest("POST", "/webhook", strings.NewReader(body))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
h.HandleWebhook(w, r, inbox)
assert.Equal(t, http.StatusOK, w.Code)
}
// ===========================
// IncomingProcessor tests
// ===========================
func TestNewIncomingProcessor_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
require.NotNil(t, p)
}
func TestIncomingProcessor_ProcessEvent_Message_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
ReplyToken: "rt",
Source: EventSource{Type: "user", UserID: "U123"},
Message: json.RawMessage(`{"type":"text","id":"msg1","text":"hello"}`),
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
require.NotNil(t, msg)
assert.Equal(t, "hello", msg.Content)
assert.Equal(t, channelpkg.ContentText, msg.ContentType)
assert.Equal(t, "U123", msg.SenderID)
}
func TestIncomingProcessor_ProcessEvent_Image_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
Source: EventSource{Type: "user", UserID: "U123"},
Message: json.RawMessage(`{"type":"image","id":"msg1","originalContentUrl":"http://img"}`),
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
require.NotNil(t, msg)
assert.Equal(t, channelpkg.ContentImage, msg.ContentType)
}
func TestIncomingProcessor_ProcessEvent_Video_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
Source: EventSource{Type: "user", UserID: "U123"},
Message: json.RawMessage(`{"type":"video","id":"msg1","originalContentUrl":"http://vid"}`),
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
assert.Equal(t, channelpkg.ContentVideo, msg.ContentType)
}
func TestIncomingProcessor_ProcessEvent_Audio_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
Source: EventSource{Type: "user", UserID: "U123"},
Message: json.RawMessage(`{"type":"audio","id":"msg1","originalContentUrl":"http://audio","duration":5000}`),
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
assert.Equal(t, channelpkg.ContentAudio, msg.ContentType)
}
func TestIncomingProcessor_ProcessEvent_File_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
Source: EventSource{Type: "user", UserID: "U123"},
Message: json.RawMessage(`{"type":"file","id":"msg1","fileName":"doc.pdf","fileSize":1024,"originalContentUrl":"http://file"}`),
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
assert.Equal(t, channelpkg.ContentFile, msg.ContentType)
}
func TestIncomingProcessor_ProcessEvent_Location_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
Source: EventSource{Type: "user", UserID: "U123"},
Message: json.RawMessage(`{"type":"location","id":"msg1","title":"T","address":"A","latitude":35.6,"longitude":139.7}`),
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
assert.Equal(t, channelpkg.ContentLocation, msg.ContentType)
}
func TestIncomingProcessor_ProcessEvent_Sticker_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
Source: EventSource{Type: "user", UserID: "U123"},
Message: json.RawMessage(`{"type":"sticker","id":"msg1","packageId":"pkg","stickerId":"stk"}`),
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
assert.Equal(t, channelpkg.ContentText, msg.ContentType)
}
func TestIncomingProcessor_ProcessEvent_UnknownMessage_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
Source: EventSource{Type: "user", UserID: "U123"},
Message: json.RawMessage(`{"type":"unknown","id":"msg1"}`),
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
assert.NotNil(t, msg)
assert.Contains(t, msg.Content, "Unsupported")
}
func TestIncomingProcessor_ProcessEvent_InvalidMessageJSON_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
Source: EventSource{Type: "user", UserID: "U123"},
Message: json.RawMessage(`invalid`),
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.Error(t, err)
assert.Nil(t, msg)
}
func TestIncomingProcessor_ProcessEvent_Follow_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "follow",
Source: EventSource{Type: "user", UserID: "U123"},
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
assert.Nil(t, msg)
}
func TestIncomingProcessor_ProcessEvent_Unfollow_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "unfollow",
Source: EventSource{Type: "user", UserID: "U123"},
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
assert.Nil(t, msg)
}
func TestIncomingProcessor_ProcessEvent_Postback_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "postback",
Source: EventSource{Type: "user", UserID: "U123"},
Postback: &PostbackData{Data: "action=test"},
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
require.NotNil(t, msg)
assert.Equal(t, "action=test", msg.Content)
}
func TestIncomingProcessor_ProcessEvent_PostbackNil_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "postback",
Source: EventSource{Type: "user", UserID: "U123"},
Postback: nil,
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
require.NotNil(t, msg)
assert.Equal(t, "", msg.Content)
}
func TestIncomingProcessor_ProcessEvent_UnknownType_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "unknown",
Source: EventSource{Type: "user", UserID: "U123"},
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
assert.Nil(t, msg)
}
func TestIncomingProcessor_ProcessEvent_NoSourceID_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
Source: EventSource{},
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.Error(t, err)
assert.Nil(t, msg)
assert.Contains(t, err.Error(), "no source ID")
}
func TestIncomingProcessor_ProcessEvent_ReplyToken_Cov7(t *testing.T) {
p := NewIncomingProcessor(nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
event := EventObject{
Type: "message",
ReplyToken: "reply_token_123",
Source: EventSource{Type: "user", UserID: "U123"},
Message: json.RawMessage(`{"type":"text","id":"msg1","text":"hello"}`),
}
msg, err := p.ProcessEvent(context.Background(), inbox, event)
require.NoError(t, err)
require.NotNil(t, msg)
assert.Equal(t, "reply_token_123", msg.Extra["reply_token"])
}
// ===========================
// OutgoingProcessor tests
// ===========================
func TestNewOutgoingProcessor_Cov7(t *testing.T) {
p := NewOutgoingProcessor(nil)
require.NotNil(t, p)
}
func TestOutgoingProcessor_SendMessage_NoSourceID_Cov7(t *testing.T) {
p := NewOutgoingProcessor(NewLineService(nil))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1, ChannelConfig: `{}`}
msg := &model.Message{Content: "hello"}
contact := &model.Contact{SourceID: ""}
_, err := p.SendMessage(context.Background(), inbox, msg, contact)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no source_id")
}
// ===========================
// EventBridge tests
// ===========================
func TestNewEventBridge_Cov7(t *testing.T) {
b := NewEventBridge(nil)
require.NotNil(t, b)
}
func TestEventBridge_OnMessageCreated_NoSourceID_Cov7(t *testing.T) {
b := NewEventBridge(NewOutgoingProcessor(NewLineService(nil)))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1, ChannelConfig: `{}`}
msg := &model.Message{Content: "hello"}
contact := &model.Contact{SourceID: ""}
_, err := b.OnMessageCreated(context.Background(), inbox, msg, contact)
assert.Error(t, err)
}
// ===========================
// LineProvider tests
// ===========================
func TestNewLineProvider_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
require.NotNil(t, p)
}
func TestLineProvider_Type_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
assert.Equal(t, channelpkg.ChannelLine, p.Type())
}
func TestLineProvider_Name_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
assert.Equal(t, "LINE", p.Name())
}
func TestLineProvider_Description_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
desc := p.Description()
assert.Contains(t, desc, "LINE")
}
func TestLineProvider_ConfigSchema_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
schema := p.ConfigSchema()
require.NotNil(t, schema)
assert.Contains(t, schema.Properties, "channel_access_token")
assert.Contains(t, schema.Properties, "channel_secret")
}
func TestLineProvider_ValidateConfig_Valid_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
config := channelpkg.ChannelConfig{
"channel_access_token": "token",
"channel_secret": "secret",
}
err := p.ValidateConfig(context.Background(), config)
assert.NoError(t, err)
}
func TestLineProvider_ValidateConfig_NoToken_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
config := channelpkg.ChannelConfig{
"channel_secret": "secret",
}
err := p.ValidateConfig(context.Background(), config)
assert.Error(t, err)
assert.Contains(t, err.Error(), "channel_access_token")
}
func TestLineProvider_ValidateConfig_NoSecret_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
config := channelpkg.ChannelConfig{
"channel_access_token": "token",
}
err := p.ValidateConfig(context.Background(), config)
assert.Error(t, err)
assert.Contains(t, err.Error(), "channel_secret")
}
func TestLineProvider_ValidateConfig_Empty_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
err := p.ValidateConfig(context.Background(), channelpkg.ChannelConfig{})
assert.Error(t, err)
}
func TestLineProvider_DefaultConfig_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
config := p.DefaultConfig()
assert.Contains(t, config, "channel_access_token")
assert.Contains(t, config, "channel_secret")
}
func TestLineProvider_OnDestroy_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
err := p.OnDestroy(context.Background(), &model.Inbox{}, channelpkg.ChannelConfig{})
assert.NoError(t, err)
}
func TestLineProvider_Capabilities_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, nil)
caps := p.Capabilities()
assert.True(t, caps.SupportsAttachments)
assert.True(t, caps.SupportsLocation)
assert.True(t, caps.SupportsReplies)
assert.True(t, caps.SupportsVoiceMessages)
assert.True(t, caps.SupportsCustomCards)
assert.True(t, caps.SupportsTemplates)
assert.False(t, caps.SupportsTypingIndicator)
assert.False(t, caps.SupportsDeliveryStatus)
}
func TestLineProvider_ProcessIncoming_InvalidJSON_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, NewIncomingProcessor(nil))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
_, err := p.ProcessIncoming(context.Background(), inbox, []byte("invalid json"))
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to parse payload")
}
func TestLineProvider_ProcessIncoming_NoEvents_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, NewIncomingProcessor(nil))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
payload := `{"destination":"dest","events":[]}`
_, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload))
assert.Error(t, err)
assert.Contains(t, err.Error(), "no events")
}
func TestLineProvider_ProcessIncoming_Valid_Cov7(t *testing.T) {
p := NewLineProvider(nil, nil, NewIncomingProcessor(nil))
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
payload := `{"destination":"dest","events":[{"type":"message","replyToken":"rt","source":{"type":"user","userId":"U123"},"message":{"type":"text","id":"msg1","text":"hello"}}]}`
msg, err := p.ProcessIncoming(context.Background(), inbox, []byte(payload))
require.NoError(t, err)
require.NotNil(t, msg)
assert.Equal(t, "hello", msg.Content)
}
func TestLineProvider_ValidateWebhookRequest_NoSecret_Cov7(t *testing.T) {
s := NewLineService(nil)
p := NewLineProvider(s, nil, nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1, ChannelConfig: `{}`}
req := &channelpkg.WebhookRequest{Body: []byte("{}"), Headers: map[string]string{}}
err := p.ValidateWebhookRequest(context.Background(), inbox, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "channel_secret")
}
func TestLineProvider_ValidateWebhookRequest_NoSignature_Cov7(t *testing.T) {
s := NewLineService(nil)
p := NewLineProvider(s, nil, nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1, ChannelConfig: `{"channel_secret":"secret"}`}
req := &channelpkg.WebhookRequest{Body: []byte("{}"), Headers: map[string]string{}}
err := p.ValidateWebhookRequest(context.Background(), inbox, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "X-Line-Signature")
}
func TestLineProvider_ValidateWebhookRequest_InvalidSignature_Cov7(t *testing.T) {
s := NewLineService(nil)
p := NewLineProvider(s, nil, nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1, ChannelConfig: `{"channel_secret":"secret"}`}
req := &channelpkg.WebhookRequest{Body: []byte("{}"), Headers: map[string]string{"X-Line-Signature": "invalid"}}
err := p.ValidateWebhookRequest(context.Background(), inbox, req)
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid HMAC")
}
func TestLineProvider_ValidateWebhookRequest_ValidSignature_Cov7(t *testing.T) {
s := NewLineService(nil)
p := NewLineProvider(s, nil, nil)
body := `{"events":[]}`
sig := hmacSHA256("secret", body)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1, ChannelConfig: `{"channel_secret":"secret"}`}
req := &channelpkg.WebhookRequest{Body: []byte(body), Headers: map[string]string{"X-Line-Signature": sig}}
err := p.ValidateWebhookRequest(context.Background(), inbox, req)
assert.NoError(t, err)
}
func TestLineProvider_SendMessage_NoSourceID_Cov7(t *testing.T) {
s := NewLineService(nil)
p := NewLineProvider(s, nil, nil)
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1, ChannelConfig: `{"channel_access_token":"token"}`}
msg := &model.Message{Content: "hello"}
contact := &model.Contact{SourceID: ""}
_, err := p.SendMessage(context.Background(), inbox, msg, contact)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no source_id")
}
// ===========================
// LineService tests (API error paths)
// ===========================
func TestLineService_ReplyMessage_NilClient_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
s := &LineService{client: nil}
_ = s.ReplyMessage(context.Background(), "token", "rt", []OutboundMsg{{Type: "text", Text: "hi"}})
}()
}
func TestLineService_PushMessage_NilClient_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
s := &LineService{client: nil}
_, _ = s.PushMessage(context.Background(), "token", "U123", []OutboundMsg{{Type: "text", Text: "hi"}})
}()
}
func TestLineService_GetUserProfile_NilClient_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
s := &LineService{client: nil}
_, _ = s.GetUserProfile(context.Background(), "token", "U123")
}()
}
func TestLineService_ValidateAccessToken_NilClient_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
s := &LineService{client: nil}
_ = s.ValidateAccessToken(context.Background(), "token")
}()
}
func TestLineService_UpdateChannel_NilRepo_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
s := &LineService{repo: nil}
_ = s.UpdateChannel(context.Background(), 1, map[string]interface{}{"key": "value"})
}()
}
func TestLineService_MarkReauthorizationRequired_NilRepo_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
s := &LineService{repo: nil}
_ = s.MarkReauthorizationRequired(context.Background(), 1)
}()
}
// ===========================
// Repository tests
// ===========================
func TestNewRepository_Cov7(t *testing.T) {
r := NewRepository(nil)
require.NotNil(t, r)
}
func TestRepository_FindByID_NilDB_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
r := NewRepository(nil)
_, _ = r.FindByID(context.Background(), 1)
}()
}
func TestRepository_FindByChannelID_NilDB_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
r := NewRepository(nil)
_, _ = r.FindByChannelID(context.Background(), "ch1")
}()
}
func TestRepository_Create_NilDB_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
r := NewRepository(nil)
_ = r.Create(context.Background(), nil)
}()
}
func TestRepository_UpdateFields_NilDB_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
r := NewRepository(nil)
_ = r.UpdateFields(context.Background(), 1, map[string]interface{}{"key": "val"})
}()
}
func TestRepository_Delete_NilDB_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
r := NewRepository(nil)
_ = r.Delete(context.Background(), 1)
}()
}
func TestRepository_List_NilDB_Cov7(t *testing.T) {
func() {
defer func() { _ = recover() }()
r := NewRepository(nil)
_, _ = r.List(context.Background(), 1)
}()
}
// ===========================
// LineMessage struct test
// ===========================
func TestLineMessage_JSON_Cov7(t *testing.T) {
msg := LineMessage{Type: "text", ID: "msg1"}
data, err := json.Marshal(msg)
require.NoError(t, err)
assert.Contains(t, string(data), "text")
assert.Contains(t, string(data), "msg1")
}
// ===========================
// Helper functions
// ===========================
// errReaderLine for testing read errors
type errReaderLine struct{}
func (errReaderLine) Read(p []byte) (n int, err error) {
return 0, io.ErrUnexpectedEOF
}
// hmacSHA256 computes HMAC-SHA256 and returns base64-encoded string
func hmacSHA256(secret, body string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(body))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
// ===========================
// Context and misc tests
// ===========================
func TestContext_Cov7(t *testing.T) {
ctx := context.Background()
assert.NotNil(t, ctx)
}
func TestStringsContains_Cov7(t *testing.T) {
assert.True(t, strings.Contains("hello world", "world"))
}
func TestFmtSprintf_Cov7(t *testing.T) {
assert.Equal(t, "test: 42", fmt.Sprintf("test: %d", 42))
}
func TestFmtErrorf_Cov7(t *testing.T) {
err := fmt.Errorf("error: %s", "detail")
assert.Contains(t, err.Error(), "detail")
}
func TestJSONMarshal_Cov7(t *testing.T) {
data, err := json.Marshal(map[string]string{"key": "value"})
require.NoError(t, err)
assert.Contains(t, string(data), "key")
}
func TestHTTPNewRequest_Cov7(t *testing.T) {
req, err := http.NewRequest("GET", "http://example.com", nil)
require.NoError(t, err)
assert.Equal(t, "GET", req.Method)
}
func TestHTTPTestNewRecorder_Cov7(t *testing.T) {
w := httptest.NewRecorder()
assert.NotNil(t, w)
}
func TestIOReadAll_Cov7(t *testing.T) {
data, err := io.ReadAll(strings.NewReader("test"))
require.NoError(t, err)
assert.Equal(t, "test", string(data))
}