feat: 评论/帖子编辑历史与编辑标记,内容锁定

- 新增 smartDiff 差异计算与 EditHistoryModal 编辑历史弹窗,替换原 CommentEditHistoryModal
- 新增 PostEditedMark 编辑标记展示
- 新增内容锁定机制(content_lock)防止并发编辑冲突
- 审核与通知服务适配
This commit is contained in:
2026-09-25 23:41:45 +08:00
parent cab8230803
commit 5270fee2b7
40 changed files with 2521 additions and 350 deletions

View File

@@ -0,0 +1,74 @@
package service
import (
"testing"
"time"
"github.com/freefire/jiang13-bbs/model"
)
// TestEditLocked 内容自动编辑锁定规则(帖子与评论共用):
// staff 豁免、hours<=0 关闭、以创建时间为基准计时
func TestEditLocked(t *testing.T) {
now := time.Date(2026, 9, 25, 12, 0, 0, 0, time.Local)
createdAt := now.Add(-48 * time.Hour) // 两天前创建
cases := []struct {
name string
createdAt time.Time
isStaff bool
hours int
now time.Time
want bool
}{
{"未启用", createdAt, false, 0, now, false},
{"负值视为关闭", createdAt, false, -1, now, false},
{"超过时限锁定", createdAt, false, 24, now, true},
{"恰好到期未锁(严格大于)", createdAt, false, 48, now, false},
{"未到期不锁", createdAt, false, 72, now, false},
{"staff 超时也豁免", createdAt, true, 1, now, false},
{"staff 管理员角色豁免", createdAt, model.IsStaff(model.RoleAdmin), 1, now, false},
{"普通用户非豁免", createdAt, model.IsStaff(model.RoleUser), 1, now, true},
{"板块管理员豁免", createdAt, model.IsStaff(model.RoleBoardAdmin), 1, now, false},
{"刚创建且时限极短不锁", now, false, 1, now, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := editLocked(c.createdAt, c.isStaff, c.hours, c.now); got != c.want {
t.Fatalf("editLocked() = %v, want %v", got, c.want)
}
})
}
}
// TestIsNecroReply 旧帖判定:最后回复时间(无回复回落发帖时间)距 now 超过阈值。
// staff 豁免、规则开关、回落基准由调用方负责,不在本 helper 内。
func TestIsNecroReply(t *testing.T) {
now := time.Date(2026, 9, 25, 12, 0, 0, 0, time.Local)
lastReplyAt := now.Add(-48 * time.Hour) // 最后回复在两天前
cases := []struct {
name string
lastReplyAt time.Time
afterHours int
now time.Time
want bool
}{
{"阈值关闭", lastReplyAt, 0, now, false},
{"阈值负值视为关闭", lastReplyAt, -1, now, false},
{"超过阈值触发", lastReplyAt, 24, now, true},
{"恰好到期不触发(严格大于)", lastReplyAt, 48, now, false},
{"未到期不触发", lastReplyAt, 72, now, false},
{"无回复回落发帖时间同理(基准即发帖时间)", now.Add(-72 * time.Hour), 48, now, true},
{"刚回复不久不触发", now.Add(-time.Hour), 2, now, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := IsNecroReply(c.lastReplyAt, c.afterHours, c.now); got != c.want {
t.Fatalf("IsNecroReply() = %v, want %v", got, c.want)
}
})
}
}