aliyun sensor server

This commit is contained in:
tamakyi
2026-09-10 18:04:19 +08:00
parent e3e59e7c69
commit 8e2325eda5
38 changed files with 3147 additions and 344 deletions
+94 -1
View File
@@ -4,6 +4,8 @@ import (
"bytes"
"fmt"
gotemplate "html/template"
"regexp"
"sort"
"strings"
"testing"
"time"
@@ -12,9 +14,37 @@ import (
"github.com/tamakyi/TamaBox/internal/conf"
"github.com/tamakyi/TamaBox/internal/db"
"github.com/tamakyi/TamaBox/internal/i18n"
"github.com/tamakyi/TamaBox/internal/security/moderation"
templatepkg "github.com/tamakyi/TamaBox/internal/template"
)
// 审核严格度下拉框的解析:不依赖属性的书写顺序或是否存在额外属性
// (模板可能被页面编辑器注入 data-* 属性),因此用正则而不是精确子串匹配。
var (
strictnessOptionRe = regexp.MustCompile(`<option value="(lax|standard|strict)"([^>]*)>`)
selectedAttrRe = regexp.MustCompile(`\bselected\b`)
)
// selectedStrictnessOptions 返回渲染结果中被标记 selected 的档位值(去重后升序)。
// 三个面板各渲染一份下拉框,正常情况下应得到恰好一个值;返回多个说明面板之间不一致。
func selectedStrictnessOptions(out string) []string {
seen := map[string]struct{}{}
var values []string
for _, m := range strictnessOptionRe.FindAllStringSubmatch(out, -1) {
if !selectedAttrRe.MatchString(m[2]) {
continue
}
if _, ok := seen[m[1]]; ok {
continue
}
seen[m[1]] = struct{}{}
values = append(values, m[1])
}
sort.Strings(values)
return values
}
// renderWithData 用给定数据渲染模板(复刻生产环境 Templater + FuncMap)。
func renderWithData(t *testing.T, name string, data map[string]interface{}) error {
t.Helper()
@@ -73,6 +103,8 @@ type mockLoggedUser struct {
Language string
HarassmentSetting string
BlockWords string
// 审核严格度档位(lax / standard / strict),设置页三个功能面板共用一份。
ModerationStrictness string
Tags string
ShowInHome bool
Acfunlink string
@@ -97,7 +129,8 @@ func profileBaseData() map[string]interface{} {
Qrcodepdpcolor: "#000", Qrcodebackcolor: "#fff", Dotscale: "0.35",
Backgroundimagealpha: "0.8", Notify: "email", Language: "zh-CN",
HarassmentSetting: "none", BlockWords: "广告,spam", Tags: "杂谈",
ChatMaxMessages: 20, DailyMessageLimit: 0,
ModerationStrictness: "standard",
ChatMaxMessages: 20, DailyMessageLimit: 0,
},
"CSRFTokenHTML": gotemplate.HTML(`<input type="hidden" name="_csrf" value="x">`),
"IsUserPage": false,
@@ -207,6 +240,26 @@ func TestRenderUserProfileTabs(t *testing.T) {
t.Fatalf("account: 屏蔽词输入缺 data-tb-max 注入,want %q", wantBlock)
}
}
// 审核严格度:提问 / 私聊 / 投票三个面板都必须渲染同一份设置,且把每个档位的
// 拦截范围平铺展示出来 —— 要求是用户按下保存前就知道哪些内容会被拦、哪些会放行。
if tab == "question" || tab == "chat" || tab == "vote" {
if got := selectedStrictnessOptions(out); len(got) != 1 || got[0] != "standard" {
t.Fatalf("%s: 审核严格度未按当前档位渲染 selected 状态,got %v", tab, got)
}
for _, key := range []string{
"admin.moderationStrictnessScope.lax",
"admin.moderationStrictnessScope.standard",
"admin.moderationStrictnessScope.strict",
} {
if want := i18n.T("zh-CN", key); !strings.Contains(out, want) {
t.Fatalf("%s: 档位拦截范围缺失 %s,want %q", tab, key, want)
}
}
// 底线拦截是强制项、任何档位不可关闭,界面上必须明确告知。
if want := i18n.T("zh-CN", "admin.moderationStrictnessBaseline"); !strings.Contains(out, want) {
t.Fatalf("%s: 缺少底线拦截说明,want %q", tab, want)
}
}
if tab == "chat" {
// 私聊面板的 number 输入框 min/max 与 conf.Limits 同源
wantChat := fmt.Sprintf(`name="chat_max_messages" class="uk-input uk-width-small" type="number" min="1" max="%d"`, conf.Limits.ChatMaxMessages)
@@ -232,6 +285,46 @@ func TestRenderUserProfileTabs(t *testing.T) {
}
}
// TestRenderProfileStrictnessReflectsEffectiveTier 覆盖「档位回显」这个易错点。
//
// 存量账号的 moderation_strictness 为空值(语义是"跟随站点默认")。若模板直接拿该字段值
// 去和三个 <option> 逐个比较,会全部不匹配 —— 浏览器随后默认选中第一项「宽松」,
// 界面显示的档位就与真实生效的档位(站点默认,通常为严格)相反,把箱主误导到最松的一档。
// 因此回显必须走「最终生效档位」。
func TestRenderProfileStrictnessReflectsEffectiveTier(t *testing.T) {
for _, tc := range []struct {
name string
stored string
want []string
}{
{name: "未设置:回显生效档位,而非浏览器默认选中的宽松", stored: "", want: []string{string(moderation.StrictnessFor(""))}},
{name: "宽松", stored: "lax", want: []string{"lax"}},
{name: "标准", stored: "standard", want: []string{"standard"}},
{name: "严格", stored: "strict", want: []string{"strict"}},
{name: "非法值:回退严格", stored: "bogus", want: []string{string(moderation.StrictnessFor("bogus"))}},
} {
t.Run(tc.name, func(t *testing.T) {
base := profileBaseData()
base["ActiveProfileTab"] = "question"
base["LoggedUser"].(*mockLoggedUser).ModerationStrictness = tc.stored
out, err := renderToString(t, "user/profile", base)
if err != nil {
t.Fatalf("render user/profile failed: %v", err)
}
got := selectedStrictnessOptions(out)
if fmt.Sprint(got) != fmt.Sprint(tc.want) {
t.Fatalf("档位回显不符:stored=%q got=%v want=%v", tc.stored, got, tc.want)
}
// 核心回归点:未设置时绝不能显示为「宽松」,那是浏览器默认选中首项的陷阱。
if tc.stored == "" && len(got) == 1 && got[0] == "lax" {
t.Fatal("未设置档位时回显为「宽松」—— 与真实生效档位相反")
}
})
}
}
// TestRenderUserQuestionList 模拟 /user/questions 的数据渲染,复现 500 错误。
func TestRenderUserQuestionList(t *testing.T) {
type mockItem struct {