- 新增管理端板块管理页面与后端接口(admin_board) - 新增旧版数据导入:legacyimport 服务、导入面板、importusers 命令行工具 - 聊天用户卡片、板块图标等 UI 组件与界面优化 - 补充 about/公告/1Panel 部署等文档 - gitignore 排除 dist/ 构建产物与 .agents/ 本地工具目录
147 lines
4.4 KiB
Go
147 lines
4.4 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/freefire/jiang13-bbs/config"
|
|
"github.com/freefire/jiang13-bbs/model"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
// staffTestDB 独立 schema 隔离的测试库(同 testOps 模式;未配置 DSN 则跳过)
|
|
func staffTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
dsn := os.Getenv("OPS_TEST_DATABASE_URL")
|
|
if dsn == "" {
|
|
t.Skip("set OPS_TEST_DATABASE_URL to an isolated PostgreSQL database")
|
|
}
|
|
u, e := url.Parse(dsn)
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
db, e := gorm.Open(postgres.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
schema := "staff_exempt_" + fmt.Sprint(time.Now().UnixNano())
|
|
if e = db.Exec("CREATE SCHEMA " + schema).Error; e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
q := u.Query()
|
|
q.Set("search_path", schema)
|
|
u.RawQuery = q.Encode()
|
|
scoped, e := gorm.Open(postgres.Open(u.String()), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
|
if e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
t.Cleanup(func() {
|
|
sql, _ := scoped.DB()
|
|
_ = sql.Close()
|
|
db.Exec("DROP SCHEMA " + schema + " CASCADE")
|
|
sql, _ = db.DB()
|
|
_ = sql.Close()
|
|
})
|
|
if e = scoped.AutoMigrate(&model.User{}, &model.Board{}, &model.Post{}, &model.Comment{}, &model.SiteSetting{},
|
|
&model.PostLotteryEntry{}, &model.ModuleConfig{}, &model.SettingsAudit{}); e != nil {
|
|
t.Fatal(e)
|
|
}
|
|
return scoped
|
|
}
|
|
|
|
func mustUser(t *testing.T, db *gorm.DB, username string, role model.Role, createdAt time.Time) model.User {
|
|
t.Helper()
|
|
u := model.User{Username: username, Password: "x", Role: role, CreatedAt: createdAt, UpdatedAt: createdAt}
|
|
if err := db.Create(&u).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return u
|
|
}
|
|
|
|
func TestCheckNewUserCooldownStaffExempt(t *testing.T) {
|
|
db := staffTestDB(t)
|
|
now := time.Now().UTC()
|
|
plain := mustUser(t, db, "plain", model.RoleUser, now)
|
|
owner := mustUser(t, db, "owner", model.RoleOwner, now)
|
|
badmin := mustUser(t, db, "badmin", model.RoleBoardAdmin, now)
|
|
|
|
ps := NewPostService(db).WithSetting(NewSettingService(db))
|
|
if err := ps.checkNewUserCooldown(plain.ID); err == nil {
|
|
t.Fatal("new plain user should be blocked by cooldown")
|
|
}
|
|
for _, u := range []model.User{owner, badmin} {
|
|
if err := ps.checkNewUserCooldown(u.ID); err != nil {
|
|
t.Fatalf("staff role %s should bypass cooldown: %v", u.Role, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilterStaffExempt(t *testing.T) {
|
|
db := staffTestDB(t)
|
|
b, _ := json.Marshal(FilterConfig{Enabled: true, Rules: []FilterRule{{
|
|
ID: "r1", Word: "bad", Scopes: []string{"body"}, Action: "block", Enabled: true,
|
|
}}})
|
|
if err := db.Create(&model.ModuleConfig{Name: "filter", Version: 1, Data: string(b)}).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
plain := mustUser(t, db, "plain", model.RoleUser, time.Now().UTC())
|
|
owner := mustUser(t, db, "owner", model.RoleOwner, time.Now().UTC())
|
|
|
|
o := NewOperations(db, &config.Config{DevMode: true})
|
|
if err := o.Filter("body", "a bad word", plain.ID); err == nil {
|
|
t.Fatal("plain user should be blocked by filter")
|
|
}
|
|
if err := o.Filter("body", "a bad word", owner.ID); err != nil {
|
|
t.Fatalf("owner should bypass filter: %v", err)
|
|
}
|
|
if err := o.Filter("body", "a bad word", 0); err == nil {
|
|
t.Fatal("anonymous should be blocked by filter")
|
|
}
|
|
}
|
|
|
|
func TestLotteryEligibleStaffBypassesCooldown(t *testing.T) {
|
|
db := staffTestDB(t)
|
|
now := time.Now().UTC()
|
|
author := mustUser(t, db, "author", model.RoleUser, now)
|
|
plain := mustUser(t, db, "plain", model.RoleUser, now)
|
|
owner := mustUser(t, db, "owner", model.RoleOwner, now)
|
|
|
|
board := model.Board{Name: "test"}
|
|
if err := db.Create(&board).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
post := model.Post{BoardID: board.ID, UserID: author.ID, Title: "lottery", Content: "draw",
|
|
PostType: model.PostTypeLottery, Status: model.ContentStatusPublished}
|
|
if err := db.Create(&post).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, uid := range []uint{plain.ID, owner.ID} {
|
|
c := model.Comment{PostID: post.ID, UserID: uid, Content: "join", Status: model.ContentStatusPublished}
|
|
if err := db.Create(&c).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
ps := NewPostService(db).WithSetting(NewSettingService(db))
|
|
ids, err := ps.lotteryEligibleIDs(post.ID, author.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := map[uint]bool{}
|
|
for _, id := range ids {
|
|
got[id] = true
|
|
}
|
|
if got[plain.ID] {
|
|
t.Fatal("new plain user should be excluded from lottery pool")
|
|
}
|
|
if !got[owner.ID] {
|
|
t.Fatal("staff new account should stay in lottery pool")
|
|
}
|
|
}
|