134 lines
4.1 KiB
Go
134 lines
4.1 KiB
Go
package setting
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
)
|
|
|
|
func TestVisitorHomePath(t *testing.T) {
|
|
full := Default() // Pages 默认全开
|
|
|
|
noMap := full
|
|
noMap.Pages = []string{"places", "stats"}
|
|
|
|
cases := []struct {
|
|
name string
|
|
set Settings
|
|
want string
|
|
}{
|
|
{"默认停在主页", full, ""},
|
|
{"空值(历史配置)停在主页", func() Settings { s := full; s.VisitorHome = ""; return s }(), ""},
|
|
{"显式主页", func() Settings { s := full; s.VisitorHome = "home"; return s }(), ""},
|
|
{"跳到地图", func() Settings { s := full; s.VisitorHome = "map"; return s }(), "/map"},
|
|
{"跳到统计", func() Settings { s := full; s.VisitorHome = "stats"; return s }(), "/stats"},
|
|
{"目标页没对访客开放则不跳", noMap, ""},
|
|
{"非法值不跳", func() Settings { s := full; s.VisitorHome = "javascript:alert(1)"; return s }(), ""},
|
|
{"不给访客任何页面时不跳", func() Settings { s := full; s.Pages = nil; s.VisitorHome = "map"; return s }(), ""},
|
|
}
|
|
for _, c := range cases {
|
|
if got := c.set.VisitorHomePath(); got != c.want {
|
|
t.Errorf("%s: VisitorHomePath = %q,期望 %q", c.name, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNormalizeVisitorHome(t *testing.T) {
|
|
for _, v := range []string{"", "home", "nope", "/map", "../admin", "MAP"} {
|
|
if got := NormalizeVisitorHome(v); got != VisitorHomeMain {
|
|
t.Errorf("NormalizeVisitorHome(%q) = %q,期望 %q", v, got, VisitorHomeMain)
|
|
}
|
|
}
|
|
for _, p := range AllPages {
|
|
if got := NormalizeVisitorHome(p.Key); got != p.Key {
|
|
t.Errorf("NormalizeVisitorHome(%q) = %q,期望原样保留", p.Key, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestVisitorHomeStore 覆盖存取:旧配置(没有该键)必须回落到主页,
|
|
// 而不是把零值当成「访客落地到空路径」。
|
|
// 只在库名带 test 的库上跑——它会改写 settings 里的 site 键,不能碰正式库。
|
|
func TestVisitorHomeStore(t *testing.T) {
|
|
dsn := os.Getenv("ROND_DSN")
|
|
if dsn == "" {
|
|
t.Skip("未设置 ROND_DSN")
|
|
}
|
|
if !strings.Contains(dsn, "test") {
|
|
t.Skipf("库名不含 test,跳过以免动到正式站的设置:%s", dsn)
|
|
}
|
|
db, err := sql.Open("pgx", dsn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
ctx := context.Background()
|
|
st := Store{DB: db}
|
|
|
|
var orig []byte
|
|
had := true
|
|
if err := db.QueryRowContext(ctx, `SELECT value FROM settings WHERE key='site'`).Scan(&orig); err != nil {
|
|
if err != sql.ErrNoRows {
|
|
t.Fatal(err)
|
|
}
|
|
had = false
|
|
}
|
|
defer func() { // 还原原配置,别把测试库的设置留下改动
|
|
if had {
|
|
db.ExecContext(ctx, `UPDATE settings SET value=$1 WHERE key='site'`, orig)
|
|
} else {
|
|
db.ExecContext(ctx, `DELETE FROM settings WHERE key='site'`)
|
|
}
|
|
}()
|
|
|
|
set := func(raw string) {
|
|
if _, err := db.ExecContext(ctx, `INSERT INTO settings (key, value) VALUES ('site', $1)
|
|
ON CONFLICT (key) DO UPDATE SET value=$1`, []byte(raw)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// 旧配置:整个键都不存在 → 默认停在主页
|
|
set(`{"pages":["map","stats"]}`)
|
|
if s, err := st.Load(ctx); err != nil {
|
|
t.Fatal(err)
|
|
} else if s.VisitorHome != VisitorHomeMain {
|
|
t.Errorf("缺键时 VisitorHome = %q,期望 %q", s.VisitorHome, VisitorHomeMain)
|
|
}
|
|
|
|
// 正常往返
|
|
s, err := st.Load(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s.VisitorHome = "map"
|
|
if err := st.Save(ctx, s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s2, err := st.Load(ctx); err != nil {
|
|
t.Fatal(err)
|
|
} else if s2.VisitorHome != "map" {
|
|
t.Errorf("存回来成了 %q,期望 map", s2.VisitorHome)
|
|
}
|
|
|
|
// 库里被塞了非法值 → 读出来必须收敛到主页
|
|
set(`{"pages":["map"],"visitor_home":"/etc/passwd"}`)
|
|
if s3, err := st.Load(ctx); err != nil {
|
|
t.Fatal(err)
|
|
} else if s3.VisitorHome != VisitorHomeMain {
|
|
t.Errorf("非法值读出 %q,期望收敛为 %q", s3.VisitorHome, VisitorHomeMain)
|
|
}
|
|
|
|
// 目标页没开放时不跳(即便设置里写着 map)
|
|
set(`{"pages":["stats"],"visitor_home":"map"}`)
|
|
if s4, err := st.Load(ctx); err != nil {
|
|
t.Fatal(err)
|
|
} else if got := s4.VisitorHomePath(); got != "" {
|
|
t.Errorf("目标页未开放却仍要跳 %q", got)
|
|
}
|
|
}
|