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, "相同密钥和载荷应产生相同签名") }