package ws import ( "context" "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" "github.com/gochat/gochat/internal/auth" "github.com/gochat/gochat/internal/config" "github.com/gochat/gochat/internal/model" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func TestWSAuthenticatorRequiresAndConsumesOneTimeTicket(t *testing.T) { gin.SetMode(gin.TestMode) jwtService := auth.NewJWTService(&config.JWTConfig{Secret: "test-secret", AccessExpiryMinutes: 15}) tickets := auth.NewWSTicketStore(nil, time.Minute) authenticator := NewWSAuthenticator(jwtService, nil, tickets) want := auth.WSTicketClaims{UserID: 7, AccountID: 3, Role: "agent", Provider: "email", ClientID: "browser-session"} ticket, err := tickets.Issue(context.Background(), want) require.NoError(t, err) ctx := wsTestContext("/cable?ticket=" + ticket) claims, err := authenticator.Authenticate(ctx) require.NoError(t, err) require.Equal(t, want.UserID, claims.UserID) require.Equal(t, want.ClientID, claims.ClientID) _, err = authenticator.Authenticate(wsTestContext("/cable?ticket=" + ticket)) require.ErrorIs(t, err, auth.ErrInvalidWSTicket) pair, err := jwtService.GenerateTokenPair(&model.User{Base: model.Base{ID: 7}, Provider: "email"}, 3, "agent") require.NoError(t, err) _, err = authenticator.Authenticate(wsTestContext("/cable?token=" + pair.AccessToken)) require.Error(t, err) } func TestWSAuthenticatorRejectsTicketAfterSessionRevocation(t *testing.T) { db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=private"), &gorm.Config{}) require.NoError(t, err) require.NoError(t, db.AutoMigrate(&model.User{}, &model.UserSession{})) user := &model.User{Name: "Agent", Email: "agent@example.com", Provider: "email", Active: true} require.NoError(t, db.Create(user).Error) require.NoError(t, db.Create(&model.UserSession{UserID: user.ID, ClientID: "browser-session"}).Error) tickets := auth.NewWSTicketStore(nil, time.Minute) ticket, err := tickets.Issue(context.Background(), auth.WSTicketClaims{ UserID: user.ID, AccountID: 3, Role: "agent", Provider: "email", ClientID: "browser-session", }) require.NoError(t, err) require.NoError(t, db.Where("user_id = ? AND client_id = ?", user.ID, "browser-session").Delete(&model.UserSession{}).Error) authenticator := NewWSAuthenticator(auth.NewJWTService(&config.JWTConfig{Secret: "test-secret"}), nil, db, tickets) _, err = authenticator.Authenticate(wsTestContext("/cable?ticket=" + ticket)) require.ErrorIs(t, err, auth.ErrInvalidWSTicket) } func wsTestContext(target string) *gin.Context { ctx, _ := gin.CreateTestContext(httptest.NewRecorder()) ctx.Request = httptest.NewRequest("GET", target, nil) return ctx }