chore: git cache cleanup
This commit is contained in:
@@ -1,626 +0,0 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"github.com/Wikid82/charon/backend/internal/api/handlers"
|
||||
"github.com/Wikid82/charon/backend/internal/models"
|
||||
)
|
||||
|
||||
// setupAuditTestDB creates a clean in-memory database for each test
|
||||
func setupAuditTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Auto-migrate required models
|
||||
err = db.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.Setting{},
|
||||
&models.ProxyHost{},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
return db
|
||||
}
|
||||
|
||||
// createTestAdminUser creates an admin user and returns their ID
|
||||
func createTestAdminUser(t *testing.T, db *gorm.DB) uint {
|
||||
t.Helper()
|
||||
admin := models.User{
|
||||
UUID: "admin-uuid-1234",
|
||||
Email: "admin@test.com",
|
||||
Name: "Test Admin",
|
||||
Role: models.RoleAdmin,
|
||||
Enabled: true,
|
||||
APIKey: "test-api-key",
|
||||
}
|
||||
require.NoError(t, admin.SetPassword("adminpassword123"))
|
||||
require.NoError(t, db.Create(&admin).Error)
|
||||
return admin.ID
|
||||
}
|
||||
|
||||
// setupRouterWithAuth creates a gin router with auth middleware mocked
|
||||
func setupRouterWithAuth(db *gorm.DB, userID uint, role string) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
|
||||
// Mock auth middleware
|
||||
r.Use(func(c *gin.Context) {
|
||||
c.Set("userID", userID)
|
||||
c.Set("role", role)
|
||||
c.Next()
|
||||
})
|
||||
|
||||
userHandler := handlers.NewUserHandler(db, nil)
|
||||
settingsHandler := handlers.NewSettingsHandler(db)
|
||||
|
||||
api := r.Group("/api")
|
||||
userHandler.RegisterRoutes(api)
|
||||
|
||||
// Settings routes
|
||||
api.GET("/settings/smtp", settingsHandler.GetSMTPConfig)
|
||||
api.POST("/settings/smtp", settingsHandler.UpdateSMTPConfig)
|
||||
api.POST("/settings/smtp/test", settingsHandler.TestSMTPConfig)
|
||||
api.POST("/settings/smtp/test-email", settingsHandler.SendTestEmail)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ==================== INVITE TOKEN SECURITY TESTS ====================
|
||||
|
||||
func TestInviteToken_MustBeUnguessable(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
// Invite a user
|
||||
body := `{"email":"user@test.com","role":"user"}`
|
||||
req := httptest.NewRequest("POST", "/api/users/invite", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusCreated, w.Code)
|
||||
|
||||
var resp map[string]any
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
var invitedUser models.User
|
||||
require.NoError(t, db.Where("email = ?", "user@test.com").First(&invitedUser).Error)
|
||||
token := invitedUser.InviteToken
|
||||
require.NotEmpty(t, token)
|
||||
|
||||
// Token MUST be at least 32 chars (64 hex = 32 bytes = 256 bits)
|
||||
assert.GreaterOrEqual(t, len(token), 64, "Invite token must be at least 64 hex chars (256 bits)")
|
||||
|
||||
// Token must be hex
|
||||
for _, c := range token {
|
||||
assert.True(t, (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'), "Token must be hex encoded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInviteToken_ExpiredCannotBeUsed(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
|
||||
// Create user with expired invite
|
||||
expiredTime := time.Now().Add(-1 * time.Hour)
|
||||
invitedAt := time.Now().Add(-50 * time.Hour)
|
||||
user := models.User{
|
||||
UUID: "invite-uuid-1234",
|
||||
Email: "expired@test.com",
|
||||
Role: models.RoleUser,
|
||||
Enabled: false,
|
||||
InviteToken: "expired-token-12345678901234567890123456789012",
|
||||
InviteExpires: &expiredTime,
|
||||
InvitedAt: &invitedAt,
|
||||
InviteStatus: "pending",
|
||||
}
|
||||
require.NoError(t, db.Create(&user).Error)
|
||||
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
// Try to validate expired token
|
||||
req := httptest.NewRequest("GET", "/api/invite/validate?token=expired-token-12345678901234567890123456789012", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusGone, w.Code, "Expired tokens should return 410 Gone")
|
||||
}
|
||||
|
||||
func TestInviteToken_CannotBeReused(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
|
||||
// Create user with already accepted invite
|
||||
invitedAt := time.Now().Add(-24 * time.Hour)
|
||||
user := models.User{
|
||||
UUID: "accepted-uuid-1234",
|
||||
Email: "accepted@test.com",
|
||||
Name: "Accepted User",
|
||||
Role: models.RoleUser,
|
||||
Enabled: true,
|
||||
InviteToken: "accepted-token-1234567890123456789012345678901",
|
||||
InvitedAt: &invitedAt,
|
||||
InviteStatus: "accepted",
|
||||
}
|
||||
require.NoError(t, user.SetPassword("somepassword"))
|
||||
require.NoError(t, db.Create(&user).Error)
|
||||
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
// Try to accept again
|
||||
body := `{"token":"accepted-token-1234567890123456789012345678901","name":"Hacker","password":"newpassword123"}`
|
||||
req := httptest.NewRequest("POST", "/api/invite/accept", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusConflict, w.Code, "Already accepted tokens should return 409 Conflict")
|
||||
}
|
||||
|
||||
// ==================== INPUT VALIDATION TESTS ====================
|
||||
|
||||
func TestInviteUser_EmailValidation(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
email string
|
||||
wantCode int
|
||||
}{
|
||||
{"empty email", "", http.StatusBadRequest},
|
||||
{"invalid email no @", "notanemail", http.StatusBadRequest},
|
||||
{"invalid email no domain", "test@", http.StatusBadRequest},
|
||||
{"sql injection attempt", "'; DROP TABLE users;--@evil.com", http.StatusBadRequest},
|
||||
{"script injection", "<script>alert('xss')</script>@evil.com", http.StatusBadRequest},
|
||||
{"valid email", "valid@example.com", http.StatusCreated},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
body := `{"email":"` + tc.email + `","role":"user"}`
|
||||
req := httptest.NewRequest("POST", "/api/users/invite", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, tc.wantCode, w.Code, "Email: %s", tc.email)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcceptInvite_PasswordValidation(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
|
||||
// Create user with valid invite
|
||||
expires := time.Now().Add(24 * time.Hour)
|
||||
invitedAt := time.Now()
|
||||
user := models.User{
|
||||
UUID: "pending-uuid-1234",
|
||||
Email: "pending@test.com",
|
||||
Role: models.RoleUser,
|
||||
Enabled: false,
|
||||
InviteToken: "valid-token-12345678901234567890123456789012345",
|
||||
InviteExpires: &expires,
|
||||
InvitedAt: &invitedAt,
|
||||
InviteStatus: "pending",
|
||||
}
|
||||
require.NoError(t, db.Create(&user).Error)
|
||||
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
password string
|
||||
wantCode int
|
||||
}{
|
||||
{"empty password", "", http.StatusBadRequest},
|
||||
{"too short", "short", http.StatusBadRequest},
|
||||
{"7 chars", "1234567", http.StatusBadRequest},
|
||||
{"8 chars valid", "12345678", http.StatusOK},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Reset user to pending state for each test
|
||||
db.Model(&user).Updates(map[string]any{
|
||||
"invite_status": "pending",
|
||||
"enabled": false,
|
||||
"password_hash": "",
|
||||
})
|
||||
|
||||
body := `{"token":"valid-token-12345678901234567890123456789012345","name":"Test User","password":"` + tc.password + `"}`
|
||||
req := httptest.NewRequest("POST", "/api/invite/accept", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, tc.wantCode, w.Code, "Password: %s", tc.password)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== AUTHORIZATION TESTS ====================
|
||||
|
||||
func TestUserEndpoints_RequireAdmin(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
|
||||
// Create regular user
|
||||
user := models.User{
|
||||
UUID: "user-uuid-1234",
|
||||
Email: "user@test.com",
|
||||
Name: "Regular User",
|
||||
Role: models.RoleUser,
|
||||
Enabled: true,
|
||||
APIKey: "user-api-key-unique",
|
||||
}
|
||||
require.NoError(t, user.SetPassword("userpassword123"))
|
||||
require.NoError(t, db.Create(&user).Error)
|
||||
|
||||
// Create a second user to test admin-only operations against a non-self target
|
||||
otherUser := models.User{
|
||||
UUID: "other-uuid-5678",
|
||||
Email: "other@test.com",
|
||||
Name: "Other User",
|
||||
Role: models.RoleUser,
|
||||
Enabled: true,
|
||||
APIKey: "other-api-key-unique",
|
||||
}
|
||||
require.NoError(t, otherUser.SetPassword("otherpassword123"))
|
||||
require.NoError(t, db.Create(&otherUser).Error)
|
||||
|
||||
// Router with regular user role
|
||||
r := setupRouterWithAuth(db, user.ID, "user")
|
||||
|
||||
otherID := fmt.Sprintf("%d", otherUser.ID)
|
||||
endpoints := []struct {
|
||||
method string
|
||||
path string
|
||||
body string
|
||||
}{
|
||||
{"GET", "/api/users", ""},
|
||||
{"POST", "/api/users", `{"email":"new@test.com","name":"New","password":"password123"}`},
|
||||
{"POST", "/api/users/invite", `{"email":"invite@test.com"}`},
|
||||
{"GET", "/api/users/" + otherID, ""},
|
||||
{"PUT", "/api/users/" + otherID, `{"name":"Updated"}`},
|
||||
{"DELETE", "/api/users/" + otherID, ""},
|
||||
{"PUT", "/api/users/" + otherID + "/permissions", `{"permission_mode":"deny_all"}`},
|
||||
}
|
||||
|
||||
for _, ep := range endpoints {
|
||||
t.Run(ep.method+" "+ep.path, func(t *testing.T) {
|
||||
var req *http.Request
|
||||
if ep.body != "" {
|
||||
req = httptest.NewRequest(ep.method, ep.path, strings.NewReader(ep.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else {
|
||||
req = httptest.NewRequest(ep.method, ep.path, http.NoBody)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Non-admin should be forbidden from %s %s", ep.method, ep.path)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSMTPEndpoints_RequireAdmin(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
|
||||
user := models.User{
|
||||
UUID: "user-uuid-5678",
|
||||
Email: "user2@test.com",
|
||||
Name: "Regular User 2",
|
||||
Role: models.RoleUser,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, user.SetPassword("userpassword123"))
|
||||
require.NoError(t, db.Create(&user).Error)
|
||||
|
||||
r := setupRouterWithAuth(db, user.ID, "user")
|
||||
|
||||
// POST endpoints should require admin
|
||||
postEndpoints := []struct {
|
||||
path string
|
||||
body string
|
||||
}{
|
||||
{"/api/settings/smtp", `{"host":"smtp.test.com","port":587,"from_address":"test@test.com","encryption":"starttls"}`},
|
||||
{"/api/settings/smtp/test", ""},
|
||||
{"/api/settings/smtp/test-email", `{"to":"test@test.com"}`},
|
||||
}
|
||||
|
||||
for _, ep := range postEndpoints {
|
||||
t.Run("POST "+ep.path, func(t *testing.T) {
|
||||
req := httptest.NewRequest("POST", ep.path, strings.NewReader(ep.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Non-admin should be forbidden from POST %s", ep.path)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== SMTP CONFIG SECURITY TESTS ====================
|
||||
|
||||
func TestSMTPConfig_PasswordMasked(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
|
||||
// Save SMTP config with password
|
||||
settings := []models.Setting{
|
||||
{Key: "smtp_host", Value: "smtp.test.com", Category: "smtp"},
|
||||
{Key: "smtp_port", Value: "587", Category: "smtp"},
|
||||
{Key: "smtp_password", Value: "supersecretpassword", Category: "smtp"},
|
||||
{Key: "smtp_from_address", Value: "test@test.com", Category: "smtp"},
|
||||
{Key: "smtp_encryption", Value: "starttls", Category: "smtp"},
|
||||
}
|
||||
for _, s := range settings {
|
||||
require.NoError(t, db.Create(&s).Error)
|
||||
}
|
||||
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/settings/smtp", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp map[string]any
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
|
||||
// Password MUST be masked
|
||||
assert.Equal(t, "********", resp["password"], "Password must be masked in response")
|
||||
assert.NotEqual(t, "supersecretpassword", resp["password"], "Real password must not be exposed")
|
||||
}
|
||||
|
||||
func TestSMTPConfig_PortValidation(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
port int
|
||||
wantCode int
|
||||
}{
|
||||
{"port 0 invalid", 0, http.StatusBadRequest},
|
||||
{"port -1 invalid", -1, http.StatusBadRequest},
|
||||
{"port 65536 invalid", 65536, http.StatusBadRequest},
|
||||
{"port 587 valid", 587, http.StatusOK},
|
||||
{"port 465 valid", 465, http.StatusOK},
|
||||
{"port 25 valid", 25, http.StatusOK},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"host": "smtp.test.com",
|
||||
"port": tc.port,
|
||||
"from_address": "test@test.com",
|
||||
"encryption": "starttls",
|
||||
})
|
||||
req := httptest.NewRequest("POST", "/api/settings/smtp", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, tc.wantCode, w.Code, "Port: %d", tc.port)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSMTPConfig_EncryptionValidation(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
encryption string
|
||||
wantCode int
|
||||
}{
|
||||
{"empty encryption invalid", "", http.StatusBadRequest},
|
||||
{"invalid encryption", "invalid", http.StatusBadRequest},
|
||||
{"tls lowercase valid", "ssl", http.StatusOK},
|
||||
{"starttls valid", "starttls", http.StatusOK},
|
||||
{"none valid", "none", http.StatusOK},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"host": "smtp.test.com",
|
||||
"port": 587,
|
||||
"from_address": "test@test.com",
|
||||
"encryption": tc.encryption,
|
||||
})
|
||||
req := httptest.NewRequest("POST", "/api/settings/smtp", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, tc.wantCode, w.Code, "Encryption: %s", tc.encryption)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== DUPLICATE EMAIL PROTECTION TESTS ====================
|
||||
|
||||
func TestInviteUser_DuplicateEmailBlocked(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
|
||||
// Create existing user
|
||||
existing := models.User{
|
||||
UUID: "existing-uuid-1234",
|
||||
Email: "existing@test.com",
|
||||
Name: "Existing User",
|
||||
Role: models.RoleUser,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&existing).Error)
|
||||
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
// Try to invite same email
|
||||
body := `{"email":"existing@test.com","role":"user"}`
|
||||
req := httptest.NewRequest("POST", "/api/users/invite", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusConflict, w.Code, "Duplicate email should return 409 Conflict")
|
||||
}
|
||||
|
||||
func TestInviteUser_EmailCaseInsensitive(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
|
||||
// Create existing user with lowercase email
|
||||
existing := models.User{
|
||||
UUID: "existing-uuid-5678",
|
||||
Email: "test@example.com",
|
||||
Name: "Existing User",
|
||||
Role: models.RoleUser,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&existing).Error)
|
||||
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
// Try to invite with different case
|
||||
body := `{"email":"TEST@EXAMPLE.COM","role":"user"}`
|
||||
req := httptest.NewRequest("POST", "/api/users/invite", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusConflict, w.Code, "Email comparison should be case-insensitive")
|
||||
}
|
||||
|
||||
// ==================== SELF-DELETION PREVENTION TEST ====================
|
||||
|
||||
func TestDeleteUser_CannotDeleteSelf(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
// Try to delete self
|
||||
req := httptest.NewRequest("DELETE", "/api/users/"+string(rune(adminID+'0')), http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
// Should be forbidden (cannot delete own account)
|
||||
assert.Equal(t, http.StatusForbidden, w.Code, "Admin should not be able to delete their own account")
|
||||
}
|
||||
|
||||
// ==================== PERMISSION MODE VALIDATION TESTS ====================
|
||||
|
||||
func TestUpdatePermissions_ValidModes(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
adminID := createTestAdminUser(t, db)
|
||||
|
||||
// Create a user to update
|
||||
user := models.User{
|
||||
UUID: "perms-user-1234",
|
||||
Email: "permsuser@test.com",
|
||||
Name: "Perms User",
|
||||
Role: models.RoleUser,
|
||||
Enabled: true,
|
||||
}
|
||||
require.NoError(t, db.Create(&user).Error)
|
||||
|
||||
r := setupRouterWithAuth(db, adminID, "admin")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
mode string
|
||||
wantCode int
|
||||
}{
|
||||
{"allow_all valid", "allow_all", http.StatusOK},
|
||||
{"deny_all valid", "deny_all", http.StatusOK},
|
||||
{"invalid mode", "invalid", http.StatusBadRequest},
|
||||
{"empty mode", "", http.StatusBadRequest},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"permission_mode": tc.mode,
|
||||
"permitted_hosts": []int{},
|
||||
})
|
||||
req := httptest.NewRequest("PUT", "/api/users/"+string(rune(user.ID+'0'))+"/permissions", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
// Note: The route path conversion is simplified; actual implementation would need proper ID parsing
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== PUBLIC ENDPOINTS ACCESS TEST ====================
|
||||
|
||||
func TestPublicEndpoints_NoAuthRequired(t *testing.T) {
|
||||
db := setupAuditTestDB(t)
|
||||
|
||||
// Router WITHOUT auth middleware
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
userHandler := handlers.NewUserHandler(db, nil)
|
||||
api := r.Group("/api")
|
||||
userHandler.RegisterRoutes(api)
|
||||
|
||||
// Create user with valid invite for testing
|
||||
expires := time.Now().Add(24 * time.Hour)
|
||||
invitedAt := time.Now()
|
||||
user := models.User{
|
||||
UUID: "public-test-uuid",
|
||||
Email: "public@test.com",
|
||||
Role: models.RoleUser,
|
||||
Enabled: false,
|
||||
InviteToken: "public-test-token-123456789012345678901234567",
|
||||
InviteExpires: &expires,
|
||||
InvitedAt: &invitedAt,
|
||||
InviteStatus: "pending",
|
||||
}
|
||||
require.NoError(t, db.Create(&user).Error)
|
||||
|
||||
// Validate invite should work without auth
|
||||
req := httptest.NewRequest("GET", "/api/invite/validate?token=public-test-token-123456789012345678901234567", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code, "ValidateInvite should be accessible without auth")
|
||||
|
||||
// Accept invite should work without auth
|
||||
body := `{"token":"public-test-token-123456789012345678901234567","name":"Public User","password":"password123"}`
|
||||
req = httptest.NewRequest("POST", "/api/invite/accept", strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code, "AcceptInvite should be accessible without auth")
|
||||
}
|
||||
Reference in New Issue
Block a user