将 model/service/handler/middleware 迁至 models/services/routers/api/modules/auth,并删除 frontend、embed_static、scripts 及误入库缓存/二进制。 Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
4.7 KiB
Go
176 lines
4.7 KiB
Go
package service
|
||
|
||
import (
|
||
"errors"
|
||
"testing"
|
||
|
||
"github.com/glebarez/sqlite"
|
||
"git.iioio.com/freefire/jiang13-forum/model"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
func setupBountyTestDB(t *testing.T) *gorm.DB {
|
||
t.Helper()
|
||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := db.AutoMigrate(&model.User{}, &model.Post{}, &model.Comment{}, &model.PointLedger{}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
prev := model.DB
|
||
model.DB = db
|
||
t.Cleanup(func() { model.DB = prev })
|
||
return db
|
||
}
|
||
|
||
func seedBountyPost(t *testing.T, db *gorm.DB, authorID uint, points int) model.Post {
|
||
t.Helper()
|
||
post := model.Post{
|
||
UserID: authorID,
|
||
BoardID: 1,
|
||
Title: "悬赏测试",
|
||
Content: "内容",
|
||
PostType: model.PostTypeBounty,
|
||
BountyPoints: points,
|
||
BountyStatus: model.BountyStatusOpen,
|
||
Status: model.ContentStatusPublished,
|
||
}
|
||
if err := db.Create(&post).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return post
|
||
}
|
||
|
||
func seedUser(t *testing.T, db *gorm.DB, id uint, points int) {
|
||
t.Helper()
|
||
u := model.User{
|
||
ID: id,
|
||
Username: "user" + string(rune('0'+id)),
|
||
Password: "hash",
|
||
Nickname: "测试",
|
||
Points: points,
|
||
}
|
||
if err := db.Create(&u).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
|
||
func seedComment(t *testing.T, db *gorm.DB, postID, userID uint, floor int, status string) {
|
||
t.Helper()
|
||
c := model.Comment{
|
||
PostID: postID,
|
||
UserID: userID,
|
||
Floor: floor,
|
||
Content: "回复",
|
||
Status: status,
|
||
}
|
||
if err := db.Create(&c).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
|
||
func TestCountEligibleBountyReplies(t *testing.T) {
|
||
db := setupBountyTestDB(t)
|
||
post := seedBountyPost(t, db, 1, 10)
|
||
|
||
n, err := CountEligibleBountyReplies(db, post.ID, 1)
|
||
if err != nil || n != 0 {
|
||
t.Fatalf("无回复时期望 0,得到 %d err=%v", n, err)
|
||
}
|
||
|
||
seedComment(t, db, post.ID, 1, 1, model.ContentStatusPublished)
|
||
n, err = CountEligibleBountyReplies(db, post.ID, 1)
|
||
if err != nil || n != 0 {
|
||
t.Fatalf("楼主自己的回复不应计入,得到 %d", n)
|
||
}
|
||
|
||
seedComment(t, db, post.ID, 2, 2, model.ContentStatusPublished)
|
||
n, err = CountEligibleBountyReplies(db, post.ID, 1)
|
||
if err != nil || n != 1 {
|
||
t.Fatalf("他人 published 回复期望 1,得到 %d", n)
|
||
}
|
||
|
||
seedComment(t, db, post.ID, 3, 3, model.ContentStatusPending)
|
||
n, err = CountEligibleBountyReplies(db, post.ID, 1)
|
||
if err != nil || n != 1 {
|
||
t.Fatalf("pending 回复不应增加计数,得到 %d", n)
|
||
}
|
||
|
||
seedComment(t, db, post.ID, 0, 4, model.ContentStatusPublished)
|
||
n, err = CountEligibleBountyReplies(db, post.ID, 1)
|
||
if err != nil || n != 2 {
|
||
t.Fatalf("游客回复应计入,得到 %d", n)
|
||
}
|
||
}
|
||
|
||
func TestCanRefundBounty(t *testing.T) {
|
||
db := setupBountyTestDB(t)
|
||
post := seedBountyPost(t, db, 1, 5)
|
||
|
||
can, reason := CanRefundBounty(&post, false)
|
||
if !can || reason != "" {
|
||
t.Fatalf("无回复时楼主应可退,can=%v reason=%q", can, reason)
|
||
}
|
||
|
||
seedComment(t, db, post.ID, 2, 1, model.ContentStatusPublished)
|
||
can, reason = CanRefundBounty(&post, false)
|
||
if can || reason != bountyRefundBlockReason {
|
||
t.Fatalf("有他人回复时楼主不可退,can=%v reason=%q", can, reason)
|
||
}
|
||
|
||
can, reason = CanRefundBounty(&post, true)
|
||
if !can || reason != "" {
|
||
t.Fatalf("管理员应可强制退,can=%v reason=%q", can, reason)
|
||
}
|
||
}
|
||
|
||
func TestRefundBountyBlockedForAuthorWithReplies(t *testing.T) {
|
||
db := setupBountyTestDB(t)
|
||
seedUser(t, db, 1, 0)
|
||
seedUser(t, db, 2, 0)
|
||
post := seedBountyPost(t, db, 1, 8)
|
||
seedComment(t, db, post.ID, 2, 1, model.ContentStatusPublished)
|
||
|
||
err := RefundBounty(post.ID, 1, false)
|
||
if !errors.Is(err, ErrBountyRefundBlocked) {
|
||
t.Fatalf("楼主有他人回复时应拒绝退回,err=%v", err)
|
||
}
|
||
}
|
||
|
||
func TestRefundBountyAllowedWithoutReplies(t *testing.T) {
|
||
db := setupBountyTestDB(t)
|
||
seedUser(t, db, 1, 0)
|
||
post := seedBountyPost(t, db, 1, 6)
|
||
|
||
if err := RefundBounty(post.ID, 1, false); err != nil {
|
||
t.Fatalf("无回复时楼主应可退回,err=%v", err)
|
||
}
|
||
var updated model.Post
|
||
if err := db.First(&updated, post.ID).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if updated.BountyStatus != model.BountyStatusRefunded || updated.BountyPoints != 0 {
|
||
t.Fatalf("状态应为 refunded 且积分为 0,得到 status=%s points=%d", updated.BountyStatus, updated.BountyPoints)
|
||
}
|
||
var author model.User
|
||
if err := db.First(&author, 1).Error; err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if author.Points != 6 {
|
||
t.Fatalf("楼主应收回 6 积分,余额=%d", author.Points)
|
||
}
|
||
}
|
||
|
||
func TestRefundBountyAdminBypassWithReplies(t *testing.T) {
|
||
db := setupBountyTestDB(t)
|
||
seedUser(t, db, 1, 0)
|
||
seedUser(t, db, 2, 0)
|
||
post := seedBountyPost(t, db, 1, 4)
|
||
seedComment(t, db, post.ID, 2, 1, model.ContentStatusPublished)
|
||
|
||
if err := RefundBounty(post.ID, 99, true); err != nil {
|
||
t.Fatalf("管理员应可强制退回,err=%v", err)
|
||
}
|
||
}
|