veza/veza-backend-api/internal/websocket/chat/handler_messages_test.go
senke 02605b0405 test(chat): Sprint 5 -- unit tests, E2E tests, feature parity validation
- Add hub_test.go: register/unregister, join/leave room, broadcast, exclude sender,
  send to user, multiple clients same user (6 tests)
- Add handler_messages_test.go: send message, missing fields, edit ownership check,
  soft delete (4 tests)
- Add handler_realtime_test.go: typing broadcast, read receipts, reactions add/remove,
  delivered status (5 tests)
- Add e2e_chat_ws_test.go: auth valid, missing token, invalid token, ping/pong
- Add e2e_chat_messages_test.go: 2-client message flow, typing indicator
- Create CHAT_FEATURE_PARITY.md: 25-feature checklist (all OK or IMPROVED)
2026-02-22 20:49:32 +01:00

195 lines
5.1 KiB
Go

package chat
import (
"context"
"encoding/json"
"testing"
"time"
"veza-backend-api/internal/models"
"veza-backend-api/internal/repositories"
"veza-backend-api/internal/services"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func setupTestHandler(t *testing.T) (*MessageHandler, *Hub, *gorm.DB) {
t.Helper()
logger := zaptest.NewLogger(t)
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(
&models.ChatMessage{},
&models.ReadReceipt{},
&models.DeliveredStatus{},
&models.MessageReaction{},
))
hub := NewHub(logger)
go hub.Run()
time.Sleep(10 * time.Millisecond)
msgRepo := repositories.NewChatMessageRepository(db)
readRepo := repositories.NewReadReceiptRepository(db)
deliveredRepo := repositories.NewDeliveredStatusRepository(db)
reactionRepo := repositories.NewReactionRepository(db)
pubsub := services.NewChatPubSubService(nil, logger)
permissions := &PermissionService{db: db, logger: logger}
rateLimiter := NewRateLimiter()
handler := NewMessageHandler(
hub, msgRepo, readRepo, deliveredRepo, reactionRepo,
pubsub, permissions, rateLimiter, logger,
)
return handler, hub, db
}
func TestHandleSendMessage_Success(t *testing.T) {
handler, hub, db := setupTestHandler(t)
ctx := context.Background()
userID := uuid.New()
roomID := uuid.New()
db.Exec("CREATE TABLE IF NOT EXISTS room_members (user_id TEXT, room_id TEXT, role TEXT DEFAULT 'member', is_muted INTEGER DEFAULT 0)")
db.Exec("INSERT INTO room_members (user_id, room_id, role, is_muted) VALUES (?, ?, 'member', 0)", userID, roomID)
client := newTestClient(hub, userID)
hub.Register(client)
hub.JoinRoom(client, roomID)
time.Sleep(20 * time.Millisecond)
msg := &IncomingMessage{
Type: TypeSendMessage,
ConversationID: &roomID,
Content: "Hello World",
}
handler.HandleSendMessage(ctx, client, msg)
time.Sleep(20 * time.Millisecond)
// Check we got ActionConfirmed and NewMessage broadcast
assert.GreaterOrEqual(t, len(client.send), 1)
// Verify message was stored in DB
var count int64
db.Model(&models.ChatMessage{}).Where("room_id = ?", roomID).Count(&count)
assert.Equal(t, int64(1), count)
}
func TestHandleSendMessage_MissingConversationID(t *testing.T) {
handler, hub, _ := setupTestHandler(t)
ctx := context.Background()
userID := uuid.New()
client := newTestClient(hub, userID)
hub.Register(client)
time.Sleep(20 * time.Millisecond)
msg := &IncomingMessage{
Type: TypeSendMessage,
Content: "Hello",
}
handler.HandleSendMessage(ctx, client, msg)
time.Sleep(20 * time.Millisecond)
assert.Len(t, client.send, 1)
resp := <-client.send
var parsed map[string]interface{}
json.Unmarshal(resp, &parsed)
assert.Equal(t, TypeError, parsed["type"])
}
func TestHandleEditMessage_OwnershipCheck(t *testing.T) {
handler, hub, db := setupTestHandler(t)
ctx := context.Background()
senderID := uuid.New()
otherID := uuid.New()
roomID := uuid.New()
msgID := uuid.New()
existingMsg := &models.ChatMessage{
ID: msgID,
ConversationID: roomID,
SenderID: senderID,
Content: "original",
MessageType: "text",
Status: "sent",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
db.Create(existingMsg)
otherClient := newTestClient(hub, otherID)
hub.Register(otherClient)
time.Sleep(20 * time.Millisecond)
msg := &IncomingMessage{
Type: TypeEditMessage,
MessageID: &msgID,
ConversationID: &roomID,
NewContent: "modified",
}
handler.HandleEditMessage(ctx, otherClient, msg)
time.Sleep(20 * time.Millisecond)
assert.Len(t, otherClient.send, 1)
resp := <-otherClient.send
var parsed map[string]interface{}
json.Unmarshal(resp, &parsed)
assert.Equal(t, TypeError, parsed["type"])
assert.Contains(t, parsed["message"], "own messages")
}
func TestHandleDeleteMessage_SoftDelete(t *testing.T) {
handler, hub, db := setupTestHandler(t)
ctx := context.Background()
userID := uuid.New()
roomID := uuid.New()
msgID := uuid.New()
db.Exec("CREATE TABLE IF NOT EXISTS room_members (user_id TEXT, room_id TEXT, role TEXT DEFAULT 'member', is_muted INTEGER DEFAULT 0)")
db.Exec("INSERT INTO room_members (user_id, room_id, role, is_muted) VALUES (?, ?, 'member', 0)", userID, roomID)
existingMsg := &models.ChatMessage{
ID: msgID,
ConversationID: roomID,
SenderID: userID,
Content: "to delete",
MessageType: "text",
Status: "sent",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
db.Create(existingMsg)
client := newTestClient(hub, userID)
hub.Register(client)
hub.JoinRoom(client, roomID)
time.Sleep(20 * time.Millisecond)
msg := &IncomingMessage{
Type: TypeDeleteMessage,
MessageID: &msgID,
ConversationID: &roomID,
}
handler.HandleDeleteMessage(ctx, client, msg)
time.Sleep(20 * time.Millisecond)
var deletedMsg models.ChatMessage
db.Where("id = ?", msgID).First(&deletedMsg)
assert.True(t, deletedMsg.IsDeleted)
}