后端: - StatsOverview/StatsDailyItem 新增 cost/profit 字段 - 新增 calcPaidByPriceDraw 三路收入分类(抽奖/对对碰/一番赏) - 新增 calcCostByInventory 成本计算(含道具卡倍数) - 修复成本统计未过滤 source_type 导致直播间免费发奖资产被错误计入 - remark.go 新增 PkgID 解析支持一番赏订单 前端: - 渠道统计弹窗新增"总成本"和"盈亏"卡片 - 趋势图新增"盈亏分析"Tab Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
172 lines
4.6 KiB
Go
172 lines
4.6 KiB
Go
package remark
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestParse_Lottery(t *testing.T) {
|
|
// 抽奖订单: lottery:activity:82|issue:89|count:1|slots:15:1
|
|
rm := Parse("lottery:activity:82|issue:89|count:1|slots:15:1")
|
|
if rm.ActivityID != 82 {
|
|
t.Errorf("ActivityID = %d, want 82", rm.ActivityID)
|
|
}
|
|
if rm.IssueID != 89 {
|
|
t.Errorf("IssueID = %d, want 89", rm.IssueID)
|
|
}
|
|
if rm.Count != 1 {
|
|
t.Errorf("Count = %d, want 1", rm.Count)
|
|
}
|
|
if rm.PkgID != 0 {
|
|
t.Errorf("PkgID = %d, want 0", rm.PkgID)
|
|
}
|
|
if len(rm.Slots) != 1 || rm.Slots[0].SlotIndex != 15 {
|
|
t.Errorf("Slots = %+v, want [{SlotIndex:15 Count:1}]", rm.Slots)
|
|
}
|
|
}
|
|
|
|
func TestParse_MatchingGamePaid(t *testing.T) {
|
|
// 对对碰付费: matching_game:issue:104|coupon:267|c:267:500
|
|
rm := Parse("matching_game:issue:104|coupon:267|c:267:500")
|
|
if rm.ActivityID != 0 {
|
|
t.Errorf("ActivityID = %d, want 0 (matching game paid has no activity prefix)", rm.ActivityID)
|
|
}
|
|
if rm.IssueID != 104 {
|
|
t.Errorf("IssueID = %d, want 104", rm.IssueID)
|
|
}
|
|
if rm.PkgID != 0 {
|
|
t.Errorf("PkgID = %d, want 0", rm.PkgID)
|
|
}
|
|
if rm.Count != 1 {
|
|
t.Errorf("Count = %d, want 1 (default)", rm.Count)
|
|
}
|
|
if len(rm.Coupons) != 1 || rm.Coupons[0].UserCouponID != 267 || rm.Coupons[0].AppliedAmount != 500 {
|
|
t.Errorf("Coupons = %+v, want [{UserCouponID:267 AppliedAmount:500}]", rm.Coupons)
|
|
}
|
|
}
|
|
|
|
func TestParse_MatchingGameFree(t *testing.T) {
|
|
// 对对碰免费(game_pass): activity:50|game_pass:12|matching_game:issue:96
|
|
rm := Parse("activity:50|game_pass:12|matching_game:issue:96")
|
|
if rm.ActivityID != 50 {
|
|
t.Errorf("ActivityID = %d, want 50", rm.ActivityID)
|
|
}
|
|
if rm.IssueID != 96 {
|
|
t.Errorf("IssueID = %d, want 96", rm.IssueID)
|
|
}
|
|
if rm.PkgID != 0 {
|
|
t.Errorf("PkgID = %d, want 0", rm.PkgID)
|
|
}
|
|
}
|
|
|
|
func TestParse_Ichiban(t *testing.T) {
|
|
// 一番赏: game_pass_package:梦的起点!|pkg_id:11|count:4
|
|
rm := Parse("game_pass_package:梦的起点!|pkg_id:11|count:4")
|
|
if rm.PkgID != 11 {
|
|
t.Errorf("PkgID = %d, want 11", rm.PkgID)
|
|
}
|
|
if rm.Count != 4 {
|
|
t.Errorf("Count = %d, want 4", rm.Count)
|
|
}
|
|
if rm.ActivityID != 0 {
|
|
t.Errorf("ActivityID = %d, want 0", rm.ActivityID)
|
|
}
|
|
if rm.IssueID != 0 {
|
|
t.Errorf("IssueID = %d, want 0", rm.IssueID)
|
|
}
|
|
}
|
|
|
|
func TestParse_IchibanSingle(t *testing.T) {
|
|
// 一番赏单包: game_pass_package:幸运一番|pkg_id:5|count:1
|
|
rm := Parse("game_pass_package:幸运一番|pkg_id:5|count:1")
|
|
if rm.PkgID != 5 {
|
|
t.Errorf("PkgID = %d, want 5", rm.PkgID)
|
|
}
|
|
if rm.Count != 1 {
|
|
t.Errorf("Count = %d, want 1", rm.Count)
|
|
}
|
|
}
|
|
|
|
func TestParse_Empty(t *testing.T) {
|
|
rm := Parse("")
|
|
if rm.ActivityID != 0 || rm.IssueID != 0 || rm.PkgID != 0 || rm.Count != 1 {
|
|
t.Errorf("empty remark should have zero IDs and count=1, got %+v", rm)
|
|
}
|
|
}
|
|
|
|
func TestParse_ActivityOnly(t *testing.T) {
|
|
// activity: 前缀(直购)
|
|
rm := Parse("activity:30|issue:45|count:2")
|
|
if rm.ActivityID != 30 {
|
|
t.Errorf("ActivityID = %d, want 30", rm.ActivityID)
|
|
}
|
|
if rm.IssueID != 45 {
|
|
t.Errorf("IssueID = %d, want 45", rm.IssueID)
|
|
}
|
|
if rm.Count != 2 {
|
|
t.Errorf("Count = %d, want 2", rm.Count)
|
|
}
|
|
}
|
|
|
|
func TestParse_ClassificationPriority(t *testing.T) {
|
|
// 验证三路分类逻辑:
|
|
// Case 1: ActivityID > 0 → 抽奖/直购
|
|
// Case 2: ActivityID==0 && IssueID > 0 → 对对碰
|
|
// Case 3: ActivityID==0 && IssueID==0 && PkgID > 0 → 一番赏
|
|
|
|
tests := []struct {
|
|
name string
|
|
remark string
|
|
wantCase string
|
|
activityID int64
|
|
issueID int64
|
|
pkgID int64
|
|
}{
|
|
{
|
|
name: "lottery → case1",
|
|
remark: "lottery:activity:10|issue:20|count:1",
|
|
wantCase: "case1",
|
|
activityID: 10, issueID: 20, pkgID: 0,
|
|
},
|
|
{
|
|
name: "matching_game paid → case2",
|
|
remark: "matching_game:issue:50",
|
|
wantCase: "case2",
|
|
activityID: 0, issueID: 50, pkgID: 0,
|
|
},
|
|
{
|
|
name: "ichiban → case3",
|
|
remark: "game_pass_package:test|pkg_id:7|count:2",
|
|
wantCase: "case3",
|
|
activityID: 0, issueID: 0, pkgID: 7,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rm := Parse(tt.remark)
|
|
if rm.ActivityID != tt.activityID {
|
|
t.Errorf("ActivityID = %d, want %d", rm.ActivityID, tt.activityID)
|
|
}
|
|
if rm.IssueID != tt.issueID {
|
|
t.Errorf("IssueID = %d, want %d", rm.IssueID, tt.issueID)
|
|
}
|
|
if rm.PkgID != tt.pkgID {
|
|
t.Errorf("PkgID = %d, want %d", rm.PkgID, tt.pkgID)
|
|
}
|
|
|
|
// 验证分类
|
|
var gotCase string
|
|
if rm.ActivityID > 0 {
|
|
gotCase = "case1"
|
|
} else if rm.IssueID > 0 {
|
|
gotCase = "case2"
|
|
} else if rm.PkgID > 0 {
|
|
gotCase = "case3"
|
|
}
|
|
if gotCase != tt.wantCase {
|
|
t.Errorf("classification = %s, want %s", gotCase, tt.wantCase)
|
|
}
|
|
})
|
|
}
|
|
}
|