feat(media-supply): resource cache sync and order flow refinements
Add a resource cache sync entrypoint (RunMediaResourceSyncOnce) driven by the new scheduler job, and rework the backlink/sync worker around it. Tune the meijiequan client and slow the default backlink sync interval from 10m to 30m to reduce upstream pressure. Trim unused public/transport surface (handler + router + swagger) and simplify admin-web order management and ops-web media-supply views, dropping stale shared-types fields.
This commit is contained in:
@@ -104,6 +104,28 @@ func TestMediaSupplySellPriceAppliesMarkupBeforeRoundUp(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaSupplyOverrideBelowCostUsesSyncedCostPrices(t *testing.T) {
|
||||
resource := SupplierMediaResource{
|
||||
CostPriceCents: 1000,
|
||||
CostPrices: map[string]int64{
|
||||
"price": 1500,
|
||||
"priceyc": 2200,
|
||||
},
|
||||
}
|
||||
if !mediaSupplyOverrideBelowCost(resource, "price", 1400, true) {
|
||||
t.Fatal("expected default price override below synced price cost to be cleared")
|
||||
}
|
||||
if mediaSupplyOverrideBelowCost(resource, "price", 1500, true) {
|
||||
t.Fatal("expected override equal to synced price cost to stay")
|
||||
}
|
||||
if !mediaSupplyOverrideBelowCost(resource, "priceyc", 2100, true) {
|
||||
t.Fatal("expected priceyc override below synced priceyc cost to be cleared")
|
||||
}
|
||||
if mediaSupplyOverrideBelowCost(resource, "priceyc", 2100, false) {
|
||||
t.Fatal("disabled override should not be treated as active loss risk")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaSupplyResourceOrderSQL(t *testing.T) {
|
||||
priceSQL := "sell_price_expr"
|
||||
if got := mediaSupplyResourceOrderSQL("price", priceSQL); got != "(sell_price_expr) ASC, r.id DESC" {
|
||||
@@ -149,9 +171,23 @@ func TestRetryableMediaSupplyErrorTreatsBusinessFailuresAsTerminal(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMediaSupplyRefreshFailureMessageIsActionable(t *testing.T) {
|
||||
got := mediaSupplyPublicErrorMessage(errMediaSupplySelectedResourcesMissing)
|
||||
if got != "媒体资源暂不可用或已下架,请重新选择媒体" {
|
||||
t.Fatalf("unexpected refresh failure message: %s", got)
|
||||
}
|
||||
|
||||
got = mediaSupplyPublicGenericErrorMessage("failed to refresh supplier price for 1 selected resources")
|
||||
if got != "媒体资源暂不可用或已下架,请重新选择媒体" {
|
||||
t.Fatalf("unexpected generic refresh failure message: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanMediaSupplyOrderRedactsPublicFields(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
errorMessage := "failed to refresh supplier price for 1 selected resources"
|
||||
externalOrderID := "U6210"
|
||||
externalOrderCode := "U6210"
|
||||
item, err := scanMediaSupplyOrder(mediaSupplyOrderScanStub{values: []any{
|
||||
int64(1),
|
||||
int64(2),
|
||||
@@ -165,8 +201,8 @@ func TestScanMediaSupplyOrderRedactsPublicFields(t *testing.T) {
|
||||
"测试标题",
|
||||
(*string)(nil),
|
||||
(*string)(nil),
|
||||
(*string)(nil),
|
||||
(*string)(nil),
|
||||
&externalOrderID,
|
||||
&externalOrderCode,
|
||||
(*string)(nil),
|
||||
int64(100),
|
||||
int64(120),
|
||||
@@ -187,9 +223,15 @@ func TestScanMediaSupplyOrderRedactsPublicFields(t *testing.T) {
|
||||
if strings.Contains(strings.ToLower(item.Supplier), "meijiequan") {
|
||||
t.Fatalf("supplier was not redacted: %s", item.Supplier)
|
||||
}
|
||||
if item.ErrorMessage == nil || *item.ErrorMessage != "媒体资源价格刷新失败,请稍后重试或重新选择媒体" {
|
||||
if item.ErrorMessage == nil || *item.ErrorMessage != "媒体资源暂不可用或已下架,请重新选择媒体" {
|
||||
t.Fatalf("unexpected public error message: %#v", item.ErrorMessage)
|
||||
}
|
||||
if item.ExternalOrderID != nil {
|
||||
t.Fatalf("external order id should not be exposed: %#v", item.ExternalOrderID)
|
||||
}
|
||||
if item.ExternalOrderCode != nil {
|
||||
t.Fatalf("invalid external order code should not be exposed: %#v", item.ExternalOrderCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeijiequanCreateOrderFormMatchesUpstreamShape(t *testing.T) {
|
||||
@@ -471,6 +513,7 @@ func TestDecodeMeijiequanAllArticleHTMLKeepsStatusAndRejectReasonColumns(t *test
|
||||
if problem.OrderCode != "WM119065" ||
|
||||
problem.Title != "合肥全屋定制怎么选?5 大主流品牌优缺点对比 + 选购建议" ||
|
||||
problem.ResourceName != "博客园(GEO排名可发)" ||
|
||||
problem.PriceCents != 300 ||
|
||||
problem.Status != "退稿申请中" ||
|
||||
problem.RejectReason != "不想发布了" {
|
||||
t.Fatalf("unexpected problem article: %#v", problem)
|
||||
@@ -478,6 +521,7 @@ func TestDecodeMeijiequanAllArticleHTMLKeepsStatusAndRejectReasonColumns(t *test
|
||||
published := page.Items[1]
|
||||
if published.OrderCode != "WM118996" ||
|
||||
published.ResourceName != "金融树洞网" ||
|
||||
published.PriceCents != 300 ||
|
||||
published.Status != "已发布" ||
|
||||
published.ExternalArticleURL != "http://jrsd.huaihuarexian.cn/caijing/11221014.html" {
|
||||
t.Fatalf("unexpected published article: %#v", published)
|
||||
@@ -507,14 +551,15 @@ func TestDecodeMeijiequanPublishedArticleJSONRejectsShowURL(t *testing.T) {
|
||||
|
||||
func TestMatchMeijiequanPublishedArticlesPrefersResourceID(t *testing.T) {
|
||||
items := []MediaSupplyOrderItem{
|
||||
{ID: 11, SupplierResourceID: "36122", ResourceNameSnapshot: "测试新闻网"},
|
||||
{ID: 12, SupplierResourceID: "36123", ResourceNameSnapshot: "另一个媒体"},
|
||||
{ID: 11, SupplierResourceID: "36122", ResourceNameSnapshot: "测试新闻网", LockedCostPriceCents: 300, LockedSellPriceCents: 500},
|
||||
{ID: 12, SupplierResourceID: "36123", ResourceNameSnapshot: "另一个媒体", LockedCostPriceCents: 300, LockedSellPriceCents: 500},
|
||||
}
|
||||
matches := matchMeijiequanPublishedArticles(mediaSupplySubmitOrder{Title: "2026合肥品牌推荐"}, items, []MeijiequanPublishedArticle{
|
||||
{
|
||||
Title: "2026合肥品牌推荐",
|
||||
ResourceID: "36123_price_0",
|
||||
ResourceName: "另一个媒体",
|
||||
PriceCents: 300,
|
||||
ExternalArticleURL: "https://news.example.test/b.html",
|
||||
},
|
||||
})
|
||||
@@ -526,6 +571,76 @@ func TestMatchMeijiequanPublishedArticlesPrefersResourceID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchMeijiequanOrderCodeArticleUsesTitleResourceAndPriceFallback(t *testing.T) {
|
||||
items := []MediaSupplyOrderItem{{
|
||||
ID: 11,
|
||||
SupplierResourceID: "36122",
|
||||
ResourceNameSnapshot: "博客园(GEO排名可发)",
|
||||
LockedCostPriceCents: 300,
|
||||
LockedSellPriceCents: 900,
|
||||
}}
|
||||
article := matchMeijiequanOrderCodeArticle(
|
||||
mediaSupplySubmitOrder{Title: "合肥全屋定制怎么选"},
|
||||
items,
|
||||
[]MeijiequanPublishedArticle{
|
||||
{OrderCode: "WM120001", Title: "合肥全屋定制怎么选", ResourceName: "博客园(GEO排名可发)", PriceCents: 900},
|
||||
{OrderCode: "WM120002", Title: "合肥全屋定制怎么选", ResourceName: "博客园(GEO排名可发)", PriceCents: 300},
|
||||
},
|
||||
)
|
||||
if article == nil || article.OrderCode != "WM120002" {
|
||||
t.Fatalf("expected title/resource/price fallback to match WM120002, got %#v", article)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchMeijiequanOrderCodeArticleDoesNotFallbackWhenOrderCodeDiffers(t *testing.T) {
|
||||
orderCode := "WM120090"
|
||||
items := []MediaSupplyOrderItem{{
|
||||
ID: 11,
|
||||
SupplierResourceID: "36122",
|
||||
ResourceNameSnapshot: "博客园(GEO排名可发)",
|
||||
LockedCostPriceCents: 300,
|
||||
LockedSellPriceCents: 900,
|
||||
}}
|
||||
article := matchMeijiequanOrderCodeArticle(
|
||||
mediaSupplySubmitOrder{Title: "合肥全屋定制怎么选", ExternalOrderCode: &orderCode},
|
||||
items,
|
||||
[]MeijiequanPublishedArticle{{
|
||||
OrderCode: "WM120091",
|
||||
Title: "合肥全屋定制怎么选",
|
||||
ResourceName: "博客园(GEO排名可发)",
|
||||
PriceCents: 300,
|
||||
}},
|
||||
)
|
||||
if article != nil {
|
||||
t.Fatalf("article with different order code should not match: %#v", article)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchMeijiequanPublishedArticlesDoesNotFallbackWhenOrderCodeDiffers(t *testing.T) {
|
||||
orderCode := "WM120090"
|
||||
items := []MediaSupplyOrderItem{{
|
||||
ID: 11,
|
||||
SupplierResourceID: "36122",
|
||||
ResourceNameSnapshot: "博客园(GEO排名可发)",
|
||||
LockedCostPriceCents: 300,
|
||||
LockedSellPriceCents: 900,
|
||||
}}
|
||||
matches := matchMeijiequanPublishedArticles(
|
||||
mediaSupplySubmitOrder{Title: "合肥全屋定制怎么选", ExternalOrderCode: &orderCode},
|
||||
items,
|
||||
[]MeijiequanPublishedArticle{{
|
||||
OrderCode: "WM120091",
|
||||
Title: "合肥全屋定制怎么选",
|
||||
ResourceName: "博客园(GEO排名可发)",
|
||||
PriceCents: 300,
|
||||
ExternalArticleURL: "https://news.example.test/a.html",
|
||||
}},
|
||||
)
|
||||
if len(matches) != 0 {
|
||||
t.Fatalf("article with different order code should not match: %#v", matches)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchMeijiequanProblemArticleReturnsRejectReasonForCustomer(t *testing.T) {
|
||||
orderCode := "WM119065"
|
||||
article := matchMeijiequanProblemArticle(
|
||||
@@ -547,6 +662,43 @@ func TestMatchMeijiequanProblemArticleReturnsRejectReasonForCustomer(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchMeijiequanProblemArticleDoesNotFallbackWhenOrderCodeDiffers(t *testing.T) {
|
||||
orderCode := "WM119065"
|
||||
article := matchMeijiequanProblemArticle(
|
||||
mediaSupplySubmitOrder{Title: "合肥全屋定制怎么选", ExternalOrderCode: &orderCode},
|
||||
[]MediaSupplyOrderItem{{
|
||||
ID: 11,
|
||||
SupplierResourceID: "36122",
|
||||
ResourceNameSnapshot: "博客园(GEO排名可发)",
|
||||
LockedCostPriceCents: 300,
|
||||
}},
|
||||
[]MeijiequanPublishedArticle{{
|
||||
OrderCode: "WM119066",
|
||||
Title: "合肥全屋定制怎么选",
|
||||
ResourceName: "博客园(GEO排名可发)",
|
||||
PriceCents: 300,
|
||||
Status: "退稿申请中",
|
||||
RejectReason: "不想发布了",
|
||||
}},
|
||||
)
|
||||
if article != nil {
|
||||
t.Fatalf("problem article with different order code should not match: %#v", article)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeijiequanOrderCodeOnlyAcceptsWMOrderNumber(t *testing.T) {
|
||||
if isLikelyMeijiequanOrderCode("U6210") {
|
||||
t.Fatal("U6210 should not be treated as an external order number")
|
||||
}
|
||||
if got := firstMeijiequanOrderCode("user U6210 created WM120090"); got != "WM120090" {
|
||||
t.Fatalf("unexpected order code extraction: %s", got)
|
||||
}
|
||||
invalidCode := "U6210"
|
||||
if got := mediaSupplyOrderCodeValue(mediaSupplySubmitOrder{ExternalOrderCode: &invalidCode}); got != "" {
|
||||
t.Fatalf("invalid external order code should not be used for matching: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaskMeijiequanUsername(t *testing.T) {
|
||||
if got := maskMeijiequanUsername("17788409108"); got != "177******08" {
|
||||
t.Fatalf("unexpected masked username: %s", got)
|
||||
|
||||
Reference in New Issue
Block a user