Files

238 lines
8.0 KiB
Go
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
)
func TestAmount(t *testing.T) {
cases := map[string]string{"5": "5", "5.5": "5.5", "05.50": "5.5", ".5": "0.5", "10.0037": "10.0037", "1.00000001": "1.00000001", "0.10": "0.1"}
for in, want := range cases {
e8, err := parseAmountE8(in, 8)
if err != nil {
t.Fatalf("%s: %v", in, err)
}
if got := fmtE8(e8); got != want {
t.Errorf("%s: got %s want %s", in, got, want)
}
}
for _, bad := range []string{"", "abc", "-1", "1.", "1,5", "1e3", "12345678901"} {
if _, err := parseAmountE8(bad, 8); err == nil {
t.Errorf("%q should fail", bad)
}
}
if _, err := parseAmountE8("1.001", 2); err == nil {
t.Error("1.001 with 2 decimals should fail")
}
if fmtE8(0) != "0" {
t.Error("zero")
}
}
func TestSlug(t *testing.T) {
for _, ok := range []string{"qianyuwing", "a1", "my-site_2", "abcdefghijklmnopqrstuvwx"} {
if !validSlug(ok) {
t.Errorf("%s should be valid", ok)
}
}
for _, bad := range []string{"a", "-ab", "Admin", "admin", "new", "login", "有中文", "abcdefghijklmnopqrstuvwxy", "a b"} {
if validSlug(bad) {
t.Errorf("%s should be invalid", bad)
}
}
}
func TestCleanText(t *testing.T) {
if got := cleanText(" hello \t\x00world​ ", 20, false); got != "hello world​" {
t.Errorf("got %q", got)
}
if got := cleanText("一二三四五六", 3, false); got != "一二三" {
t.Errorf("got %q", got)
}
if got := cleanText("a\n\n\n\nb\n", 20, true); got != "a\n\nb" {
t.Errorf("got %q", got)
}
if nickKey(" QianyuWing X ") != "qianyuwing x" {
t.Error("nickKey")
}
}
func TestValidateURLs(t *testing.T) {
if _, err := validateBinanceLink("https://app.binance.com/uni-qr/abc"); err != nil {
t.Error(err)
}
for _, bad := range []string{"http://app.binance.com/x", "https://binance.com.evil.io/x", "https://evil.io/binance.com", "javascript:alert(1)"} {
if _, err := validateBinanceLink(bad); err == nil {
t.Errorf("%s should fail", bad)
}
}
}
func TestXLink(t *testing.T) {
for in, want := range map[string]string{"@qianyuwing": "https://x.com/qianyuwing", "qianyuwing_1": "https://x.com/qianyuwing_1", "https://x.com/QianyuWing/status/1": "https://x.com/QianyuWing",
"twitter.com/abc?s=1": "https://x.com/abc", "https://www.x.com/abc/": "https://x.com/abc", "": ""} {
if got, err := validateXLink(in); err != nil || got != want {
t.Errorf("%q: got %q %v want %q", in, got, err, want)
}
}
for _, bad := range []string{"https://evil.io/x", "https://x.com/", "https://x.com/this-is-not-valid", "@toolongtoolongtoolong"} {
if _, err := validateXLink(bad); err == nil {
t.Errorf("%q should fail", bad)
}
}
if xHandle("https://x.com/abc") != "abc" {
t.Error("xHandle")
}
}
func TestSpamAndTweet(t *testing.T) {
for _, bad := range []string{"加微信 abc", "vx: 123", "看 www.evil.com", "https://x.y", "shop.xyz 好物", "QQ 12345678", "电报找我", "打 13812345678", "wechat me"} {
if !hasContact(bad) {
t.Errorf("%q 应判为广告", bad)
}
}
for _, ok := range []string{"别买烟了,去吃饭", "加油要饭", "我的名字 qianyuwing", "5 块钱拿去", "v神牛逼", "devx 团队加油", "avx512"} {
if hasContact(ok) {
t.Errorf("%q 不应判为广告", ok)
}
}
for in, want := range map[string]string{"https://x.com/i/web/status/1234567890": "1234567890", "https://x.com/jack/status/20": "20", "https://twitter.com/a_b/statuses/1234567890123?s=20": "1234567890123", "x.com/jack/status/20": "20", "https://x.com/jack": "", "junk": ""} {
if got := tweetIDFrom(in); got != want {
t.Errorf("tweetIDFrom(%q)=%q want %q", in, got, want)
}
}
if tok := tweetToken("20"); tok == "" || strings.ContainsAny(tok, "0.") {
t.Errorf("token 不应含 0 或小数点: %q", tok)
}
for in, want := range map[string]string{"@qianyuwing": "qianyuwing", "qianyuwing_1": "qianyuwing_1", "https://x.com/QianyuWing/status/1": "QianyuWing", "bad name": "", "": ""} {
if got := normHandle(in); got != want {
t.Errorf("normHandle(%q)=%q want %q", in, got, want)
}
}
if ws := weekStart(); ws <= 0 || ms()-ws > 7*24*3600*1000 || ms() < ws {
t.Errorf("weekStart 不合理: %d", ws)
}
}
func TestClientIP(t *testing.T) {
mk := func(remote string, hdr map[string]string) *http.Request {
r := httptest.NewRequest("GET", "/", nil)
r.RemoteAddr = remote
for k, v := range hdr {
r.Header.Set(k, v)
}
return r
}
// 不信代理:忽略所有头
if ip := clientIP(mk("1.2.3.4:5", map[string]string{"X-Forwarded-For": "9.9.9.9"}), false, "X-Forwarded-For"); ip != "1.2.3.4" {
t.Errorf("got %s", ip)
}
// 信代理但请求不是从内网来的:头无效
if ip := clientIP(mk("1.2.3.4:5", map[string]string{"X-Forwarded-For": "9.9.9.9"}), true, "X-Forwarded-For"); ip != "1.2.3.4" {
t.Errorf("got %s", ip)
}
// 反代来的:取最后一跳
if ip := clientIP(mk("127.0.0.1:5", map[string]string{"X-Forwarded-For": "6.6.6.6, 9.9.9.9"}), true, "X-Forwarded-For"); ip != "9.9.9.9" {
t.Errorf("got %s", ip)
}
// 配置的其它头才生效;伪造的 X-Real-IP 不看
if ip := clientIP(mk("127.0.0.1:5", map[string]string{"X-Real-IP": "6.6.6.6", "CF-Connecting-IP": "7.7.7.7"}), true, "CF-Connecting-IP"); ip != "7.7.7.7" {
t.Errorf("got %s", ip)
}
if ip := clientIP(mk("127.0.0.1:5", map[string]string{"X-Real-IP": "6.6.6.6"}), true, "X-Forwarded-For"); ip != "127.0.0.1" {
t.Errorf("got %s", ip)
}
// 非法值回退
if ip := clientIP(mk("127.0.0.1:5", map[string]string{"X-Forwarded-For": "evil"}), true, "X-Forwarded-For"); ip != "127.0.0.1" {
t.Errorf("got %s", ip)
}
}
func TestCoinCaps(t *testing.T) {
st, err := openStore(t.TempDir() + "/c.db")
if err != nil {
t.Fatal(err)
}
defer st.Close()
if _, err := st.db.Exec(`INSERT INTO sites(slug,name,pass_hash,created_at,updated_at) VALUES('s','s','',0,0)`); err != nil {
t.Fatal(err)
}
add := func(v, ip string) (bool, string) {
ok, msg, err := st.AddCoin(1, v, ip, "2000-01-01", 2, 3, 0)
if err != nil {
t.Fatal(err)
}
return ok, msg
}
if ok, _ := add("v1", "ip1"); !ok {
t.Error("第一次应成功")
}
if ok, _ := add("v1", "ip1"); !ok {
t.Error("perDay=2 第二次应成功")
}
if ok, msg := add("v1", "ip1"); ok || !strings.Contains(msg, "明天") {
t.Errorf("第三次应被拦: %v %s", ok, msg)
}
if ok, _ := add("v2", "ip1"); !ok {
t.Error("同 IP 第三个应成功(ipCap=3)")
}
if ok, msg := add("v3", "ip1"); ok || !strings.Contains(msg, "网络") {
t.Errorf("ipCap 应拦: %v %s", ok, msg)
}
if ok, _ := add("v3", "ip2"); !ok {
t.Error("换 IP 应成功")
}
total, today, _ := st.CoinStats(1)
if total != 4 || today != 0 {
t.Errorf("计数: total=%d today=%d(today 按当前日期查,测试用固定日期)", total, today)
}
}
// TestVisitorCookie 访客标识必须由本站签发,脚本自造的 24 位字符串无效。
func TestVisitorCookie(t *testing.T) {
sec := []byte("test-secret")
v := signVisitor(sec, "0123456789abcdef01234567", 1700000000)
if parseVisitor(sec, v) != 1700000000 {
t.Error("自己签的应通过")
}
for _, bad := range []string{"", "0123456789abcdef01234567", v + "x", "0123456789abcdef01234567.1700000000.deadbeefdeadbeef"} {
if parseVisitor(sec, bad) != 0 {
t.Errorf("伪造的应拒绝: %q", bad)
}
}
if parseVisitor([]byte("other"), v) != 0 {
t.Error("换密钥应拒绝")
}
}
// TestCoinIPTotal 一个 IP 挨个站刷:全站总量上限拦得住。
func TestCoinIPTotal(t *testing.T) {
st, err := openStore(filepath.Join(t.TempDir(), "s.db"))
if err != nil {
t.Fatal(err)
}
defer st.Close()
for i := 0; i < 5; i++ {
if _, err := st.db.Exec(`INSERT INTO sites(slug,name,pass_hash,created_at,updated_at) VALUES(?,'s','',0,0)`, fmt.Sprintf("s%d", i)); err != nil {
t.Fatal(err)
}
}
got := 0
for site := int64(1); site <= 5; site++ {
for v := 0; v < 3; v++ { // 每站换 cookie 丢 3 次
if ok, _, err := st.AddCoin(site, fmt.Sprintf("v%d-%d", site, v), "ip1", "2000-01-01", 1, 2, 4); err != nil {
t.Fatal(err)
} else if ok {
got++
}
}
}
if got != 4 {
t.Errorf("全站总量上限 4,实际记了 %d", got)
}
}