2026-08-09 22:55:14 +08:00
package service
import (
"database/sql"
2026-08-10 14:26:03 +08:00
"errors"
2026-08-10 15:11:38 +08:00
"strings"
2026-08-09 22:55:14 +08:00
"testing"
"time"
"cmautobuy/admin/model"
"cmautobuy/admin/repository"
)
2026-08-10 14:26:03 +08:00
func TestAssociateSybPdd_兼容商品ID并复用已有采集结果 ( t * testing . T ) {
db := newTestDB ( t )
seedWorkflowOrder ( t , db , "SYB-PDD-ID" , "SP-PDD-ID" , "黑色,M" )
if _ , err := repository . EnsurePddProduct ( db , "737116531267" , pddURLA ); err != nil {
t . Fatal ( err )
}
if err := repository . SetCollectResult ( db , "737116531267" , "已采集商品" , "已有店铺" , collectedThreeDimensions ); err != nil {
t . Fatal ( err )
}
goodsID , err := AssociateSybPdd ( db , "SYB-PDD-ID" , " 737116531267 " , false )
if err != nil || goodsID != "737116531267" {
t . Fatalf ( "纯商品 ID 关联结果 goodsID=%q err=%v" , goodsID , err )
}
product , err := repository . GetPddProductByGoodsID ( db , goodsID )
if err != nil || product == nil {
t . Fatalf ( "读取复用商品失败: product=%+v err=%v" , product , err )
}
if product . URL != pddURLA || product . CollectStatus != model . CollectCollected ||
product . Title != "已采集商品" || product . SkusJSON != collectedThreeDimensions {
t . Fatalf ( "已有采集结果不应被清空或重置: %+v" , product )
}
var productCount , taskCount int
db . QueryRow ( `SELECT COUNT(*) FROM pdd_products WHERE goods_id = ?` , goodsID ). Scan ( & productCount )
db . QueryRow ( `SELECT COUNT(*) FROM tasks` ). Scan ( & taskCount )
if productCount != 1 || taskCount != 0 {
t . Fatalf ( "应复用一条商品且不自动建任务: products=%d tasks=%d" , productCount , taskCount )
}
}
func TestAssociateSybPdd_新商品ID生成标准链接且完整链接保持兼容 ( t * testing . T ) {
t . Run ( "纯商品ID" , func ( t * testing . T ) {
db := newTestDB ( t )
seedWorkflowOrder ( t , db , "SYB-NEW-ID" , "SP-NEW-ID" , "白色,L" )
goodsID , err := AssociateSybPdd ( db , "SYB-NEW-ID" , "123456789" , false )
if err != nil || goodsID != "123456789" {
t . Fatalf ( "关联结果 goodsID=%q err=%v" , goodsID , err )
}
product , _ := repository . GetPddProductByGoodsID ( db , goodsID )
if product == nil || product . URL != canonicalPddGoodsURL + goodsID || product . CollectStatus != model . CollectPending {
t . Fatalf ( "新商品 ID 没有生成待采集标准档案: %+v" , product )
}
})
t . Run ( "完整链接" , func ( t * testing . T ) {
db := newTestDB ( t )
seedWorkflowOrder ( t , db , "SYB-FULL-URL" , "SP-FULL-URL" , "白色,L" )
goodsID , err := AssociateSybPdd ( db , "SYB-FULL-URL" , pddURLA , false )
if err != nil || goodsID != "737116531267" {
t . Fatalf ( "原完整链接行为回归: goodsID=%q err=%v" , goodsID , err )
}
})
}
func TestAssociateSybPdd_商品ID输入边界和换品确认 ( t * testing . T ) {
for _ , input := range [] string { "12345" , "1234567890123456789012345" , "737116abc" , "普通文本" } {
t . Run ( input , func ( t * testing . T ) {
db := newTestDB ( t )
seedWorkflowOrder ( t , db , "SYB-BAD-ID" , "SP-BAD-ID" , "黑色,M" )
if _ , err := AssociateSybPdd ( db , "SYB-BAD-ID" , input , false ); ! errors . Is ( err , ErrBadPddURL ) {
t . Fatalf ( "输入 %q 应返回 ErrBadPddURL,实际 %v" , input , err )
}
})
}
db := newTestDB ( t )
seedWorkflowOrder ( t , db , "SYB-REPLACE" , "SP-REPLACE" , "黑色,M" )
if _ , err := AssociateSybPdd ( db , "SYB-REPLACE" , "737116531267" , false ); err != nil {
t . Fatal ( err )
}
if _ , err := AssociateSybPdd ( db , "SYB-REPLACE" , "937122477375" , false ); ! errors . Is ( err , ErrPddAssociationReplaceRequired ) {
t . Fatalf ( "纯 ID 换成其他商品仍应要求确认,实际 %v" , err )
}
if _ , err := AssociateSybPdd ( db , "SYB-REPLACE" , "937122477375" , true ); err != nil {
t . Fatalf ( "确认后应允许更换: %v" , err )
}
}
2026-08-09 22:55:14 +08:00
const collectedThreeDimensions = `{
"goods_id":"737116531267",
"price_granularity":"sku",
"dimensions":[{"key":"color","name":"颜色"},{"key":"size","name":"尺码"},{"key":"style","name":"款式"}],
"skus":[
{"options":{"style":"常规","size":"M码","color":"黑色"},"price_cent":3990,"available":true},
{"options":{"style":"加绒","size":"L码","color":"白色"},"price_cent":4590,"available":true},
{"options":{"style":"下架","size":"S码","color":"红色"},"price_cent":2990,"available":false}
]
}`
func seedPurchasableWorkflow ( t * testing . T , db * sql . DB , sybID string ) string {
t . Helper ()
order := seedWorkflowOrder ( t , db , sybID , "SP-1" , "黑色,M" )
order . PriceTwdCent = 999900
if _ , err := repository . UpsertSybOrder ( db , order ); err != nil {
t . Fatal ( err )
}
if _ , err := AssociateShopeePdd ( db , "SP-1" , pddURLA , false ); err != nil {
t . Fatal ( err )
}
if err := repository . SetCollectResult ( db , "737116531267" , "PDD 上衣" , "店铺" , collectedThreeDimensions ); err != nil {
t . Fatal ( err )
}
key , _ := OptionKey ( map [ string ] string { "color" : "黑色" , "size" : "M码" , "style" : "常规" })
return key
}
func TestSybMapping_动态维度保存并复用 ( t * testing . T ) {
db := newTestDB ( t )
key := seedPurchasableWorkflow ( t , db , "SYB-1" )
detail , err := GetSybProcessingDetail ( db , "SYB-1" )
if err != nil {
t . Fatal ( err )
}
if len ( detail . PddDimensionNames ) != 3 || detail . PddDimensionNames [ 2 ] != "款式" || len ( detail . PddChoices ) != 2 {
t . Fatalf ( "动态维度/可用组合不对: names=%v choices=%+v" , detail . PddDimensionNames , detail . PddChoices )
}
if err := SaveSybMapping ( db , "SYB-1" , key , "USR-1" ); err != nil {
t . Fatal ( err )
}
2026-08-10 02:21:31 +08:00
detail , err = GetSybProcessingDetail ( db , "SYB-1" )
if err != nil {
t . Fatal ( err )
}
2026-08-09 22:55:14 +08:00
if ! detail . MappingValid {
2026-08-10 12:24:23 +08:00
t . Fatal ( "保存后相同蝦皮商品规格 + 当前 PDD 商品应自动复用" )
2026-08-09 22:55:14 +08:00
}
2026-08-10 02:21:31 +08:00
list , err := ListSybOrdersView ( db , "" , SybStagePurchaseReady , 1 )
if err != nil {
t . Fatal ( err )
}
2026-08-09 22:55:14 +08:00
if list . Total != 1 || ! list . Rows [ 0 ]. CanPurchase || list . Rows [ 0 ]. DefaultMaxPrice != "39.90" {
t . Fatalf ( "可采购阶段不对: %+v" , list )
}
}
2026-08-10 12:24:23 +08:00
func TestSybMapping_同商品同规格第二条明细自动命中 ( t * testing . T ) {
db := newTestDB ( t )
key := seedPurchasableWorkflow ( t , db , "SYB-1" )
if err := SaveSybMapping ( db , "SYB-1" , key , "USR-1" ); err != nil {
t . Fatal ( err )
}
seedWorkflowOrder ( t , db , "SYB-2" , "SP-1" , " 黑色,M " )
detail , err := GetSybProcessingDetail ( db , "SYB-2" )
if err != nil || detail == nil || ! detail . MappingValid || detail . Stage != SybStagePurchaseReady {
t . Fatalf ( "第二条相同规格应自动命中: detail=%+v err=%v" , detail , err )
}
}
func TestSybMapping_解析失败原文仍可保存且空规格被阻断 ( t * testing . T ) {
db := newTestDB ( t )
key := seedPurchasableWorkflow ( t , db , "SYB-BASE" )
seedWorkflowOrder ( t , db , "SYB-DIRTY" , "SP-1" , "紅色,3XL建議80-90公斤】" )
if err := SaveSybMapping ( db , "SYB-DIRTY" , key , "USR-1" ); err != nil {
t . Fatalf ( "规格解析失败不应影响身份键映射: %v" , err )
}
seedWorkflowOrder ( t , db , "SYB-EMPTY" , "SP-1" , "" )
if err := SaveSybMapping ( db , "SYB-EMPTY" , key , "USR-1" ); err == nil {
t . Fatal ( "空规格不得保存映射" )
}
if _ , err := AssociateSybPdd ( db , "SYB-EMPTY" , pddURLA , false ); err == nil {
t . Fatal ( "空规格不得进入采购关联流程" )
}
}
2026-08-09 22:55:14 +08:00
func TestSybMapping_换品和选项消失都会失效 ( t * testing . T ) {
t . Run ( "换 PDD 商品隔离旧映射" , func ( t * testing . T ) {
db := newTestDB ( t )
key := seedPurchasableWorkflow ( t , db , "SYB-1" )
SaveSybMapping ( db , "SYB-1" , key , "USR-1" )
if _ , err := AssociateShopeePdd ( db , "SP-1" , pddURLB , true ); err != nil {
t . Fatal ( err )
}
repository . SetCollectResult ( db , "937122477375" , "另一个商品" , "店铺" , stringsReplaceGoodsID ( collectedThreeDimensions ))
detail , _ := GetSybProcessingDetail ( db , "SYB-1" )
if detail . MappingValid {
t . Fatal ( "更换 PDD 商品后不得复用旧映射" )
}
2026-08-10 12:24:23 +08:00
if _ , err := AssociateShopeePdd ( db , "SP-1" , pddURLA , true ); err != nil {
t . Fatal ( err )
}
detail , _ = GetSybProcessingDetail ( db , "SYB-1" )
if ! detail . MappingValid {
t . Fatal ( "换回原 PDD 商品后应复用原映射" )
}
2026-08-09 22:55:14 +08:00
})
t . Run ( "最新采集结果删除原选项" , func ( t * testing . T ) {
db := newTestDB ( t )
key := seedPurchasableWorkflow ( t , db , "SYB-1" )
SaveSybMapping ( db , "SYB-1" , key , "USR-1" )
changed := `{"goods_id":"737116531267","dimensions":[{"key":"color","name":"颜色"}],"skus":[{"options":{"color":"白色"},"price_cent":1000,"available":true}]}`
repository . SetCollectResult ( db , "737116531267" , "PDD 上衣" , "店铺" , changed )
detail , _ := GetSybProcessingDetail ( db , "SYB-1" )
if detail . MappingValid || detail . Stage != SybStageMappingPending {
t . Fatalf ( "失效映射应回到待匹配: valid=%t stage=%s" , detail . MappingValid , detail . Stage )
}
})
}
2026-08-10 12:41:17 +08:00
func TestSybMapping_建议审计追加陈旧保护与事务回滚 ( t * testing . T ) {
db := newTestDB ( t )
order := seedWorkflowOrder ( t , db , "SYB-AUDIT" , "SP-AUDIT" , "灰色,L建議53-57公斤" )
if _ , err := AssociateShopeePdd ( db , "SP-AUDIT" , pddURLA , false ); err != nil {
t . Fatal ( err )
}
raw := `{"goods_id":"737116531267","dimensions":[{"key":"color","name":"颜色分类"},{"key":"size","name":"尺码"}],"skus":[{"options":{"color":"灰色中长款","size":"L( 80-115斤)"},"price_cent":3990,"available":true},{"options":{"color":"黑色","size":"L( 80-115斤)"},"price_cent":3990,"available":true}]}`
if err := repository . SetCollectResult ( db , "737116531267" , "PDD" , "" , raw ); err != nil {
t . Fatal ( err )
}
detail , err := GetSybProcessingDetail ( db , order . SybID )
if err != nil {
t . Fatal ( err )
}
var suggested , other string
for _ , c := range detail . PddChoices {
if c . Recommended {
suggested = c . Key
} else {
other = c . Key
}
}
if suggested == "" || detail . ContextVersion == "" {
t . Fatalf ( "无建议: %+v" , detail )
}
var unopenedMappings , unopenedDecisions int
db . QueryRow ( `SELECT COUNT(*) FROM spec_mappings WHERE shopee_goods_id='SP-AUDIT'` ). Scan ( & unopenedMappings )
db . QueryRow ( `SELECT COUNT(*) FROM spec_mapping_decisions WHERE shopee_goods_id='SP-AUDIT'` ). Scan ( & unopenedDecisions )
if unopenedMappings != 0 || unopenedDecisions != 0 {
t . Fatal ( "只打开或关闭弹窗不得写库" )
}
if err := SaveSybMapping ( db , order . SybID , other , "USR" , detail . ContextVersion ); err != nil {
t . Fatal ( err )
}
if err := SaveSybMapping ( db , order . SybID , suggested , "USR" , mappingContextVersionMust ( t , db , order . SybID )); err != nil {
t . Fatal ( err )
}
var total , accepted int
db . QueryRow ( `SELECT COUNT(*),SUM(accepted) FROM spec_mapping_decisions WHERE shopee_goods_id='SP-AUDIT'` ). Scan ( & total , & accepted )
if total != 2 || accepted != 1 {
t . Fatalf ( "审计 total=%d accepted=%d" , total , accepted )
}
old := mappingContextVersionMust ( t , db , order . SybID )
if err := repository . SetCollectResult ( db , "737116531267" , "PDD2" , "" , raw + " " ); err != nil {
t . Fatal ( err )
}
if err := SaveSybMapping ( db , order . SybID , suggested , "USR" , old ); err == nil {
t . Fatal ( "陈旧弹窗必须拒绝" )
}
before := suggested
mustExecService ( t , db , `DROP TABLE spec_mapping_decisions` )
if err := SaveSybMapping ( db , order . SybID , other , "USR" , mappingContextVersionMust ( t , db , order . SybID )); err == nil {
t . Fatal ( "审计失败必须回滚" )
}
var current string
db . QueryRow ( `SELECT pdd_option_key FROM spec_mappings WHERE shopee_goods_id='SP-AUDIT'` ). Scan ( & current )
if current != before {
t . Fatal ( "映射未回滚" )
}
}
func mappingContextVersionMust ( t * testing . T , db * sql . DB , id string ) string {
t . Helper ()
c , e := repository . GetSybOrderContext ( db , id )
if e != nil {
t . Fatal ( e )
}
return mappingContextVersion ( * c )
}
func mustExecService ( t * testing . T , db * sql . DB , q string ) {
t . Helper ()
if _ , e := db . Exec ( q ); e != nil {
t . Fatal ( e )
}
}
2026-08-09 22:55:14 +08:00
func stringsReplaceGoodsID ( raw string ) string {
return `{"goods_id":"937122477375","price_granularity":"sku","dimensions":[{"key":"color","name":"颜色"},{"key":"size","name":"尺码"},{"key":"style","name":"款式"}],"skus":[{"options":{"style":"常规","size":"M码","color":"黑色"},"price_cent":3990,"available":true}]}`
}
func TestCreatePurchaseTasks_安全字段和重复保护 ( t * testing . T ) {
db := newTestDB ( t )
key := seedPurchasableWorkflow ( t , db , "SYB-1" )
if err := SaveSybMapping ( db , "SYB-1" , key , "USR-1" ); err != nil {
t . Fatal ( err )
}
admin , _ , _ := prepareClientAssignmentUsers ( t , db )
if err := RegisterClient ( db , model . Client { ClientID : "CLIENT-1" , Name : "采购机" }, true ); err != nil {
t . Fatal ( err )
}
result , err := CreatePurchaseTasks ( db , admin ,
[] PurchaseTaskRequest {{ SybID : "SYB-1" , MaxPriceCent : 4200 }}, "CLIENT-1" )
if err != nil || result . Created != 1 || len ( result . Failures ) != 0 {
t . Fatalf ( "创建结果=%+v err=%v" , result , err )
}
var task model . Task
var status string
err = db . QueryRow ( `SELECT status, assigned_client, syb_id, order_no, goods_id,
shopee_sku_id, pdd_goods_url, pdd_goods_id, pdd_options, quantity, max_price_cent
FROM tasks WHERE task_type='purchase' AND syb_id='SYB-1'` ). Scan (
& status , & task . AssignedClient , & task . SybID , & task . OrderNo , & task . GoodsID ,
& task . ShopeeSKUID , & task . PddGoodsURL , & task . PddGoodsID , & task . PddOptions ,
& task . Quantity , & task . MaxPriceCent )
if err != nil {
t . Fatal ( err )
}
if status != "assigned" || task . MaxPriceCent != 4200 || task . MaxPriceCent == 999900 ||
task . Quantity != 1 || task . PddOptions != key || task . AssignedClient != "CLIENT-1" {
t . Fatalf ( "采购任务字段不安全或不完整: status=%s task=%+v" , status , task )
}
claimed , err := repository . ClaimNextTask ( db , "CLIENT-1" , [] string { "purchase" })
if err != nil || claimed == nil || claimed . PddOptions != key ||
claimed . Quantity != 1 || claimed . MaxPriceCent != 4200 {
t . Fatalf ( "现有 Client 领取契约读不到完整采购参数: task=%+v err=%v" , claimed , err )
}
second , err := CreatePurchaseTasks ( db , admin ,
[] PurchaseTaskRequest {{ SybID : "SYB-1" , MaxPriceCent : 4300 }}, "CLIENT-1" )
if err != nil || second . Created != 0 || len ( second . Failures ) != 1 {
t . Fatalf ( "重复提交应逐条拒绝: result=%+v err=%v" , second , err )
}
}
func TestCreatePurchaseTasks_客户端权限和逐条失败 ( t * testing . T ) {
db := newTestDB ( t )
key := seedPurchasableWorkflow ( t , db , "SYB-1" )
SaveSybMapping ( db , "SYB-1" , key , "USR-1" )
admin , buyerA , buyerB := prepareClientAssignmentUsers ( t , db )
RegisterClient ( db , model . Client { ClientID : "CLIENT-A" }, true )
AssignClient ( db , admin , "CLIENT-A" , buyerA . UserID , time . Now ())
if _ , err := CreatePurchaseTasks ( db , buyerB ,
[] PurchaseTaskRequest {{ SybID : "SYB-1" , MaxPriceCent : 4200 }}, "CLIENT-A" ); err == nil {
t . Fatal ( "采购员不得使用其他采购员的客户端" )
}
result , err := CreatePurchaseTasks ( db , buyerA ,
[] PurchaseTaskRequest {{ SybID : "SYB-1" , MaxPriceCent : 0 }, { SybID : "NOT-FOUND" , MaxPriceCent : 100 }}, "CLIENT-A" )
if err != nil || result . Created != 0 || len ( result . Failures ) != 2 {
t . Fatalf ( "业务失败应逐条返回且不创建: result=%+v err=%v" , result , err )
}
}
func TestCreatePurchaseTasks_部分业务失败不影响有效明细 ( t * testing . T ) {
db := newTestDB ( t )
key := seedPurchasableWorkflow ( t , db , "SYB-1" )
SaveSybMapping ( db , "SYB-1" , key , "USR-1" )
admin , _ , _ := prepareClientAssignmentUsers ( t , db )
RegisterClient ( db , model . Client { ClientID : "CLIENT-1" }, true )
result , err := CreatePurchaseTasks ( db , admin , [] PurchaseTaskRequest {
{ SybID : "NOT-FOUND" , MaxPriceCent : 100 },
{ SybID : "SYB-1" , MaxPriceCent : 4200 },
}, "CLIENT-1" )
if err != nil || result . Created != 1 || len ( result . Failures ) != 1 || result . Failures [ 0 ]. SybID != "NOT-FOUND" {
t . Fatalf ( "部分失败边界不对: result=%+v err=%v" , result , err )
}
}
2026-08-10 15:11:38 +08:00
func TestCreatePurchaseTasksWithOptions_正常采购员可创建真实任务 ( t * testing . T ) {
db := newTestDB ( t )
key := seedPurchasableWorkflow ( t , db , "SYB-LIVE" )
SaveSybMapping ( db , "SYB-LIVE" , key , "USR-1" )
admin , buyer , _ := prepareClientAssignmentUsers ( t , db )
if err := RegisterClient ( db , model . Client { ClientID : "CLIENT-LIVE" , Capabilities : `{"purchase_mode":"live"}` }, true ); err != nil {
t . Fatal ( err )
}
if _ , _ , err := AssignClient ( db , admin , "CLIENT-LIVE" , buyer . UserID , time . Now ()); err != nil {
t . Fatal ( err )
}
result , err := CreatePurchaseTasksWithOptions ( db , buyer ,
[] PurchaseTaskRequest {{ SybID : "SYB-LIVE" , MaxPriceCent : 4200 }},
PurchaseTaskOptions { ClientID : "CLIENT-LIVE" , ExecutionMode : model . TaskExecutionLive ,
LiveAcknowledged : true , LiveConfirmation : LivePurchaseConfirmation })
if err != nil || result . Created != 1 {
t . Fatalf ( "正常采购员创建真实任务失败: result=%+v err=%v" , result , err )
}
var mode , confirmedBy string
var confirmedAt sql . NullString
if err := db . QueryRow ( `SELECT execution_mode,live_confirmed_by,live_confirmed_at FROM tasks WHERE syb_id='SYB-LIVE'` ). Scan ( & mode , & confirmedBy , & confirmedAt ); err != nil {
t . Fatal ( err )
}
if mode != "live" || confirmedBy != buyer . UserID || ! confirmedAt . Valid {
t . Fatalf ( "真实任务审计不完整: mode=%s by=%s at=%+v" , mode , confirmedBy , confirmedAt )
}
}
func TestCreatePurchaseTasksWithOptions_真实模式安全门禁 ( t * testing . T ) {
db := newTestDB ( t )
key := seedPurchasableWorkflow ( t , db , "SYB-GATE" )
SaveSybMapping ( db , "SYB-GATE" , key , "USR-1" )
admin , _ , _ := prepareClientAssignmentUsers ( t , db )
RegisterClient ( db , model . Client { ClientID : "CLIENT-DRY" , Capabilities : `{"purchase_mode":"dry_run"}` }, true )
request := [] PurchaseTaskRequest {{ SybID : "SYB-GATE" , MaxPriceCent : 4200 }}
if _ , err := CreatePurchaseTasksWithOptions ( db , admin , request , PurchaseTaskOptions {
ClientID : "CLIENT-DRY" , ExecutionMode : model . TaskExecutionLive ,
LiveAcknowledged : true , LiveConfirmation : LivePurchaseConfirmation ,
}); err == nil || ! strings . Contains ( err . Error (), "未声明 live" ) {
t . Fatalf ( "dry_run 客户端必须被拒绝: %v" , err )
}
if _ , err := CreatePurchaseTasksWithOptions ( db , admin , request , PurchaseTaskOptions {
ClientID : "CLIENT-DRY" , ExecutionMode : model . TaskExecutionLive ,
LiveAcknowledged : true , LiveConfirmation : "错误短语" ,
}); err == nil || ! strings . Contains ( err . Error (), "必须勾选" ) {
t . Fatalf ( "错误确认短语必须被拒绝: %v" , err )
}
disabled := * admin
disabled . Status = model . UserDisabled
if _ , err := CreatePurchaseTasks ( db , & disabled , request , "CLIENT-DRY" ); err == nil || ! strings . Contains ( err . Error (), "不是正常状态" ) {
t . Fatalf ( "禁用账号必须被拒绝: %v" , err )
}
}
2026-08-09 22:55:14 +08:00
func TestParsePriceYuanToCent_精确转换 ( t * testing . T ) {
for raw , want := range map [ string ] int64 { "39.90" : 3990 , "1" : 100 , "0.01" : 1 , "12.3" : 1230 } {
got , err := ParsePriceYuanToCent ( raw )
if err != nil || got != want {
t . Fatalf ( "%s => %d, %v; want %d" , raw , got , err , want )
}
}
for _ , raw := range [] string { "" , "0" , "-1" , "1.234" , "abc" } {
if _ , err := ParsePriceYuanToCent ( raw ); err == nil {
t . Fatalf ( "应拒绝 %q" , raw )
}
}
}