mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 14:06:36 +08:00
352 lines
9.8 KiB
Go
352 lines
9.8 KiB
Go
/*
|
|
Copyright 2026 linux.do
|
|
Modified by Arctel.net, 2026
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package user
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/apps/oauth"
|
|
"github.com/Rain-kl/Wavelet/internal/model"
|
|
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
|
"github.com/Rain-kl/Wavelet/internal/util"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func setupTestRouter(authUser *model.User) *gin.Engine {
|
|
gin.SetMode(gin.TestMode)
|
|
r := gin.New()
|
|
adminGroup := r.Group("/api/v1/admin")
|
|
|
|
// Mock authentication middleware
|
|
adminGroup.Use(func(c *gin.Context) {
|
|
if authUser != nil {
|
|
util.SetToContext(c, oauth.UserObjKey, authUser)
|
|
}
|
|
c.Next()
|
|
})
|
|
|
|
adminGroup.GET("/users", ListUsers)
|
|
adminGroup.POST("/users", CreateUser)
|
|
adminGroup.PUT("/users/:id/status", UpdateUserStatus)
|
|
return r
|
|
}
|
|
|
|
func TestListUsers(t *testing.T) {
|
|
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Seed users
|
|
users := []model.User{
|
|
{
|
|
ID: 1001,
|
|
Username: "alice",
|
|
Nickname: "Alice Nickname",
|
|
IsActive: true,
|
|
IsAdmin: false,
|
|
LastLoginAt: time.Now(),
|
|
},
|
|
{
|
|
ID: 1002,
|
|
Username: "bob",
|
|
Nickname: "Bob Nickname",
|
|
IsActive: true,
|
|
IsAdmin: false,
|
|
LastLoginAt: time.Now(),
|
|
},
|
|
{
|
|
ID: 1003,
|
|
Username: "charlie",
|
|
Nickname: "Charlie Nickname",
|
|
IsActive: false,
|
|
IsAdmin: true,
|
|
LastLoginAt: time.Now(),
|
|
},
|
|
}
|
|
|
|
for _, u := range users {
|
|
if err := dbConn.Create(&u).Error; err != nil {
|
|
t.Fatalf("failed to seed user: %v", err)
|
|
}
|
|
}
|
|
|
|
adminUser := &model.User{ID: 1003, Username: "charlie", IsAdmin: true}
|
|
router := setupTestRouter(adminUser)
|
|
|
|
t.Run("basic pagination list", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", "/api/v1/admin/users?page=1&page_size=2", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var resp util.ResponseAny
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
|
|
// Parse data map to our structure
|
|
dataBytes, _ := json.Marshal(resp.Data)
|
|
var listResp listUsersResponse
|
|
if err := json.Unmarshal(dataBytes, &listResp); err != nil {
|
|
t.Fatalf("failed to parse list response: %v", err)
|
|
}
|
|
|
|
if len(listResp.Users) != 2 {
|
|
t.Errorf("expected 2 users, got %d", len(listResp.Users))
|
|
}
|
|
if listResp.Total != 3 {
|
|
t.Errorf("expected total 3, got %d", listResp.Total)
|
|
}
|
|
// Ordered by ID DESC
|
|
if listResp.Users[0].ID != 1003 || listResp.Users[1].ID != 1002 {
|
|
t.Errorf("expected ordered DESC, got first ID %d, second ID %d", listResp.Users[0].ID, listResp.Users[1].ID)
|
|
}
|
|
})
|
|
|
|
t.Run("filter by user_id", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", "/api/v1/admin/users?page=1&page_size=10&user_id=1001", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
var resp util.ResponseAny
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
|
|
dataBytes, _ := json.Marshal(resp.Data)
|
|
var listResp listUsersResponse
|
|
json.Unmarshal(dataBytes, &listResp)
|
|
|
|
if len(listResp.Users) != 1 || listResp.Users[0].ID != 1001 {
|
|
t.Errorf("expected 1 user with ID 1001, got total %d", len(listResp.Users))
|
|
}
|
|
})
|
|
|
|
t.Run("filter by username prefix", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", "/api/v1/admin/users?page=1&page_size=10&username=bo", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
var resp util.ResponseAny
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
|
|
dataBytes, _ := json.Marshal(resp.Data)
|
|
var listResp listUsersResponse
|
|
json.Unmarshal(dataBytes, &listResp)
|
|
|
|
if len(listResp.Users) != 1 || listResp.Users[0].Username != "bob" {
|
|
t.Errorf("expected bob, got %v", listResp.Users)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid pagination parameter", func(t *testing.T) {
|
|
req, _ := http.NewRequest("GET", "/api/v1/admin/users?page=0&page_size=10", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400 Bad Request, got %d", w.Code)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestUpdateUserStatus(t *testing.T) {
|
|
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
// Seed users
|
|
regularUser := model.User{
|
|
ID: 1001,
|
|
Username: "alice",
|
|
IsActive: true,
|
|
IsAdmin: false,
|
|
}
|
|
adminUser := model.User{
|
|
ID: 1002,
|
|
Username: "bob",
|
|
IsActive: true,
|
|
IsAdmin: true,
|
|
}
|
|
|
|
dbConn.Create(®ularUser)
|
|
dbConn.Create(&adminUser)
|
|
|
|
router := setupTestRouter(&adminUser)
|
|
|
|
t.Run("disable regular user successfully", func(t *testing.T) {
|
|
payload := updateUserStatusRequest{IsActive: false}
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("PUT", "/api/v1/admin/users/1001/status", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
// Verify DB status
|
|
var u model.User
|
|
dbConn.First(&u, 1001)
|
|
if u.IsActive {
|
|
t.Error("user should be deactivated in the database")
|
|
}
|
|
})
|
|
|
|
t.Run("cannot disable admin user", func(t *testing.T) {
|
|
payload := updateUserStatusRequest{IsActive: false}
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("PUT", "/api/v1/admin/users/1002/status", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusForbidden {
|
|
t.Errorf("expected 403 Forbidden, got %d. Body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var resp util.ResponseAny
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
if resp.ErrorMsg != cannotDisable {
|
|
t.Errorf("expected error message '%s', got '%s'", cannotDisable, resp.ErrorMsg)
|
|
}
|
|
})
|
|
|
|
t.Run("cannot enable/disable non-existent user", func(t *testing.T) {
|
|
payload := updateUserStatusRequest{IsActive: false}
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("PUT", "/api/v1/admin/users/9999/status", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("expected 404 Not Found, got %d. Body: %s", w.Code, w.Body.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCreateUser(t *testing.T) {
|
|
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
|
defer cleanup()
|
|
|
|
adminUser := &model.User{ID: 1003, Username: "charlie", IsAdmin: true}
|
|
router := setupTestRouter(adminUser)
|
|
|
|
t.Run("create user successfully", func(t *testing.T) {
|
|
payload := createUserRequest{
|
|
Username: "newuser",
|
|
Password: "newpassword123",
|
|
Nickname: "New Nickname",
|
|
IsActive: true,
|
|
IsAdmin: false,
|
|
}
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", "/api/v1/admin/users", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected 200 OK, got %d. Body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var resp util.ResponseAny
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
|
|
if resp.ErrorMsg != "" {
|
|
t.Errorf("expected empty error message, got '%s'", resp.ErrorMsg)
|
|
}
|
|
|
|
dataBytes, _ := json.Marshal(resp.Data)
|
|
var resUser user
|
|
if err := json.Unmarshal(dataBytes, &resUser); err != nil {
|
|
t.Fatalf("failed to parse response data: %v", err)
|
|
}
|
|
|
|
if resUser.Username != "newuser" || resUser.Nickname != "New Nickname" || !resUser.IsActive || resUser.IsAdmin {
|
|
t.Errorf("unexpected user values: %+v", resUser)
|
|
}
|
|
|
|
// Verify in DB
|
|
var dbUser model.User
|
|
if err := dbConn.Where("username = ?", "newuser").First(&dbUser).Error; err != nil {
|
|
t.Fatalf("failed to find user in db: %v", err)
|
|
}
|
|
if !dbUser.CheckPassword("newpassword123") {
|
|
t.Error("password was not hashed correctly")
|
|
}
|
|
})
|
|
|
|
t.Run("create user with duplicate username", func(t *testing.T) {
|
|
// Create the first user
|
|
existing := model.User{
|
|
ID: 2001,
|
|
Username: "dupuser",
|
|
Nickname: "Dup User",
|
|
}
|
|
dbConn.Create(&existing)
|
|
|
|
payload := createUserRequest{
|
|
Username: "dupuser",
|
|
Password: "password123",
|
|
Nickname: "Another Nick",
|
|
IsActive: true,
|
|
}
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", "/api/v1/admin/users", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400 Bad Request, got %d. Body: %s", w.Code, w.Body.String())
|
|
}
|
|
|
|
var resp util.ResponseAny
|
|
json.Unmarshal(w.Body.Bytes(), &resp)
|
|
if resp.ErrorMsg != usernameExists {
|
|
t.Errorf("expected error '%s', got '%s'", usernameExists, resp.ErrorMsg)
|
|
}
|
|
})
|
|
|
|
t.Run("validation error - password too short", func(t *testing.T) {
|
|
payload := createUserRequest{
|
|
Username: "shortpass",
|
|
Password: "123",
|
|
IsActive: true,
|
|
}
|
|
body, _ := json.Marshal(payload)
|
|
req, _ := http.NewRequest("POST", "/api/v1/admin/users", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400 Bad Request, got %d. Body: %s", w.Code, w.Body.String())
|
|
}
|
|
})
|
|
}
|