Files
cmautobuy/admin/main_test.go
T

136 lines
4.6 KiB
Go

package main
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"cmautobuy/admin/repository"
"cmautobuy/admin/service"
)
// 五个主页面都走一次真实路由和模板渲染。
// 这样模板字段写错或新增列漏接时,测试阶段就会失败,不必等人工点页面。
func TestMainPagesReturnOK(t *testing.T) {
db, err := repository.Open(t.TempDir())
if err != nil {
t.Fatalf("打开测试数据库失败: %v", err)
}
defer db.Close()
if err := repository.Migrate(db); err != nil {
t.Fatalf("迁移测试数据库失败: %v", err)
}
router, err := newRouter(db)
if err != nil {
t.Fatalf("组装路由失败: %v", err)
}
// 全新库的业务页必须先去初始化,不能匿名打开。
unauthenticated := httptest.NewRequest(http.MethodGet, "/shopee", nil)
unauthenticatedResponse := httptest.NewRecorder()
router.ServeHTTP(unauthenticatedResponse, unauthenticated)
if unauthenticatedResponse.Code != http.StatusSeeOther ||
unauthenticatedResponse.Header().Get("Location") != "/setup" {
t.Fatalf("全新库访问业务页应跳转 /setup,实际 %d %s",
unauthenticatedResponse.Code, unauthenticatedResponse.Header().Get("Location"))
}
if err := service.SetupInitialAdmin(db, "admin", "test-password", "test-password", time.Now()); err != nil {
t.Fatalf("准备测试管理员失败: %v", err)
}
token, _, _, err := service.Login(db, "admin", "test-password", time.Now())
if err != nil {
t.Fatalf("准备测试登录 Session 失败: %v", err)
}
addAuth := func(request *http.Request) {
request.AddCookie(&http.Cookie{Name: "cmautobuy_session", Value: token})
}
for _, path := range []string{"/shopee", "/pdd", "/syb", "/tasks", "/clients"} {
t.Run(path, func(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, path, nil)
addAuth(request)
response := httptest.NewRecorder()
router.ServeHTTP(response, request)
if response.Code != http.StatusOK {
t.Fatalf("GET %s = %d,期望 200,响应:%s",
path, response.Code, response.Body.String())
}
})
}
sybRequest := httptest.NewRequest(http.MethodGet, "/syb", nil)
addAuth(sybRequest)
sybResponse := httptest.NewRecorder()
router.ServeHTTP(sybResponse, sybRequest)
for _, want := range []string{
"指定日期同步", `name="date_from"`, `name="date_to"`,
"按顺运宝货运单创建日期(UTC+8)同步",
} {
if !strings.Contains(sybResponse.Body.String(), want) {
t.Errorf("顺运宝页面缺少 %q", want)
}
}
pdd, err := repository.EnsurePddProduct(
db, "737116531267",
"https://mobile.yangkeduo.com/goods.html?goods_id=737116531267")
if err != nil {
t.Fatalf("准备 PDD 商品失败: %v", err)
}
resultJSON := `{
"price_granularity":"color",
"dimensions":[{"key":"color","name":"颜色分类"},{"key":"size","name":"尺码"}],
"skus":[
{"options":{"color":"黑色","size":"M"},"price_cent":470,
"price_observed_at":{"color":"黑色","size":"M"},"available":true},
{"options":{"color":"黑色","size":"L"},"price_cent":470,
"price_observed_at":{"color":"黑色","size":"M"},"available":true}
]
}`
if err := repository.SetCollectResult(
db, pdd.GoodsID, "测试商品", "测试旗舰店", resultJSON); err != nil {
t.Fatalf("准备采集结果失败: %v", err)
}
request := httptest.NewRequest(http.MethodGet, "/pdd/detail?id=1", nil)
addAuth(request)
response := httptest.NewRecorder()
router.ServeHTTP(response, request)
if response.Code != http.StatusOK {
t.Fatalf("GET /pdd/detail = %d,响应:%s", response.Code, response.Body.String())
}
body := response.Body.String()
for _, want := range []string{
"测试旗舰店", "价格按颜色采样", "✓ 实测", "推断",
} {
if !strings.Contains(body, want) {
t.Errorf("PDD 详情缺少 %q,响应:%s", want, body)
}
}
}
// 公共弹窗不能只看 click.target 关闭:从密码框拖选到遮罩松开时,
// 浏览器会把 click 目标合成为遮罩。这个静态守卫确保 #56 的手势起点判断不被删掉;
// 真实指针行为另用 Chrome 回归验证。
func TestModalBackdropRequiresPointerStart(t *testing.T) {
js, err := staticFS.ReadFile("static/js/app.js")
if err != nil {
t.Fatalf("读取公共 JavaScript 失败: %v", err)
}
source := string(js)
for _, required := range []string{
`addEventListener("pointerdown"`,
`pointerStartedOnBackdrop = e.target === modal`,
`e.target === modal && pointerStartedOnBackdrop`,
`addEventListener("pointercancel"`,
} {
if !strings.Contains(source, required) {
t.Errorf("公共弹窗缺少拖选防误关闭守卫 %q", required)
}
}
}