155 lines
5.1 KiB
Go
155 lines
5.1 KiB
Go
|
|
package handlers
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"veza-backend-api/internal/models"
|
||
|
|
|
||
|
|
"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"
|
||
|
|
)
|
||
|
|
|
||
|
|
func setupUpgradeCreatorTest(t *testing.T) (*gin.Engine, *gorm.DB, uuid.UUID) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||
|
|
require.NoError(t, err)
|
||
|
|
db.Exec("PRAGMA foreign_keys = ON")
|
||
|
|
require.NoError(t, db.AutoMigrate(&models.User{}))
|
||
|
|
|
||
|
|
userID := uuid.New()
|
||
|
|
// Default seed: verified user with role=user. Individual tests can override.
|
||
|
|
require.NoError(t, db.Create(&models.User{
|
||
|
|
ID: userID,
|
||
|
|
Username: "upgrader",
|
||
|
|
Email: "upgrade@example.com",
|
||
|
|
Role: "user",
|
||
|
|
IsVerified: true,
|
||
|
|
}).Error)
|
||
|
|
|
||
|
|
logger := zaptest.NewLogger(t)
|
||
|
|
router := gin.New()
|
||
|
|
router.Use(func(c *gin.Context) { c.Set("user_id", userID); c.Next() })
|
||
|
|
router.POST("/users/me/upgrade-creator", UpgradeToCreator(db, nil, logger))
|
||
|
|
|
||
|
|
return router, db, userID
|
||
|
|
}
|
||
|
|
|
||
|
|
func postUpgrade(t *testing.T, router *gin.Engine) *httptest.ResponseRecorder {
|
||
|
|
req := httptest.NewRequest(http.MethodPost, "/users/me/upgrade-creator", nil)
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
router.ServeHTTP(w, req)
|
||
|
|
return w
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUpgradeToCreator_VerifiedUserIsPromoted(t *testing.T) {
|
||
|
|
router, db, userID := setupUpgradeCreatorTest(t)
|
||
|
|
|
||
|
|
w := postUpgrade(t, router)
|
||
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
||
|
|
|
||
|
|
var resp UpgradeCreatorResponse
|
||
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||
|
|
assert.Equal(t, "creator", resp.Role)
|
||
|
|
assert.False(t, resp.AlreadyElevated)
|
||
|
|
require.NotNil(t, resp.PromotedToCreatorAt)
|
||
|
|
assert.WithinDuration(t, time.Now(), *resp.PromotedToCreatorAt, 5*time.Second)
|
||
|
|
|
||
|
|
var after models.User
|
||
|
|
require.NoError(t, db.First(&after, "id = ?", userID).Error)
|
||
|
|
assert.Equal(t, "creator", after.Role)
|
||
|
|
require.NotNil(t, after.PromotedToCreatorAt)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUpgradeToCreator_UnverifiedIsRefused(t *testing.T) {
|
||
|
|
router, db, userID := setupUpgradeCreatorTest(t)
|
||
|
|
// Flip is_verified=false; default seed had it true.
|
||
|
|
require.NoError(t, db.Model(&models.User{}).Where("id = ?", userID).Update("is_verified", false).Error)
|
||
|
|
|
||
|
|
w := postUpgrade(t, router)
|
||
|
|
assert.Equal(t, http.StatusForbidden, w.Code)
|
||
|
|
assert.Contains(t, w.Body.String(), "EMAIL_NOT_VERIFIED")
|
||
|
|
|
||
|
|
var after models.User
|
||
|
|
require.NoError(t, db.First(&after, "id = ?", userID).Error)
|
||
|
|
assert.Equal(t, "user", after.Role, "role must not flip when email is not verified")
|
||
|
|
assert.Nil(t, after.PromotedToCreatorAt)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUpgradeToCreator_AlreadyCreatorIsIdempotent(t *testing.T) {
|
||
|
|
router, db, userID := setupUpgradeCreatorTest(t)
|
||
|
|
earlier := time.Now().Add(-48 * time.Hour).UTC()
|
||
|
|
require.NoError(t, db.Model(&models.User{}).Where("id = ?", userID).Updates(map[string]interface{}{
|
||
|
|
"role": "creator",
|
||
|
|
"promoted_to_creator_at": earlier,
|
||
|
|
}).Error)
|
||
|
|
|
||
|
|
w := postUpgrade(t, router)
|
||
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
||
|
|
|
||
|
|
var resp UpgradeCreatorResponse
|
||
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||
|
|
assert.Equal(t, "creator", resp.Role)
|
||
|
|
assert.True(t, resp.AlreadyElevated)
|
||
|
|
require.NotNil(t, resp.PromotedToCreatorAt)
|
||
|
|
assert.WithinDuration(t, earlier, *resp.PromotedToCreatorAt, time.Second,
|
||
|
|
"idempotent path must preserve the original promotion timestamp")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUpgradeToCreator_AdminIsIdempotentWithoutTimestamp(t *testing.T) {
|
||
|
|
router, db, userID := setupUpgradeCreatorTest(t)
|
||
|
|
// Admin was assigned out-of-band — no promoted_to_creator_at.
|
||
|
|
require.NoError(t, db.Model(&models.User{}).Where("id = ?", userID).Update("role", "admin").Error)
|
||
|
|
|
||
|
|
w := postUpgrade(t, router)
|
||
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
||
|
|
|
||
|
|
var resp UpgradeCreatorResponse
|
||
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||
|
|
assert.Equal(t, "admin", resp.Role, "admin must keep their role, not be downgraded to creator")
|
||
|
|
assert.True(t, resp.AlreadyElevated)
|
||
|
|
assert.Nil(t, resp.PromotedToCreatorAt, "admin assigned without the self-service flow has no timestamp")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUpgradeToCreator_UserNotFound(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||
|
|
require.NoError(t, err)
|
||
|
|
require.NoError(t, db.AutoMigrate(&models.User{}))
|
||
|
|
|
||
|
|
logger := zaptest.NewLogger(t)
|
||
|
|
router := gin.New()
|
||
|
|
// Inject a user_id that doesn't exist in the DB
|
||
|
|
router.Use(func(c *gin.Context) { c.Set("user_id", uuid.New()); c.Next() })
|
||
|
|
router.POST("/users/me/upgrade-creator", UpgradeToCreator(db, nil, logger))
|
||
|
|
|
||
|
|
w := postUpgrade(t, router)
|
||
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||
|
|
assert.Contains(t, w.Body.String(), "USER_NOT_FOUND")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestUpgradeToCreator_NoAuthContext(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||
|
|
require.NoError(t, err)
|
||
|
|
require.NoError(t, db.AutoMigrate(&models.User{}))
|
||
|
|
|
||
|
|
logger := zaptest.NewLogger(t)
|
||
|
|
router := gin.New()
|
||
|
|
// No middleware that sets user_id
|
||
|
|
router.POST("/users/me/upgrade-creator", UpgradeToCreator(db, nil, logger))
|
||
|
|
|
||
|
|
w := postUpgrade(t, router)
|
||
|
|
assert.Equal(t, http.StatusUnauthorized, w.Code)
|
||
|
|
assert.Contains(t, w.Body.String(), "UNAUTHORIZED")
|
||
|
|
}
|