182 lines
5.7 KiB
Plaintext
182 lines
5.7 KiB
Plaintext
package service
|
|
|
|
import (
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// setupPushDeliveryService 创建 PushTokenRepo + PushDeliveryService 测试实例。
|
|
func setupPushDeliveryService(t *testing.T) (*gorm.DB, *repository.PushTokenRepo, *PushDeliveryService) {
|
|
t.Helper()
|
|
db := setupServiceTestDB(t)
|
|
repo := repository.NewPushTokenRepo(db)
|
|
svc := NewPushDeliveryService(repo, "test-vapid-public", "test-vapid-private", "mailto:test@example.com")
|
|
return db, repo, svc
|
|
}
|
|
|
|
// setupWebhookDeliveryService 创建 WebhookSubscriptionRepo + WebhookDeliveryService 测试实例。
|
|
func setupWebhookDeliveryService(t *testing.T) (*gorm.DB, *repository.WebhookSubscriptionRepo, *WebhookDeliveryService) {
|
|
t.Helper()
|
|
db := setupServiceTestDB(t)
|
|
repo := repository.NewWebhookSubscriptionRepo(db)
|
|
svc := NewWebhookDeliveryService(repo)
|
|
return db, repo, svc
|
|
}
|
|
|
|
// ========== PushDeliveryService.SendPushNotification ==========
|
|
|
|
func TestPushDeliveryService_SendPushNotification_成功(t *testing.T) {
|
|
db, _, svc := setupPushDeliveryService(t)
|
|
account := createTestAccount(t, db)
|
|
user := createTestUser(t, db, account.ID)
|
|
|
|
// 创建推送令牌
|
|
createTestPushToken(t, db, user.ID, "push-token-ios-001", "ios")
|
|
createTestPushToken(t, db, user.ID, "push-token-android-002", "android")
|
|
|
|
payload := PushPayload{
|
|
Title: "测试推送",
|
|
Body: "这是一条测试推送消息",
|
|
Data: map[string]interface{}{"conversation_id": 123},
|
|
}
|
|
|
|
err := svc.SendPushNotification(context.Background(), user.ID, payload)
|
|
require.NoError(t, err)
|
|
// 当前实现仅记录日志,不返回错误
|
|
}
|
|
|
|
func TestPushDeliveryService_SendPushNotification_无令牌(t *testing.T) {
|
|
db, _, svc := setupPushDeliveryService(t)
|
|
account := createTestAccount(t, db)
|
|
user := createTestUser(t, db, account.ID)
|
|
|
|
payload := PushPayload{
|
|
Title: "测试推送",
|
|
Body: "无令牌推送",
|
|
}
|
|
|
|
err := svc.SendPushNotification(context.Background(), user.ID, payload)
|
|
require.NoError(t, err)
|
|
// 无令牌时跳过推送,不返回错误
|
|
}
|
|
|
|
// ========== WebhookDeliveryService.DeliverEvent ==========
|
|
|
|
func TestWebhookDeliveryService_DeliverEvent_成功(t *testing.T) {
|
|
db, _, svc := setupWebhookDeliveryService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// 使用 httptest 创建模拟 webhook 端点
|
|
var receivedRequest struct {
|
|
body []byte
|
|
signature string
|
|
event string
|
|
deliveryID string
|
|
mu sync.Mutex
|
|
}
|
|
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
receivedRequest.mu.Lock()
|
|
defer receivedRequest.mu.Unlock()
|
|
|
|
bodyBytes, _ := io.ReadAll(r.Body)
|
|
receivedRequest.body = bodyBytes
|
|
receivedRequest.signature = r.Header.Get("X-Webhook-Signature")
|
|
receivedRequest.event = r.Header.Get("X-Webhook-Event")
|
|
receivedRequest.deliveryID = r.Header.Get("X-Webhook-Delivery-ID")
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
t.Cleanup(func() { testServer.Close() })
|
|
|
|
// 创建 webhook 订阅指向测试服务器
|
|
eventsJSON, _ := json.Marshal([]string{"message_created"})
|
|
sub := &model.WebhookSubscription{
|
|
AccountID: account.ID,
|
|
URL: testServer.URL,
|
|
Events: eventsJSON,
|
|
Secret: "test-webhook-secret-key",
|
|
Active: true,
|
|
}
|
|
require.NoError(t, db.Create(sub).Error)
|
|
|
|
payload := map[string]interface{}{
|
|
"conversation_id": 42,
|
|
"message_id": 100,
|
|
"content": "Hello webhook",
|
|
}
|
|
|
|
err := svc.DeliverEvent(context.Background(), account.ID, "message_created", payload)
|
|
require.NoError(t, err)
|
|
|
|
// 验证测试服务器收到了正确的请求
|
|
receivedRequest.mu.Lock()
|
|
defer receivedRequest.mu.Unlock()
|
|
|
|
assert.NotEmpty(t, receivedRequest.body, "webhook 端点应收到请求体")
|
|
|
|
// 验证 X-Webhook-Signature 头
|
|
expectedSig := SignPayload(receivedRequest.body, "test-webhook-secret-key")
|
|
assert.Equal(t, expectedSig, receivedRequest.signature, "X-Webhook-Signature 应匹配 HMAC-SHA256 签名")
|
|
|
|
// 验证 X-Webhook-Event 头
|
|
assert.Equal(t, "message_created", receivedRequest.event, "X-Webhook-Event 应为 message_created")
|
|
|
|
// 验证 X-Webhook-Delivery-ID 头
|
|
assert.NotEmpty(t, receivedRequest.deliveryID, "X-Webhook-Delivery-ID 不应为空")
|
|
}
|
|
|
|
func TestWebhookDeliveryService_DeliverEvent_无订阅(t *testing.T) {
|
|
db, _, svc := setupWebhookDeliveryService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
payload := map[string]interface{}{"id": 1}
|
|
err := svc.DeliverEvent(context.Background(), account.ID, "message_created", payload)
|
|
require.NoError(t, err)
|
|
// 无匹配订阅时不发送,不返回错误
|
|
}
|
|
|
|
// ========== SignPayload ==========
|
|
|
|
func TestSignPayload_正确计算HMACSHA256(t *testing.T) {
|
|
payload := []byte(`{"event":"message_created","data":{"id":1}}`)
|
|
secret := "my-secret-key"
|
|
|
|
// 手动计算 HMAC-SHA256 用于比对
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
mac.Write(payload)
|
|
expectedHex := hex.EncodeToString(mac.Sum(nil))
|
|
|
|
result := SignPayload(payload, secret)
|
|
assert.Equal(t, expectedHex, result)
|
|
}
|
|
|
|
func TestSignPayload_不同密钥产生不同签名(t *testing.T) {
|
|
payload := []byte(`{"event":"test"}`)
|
|
|
|
sig1 := SignPayload(payload, "secret-one")
|
|
sig2 := SignPayload(payload, "secret-two")
|
|
assert.NotEqual(t, sig1, sig2, "不同密钥应产生不同签名")
|
|
}
|
|
|
|
func TestSignPayload_相同密钥和载荷产生相同签名(t *testing.T) {
|
|
payload := []byte(`{"event":"consistent"}`)
|
|
secret := "same-secret"
|
|
|
|
sig1 := SignPayload(payload, secret)
|
|
sig2 := SignPayload(payload, secret)
|
|
assert.Equal(t, sig1, sig2, "相同密钥和载荷应产生相同签名")
|
|
} |