Files
hfb_sys/backend/internal/modules/order/repository_test.go
T

368 lines
12 KiB
Go

package order
import (
"testing"
"time"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/timeutil"
"gorm.io/datatypes"
)
func TestCalculateCheckoutSettlementRefundsUnusedRent(t *testing.T) {
order := model.RentalOrder{
RentAmountCent: 38300,
OwnerRentAmountCent: 35300,
DepositAmountCent: 15000,
AccountSnapshot: datatypes.JSON([]byte(`{
"haf_coin_amount": 100000000,
"asset_summary": {
"price_breakdown": {
"buyer_coin_base_price": 263,
"seller_coin_base_price": 233,
"consumable_price": 120
}
}
}`)),
}
settlement := calculateCheckoutSettlement(order, 700, 90, 0)
// 角精度:243.7 = roundMoney(236.7 + 7)
if settlement.ActualRentAmountCent != 24370 {
t.Fatalf("ActualRentAmountCent = %d, want 24370", settlement.ActualRentAmountCent)
}
// 角精度:216.7 = roundMoney(209.7 + 7) 卖家币价+消耗品
if settlement.OwnerRentIncomeCent != 21670 {
t.Fatalf("OwnerRentIncomeCent = %d, want 21670", settlement.OwnerRentIncomeCent)
}
// 角精度:27.0 = roundMoney(27) 平台费
if settlement.PlatformFeeCent != 2700 {
t.Fatalf("PlatformFeeCent = %d, want 2700", settlement.PlatformFeeCent)
}
// 角精度:139.3 = roundMoney(383 - 243.7)
if settlement.RentRefundCent != 13930 {
t.Fatalf("RentRefundCent = %d, want 13930", settlement.RentRefundCent)
}
if settlement.DepositRefundCent != 15000 {
t.Fatalf("DepositRefundCent = %d, want 15000", settlement.DepositRefundCent)
}
// 角精度:289.3 = roundMoney(139.3 + 150)
if settlement.RenterRefundCent != 28930 {
t.Fatalf("RenterRefundCent = %d, want 28930", settlement.RenterRefundCent)
}
}
func TestCalculateCheckoutSettlementUsesBuyerAndSellerRatiosSeparately(t *testing.T) {
order := model.RentalOrder{
RentAmountCent: 38300,
OwnerRentAmountCent: 35300,
DepositAmountCent: 15000,
AccountSnapshot: datatypes.JSON([]byte(`{
"haf_coin_amount": 100000000,
"asset_summary": {
"price_breakdown": {
"buyer_coin_base_price": 263,
"seller_coin_base_price": 233,
"consumable_price": 120
}
}
}`)),
}
settlement := calculateCheckoutSettlement(order, 0, 50, 0)
// 角精度:131.5 = roundMoney(131.5)
if settlement.ActualRentAmountCent != 13150 {
t.Fatalf("租客侧实际租金 = %d, want 13150", settlement.ActualRentAmountCent)
}
// 角精度:116.5 = roundMoney(116.5)
if settlement.OwnerRentIncomeCent != 11650 {
t.Fatalf("卖家侧租金收入 = %d, want 11650", settlement.OwnerRentIncomeCent)
}
// 角精度:15.0 = roundMoney(15)
if settlement.PlatformFeeCent != 1500 {
t.Fatalf("平台差价 = %d, want 1500", settlement.PlatformFeeCent)
}
}
func TestCalculateCheckoutSettlementAddsDepositCompensation(t *testing.T) {
order := model.RentalOrder{
RentAmountCent: 38300,
OwnerRentAmountCent: 35300,
DepositAmountCent: 15000,
AccountSnapshot: datatypes.JSON([]byte(`{
"haf_coin_amount": 100000000,
"asset_summary": {
"price_breakdown": {
"buyer_coin_base_price": 263,
"seller_coin_base_price": 233,
"consumable_price": 120
}
}
}`)),
}
settlement := calculateCheckoutSettlement(order, 12000, 100, 3000)
if settlement.ActualRentAmountCent != 38300 {
t.Fatalf("ActualRentAmountCent = %d, want 38300", settlement.ActualRentAmountCent)
}
if settlement.OwnerRentIncomeCent != 35300 {
t.Fatalf("OwnerRentIncomeCent = %d, want 35300", settlement.OwnerRentIncomeCent)
}
if settlement.DepositCompensationCent != 3000 {
t.Fatalf("DepositCompensationCent = %d, want 3000", settlement.DepositCompensationCent)
}
if settlement.OwnerIncomeCent != 38300 {
t.Fatalf("OwnerIncomeCent = %d, want 38300", settlement.OwnerIncomeCent)
}
if settlement.RenterRefundCent != 12000 {
t.Fatalf("RenterRefundCent = %d, want 12000", settlement.RenterRefundCent)
}
}
func TestCalculateCheckoutSettlementAllowsOvershootFromDeposit(t *testing.T) {
// 发布 100M,实际消耗 180M;按基价线性外推,打超从押金扣
order := model.RentalOrder{
RentAmountCent: 26300, // 仅币预收(无消耗品)
OwnerRentAmountCent: 23300,
DepositAmountCent: 30000,
AccountSnapshot: datatypes.JSON([]byte(`{
"haf_coin_amount": 100000000,
"asset_summary": {
"price_breakdown": {
"buyer_coin_base_price": 263,
"seller_coin_base_price": 233,
"consumable_price": 0
}
}
}`)),
}
settlement := calculateCheckoutSettlement(order, 0, 180, 0)
// 180/100 * 26300 = 47340
if settlement.ActualRentAmountCent != 47340 {
t.Fatalf("ActualRentAmountCent = %d, want 47340", settlement.ActualRentAmountCent)
}
if settlement.OwnerRentIncomeCent != 41940 { // 180/100 * 23300
t.Fatalf("OwnerRentIncomeCent = %d, want 41940", settlement.OwnerRentIncomeCent)
}
if settlement.OvershootAmountCent != 21040 { // 47340 - 26300
t.Fatalf("OvershootAmountCent = %d, want 21040", settlement.OvershootAmountCent)
}
if settlement.ShortfallCent != 0 {
t.Fatalf("ShortfallCent = %d, want 0", settlement.ShortfallCent)
}
if settlement.RentRefundCent != 0 {
t.Fatalf("RentRefundCent = %d, want 0", settlement.RentRefundCent)
}
// 押金 30000 - 打超 21040 = 8960
if settlement.DepositRefundCent != 8960 {
t.Fatalf("DepositRefundCent = %d, want 8960", settlement.DepositRefundCent)
}
}
func TestCalculateCheckoutSettlementShortfallWhenDepositInsufficient(t *testing.T) {
order := model.RentalOrder{
RentAmountCent: 26300,
OwnerRentAmountCent: 23300,
DepositAmountCent: 5000,
AccountSnapshot: datatypes.JSON([]byte(`{
"haf_coin_amount": 100000000,
"asset_summary": {
"price_breakdown": {
"buyer_coin_base_price": 263,
"seller_coin_base_price": 233,
"consumable_price": 0
}
}
}`)),
}
settlement := calculateCheckoutSettlement(order, 0, 180, 0)
// overshoot 21040, deposit 5000 → shortfall 16040
if settlement.OvershootAmountCent != 21040 {
t.Fatalf("OvershootAmountCent = %d, want 21040", settlement.OvershootAmountCent)
}
if settlement.ShortfallCent != 16040 {
t.Fatalf("ShortfallCent = %d, want 16040", settlement.ShortfallCent)
}
if settlement.DepositRefundCent != 0 {
t.Fatalf("DepositRefundCent = %d, want 0", settlement.DepositRefundCent)
}
}
func TestCalculateCheckoutSettlementUsesExplicitRatiosForActualConsume(t *testing.T) {
// buyer 1:40, seller 1:45;消耗 180M
// 租客: 180*100/40 = 450 元;号主: 180*100/45 = 400 元
order := model.RentalOrder{
RentAmountCent: 25000, // 100M 预收
OwnerRentAmountCent: 22222,
DepositAmountCent: 50000,
AccountSnapshot: datatypes.JSON([]byte(`{
"haf_coin_amount": 100000000,
"asset_summary": {
"price_breakdown": {
"buyer_ratio": 40,
"seller_ratio": 45,
"buyer_coin_base_price": 250,
"seller_coin_base_price": 222.22,
"consumable_price": 0
}
}
}`)),
}
settlement := calculateCheckoutSettlement(order, 0, 180, 0)
if settlement.ActualRentAmountCent != 45000 {
t.Fatalf("ActualRentAmountCent = %d, want 45000", settlement.ActualRentAmountCent)
}
if settlement.OwnerRentIncomeCent != 40000 {
t.Fatalf("OwnerRentIncomeCent = %d, want 40000", settlement.OwnerRentIncomeCent)
}
if settlement.PlatformFeeCent != 5000 {
t.Fatalf("PlatformFeeCent = %d, want 5000", settlement.PlatformFeeCent)
}
if settlement.OvershootAmountCent != 20000 {
t.Fatalf("OvershootAmountCent = %d, want 20000", settlement.OvershootAmountCent)
}
if settlement.ShortfallCent != 0 {
t.Fatalf("ShortfallCent = %d, want 0", settlement.ShortfallCent)
}
}
func TestCalculateCheckoutSettlementCapsFullUseRatioRounding(t *testing.T) {
// 发布量刚好用完时,比例换算的四舍五入不能把线下待打款抬高。
order := model.RentalOrder{
RentAmountCent: 37210,
OwnerRentAmountCent: 34000,
PlatformFeeCent: 3210,
DepositAmountCent: 50000,
AccountSnapshot: datatypes.JSON([]byte(`{
"haf_coin_amount": 160000000,
"asset_summary": {
"price_breakdown": {
"buyer_ratio": 43,
"seller_ratio": 47,
"buyer_coin_base_price": 372.1,
"seller_coin_base_price": 340,
"consumable_price": 0
}
}
}`)),
}
settlement := calculateCheckoutSettlement(order, 0, 160, 0)
if settlement.ActualRentAmountCent != order.RentAmountCent {
t.Fatalf("ActualRentAmountCent = %d, want %d", settlement.ActualRentAmountCent, order.RentAmountCent)
}
if settlement.OwnerRentIncomeCent != order.OwnerRentAmountCent {
t.Fatalf("OwnerRentIncomeCent = %d, want %d", settlement.OwnerRentIncomeCent, order.OwnerRentAmountCent)
}
if settlement.PlatformFeeCent != order.PlatformFeeCent {
t.Fatalf("PlatformFeeCent = %d, want %d", settlement.PlatformFeeCent, order.PlatformFeeCent)
}
if settlement.OvershootAmountCent != 0 {
t.Fatalf("OvershootAmountCent = %d, want 0", settlement.OvershootAmountCent)
}
}
func TestCalculateDepositWaiverUsesSharedRemainingQuota(t *testing.T) {
paid, waived := calculateDepositWaiver(500, 300, 0)
if paid != 200 || waived != 300 {
t.Fatalf("paid = %d, waived = %d, want 200, 300", paid, waived)
}
paid, waived = calculateDepositWaiver(500, 300, 200)
if paid != 400 || waived != 100 {
t.Fatalf("paid = %d, waived = %d, want 400, 100", paid, waived)
}
paid, waived = calculateDepositWaiver(500, 300, 300)
if paid != 500 || waived != 0 {
t.Fatalf("paid = %d, waived = %d, want 500, 0", paid, waived)
}
}
func TestArchiveAssetsMovesListingOffline(t *testing.T) {
now := time.Now()
listing := model.RentalListing{
Status: listingStatusRented,
InTransaction: true,
PublishedAt: &now,
}
account := model.GameAccount{Status: accountStatusRented}
if err := archiveAssets(nil, &listing, &account); err != nil {
t.Fatalf("archiveAssets: %v", err)
}
if listing.Status != listingStatusOffline {
t.Fatalf("listing.Status = %q, want %q", listing.Status, listingStatusOffline)
}
if listing.InTransaction {
t.Fatal("listing.InTransaction = true, want false")
}
if listing.PublishedAt != nil {
t.Fatal("listing.PublishedAt is not nil")
}
if account.Status != accountStatusOffline {
t.Fatalf("account.Status = %q, want %q", account.Status, accountStatusOffline)
}
}
func TestCompleteAssetsMovesListingCompleted(t *testing.T) {
now := time.Now()
listing := model.RentalListing{
Status: listingStatusRented,
InTransaction: true,
PublishedAt: &now,
}
account := model.GameAccount{Status: accountStatusRented}
if err := completeAssets(nil, &listing, &account); err != nil {
t.Fatalf("completeAssets: %v", err)
}
if listing.Status != listingStatusCompleted {
t.Fatalf("listing.Status = %q, want %q", listing.Status, listingStatusCompleted)
}
if listing.InTransaction {
t.Fatal("listing.InTransaction = true, want false")
}
if listing.PublishedAt != nil {
t.Fatal("listing.PublishedAt is not nil")
}
if account.Status != accountStatusOffline {
t.Fatalf("account.Status = %q, want %q", account.Status, accountStatusOffline)
}
}
func TestNewOrderNoUsesShanghaiTimeWhenLocalIsUTC(t *testing.T) {
oldLocal := time.Local
time.Local = time.UTC
defer func() {
time.Local = oldLocal
}()
loc := timeutil.ShanghaiLocation()
before := time.Now().In(loc)
orderNo, err := newOrderNo()
after := time.Now().In(loc)
if err != nil {
t.Fatalf("newOrderNo() error = %v", err)
}
if len(orderNo) != len("RO20260603123456789") {
t.Fatalf("orderNo length = %d, want %d: %s", len(orderNo), len("RO20260603123456789"), orderNo)
}
parsed, err := time.ParseInLocation("20060102150405", orderNo[2:16], loc)
if err != nil {
t.Fatalf("parse order no time: %v", err)
}
if parsed.Before(before.Add(-2*time.Second)) || parsed.After(after.Add(2*time.Second)) {
t.Fatalf("order no time = %s, want between %s and %s", parsed, before, after)
}
}