Files
cmautobuy/admin/user_integration_test.go

122 lines
5.0 KiB
Go

package main
import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"cmautobuy/admin/repository"
"cmautobuy/admin/service"
)
func Test用户管理管理员与采购员权限边界(t *testing.T) {
db := newMySQLTestDB(t)
now := time.Now()
if err := service.SetupInitialAdmin(db, "admin", "admin-password", "admin-password", now); err != nil {
t.Fatal(err)
}
admin, _ := repository.FindUserByUsername(db, "admin")
if err := service.CreatePurchaser(db, admin, "buyer", "buyer-password", "buyer-password", now); err != nil {
t.Fatal(err)
}
router, err := newRouter(db)
if err != nil {
t.Fatal(err)
}
adminToken, _, _, err := service.Login(db, "admin", "admin-password", now)
if err != nil {
t.Fatal(err)
}
adminPageRequest := httptest.NewRequest(http.MethodGet, "/users", nil)
adminPageRequest.AddCookie(&http.Cookie{Name: "cmautobuy_session", Value: adminToken})
adminPage := httptest.NewRecorder()
router.ServeHTTP(adminPage, adminPageRequest)
if adminPage.Code != http.StatusOK {
t.Fatalf("管理员 GET /users = %d,响应:%s", adminPage.Code, adminPage.Body.String())
}
for _, want := range []string{"用户管理", "新增采购员", "buyer", "重置密码", "data-confirm-submit"} {
if !strings.Contains(adminPage.Body.String(), want) {
t.Errorf("用户管理页缺少 %q", want)
}
}
if strings.Count(adminPage.Body.String(), `minlength="6"`) != 6 ||
strings.Count(adminPage.Body.String(), "至少 6 个字符") != 3 {
t.Fatalf("创建和重置密码表单没有统一使用 6 个字符规则")
}
for _, secret := range []string{"admin-password", "buyer-password"} {
if strings.Contains(adminPage.Body.String(), secret) {
t.Fatalf("用户管理页泄露密码 %q", secret)
}
}
csrfCookie := findResponseCookie(t, adminPage, "cmautobuy_csrf")
duplicate := postFormRequest("/users/create", url.Values{
"csrf_token": {csrfCookie.Value}, "username": {"BUYER"},
"password": {"another-password"}, "password_confirm": {"another-password"},
}, csrfCookie)
duplicate.AddCookie(&http.Cookie{Name: "cmautobuy_session", Value: adminToken})
duplicateResponse := httptest.NewRecorder()
router.ServeHTTP(duplicateResponse, duplicate)
if duplicateResponse.Code != http.StatusBadRequest ||
!strings.Contains(duplicateResponse.Body.String(), "用户名已经存在") ||
!strings.Contains(duplicateResponse.Body.String(), `value="BUYER"`) {
t.Fatalf("重复用户名应保留用户名并重开表单: %d %s", duplicateResponse.Code, duplicateResponse.Body.String())
}
if strings.Contains(duplicateResponse.Body.String(), "another-password") {
t.Fatal("校验失败页面不能回显密码")
}
// 即使已登录管理员,没有 CSRF 也不能创建账号。
noCSRF := postFormRequest("/users/create", url.Values{
"username": {"staff"}, "password": {"staff-password"},
"password_confirm": {"staff-password"},
})
noCSRF.AddCookie(&http.Cookie{Name: "cmautobuy_session", Value: adminToken})
noCSRFResponse := httptest.NewRecorder()
router.ServeHTTP(noCSRFResponse, noCSRF)
if noCSRFResponse.Code != http.StatusForbidden {
t.Fatalf("无 CSRF 创建采购员 = %d,期望 403", noCSRFResponse.Code)
}
create := postFormRequest("/users/create", url.Values{
"csrf_token": {csrfCookie.Value}, "username": {"staff"},
"password": {"staff-password"}, "password_confirm": {"staff-password"},
}, csrfCookie)
create.AddCookie(&http.Cookie{Name: "cmautobuy_session", Value: adminToken})
createResponse := httptest.NewRecorder()
router.ServeHTTP(createResponse, create)
if createResponse.Code != http.StatusSeeOther || !strings.HasPrefix(createResponse.Header().Get("Location"), "/users?") {
t.Fatalf("创建采购员 = %d %s", createResponse.Code, createResponse.Header().Get("Location"))
}
if _, err := repository.FindUserByUsername(db, "STAFF"); err != nil {
t.Fatalf("HTTP 创建的采购员不存在: %v", err)
}
buyerToken, _, _, err := service.Login(db, "buyer", "buyer-password", now)
if err != nil {
t.Fatal(err)
}
buyerBusiness := httptest.NewRequest(http.MethodGet, "/shopee", nil)
buyerBusiness.AddCookie(&http.Cookie{Name: "cmautobuy_session", Value: buyerToken})
buyerBusinessResponse := httptest.NewRecorder()
router.ServeHTTP(buyerBusinessResponse, buyerBusiness)
if buyerBusinessResponse.Code != http.StatusOK {
t.Fatalf("采购员应可访问业务页,实际 %d", buyerBusinessResponse.Code)
}
if strings.Contains(buyerBusinessResponse.Body.String(), `href="/users"`) {
t.Fatal("采购员导航不应显示用户管理入口")
}
buyerUsers := httptest.NewRequest(http.MethodGet, "/users", nil)
buyerUsers.AddCookie(&http.Cookie{Name: "cmautobuy_session", Value: buyerToken})
buyerUsersResponse := httptest.NewRecorder()
router.ServeHTTP(buyerUsersResponse, buyerUsers)
if buyerUsersResponse.Code != http.StatusForbidden || !strings.Contains(buyerUsersResponse.Body.String(), "只有管理员") {
t.Fatalf("采购员访问 /users = %d,响应:%s", buyerUsersResponse.Code, buyerUsersResponse.Body.String())
}
}