129 lines
5.1 KiB
Go
129 lines
5.1 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, err := repository.Open(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
if err := repository.Migrate(db); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
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())
|
|
}
|
|
}
|