桌面 GUI 外链修复 + 主题顺序调整(含结构扁平化进行中的工作区基线)

- 集成 tauri-plugin-opener:Cargo/lib.rs/capabilities/npm 四处接线,新增 src/openExternal.ts(桌面走 opener,浏览器回退 window.open,相对路径绝对化)
- 关于页 4 个链接、活动栏官网菜单、代理错误横幅 2 处健康检查链接全部改走 openExternal,桌面端可正常跳转
- 设置→外观 主题顺序调整为 深色/浅色/跟随系统
- release 二进制重编并验证(浏览器实测关于页链接可开文档页)
- 附带工作区中未提交的扁平化重构现状(另一会话进行中)
- homepage 落地页与文档终检通过(8787 / 与 /docs 双 200)
This commit is contained in:
logan committed 2026-10-16 03:03:36 +08:00
1 parent 6452026fd6
commit 2bb4ef7a95
299 files changed
+6059 -3322

No files matched your search

+150
View File
@@ -0,0 +1,150 @@
package mocksrv
import (
"encoding/json"
"regexp"
"strings"
"testing"
"time"
)
func TestGenerateFormats(t *testing.T) {
cases := []struct {
expr string
re string
}{
{"@integer(3, 7), extra", ""},
}
_ = cases
checks := map[string]*regexp.Regexp{
"@integer(3, 7)": regexp.MustCompile(`^[3-7]$`),
"@float(1, 9, 2)": regexp.MustCompile(`^\d\.\d{2}$`),
"@email": regexp.MustCompile(`^[\w.]+@[\w.]+\.\w+$`),
"@phone": regexp.MustCompile(`^1[3-9]\d{9}$`),
"@guid": regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`),
"@id": regexp.MustCompile(`^\d{17}[\dX]$`),
"@datetime(YYYY-MM-DD HH:mm:ss)": regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`),
"@timestamp": regexp.MustCompile(`^\d{12,}$`),
"@boolean": regexp.MustCompile(`^(true|false)$`),
"@cname": regexp.MustCompile(`^\p{Han}{2,3}$`),
"@county(true)": regexp.MustCompile(`\p{Han}+(市)?\p{Han}+区$`),
"@pick(\"男\", \"女\")": regexp.MustCompile(`^(男|女)$`),
"@notExistThing": regexp.MustCompile(`^@notExistThing$`), // 未知表达式原样
"普通文本": regexp.MustCompile(`^普通文本$`),
}
for expr, re := range checks {
for i := 0; i < 30; i++ {
out := Generate(expr)
if !re.MatchString(out) {
t.Fatalf("%s → %q 不匹配 %v", expr, out, re)
}
}
}
}
func TestGenerateJSONLeaves(t *testing.T) {
tpl := `{"username":"@cname","age":"@integer(18,65)","vip":"@boolean","keep":"固定值"}`
var v map[string]interface{}
if err := json.Unmarshal([]byte(GenerateJSON(tpl)), &v); err != nil {
t.Fatalf("生成结果非 JSON: %v", err)
}
age, ok := v["age"].(float64)
if !ok || age < 18 || age > 65 {
t.Fatalf("age 应为 18-65 的数字,得到 %v", v["age"])
}
if _, ok := v["vip"].(bool); !ok {
t.Fatalf("vip 应为布尔,得到 %T", v["vip"])
}
if v["keep"] != "固定值" {
t.Fatalf("非表达式叶子应原样保留")
}
if s, _ := v["username"].(string); len(s) < 2 {
t.Fatalf("username 生成异常: %v", v["username"])
}
}
func TestGenerateJSONTopLevelAndInvalid(t *testing.T) {
if out := GenerateJSON("@cname"); len([]rune(out)) < 2 {
t.Fatalf("顶层表达式应生成: %q", out)
}
if out := GenerateJSON("not-json"); out != "not-json" {
t.Fatalf("非法 JSON 原样返回: %q", out)
}
}
func TestLimiter(t *testing.T) {
l := newLimiter()
now := time.Now()
// 同一秒内第 21 次拒绝(QPS)
pass := 0
for i := 0; i < 25; i++ {
if l.allow("k", now) {
pass++
}
}
if pass != LimitQPS {
t.Fatalf("QPS 应放行 %d,实际 %d", LimitQPS, pass)
}
// 后续秒窗重置但 60 秒窗累计:S2 放 20、S3 放 20(累计 60),S4 拒绝(QPM)
for _, off := range []time.Duration{2 * time.Second, 3 * time.Second} {
pass2 := 0
for i := 0; i < 25; i++ {
if l.allow("k", now.Add(off)) {
pass2++
}
}
if pass2 != 20 {
t.Fatalf("+%v 应放行 20(QPS),实际 %d", off, pass2)
}
}
if l.allow("k", now.Add(4*time.Second)) {
t.Fatalf("60 秒窗口内累计达 60 应拒绝")
}
// 不同 key 相互独立
if !l.allow("other", now.Add(3*time.Second)) {
t.Fatal("不同 key 不应被牵连")
}
// 滑动窗口过期后恢复
l2 := newLimiter()
t0 := time.Now()
for i := 0; i < LimitQPM; i++ {
if !l2.allow("k2", t0.Add(time.Duration(i)*time.Second)) {
t.Fatalf("分散 60 秒内应全部放行(第 %d 次被拒)", i+1)
}
}
if l2.allow("k2", t0.Add(59*time.Second)) {
t.Fatal("60 次命中仍在窗口内应被拒")
}
if !l2.allow("k2", t0.Add(130*time.Second)) {
t.Fatal("窗口滑过后应恢复放行")
}
}
func TestNormalizePath(t *testing.T) {
if normalizePath("api/users/") != "/api/users" {
t.Fatalf("got %q", normalizePath("api/users/"))
}
if normalizePath("") != "/" {
t.Fatalf("空路径 → /")
}
if normalizePath("/x/") != "/x" {
t.Fatalf("去尾斜杠")
}
_ = strings.TrimSpace
}
func TestResponseBodyFormats(t *testing.T) {
for _, body := range []string{"@cname", `{"name":"@cname"}`, "plain text\n"} {
if got := responseBody("text/plain", body); got != body {
t.Fatalf("text changed: %q", got)
}
}
var decoded map[string]int
got := responseBody("application/json; charset=utf-8", `{"n":"@integer(1,1)"}`)
if err := json.Unmarshal([]byte(got), &decoded); err != nil || decoded["n"] != 1 {
t.Fatalf("JSON template not expanded: %q", got)
}
}