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) } }