Files
rill/internal/usergroup/user_group_test.go
T
kevin b683fb293e 增加注册登录与鉴权,并按功能拆分 internal 模块
- 新增 /api/auth/register、/api/auth/login,JWT 签发与 Bearer 鉴权中间件
- notes 需登录,users/user-groups 仅管理员;auth 配置项随版本 1→2 自动补全
- internal/api 仅保留路由装配,拆分为 auth/user/usergroup/note/httpx/testutil
- 同步更新 Swagger 文档与前端注册接口路径
2026-09-20 02:03:06 +08:00

172 lines
5.8 KiB
Go

package usergroup_test
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"rill/internal/model"
"rill/internal/testutil"
)
func decodeUserGroup(t *testing.T, w *httptest.ResponseRecorder) model.UserGroup {
t.Helper()
var group model.UserGroup
if err := json.Unmarshal(w.Body.Bytes(), &group); err != nil {
t.Fatalf("解析响应失败: %v, body=%s", err, w.Body.String())
}
return group
}
func TestUserGroupSeeds(t *testing.T) {
env := testutil.Setup(t)
r := env.AdminRouter()
w := testutil.Call(t, r, http.MethodGet, "/api/user-groups", nil)
if w.Code != http.StatusOK {
t.Fatalf("列表状态码 = %d, 期望 %d", w.Code, http.StatusOK)
}
var list struct {
Items []model.UserGroup `json:"items"`
Total int64 `json:"total"`
}
if err := json.Unmarshal(w.Body.Bytes(), &list); err != nil {
t.Fatalf("解析列表响应失败: %v", err)
}
if list.Total != 2 || len(list.Items) != 2 {
t.Fatalf("内置组数量异常: total=%d, items=%d", list.Total, len(list.Items))
}
admin, user := list.Items[0], list.Items[1]
if admin.ID != model.GroupIDAdmin || admin.Name != "admin" || !admin.IsSystem {
t.Errorf("admin 组异常: %+v", admin)
}
if user.ID != model.GroupIDUser || user.Name != "user" || !user.IsSystem {
t.Errorf("user 组异常: %+v", user)
}
}
func TestUserGroupCRUD(t *testing.T) {
env := testutil.Setup(t)
r := env.AdminRouter()
w := testutil.Call(t, r, http.MethodPost, "/api/user-groups", map[string]string{"name": "ops", "description": "运维组"})
if w.Code != http.StatusCreated {
t.Fatalf("创建状态码 = %d, 期望 %d, body=%s", w.Code, http.StatusCreated, w.Body.String())
}
created := decodeUserGroup(t, w)
if created.ID != 2 || created.Name != "ops" || created.IsSystem {
t.Fatalf("创建结果异常: %+v", created)
}
w = testutil.Call(t, r, http.MethodPost, "/api/user-groups", map[string]string{"name": "dev"})
if w.Code != http.StatusCreated {
t.Fatalf("创建第二个组状态码 = %d, 期望 %d", w.Code, http.StatusCreated)
}
if second := decodeUserGroup(t, w); second.ID != 3 {
t.Errorf("新组 ID = %d, 期望 3", second.ID)
}
detailPath := fmt.Sprintf("/api/user-groups/%d", created.ID)
w = testutil.Call(t, r, http.MethodGet, detailPath, nil)
if w.Code != http.StatusOK {
t.Fatalf("详情状态码 = %d, 期望 %d", w.Code, http.StatusOK)
}
if got := decodeUserGroup(t, w); got.ID != created.ID {
t.Errorf("详情 ID = %d, 期望 %d", got.ID, created.ID)
}
w = testutil.Call(t, r, http.MethodPut, detailPath, map[string]string{"name": "ops2", "description": "更新后"})
if w.Code != http.StatusOK {
t.Fatalf("更新状态码 = %d, 期望 %d, body=%s", w.Code, http.StatusOK, w.Body.String())
}
if updated := decodeUserGroup(t, w); updated.Name != "ops2" || updated.Description != "更新后" {
t.Errorf("更新结果异常: %+v", updated)
}
w = testutil.Call(t, r, http.MethodDelete, detailPath, nil)
if w.Code != http.StatusNoContent {
t.Fatalf("删除状态码 = %d, 期望 %d", w.Code, http.StatusNoContent)
}
w = testutil.Call(t, r, http.MethodGet, detailPath, nil)
if w.Code != http.StatusNotFound {
t.Errorf("删除后详情状态码 = %d, 期望 %d", w.Code, http.StatusNotFound)
}
}
func TestUserGroupProtected(t *testing.T) {
env := testutil.Setup(t)
r := env.AdminRouter()
for _, id := range []uint{model.GroupIDAdmin, model.GroupIDUser} {
w := testutil.Call(t, r, http.MethodDelete, fmt.Sprintf("/api/user-groups/%d", id), nil)
if w.Code != http.StatusConflict {
t.Errorf("删除内置组 %d 状态码 = %d, 期望 %d", id, w.Code, http.StatusConflict)
}
}
w := testutil.Call(t, r, http.MethodPost, "/api/user-groups", map[string]string{"name": "admin"})
if w.Code != http.StatusConflict {
t.Errorf("重复组名状态码 = %d, 期望 %d", w.Code, http.StatusConflict)
}
}
func TestUserGroupDeleteWithMembers(t *testing.T) {
env := testutil.Setup(t)
r := env.AdminRouter()
w := testutil.Call(t, r, http.MethodPost, "/api/user-groups", map[string]string{"name": "ops"})
if w.Code != http.StatusCreated {
t.Fatalf("创建组失败: %d, body=%s", w.Code, w.Body.String())
}
group := decodeUserGroup(t, w)
w = testutil.Call(t, r, http.MethodPost, "/api/users", map[string]any{
"username": "carol",
"email": "carol@example.com",
"password": "secret123",
"group_ids": []uint{group.ID},
})
if w.Code != http.StatusCreated {
t.Fatalf("创建用户失败: %d, body=%s", w.Code, w.Body.String())
}
user := testutil.DecodeUser(t, w)
groupPath := fmt.Sprintf("/api/user-groups/%d", group.ID)
w = testutil.Call(t, r, http.MethodDelete, groupPath, nil)
if w.Code != http.StatusConflict {
t.Fatalf("删除有成员的组状态码 = %d, 期望 %d, body=%s", w.Code, http.StatusConflict, w.Body.String())
}
w = testutil.Call(t, r, http.MethodDelete, fmt.Sprintf("/api/users/%d", user.ID), nil)
if w.Code != http.StatusNoContent {
t.Fatalf("删除用户失败: %d", w.Code)
}
w = testutil.Call(t, r, http.MethodDelete, groupPath, nil)
if w.Code != http.StatusNoContent {
t.Errorf("成员移除后删除组状态码 = %d, 期望 %d, body=%s", w.Code, http.StatusNoContent, w.Body.String())
}
}
func TestUserGroupValidation(t *testing.T) {
env := testutil.Setup(t)
r := env.AdminRouter()
w := testutil.Call(t, r, http.MethodPost, "/api/user-groups", map[string]string{"description": "缺少名称"})
if w.Code != http.StatusBadRequest {
t.Errorf("缺少名称状态码 = %d, 期望 %d", w.Code, http.StatusBadRequest)
}
w = testutil.Call(t, r, http.MethodGet, "/api/user-groups/abc", nil)
if w.Code != http.StatusBadRequest {
t.Errorf("非法 id 状态码 = %d, 期望 %d", w.Code, http.StatusBadRequest)
}
w = testutil.Call(t, r, http.MethodGet, "/api/user-groups/99", nil)
if w.Code != http.StatusNotFound {
t.Errorf("不存在组状态码 = %d, 期望 %d", w.Code, http.StatusNotFound)
}
}