桌面 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:
1 parent
6452026fd6
commit
2bb4ef7a95
299 files changed
+6059
-3322
No files matched your search
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user