package api // v3.1:自定义提取码与站点域名单元测试。 import ( "encoding/json" "net/http" "net/http/httptest" "net/url" "strings" "testing" "github.com/gin-gonic/gin" ) // postForm 以 urlencoded 表单调用 handler(v3.1 测试辅助)。 func postForm(d *Deps, path string, fields map[string]string) *httptest.ResponseRecorder { form := url.Values{} for k, v := range fields { form.Set(k, v) } req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(form.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") var handler gin.HandlerFunc switch path { case "/share/text": handler = d.shareText default: handler = func(c *gin.Context) { c.AbortWithStatus(http.StatusNotFound) } } return invoke(handler, req) } func TestValidatePickupCode(t *testing.T) { // 合法:空(用随机码) if err := validatePickupCode(""); err != nil { t.Fatalf("空码应合法: %v", err) } // 合法:5-8 位字母数字(L3:最小长度由 4 提升至 5) for _, c := range []string{"abcde", "AB123", "12345678", "a1B2c"} { if err := validatePickupCode(c); err != nil { t.Fatalf("合法码 %s 不应报错: %v", c, err) } } // 非法:长度(4 位及以下不再允许) for _, c := range []string{"abcd", "a1B2", "abc", "123456789"} { if err := validatePickupCode(c); err == nil { t.Fatalf("非法长度 %s 应报错", c) } } // 非法:字符 for _, c := range []string{"ab c1", "提码", "ab-cd", "ab.cd", "ab+cd"} { if err := validatePickupCode(c); err == nil { t.Fatalf("非法字符 %s 应报错", c) } } } func TestNormalizeSiteDomain(t *testing.T) { // 空 = 当前地址 d, err := normalizeSiteDomain("") if err != nil { t.Fatalf("unexpected err: %v", err) } if d != "" { t.Fatalf("want empty, got %q", d) } // 完整 URL d, err = normalizeSiteDomain("https://share.example.com") if err != nil { t.Fatalf("unexpected err: %v", err) } if d != "https://share.example.com" { t.Fatalf("want https://share.example.com, got %q", d) } // 带端口 + 去尾斜杠 d, err = normalizeSiteDomain("http://192.168.1.5:8466/") if err != nil { t.Fatalf("unexpected err: %v", err) } if d != "http://192.168.1.5:8466" { t.Fatalf("want http://192.168.1.5:8466, got %q", d) } // 裸主机自动补 http d, err = normalizeSiteDomain("share.example.com") if err != nil { t.Fatalf("unexpected err: %v", err) } if d != "http://share.example.com" { t.Fatalf("want http://share.example.com, got %q", d) } // 非法:路径 / 协议 for _, bad := range []string{"https://a.com/path", "ftp://a.com", "javascript:alert(1)"} { if _, err := normalizeSiteDomain(bad); err == nil { t.Fatalf("非法域名 %s 应报错", bad) } } } // TestShareTextTextPlainCompat 复刻真实浏览器请求形态: // 旧前端 bundle 发 text/plain Content-Type + urlencoded body(fetch 字符串 body 默认头)。 // 修复前:该形态被静默存成空文本(bug 1)或 400「分享内容不能为空」(bug 2)。 func TestShareTextTextPlainCompat(t *testing.T) { d := newPolicyTestDeps(t) body := strings.NewReader("text=111&expire_value=1&expire_style=day&code=") req := httptest.NewRequest(http.MethodPost, "/share/text", body) req.Header.Set("Content-Type", "text/plain;charset=UTF-8") w := invoke(d.shareText, req) if w.Code != http.StatusOK { t.Fatalf("text/plain+urlencoded 应 200: %d %s", w.Code, w.Body.String()) } // JSON 体但 Content-Type 缺失/为 text/plain 也应可解析 req2 := httptest.NewRequest(http.MethodPost, "/share/text", strings.NewReader(`{"text":"无头JSON","expire_value":1,"expire_style":"day"}`)) req2.Header.Set("Content-Type", "text/plain;charset=UTF-8") w2 := invoke(d.shareText, req2) if w2.Code != http.StatusOK { t.Fatalf("text/plain+JSON体 应 200: %d %s", w2.Code, w2.Body.String()) } // 取件确认内容真实落库 req3 := httptest.NewRequest(http.MethodPost, "/share/select", strings.NewReader(`{"code":"`+codeOf(w)+`"}`)) req3.Header.Set("Content-Type", "application/json") w3 := invoke(d.shareSelectPost, req3) if !strings.Contains(w3.Body.String(), "111") { t.Fatalf("落库内容应为 111: %s", w3.Body.String()) } } // codeOf 从创建响应提取取件码。 func codeOf(w *httptest.ResponseRecorder) string { var env struct { Data struct { Code string `json:"code"` } `json:"data"` } _ = json.Unmarshal(w.Body.Bytes(), &env) return env.Data.Code } func TestShareTextCustomCode(t *testing.T) { d := newPolicyTestDeps(t) // 自定义码成功创建 w := postForm(d, "/share/text", map[string]string{"text": "自定义码测试", "code": "MYCODE1"}) if w.Code != http.StatusOK { t.Fatalf("自定义码创建失败: %d %s", w.Code, w.Body.String()) } // 重复占用 → 400 w = postForm(d, "/share/text", map[string]string{"text": "第二条", "code": "MYCODE1"}) if w.Code != http.StatusBadRequest { t.Fatalf("占用码应 400: %d %s", w.Code, w.Body.String()) } // 非法码 → 400 w = postForm(d, "/share/text", map[string]string{"text": "第三条", "code": "abc"}) if w.Code != http.StatusBadRequest { t.Fatalf("过短码应 400: %d %s", w.Code, w.Body.String()) } // 空码 → 随机码仍正常 w = postForm(d, "/share/text", map[string]string{"text": "第四条", "code": ""}) if w.Code != http.StatusOK { t.Fatalf("空码应回退随机: %d %s", w.Code, w.Body.String()) } }