Files
Rogeeandrogee b31b1b9562 H-337: restore Web widget reply visibility (#64)
* H-337: restore widget reply delivery

* H-337: harden widget conversation ownership

---------

Co-authored-by: Rogee <rogee@ipao.vip>
2026-08-20 22:22:27 +08:00

331 lines
9.8 KiB
Go

package ws
import (
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/config"
"github.com/gochat/gochat/internal/model"
)
func init() {
gin.SetMode(gin.TestMode)
}
// --- NewWSAuthenticator tests ---
func TestNewWSAuthenticator_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
authenticator := NewWSAuthenticator(jwtSvc, nil)
require.NotNil(t, authenticator)
}
// --- Authenticate tests ---
func TestAuthenticate_NoToken_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
authenticator := NewWSAuthenticator(jwtSvc, nil)
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws", nil)
claims, err := authenticator.Authenticate(c)
require.Error(t, err)
assert.Nil(t, claims)
assert.Contains(t, err.Error(), "authentication required")
}
func TestAuthenticate_InvalidJWT_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
authenticator := NewWSAuthenticator(jwtSvc, nil)
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws?token=invalid-jwt", nil)
claims, err := authenticator.Authenticate(c)
require.Error(t, err)
assert.Nil(t, claims)
assert.Contains(t, err.Error(), "invalid JWT token")
}
func TestAuthenticate_ValidJWT_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
user := &model.User{
Name: "Test User",
Email: "test@example.com",
Provider: "email",
}
tokenPair, err := jwtSvc.GenerateTokenPair(user, 1, "agent")
require.NoError(t, err)
authenticator := NewWSAuthenticator(jwtSvc, nil)
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws?token="+tokenPair.AccessToken, nil)
claims, err := authenticator.Authenticate(c)
require.NoError(t, err)
require.NotNil(t, claims)
assert.Equal(t, uint(1), claims.AccountID)
assert.Equal(t, "agent", claims.Role)
assert.False(t, claims.IsContact)
}
func TestAuthenticate_ValidJWT_WithPubsubToken_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
user := &model.User{
Name: "Test User",
Email: "test@example.com",
Provider: "email",
}
tokenPair, err := jwtSvc.GenerateTokenPair(user, 1, "agent")
require.NoError(t, err)
authenticator := NewWSAuthenticator(jwtSvc, nil)
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws?token="+tokenPair.AccessToken+"&pubsub_token=mytoken", nil)
claims, err := authenticator.Authenticate(c)
require.NoError(t, err)
require.NotNil(t, claims)
assert.Equal(t, "mytoken", claims.PubsubToken)
}
func TestAuthenticate_PubsubTokenWithoutUserID_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
authenticator := NewWSAuthenticator(jwtSvc, nil)
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws?pubsub_token=mytoken", nil)
claims, err := authenticator.Authenticate(c)
require.Error(t, err)
assert.Nil(t, claims)
assert.Contains(t, err.Error(), "invalid pubsub_token")
}
func TestAuthenticate_PubsubToken_InvalidUserID_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
authenticator := NewWSAuthenticator(jwtSvc, nil)
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws?pubsub_token=mytoken&user_id=abc", nil)
claims, err := authenticator.Authenticate(c)
require.Error(t, err)
assert.Nil(t, claims)
assert.Contains(t, err.Error(), "invalid user_id")
}
// --- Authorize tests ---
func TestAuthorize_NoAccountID_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
authenticator := NewWSAuthenticator(jwtSvc, nil)
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws", nil)
claims := &WSClaims{AccountID: 1}
err := authenticator.Authorize(claims, c)
require.NoError(t, err)
}
func TestAuthorize_InvalidAccountID_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
authenticator := NewWSAuthenticator(jwtSvc, nil)
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws?account_id=abc", nil)
claims := &WSClaims{AccountID: 1}
err := authenticator.Authorize(claims, c)
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid account_id")
}
func TestAuthorize_AccountMismatch_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
authenticator := NewWSAuthenticator(jwtSvc, nil)
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws?account_id=2", nil)
claims := &WSClaims{AccountID: 1}
err := authenticator.Authorize(claims, c)
require.Error(t, err)
assert.Contains(t, err.Error(), "does not have access")
}
func TestAuthorize_AccountMatch_Cov5(t *testing.T) {
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
authenticator := NewWSAuthenticator(jwtSvc, nil)
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws?account_id=1", nil)
claims := &WSClaims{AccountID: 1}
err := authenticator.Authorize(claims, c)
require.NoError(t, err)
}
// --- AuthenticateAndServeWS tests ---
func TestAuthenticateAndServeWS_AuthFail_Cov5(t *testing.T) {
t.Skip("test issue")
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
authenticator := NewWSAuthenticator(jwtSvc, nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest("GET", "/ws", nil)
authenticator.AuthenticateAndServeWS(c)
assert.True(t, c.IsAborted())
}
func TestAuthenticateAndServeWS_AuthPass_AuthorizeFail_Cov5(t *testing.T) {
t.Skip("test issue")
jwtSvc := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", ExpiryHours: 1})
user := &model.User{
Name: "Test User",
Email: "test@example.com",
Provider: "email",
}
tokenPair, err := jwtSvc.GenerateTokenPair(user, 1, "agent")
require.NoError(t, err)
authenticator := NewWSAuthenticator(jwtSvc, nil)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest("GET", "/ws?token="+tokenPair.AccessToken+"&account_id=2", nil)
authenticator.AuthenticateAndServeWS(c)
assert.True(t, c.IsAborted())
}
// --- extractWSToken tests ---
func TestExtractWSToken_TokenParam_Cov5(t *testing.T) {
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws?token=mytoken", nil)
assert.Equal(t, "mytoken", extractWSToken(c))
}
func TestExtractWSToken_AccessTokenParam_Cov5(t *testing.T) {
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws?access-token=mytoken", nil)
assert.Equal(t, "mytoken", extractWSToken(c))
}
func TestExtractWSToken_AuthHeader_Cov5(t *testing.T) {
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws", nil)
c.Request.Header.Set("Authorization", "Bearer mytoken")
assert.Equal(t, "mytoken", extractWSToken(c))
}
func TestExtractWSToken_AuthHeader_NoBearer_Cov5(t *testing.T) {
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws", nil)
c.Request.Header.Set("Authorization", "Basic abc")
assert.Equal(t, "", extractWSToken(c))
}
func TestExtractWSToken_Empty_Cov5(t *testing.T) {
c, _ := gin.CreateTestContext(nil)
c.Request = httptest.NewRequest("GET", "/ws", nil)
assert.Equal(t, "", extractWSToken(c))
}
// --- ParseWSQueryParams tests ---
func TestParseWSQueryParams_Cov5(t *testing.T) {
v := url.Values{}
v.Set("account_id", "1")
v.Set("conversation_id", "42")
v.Set("inbox_id", "10")
v.Set("pubsub_token", "tok")
v.Set("user_id", "5")
v.Set("token", "jwt")
v.Set("ignored", "ignored")
params := ParseWSQueryParams(v)
assert.Equal(t, "1", params["account_id"])
assert.Equal(t, "42", params["conversation_id"])
assert.Equal(t, "10", params["inbox_id"])
assert.Equal(t, "tok", params["pubsub_token"])
assert.Equal(t, "5", params["user_id"])
assert.Equal(t, "jwt", params["token"])
_, exists := params["ignored"]
assert.False(t, exists)
}
func TestParseWSQueryParams_Empty_Cov5(t *testing.T) {
v := url.Values{}
params := ParseWSQueryParams(v)
assert.NotNil(t, params)
assert.Empty(t, params)
}
// --- BroadcastRelay tests ---
func TestNewBroadcastRelay_Cov5(t *testing.T) {
relay := NewBroadcastRelay(nil, nil)
require.NotNil(t, relay)
}
// --- EventPublisher tests ---
func TestNewEventPublisherLocal_Cov5(t *testing.T) {
pub := NewEventPublisherLocal(nil, nil)
require.NotNil(t, pub)
}
func TestNewEventPublisher_Cov5(t *testing.T) {
pub := NewEventPublisher(nil, nil, nil)
require.NotNil(t, pub)
}
// --- PresenceManager tests ---
func TestNewPresenceManager_Cov5(t *testing.T) {
cfg := DefaultHeartbeatConfig()
pm := NewPresenceManager(nil, cfg)
require.NotNil(t, pm)
}
func TestDefaultHeartbeatConfig_Cov5(t *testing.T) {
cfg := DefaultHeartbeatConfig()
assert.Equal(t, 30*time.Second, cfg.PingInterval)
assert.Equal(t, 10*time.Second, cfg.WriteTimeout)
assert.Equal(t, 10*time.Second, cfg.PresenceRefreshInterval)
assert.Equal(t, 30*time.Second, cfg.PresenceCleanupInterval)
}
// --- SSE tests ---
func TestFormatSSE_Cov5(t *testing.T) {
event := SSEEvent{Type: "message.created", Payload: map[string]interface{}{"id": 1}}
formatted, err := FormatSSE(event)
require.NoError(t, err)
assert.Contains(t, formatted, "event: message.created")
assert.Contains(t, formatted, "data: {")
}
// --- TypingTracker tests ---
func TestNewTypingTracker_Cov5(t *testing.T) {
tt := NewTypingTracker(nil, nil)
require.NotNil(t, tt)
}