package main import ( "net/http" "net/http/httptest" "os" "strings" "testing" "time" "cmautobuy/admin/model" "cmautobuy/admin/repository" "cmautobuy/admin/service" ) func TestPaginationCSS_桌面右对齐且窄窗口不遮挡内容(t *testing.T) { content, err := os.ReadFile("static/css/app.css") if err != nil { t.Fatal(err) } css := string(content) statusbarRightAligned := `.statusbar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: flex-end;` for _, want := range []string{ statusbarRightAligned, "flex: 0 1 auto", "text-align: right", "margin-left: 0", "flex: 0 0 auto", "@media (max-width: 760px)", "position: static", ".statusbar > span:first-child { flex-basis: 100%; }", } { if !strings.Contains(css, want) { t.Fatalf("分页布局 CSS 缺少 %q", want) } } } func TestModuleNavigation_按账号保存稳定列表状态且安全回退(t *testing.T) { header, err := os.ReadFile("templates/partials/header.html") if err != nil { t.Fatal(err) } js, err := os.ReadFile("static/js/app.js") if err != nil { t.Fatal(err) } headerSource := string(header) for _, want := range []string{ "data-module-navigation", "data-ui-state-user=\"{{if .CurrentUser}}{{.CurrentUser.UserID}}{{end}}\"", "data-module-root=\"/shopee\"", "data-module-root=\"/pdd\"", "data-module-root=\"/syb\"", "data-module-root=\"/tasks\"", "data-module-root=\"/clients\"", "data-module-root=\"/users\"", } { if !strings.Contains(headerSource, want) { t.Errorf("公共导航缺少模块状态标识 %q", want) } } jsSource := string(js) for _, want := range []string{ "var MODULE_QUERY_KEYS = {", "\"/shopee\": [\"goods_id\", \"status\", \"shop\", \"image\", \"page\"]", "\"/syb\": [\"order_no\", \"shop\", \"stage\", \"page\", \"date_from\", \"date_to\"]", "var MODULE_STATE_PREFIX = \"cmautobuy:module-state:\"", "parsed.origin !== window.location.origin || parsed.pathname !== moduleRoot", "sessionStorage.setItem(", "sessionStorage.removeItem(moduleStateStorageKey(userID, moduleRoot))", "setupModuleNavigationState();", } { if !strings.Contains(jsSource, want) { t.Errorf("模块状态脚本缺少安全或交互约束 %q", want) } } whitelistStart := strings.Index(jsSource, "var MODULE_QUERY_KEYS") whitelistEnd := strings.Index(jsSource, "var MODULE_STATE_PREFIX") if whitelistStart == -1 || whitelistEnd <= whitelistStart { t.Fatal("没有找到模块查询参数白名单") } whitelist := jsSource[whitelistStart:whitelistEnd] for _, forbidden := range []string{"\"msg\"", "\"error\"", "\"history\"", "\"login\"", "\"change_password\""} { if strings.Contains(whitelist, forbidden) { t.Errorf("稳定状态白名单不应包含临时参数 %s", forbidden) } } } func TestPurchaseModal_未选规格行保持隐藏且不提交(t *testing.T) { files := map[string][]string{ "static/css/app.css": { ".purchase-confirm-row[hidden] { display: none; }", }, "templates/syb/list.html": { `class="purchase-confirm-row" data-purchase-row="{{.SybID}}" hidden`, `name="mapping_option_key" value="{{.MappingOptionKey}}" disabled`, `name="context_version" value="{{.ContextVersion}}" disabled`, `name="max_price_yuan" min="0.01" step="0.01" value="{{.DefaultMaxPrice}}" required disabled`, }, "static/js/app.js": { "row.hidden = !enabled;", "input.disabled = !enabled;", }, } for path, wants := range files { content, err := os.ReadFile(path) if err != nil { t.Fatal(err) } for _, want := range wants { if !strings.Contains(string(content), want) { t.Errorf("%s 缺少采购弹窗回归约束 %q", path, want) } } } } func TestSybBulkActions_按服务端动作能力隔离勾选项(t *testing.T) { files := map[string][]string{ "templates/syb/list.html": { `data-need-checked="collect"`, `data-need-checked="purchase"`, `data-select-action="{{if .CanCollect}}collect{{else if .CanPurchase}}purchase{{end}}"`, `{{if .CanCollect}}form="syb-collect-form"{{end}}`, `action="/syb/collect-pdd-batch"`, `id="syb-collect-modal"`, }, "static/js/app.js": { `supportsSelectAction(box, action)`, `checkedCount(action)`, `supportsSelectAction(box, "purchase")`, `getAttribute("data-collect-action")`, }, "handler/web/web.go": { `pages.POST("/syb/collect-pdd-batch", h.SybCollectPddBatch)`, }, } for path, wants := range files { content, err := os.ReadFile(path) if err != nil { t.Fatal(err) } for _, want := range wants { if !strings.Contains(string(content), want) { t.Errorf("%s 缺少动作隔离约束 %q", path, want) } } } } func TestPddDetailRecollect_使用独立入口并可选客户端(t *testing.T) { content, err := os.ReadFile("templates/pdd/edit_modal.html") if err != nil { t.Fatal(err) } page := string(content) for _, want := range []string{ `action="/pdd/recollect"`, `name="goods_id"`, `name="client_id"`, "不指定(任意客户端可领取)", "成功后再用新结果覆盖", } { if !strings.Contains(page, want) { t.Errorf("PDD 详情重新采集缺少 %q", want) } } if strings.Contains(page, `id="pdd-recollect-form" method="post" action="/pdd/collect"`) { t.Error("重新采集不能继续复用会跳过已采集商品的普通批量入口") } } func TestTaskDetail_采购核单结果使用独立只读分组(t *testing.T) { content, err := os.ReadFile("templates/task/detail_modal.html") if err != nil { t.Fatal(err) } page := string(content) for _, want := range []string{ "PDD 核单结果", "PDD 订单编号", "下单时间", "上报时付款状态", "{{if .IsPurchase}}", "{{if .HasPddOrder}}", "完整结果(result_data)", } { if !strings.Contains(page, want) { t.Errorf("采购任务详情缺少 %q", want) } } } func TestTaskPage_创建人筛选列表列和详情字段齐全(t *testing.T) { files := map[string][]string{ "templates/task/list.html": { `{{if .ShowCreator}}`, `name="creator"`, `{{.CreatorText}}`, `>筛选`, `