veza/veza-backend-api/internal/integration/e2e_chat_ws_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

182 lines
4.3 KiB
Go

//go:build integration
// +build integration
package integration
import (
"context"
"fmt"
"net/http/httptest"
"os"
"testing"
"time"
"veza-backend-api/internal/api"
"veza-backend-api/internal/config"
"veza-backend-api/internal/database"
"veza-backend-api/internal/metrics"
"veza-backend-api/internal/models"
"veza-backend-api/internal/services"
"github.com/coder/websocket"
"github.com/gin-gonic/gin"
"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"
)
const testJWTSecret = "test-secret-key-minimum-32-characters-long"
func setupChatTestRouter(t *testing.T) (*httptest.Server, *gorm.DB, func()) {
t.Helper()
os.Setenv("ENABLE_CLAMAV", "false")
os.Setenv("CLAMAV_REQUIRED", "false")
gin.SetMode(gin.TestMode)
router := gin.New()
logger := zaptest.NewLogger(t)
mockGormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, mockGormDB.AutoMigrate(
&models.User{},
&models.Role{},
&models.Permission{},
&models.ChatMessage{},
&models.ReadReceipt{},
&models.DeliveredStatus{},
&models.MessageReaction{},
))
// Create room_members table for permissions
mockGormDB.Exec(`CREATE TABLE IF NOT EXISTS room_members (
user_id TEXT NOT NULL,
room_id TEXT NOT NULL,
role TEXT DEFAULT 'member',
is_muted INTEGER DEFAULT 0
)`)
// Create rooms table
mockGormDB.Exec(`CREATE TABLE IF NOT EXISTS rooms (
id TEXT PRIMARY KEY,
name TEXT,
type TEXT DEFAULT 'public'
)`)
mockDB := &database.Database{GormDB: mockGormDB}
metrics.InitPrometheus()
cfg := &config.Config{
Env: "test",
ChatJWTSecret: testJWTSecret,
HandlerTimeout: 30 * time.Second,
LogLevel: "DEBUG",
}
apiRouter := api.NewAPIRouter(mockDB, cfg)
err = apiRouter.Setup(router)
require.NoError(t, err)
ts := httptest.NewServer(router)
cleanup := func() {
ts.Close()
}
// Inject logger
_ = logger
return ts, mockGormDB, cleanup
}
func TestChatWS_AuthValid(t *testing.T) {
ts, _, cleanup := setupChatTestRouter(t)
defer cleanup()
chatService := services.NewChatService(testJWTSecret, nil)
userID := uuid.New()
tokenResp, err := chatService.GenerateToken(userID, "testuser")
require.NoError(t, err)
wsURL := fmt.Sprintf("%s/api/v1/ws?token=%s", ts.URL, tokenResp.Token)
wsURL = "ws" + wsURL[4:]
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn, _, err := websocket.Dial(ctx, wsURL, nil)
require.NoError(t, err)
defer conn.Close(websocket.StatusNormalClosure, "")
// Should receive ActionConfirmed connected message
_, msg, err := conn.Read(ctx)
require.NoError(t, err)
assert.Contains(t, string(msg), "connected")
}
func TestChatWS_AuthMissingToken(t *testing.T) {
ts, _, cleanup := setupChatTestRouter(t)
defer cleanup()
wsURL := fmt.Sprintf("%s/api/v1/ws", ts.URL)
wsURL = "ws" + wsURL[4:]
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, _, err := websocket.Dial(ctx, wsURL, nil)
assert.Error(t, err)
}
func TestChatWS_AuthInvalidToken(t *testing.T) {
ts, _, cleanup := setupChatTestRouter(t)
defer cleanup()
wsURL := fmt.Sprintf("%s/api/v1/ws?token=invalid-token", ts.URL)
wsURL = "ws" + wsURL[4:]
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, _, err := websocket.Dial(ctx, wsURL, nil)
assert.Error(t, err)
}
func TestChatWS_PingPong(t *testing.T) {
ts, _, cleanup := setupChatTestRouter(t)
defer cleanup()
chatService := services.NewChatService(testJWTSecret, nil)
userID := uuid.New()
tokenResp, err := chatService.GenerateToken(userID, "testuser")
require.NoError(t, err)
wsURL := fmt.Sprintf("%s/api/v1/ws?token=%s", ts.URL, tokenResp.Token)
wsURL = "ws" + wsURL[4:]
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn, _, err := websocket.Dial(ctx, wsURL, nil)
require.NoError(t, err)
defer conn.Close(websocket.StatusNormalClosure, "")
// Read ActionConfirmed
_, _, err = conn.Read(ctx)
require.NoError(t, err)
// Send Ping
err = conn.Write(ctx, websocket.MessageText, []byte(`{"type":"Ping"}`))
require.NoError(t, err)
// Expect Pong
_, msg, err := conn.Read(ctx)
require.NoError(t, err)
assert.Contains(t, string(msg), "Pong")
}