2026-08-10 16:33:42 +08:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"html/template"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
|
2026-08-12 09:56:15 +08:00
|
|
|
"cmautobuy/admin/model"
|
2026-08-10 16:33:42 +08:00
|
|
|
"cmautobuy/admin/service"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestSybListTemplate_区分标题图片与阶段动作(t *testing.T) {
|
|
|
|
|
tmpl, err := template.ParseFS(templateFS, "templates/*/*.html")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2026-08-10 16:34:55 +08:00
|
|
|
rows := []service.SybOrderView{
|
|
|
|
|
{
|
|
|
|
|
SybID: "SYB-READY", OrderNo: "ORDER-READY", Title: "可采购商品",
|
|
|
|
|
ImageURL: "https://example.test/original.jpg", Stage: service.SybStagePurchaseReady,
|
|
|
|
|
StageText: "可创建采购任务", StageHelp: "规格映射有效", ActionText: "创建采购任务",
|
2026-08-14 10:30:08 +08:00
|
|
|
CanPurchase: true, CanAIMatch: true, SelectionActions: "purchase ai",
|
|
|
|
|
Quantity: 2, DefaultUnitPrice: "11.80", DefaultTotalPrice: "23.60",
|
2026-08-10 16:34:55 +08:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
SybID: "SYB-TASK", OrderNo: "ORDER-TASK", Title: "已有任务商品",
|
|
|
|
|
Stage: service.SybStageTaskCreated, StageText: "已创建采购任务",
|
|
|
|
|
StageHelp: "已有任务", ActionText: "查看采购任务",
|
|
|
|
|
},
|
2026-08-11 18:05:27 +08:00
|
|
|
{
|
|
|
|
|
SybID: "SYB-DONE", OrderNo: "ORDER-DONE", Title: "采购完成商品",
|
|
|
|
|
Stage: service.SybStagePurchaseCompleted, StageText: "采购完成",
|
|
|
|
|
StageHelp: "采购任务已成功完成", ActionText: "查看采购结果",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
SybID: "SYB-REVIEW", OrderNo: "ORDER-REVIEW", Title: "待核对商品",
|
|
|
|
|
Stage: service.SybStagePurchaseReview, StageText: "采购待人工核对",
|
2026-08-13 14:35:34 +08:00
|
|
|
StageHelp: "禁止重新采购", ActionText: "核对采购订单",
|
2026-08-11 18:05:27 +08:00
|
|
|
},
|
2026-08-10 16:34:55 +08:00
|
|
|
{
|
|
|
|
|
SybID: "SYB-PDD", OrderNo: "ORDER-PDD", Title: "待关联商品",
|
|
|
|
|
Stage: service.SybStagePddMissing, StageText: "未关联 PDD",
|
|
|
|
|
StageHelp: "下一步关联采购商品", ActionText: "关联 PDD",
|
2026-08-11 14:59:26 +08:00
|
|
|
SelectionHint: "当前阶段不能批量操作:下一步关联采购商品",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
SybID: "SYB-COLLECT", OrderNo: "ORDER-COLLECT", Title: "待采集商品",
|
|
|
|
|
Stage: service.SybStagePddPending, StageText: "PDD 待采集",
|
|
|
|
|
StageHelp: "尚未创建采集任务", ActionText: "创建采集任务",
|
2026-08-14 10:30:08 +08:00
|
|
|
CanCollect: true, SelectionActions: "collect", SelectionHint: "可批量创建 PDD 采集任务",
|
2026-08-10 16:34:55 +08:00
|
|
|
},
|
|
|
|
|
}
|
2026-08-10 16:33:42 +08:00
|
|
|
var output bytes.Buffer
|
|
|
|
|
data := map[string]any{
|
|
|
|
|
"Title": "顺运宝数据", "Active": "syb", "CSRFToken": "test-csrf",
|
2026-08-12 16:26:22 +08:00
|
|
|
"Rows": rows, "PurchaseRows": rows,
|
|
|
|
|
"DetailURL": "/syb/detail", "StageOptions": service.SybStageOptions(),
|
2026-08-13 14:21:37 +08:00
|
|
|
"SyncDateFrom": "2026-08-12", "SyncDateTo": "2026-08-13",
|
2026-08-13 14:58:19 +08:00
|
|
|
"Keyword": "ORDER-READY,ORDER-MISSING",
|
|
|
|
|
"OrderNoFeedback": "已输入 2 个订单号;当前筛选命中 1 个订单、1 条商品明细。 当前筛选未命中:ORDER-MISSING",
|
2026-08-10 16:33:42 +08:00
|
|
|
}
|
|
|
|
|
if err := tmpl.ExecuteTemplate(&output, "syb/list", data); err != nil {
|
|
|
|
|
t.Fatalf("渲染顺运宝列表失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
body := output.String()
|
|
|
|
|
for _, want := range []string{
|
|
|
|
|
`class="table-title-action" data-detail-open-id="SYB-READY"`,
|
|
|
|
|
`data-image-preview-url="https://example.test/original.jpg"`,
|
|
|
|
|
`aria-label="查看 可采购商品 的原图"`,
|
|
|
|
|
`id="image-preview-modal"`,
|
2026-08-10 16:34:55 +08:00
|
|
|
`data-purchase-id="SYB-READY"`,
|
2026-08-12 14:43:18 +08:00
|
|
|
`data-purchase-quantity="2"`,
|
|
|
|
|
`for="purchase-unit-price-SYB-READY"`,
|
|
|
|
|
`name="unit_price_limit_yuan"`,
|
|
|
|
|
`订单总价上限:`,
|
|
|
|
|
`¥23.60`,
|
2026-08-10 16:34:55 +08:00
|
|
|
`href="/tasks?type=purchase`,
|
|
|
|
|
`q=ORDER-TASK"`,
|
2026-08-11 18:05:27 +08:00
|
|
|
`q=ORDER-DONE"`,
|
|
|
|
|
`q=ORDER-REVIEW"`,
|
2026-08-10 16:34:55 +08:00
|
|
|
`data-detail-open-id="SYB-PDD"`,
|
2026-08-11 14:59:26 +08:00
|
|
|
`action="/syb/collect-pdd-batch"`,
|
|
|
|
|
`data-need-checked="collect"`,
|
|
|
|
|
`data-need-checked="purchase"`,
|
2026-08-14 10:30:08 +08:00
|
|
|
`data-ai-match-open`,
|
2026-08-11 14:59:26 +08:00
|
|
|
`data-collect-action="collect"`,
|
|
|
|
|
`data-select-action="collect"`,
|
2026-08-14 10:30:08 +08:00
|
|
|
`data-select-action="purchase ai"`,
|
2026-08-11 14:59:26 +08:00
|
|
|
`form="syb-collect-form"`,
|
|
|
|
|
`id="syb-collect-modal"`,
|
2026-08-13 15:28:45 +08:00
|
|
|
`aria-label="创建采集任务">创建采集</button>`,
|
|
|
|
|
`aria-label="创建采购任务">创建采购</button>`,
|
2026-08-13 14:21:37 +08:00
|
|
|
`<h2 id="syb-collect-modal-title">创建采集任务</h2>`,
|
2026-08-13 15:28:45 +08:00
|
|
|
`<h2 id="purchase-modal-title">确认创建采购任务</h2>`,
|
|
|
|
|
`class="toolbar syb-toolbar"`,
|
|
|
|
|
`class="visually-hidden" for="sync-date-from">开始日期</label>`,
|
|
|
|
|
`class="syb-date-separator" aria-hidden="true">-</span>`,
|
|
|
|
|
`class="visually-hidden" for="sync-date-to">结束日期</label>`,
|
|
|
|
|
`class="inline grow syb-filter-form"`,
|
2026-08-13 14:21:37 +08:00
|
|
|
`class="syb-search-input"`,
|
2026-08-13 14:58:19 +08:00
|
|
|
`订单号(可多条)`,
|
|
|
|
|
`textarea id="q" name="order_no" rows="1"`,
|
|
|
|
|
`data-syb-order-search`,
|
|
|
|
|
`maxlength="4000"`,
|
|
|
|
|
`支持中英文逗号、分号、空格、Tab 或换行分隔,最多 100 个订单号`,
|
|
|
|
|
`role="status">已输入 2 个订单号`,
|
|
|
|
|
`当前筛选未命中:ORDER-MISSING`,
|
2026-08-13 14:21:37 +08:00
|
|
|
`href="/syb?date_from=2026-08-12`,
|
|
|
|
|
`date_to=2026-08-13">清除</a>`,
|
|
|
|
|
`class="table-wrap syb-table-wrap"`,
|
2026-08-13 14:35:34 +08:00
|
|
|
`class="syb-page-body"`,
|
2026-08-13 14:21:37 +08:00
|
|
|
`<th class="syb-col-shop">店铺</th>`,
|
|
|
|
|
`<th class="syb-col-spec">规格</th>`,
|
|
|
|
|
`value="SYB-READY" name="ids"`,
|
2026-08-11 14:59:26 +08:00
|
|
|
`同一 PDD 商品关联多条货运单时只创建一条任务`,
|
2026-08-13 14:35:34 +08:00
|
|
|
`>核对采购订单</a>`,
|
|
|
|
|
`title="禁止重新采购"`,
|
2026-08-11 14:59:26 +08:00
|
|
|
`title="当前阶段不能批量操作:下一步关联采购商品"`,
|
2026-08-10 16:33:42 +08:00
|
|
|
} {
|
|
|
|
|
if !strings.Contains(body, want) {
|
|
|
|
|
t.Errorf("顺运宝列表缺少 %q", want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(body, `<tr data-detail-id=`) {
|
|
|
|
|
t.Error("顺运宝列表不应继续使用含义模糊的整行双击入口")
|
|
|
|
|
}
|
2026-08-13 14:21:37 +08:00
|
|
|
for _, removed := range []string{
|
|
|
|
|
`<th>货运单 ID</th>`,
|
2026-08-13 14:35:34 +08:00
|
|
|
`<th>处理阶段</th>`,
|
2026-08-13 14:21:37 +08:00
|
|
|
`<td>SYB-READY</td>`,
|
|
|
|
|
`>创建 PDD 采集任务</button>`,
|
|
|
|
|
`<h2 id="syb-collect-modal-title">创建 PDD 采集任务</h2>`,
|
2026-08-13 15:28:45 +08:00
|
|
|
`data-need-checked="collect">创建采集任务</button>`,
|
|
|
|
|
`data-purchase-open>创建采购任务</button>`,
|
2026-08-13 14:21:37 +08:00
|
|
|
`每行只显示当前最需要完成的下一步`,
|
2026-08-13 14:35:34 +08:00
|
|
|
`按顺运宝货运单创建日期(UTC+8)同步`,
|
2026-08-13 14:21:37 +08:00
|
|
|
} {
|
|
|
|
|
if strings.Contains(body, removed) {
|
|
|
|
|
t.Errorf("顺运宝列表仍包含已移除内容 %q", removed)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-10 16:33:42 +08:00
|
|
|
}
|
2026-08-12 09:56:15 +08:00
|
|
|
|
2026-08-13 14:58:19 +08:00
|
|
|
func TestSybListTemplate_订单号错误就地显示(t *testing.T) {
|
|
|
|
|
tmpl, err := template.ParseFS(templateFS, "templates/*/*.html")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
data := map[string]any{
|
|
|
|
|
"Title": "顺运宝数据", "Active": "syb", "CSRFToken": "test-csrf",
|
|
|
|
|
"OrderNoError": "一次最多搜索 100 个不同订单号",
|
|
|
|
|
"Keyword": "ORDER-001",
|
|
|
|
|
}
|
|
|
|
|
if err := tmpl.ExecuteTemplate(&output, "syb/list", data); err != nil {
|
|
|
|
|
t.Fatalf("渲染顺运宝订单号错误失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
body := output.String()
|
|
|
|
|
for _, want := range []string{
|
|
|
|
|
`aria-invalid="true"`, `aria-describedby="order-no-error"`,
|
|
|
|
|
`id="order-no-error"`, `role="alert">一次最多搜索 100 个不同订单号`,
|
|
|
|
|
} {
|
|
|
|
|
if !strings.Contains(body, want) {
|
|
|
|
|
t.Errorf("订单号错误提示缺少 %q", want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-14 17:45:45 +08:00
|
|
|
func TestSybListTemplate_AI匹配失败自动弹窗且转义(t *testing.T) {
|
|
|
|
|
tmpl, err := template.ParseFS(templateFS, "templates/*/*.html")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
data := map[string]any{
|
|
|
|
|
"Title": "顺运宝数据", "Active": "syb", "CSRFToken": "test-csrf",
|
|
|
|
|
"AIMatchError": `<script>alert("secret")</script>`,
|
|
|
|
|
}
|
|
|
|
|
if err := tmpl.ExecuteTemplate(&output, "syb/list", data); err != nil {
|
|
|
|
|
t.Fatalf("渲染 AI 匹配错误弹窗失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
body := output.String()
|
|
|
|
|
for _, want := range []string{
|
|
|
|
|
`id="syb-ai-match-error-modal" data-auto-open-modal hidden`,
|
|
|
|
|
`role="alertdialog"`,
|
|
|
|
|
`id="syb-ai-match-error-message"`,
|
|
|
|
|
`<script>alert("secret")</script>`,
|
|
|
|
|
`规格映射和采购任务都没有被改动`,
|
|
|
|
|
} {
|
|
|
|
|
if !strings.Contains(body, want) {
|
|
|
|
|
t.Errorf("AI 匹配错误弹窗缺少 %q", want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(body, `<script>alert("secret")</script>`) {
|
|
|
|
|
t.Fatal("AI 匹配错误未经 html/template 转义")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-12 09:56:15 +08:00
|
|
|
func TestSybDetailTemplate_单条采集可选执行客户端(t *testing.T) {
|
|
|
|
|
tmpl, err := template.ParseFS(templateFS, "templates/*/*.html")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
data := map[string]any{
|
|
|
|
|
"D": &service.SybProcessingDetail{
|
|
|
|
|
SybID: "SYB-1", OrderNo: "ORDER-1", Title: "待采集商品",
|
|
|
|
|
PddGoodsID: "737116531267", CanCollect: true,
|
|
|
|
|
},
|
|
|
|
|
"CSRFToken": "test-csrf",
|
|
|
|
|
"AssignableClients": []service.ClientView{{
|
|
|
|
|
Client: model.Client{ClientID: "CLIENT-1", Name: "仓库手机"}, Status: "在线",
|
|
|
|
|
}},
|
|
|
|
|
}
|
|
|
|
|
var output bytes.Buffer
|
|
|
|
|
if err := tmpl.ExecuteTemplate(&output, "syb/detail_modal", data); err != nil {
|
|
|
|
|
t.Fatalf("渲染顺运宝详情失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
body := output.String()
|
|
|
|
|
for _, want := range []string{
|
|
|
|
|
`action="/syb/collect-pdd"`,
|
|
|
|
|
`label for="syb-detail-collect-client"`,
|
|
|
|
|
`select id="syb-detail-collect-client" name="client_id"`,
|
|
|
|
|
`option value="">不指定(任意客户端可领取)`,
|
|
|
|
|
`option value="CLIENT-1">仓库手机(CLIENT-1,在线)`,
|
|
|
|
|
`指定后任务只等待该客户端领取`,
|
|
|
|
|
} {
|
|
|
|
|
if !strings.Contains(body, want) {
|
|
|
|
|
t.Errorf("顺运宝详情缺少 %q", want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|