Files
kra-new/cmd/password_reset_http_test.go
T

115 lines
3.8 KiB
Go

package main
import (
"context"
"encoding/json"
"github.com/gin-gonic/gin"
"github.com/glebarez/sqlite"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
businessbiz "kra/internal/biz/business"
businessdata "kra/internal/data/business"
systemdata "kra/internal/data/system"
"kra/internal/server/handler"
"kra/internal/server/router"
businessservice "kra/internal/service/business"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"time"
)
type resetHTTPSMS struct {
code string
calls int
}
func (s *resetHTTPSMS) SendCode(_ context.Context, _ string, code string) error {
s.code = code
s.calls++
return nil
}
func TestPasswordResetAnonymousHTTPFlow(t *testing.T) {
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "reset.db")), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
sqlDB, err := db.DB()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = sqlDB.Close() })
for _, step := range append(systemdata.Migrations(), businessdata.Migrations()...) {
if err := step.Migrate(db); err != nil {
t.Fatal(err)
}
}
before := time.Now().Add(-time.Hour)
if err := db.Table("sys_users").Create(map[string]any{"id": 7, "uuid": "reset-test", "username": "recover", "phone": "13800138000", "enable": 1, "business_status": 0, "password": "old-hash", "password_updated_at": before}).Error; err != nil {
t.Fatal(err)
}
sms := &resetHTTPSMS{}
service := businessservice.NewPasswordResetService(businessbiz.NewPasswordResetUsecase(businessdata.NewPasswordResetRepo(businessHTTPData{db}), sms))
engine := gin.New()
router.RegisterBusinessPassword(engine.Group(""), handler.NewBusinessPassword(service))
server := httptest.NewServer(engine)
defer server.Close()
call := func(action string, payload map[string]any, expected int) map[string]any {
t.Helper()
encoded, _ := json.Marshal(payload)
response, err := http.Post(server.URL+"/sw/auth/forgot-password/"+action, "application/json", strings.NewReader(string(encoded)))
if err != nil {
t.Fatal(err)
}
defer response.Body.Close()
var body map[string]any
if err := json.NewDecoder(response.Body).Decode(&body); err != nil {
t.Fatal(err)
}
if response.StatusCode != 200 || body["code"] != float64(expected) {
t.Fatalf("%s 结果不正确: %+v", action, body)
}
data, _ := body["data"].(map[string]any)
return data
}
phone := call("check", map[string]any{"username": "recover"}, 0)
if phone["maskedPhone"] != "138****8000" {
t.Fatal("接口手机号未脱敏")
}
call("send-code", map[string]any{"username": "recover"}, 0)
call("send-code", map[string]any{"username": "recover"}, 7)
if sms.calls != 1 {
t.Fatal("HTTP 发码限流无效")
}
wrong := "000000"
if sms.code == wrong {
wrong = "111111"
}
call("verify-code", map[string]any{"username": "recover", "smsCode": wrong}, 7)
verified := call("verify-code", map[string]any{"username": "recover", "smsCode": sms.code}, 0)
token, ok := verified["resetToken"].(string)
if !ok || len(token) != 64 {
t.Fatal("未返回一次性重置凭证")
}
call("verify-code", map[string]any{"username": "recover", "smsCode": sms.code}, 7)
call("reset", map[string]any{"resetToken": token, "newPassword": "abc123", "confirmPassword": "abc123"}, 0)
var user struct {
Password string
PasswordUpdatedAt time.Time
}
if err := db.Table("sys_users").Where("id = ?", 7).Take(&user).Error; err != nil {
t.Fatal(err)
}
if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte("abc123")) != nil || !user.PasswordUpdatedAt.After(before) {
t.Fatal("新密码或会话失效版本未保存")
}
call("reset", map[string]any{"resetToken": token, "newPassword": "abc124", "confirmPassword": "abc124"}, 7)
var count int64
if err := db.Table("sys_business_password_resets").Count(&count).Error; err != nil || count != 0 {
t.Fatal("重置凭证未在事务中消费")
}
}