fee_test.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package service
  2. import (
  3. "fmt"
  4. "testing"
  5. "time"
  6. "github.com/glebarez/sqlite"
  7. "github.com/stretchr/testify/require"
  8. "gorm.io/gorm"
  9. "wails-app/internal/dao"
  10. "wails-app/internal/global"
  11. )
  12. func setupFeeTest(t *testing.T) {
  13. t.Helper()
  14. dsn := fmt.Sprintf("file:fee-%d?mode=memory&cache=shared", time.Now().UnixNano())
  15. db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
  16. require.NoError(t, err)
  17. sqlDB, err := db.DB()
  18. require.NoError(t, err)
  19. t.Cleanup(func() {
  20. _ = sqlDB.Close()
  21. if global.GVA_DB == db {
  22. global.GVA_DB = nil
  23. }
  24. })
  25. global.GVA_DB = db
  26. require.NoError(t, db.AutoMigrate(&dao.Vehicle{}, &dao.VehicleType{}, &dao.Owner{}, &dao.FeeConfig{}))
  27. }
  28. func seedVehicle(t *testing.T, typeID uint, plate string) *dao.Vehicle {
  29. t.Helper()
  30. vehicle := dao.Vehicle{PlateNumber: plate, VehicleTypeID: typeID}
  31. require.NoError(t, global.GVA_DB.Create(&vehicle).Error)
  32. return &vehicle
  33. }
  34. // seedConfig 创建收费配置:免费 15 分钟,起步 5 元,每 30 分钟 3 元。
  35. func seedConfig(t *testing.T, typeID uint, mut func(*dao.FeeConfig)) {
  36. t.Helper()
  37. config := dao.FeeConfig{
  38. VehicleTypeID: typeID, StartTime: 15, StartFee: 5,
  39. UnitTime: 30, UnitFee: 3, DailyMaxFee: 30, VIPDiscount: 1.0,
  40. }
  41. if mut != nil {
  42. mut(&config)
  43. }
  44. require.NoError(t, global.GVA_DB.Create(&config).Error)
  45. }
  46. func TestCalculateFeeRejectsMissingConfig(t *testing.T) {
  47. setupFeeTest(t)
  48. vehicle := seedVehicle(t, 99, "FEE-001")
  49. fee, err := CalculateFee(vehicle, 60)
  50. require.ErrorIs(t, err, ErrFeeConfigNotFound)
  51. require.Zero(t, fee)
  52. }
  53. func TestCalculateFeeRejectsInvalidUnitTime(t *testing.T) {
  54. setupFeeTest(t)
  55. vehicle := seedVehicle(t, 99, "FEE-002")
  56. seedConfig(t, 99, func(c *dao.FeeConfig) { c.UnitTime = 0 })
  57. fee, err := CalculateFee(vehicle, 60)
  58. require.ErrorIs(t, err, ErrFeeConfigInvalid)
  59. require.Zero(t, fee)
  60. }
  61. func TestCalculateFeeFreePeriodAndNormalBilling(t *testing.T) {
  62. setupFeeTest(t)
  63. vehicle := seedVehicle(t, 1, "FEE-003")
  64. seedConfig(t, 1, nil)
  65. // 免费时长内
  66. fee, err := CalculateFee(vehicle, 15)
  67. require.NoError(t, err)
  68. require.Zero(t, fee)
  69. // 30 分钟:5 + ceil(15/30)*3 = 8
  70. fee, err = CalculateFee(vehicle, 30)
  71. require.NoError(t, err)
  72. require.Equal(t, 8.0, fee)
  73. // 45 分钟:5 + ceil(30/30)*3 = 8(免费 15 后正好 1 个单位)
  74. fee, err = CalculateFee(vehicle, 45)
  75. require.NoError(t, err)
  76. require.Equal(t, 8.0, fee)
  77. // 46 分钟:5 + ceil(31/30)*3 = 11(不足一个单位按一个单位)
  78. fee, err = CalculateFee(vehicle, 46)
  79. require.NoError(t, err)
  80. require.Equal(t, 11.0, fee)
  81. }
  82. func TestCalculateFeeDailyCapWithinOneDay(t *testing.T) {
  83. setupFeeTest(t)
  84. vehicle := seedVehicle(t, 1, "FEE-004")
  85. seedConfig(t, 1, nil)
  86. // 10 小时:5 + ceil(585/30)*3 = 65 → 封顶 30
  87. fee, err := CalculateFee(vehicle, 600)
  88. require.NoError(t, err)
  89. require.Equal(t, 30.0, fee)
  90. }
  91. func TestCalculateFeeMultiDayCapAccumulates(t *testing.T) {
  92. setupFeeTest(t)
  93. vehicle := seedVehicle(t, 1, "FEE-005")
  94. seedConfig(t, 1, nil)
  95. // 26 小时(跨 2 天):原始 5 + ceil(1545/30)*3 = 161 → 封顶 2×30 = 60
  96. fee, err := CalculateFee(vehicle, 1560)
  97. require.NoError(t, err)
  98. require.Equal(t, 60.0, fee)
  99. // 3 天整:封顶 3×30 = 90
  100. fee, err = CalculateFee(vehicle, 3*1440)
  101. require.NoError(t, err)
  102. require.Equal(t, 90.0, fee)
  103. }
  104. func TestCalculateFeeNoCapMultiDayChargesFull(t *testing.T) {
  105. setupFeeTest(t)
  106. vehicle := seedVehicle(t, 1, "FEE-006")
  107. seedConfig(t, 1, func(c *dao.FeeConfig) { c.DailyMaxFee = 0 })
  108. // 2 天:5 + ceil(2865/30)*3 = 5 + 288 = 293(无封顶)
  109. fee, err := CalculateFee(vehicle, 2*1440)
  110. require.NoError(t, err)
  111. require.Equal(t, 293.0, fee)
  112. }
  113. func TestCalculateFeeZeroConfigMeansExplicitFree(t *testing.T) {
  114. setupFeeTest(t)
  115. vehicle := seedVehicle(t, 1, "FEE-007")
  116. seedConfig(t, 1, func(c *dao.FeeConfig) {
  117. c.StartTime, c.StartFee, c.UnitFee, c.DailyMaxFee = 0, 0, 0, 0
  118. })
  119. fee, err := CalculateFee(vehicle, 600)
  120. require.NoError(t, err)
  121. require.Zero(t, fee)
  122. }
  123. func TestCalculateFeeVIPFree(t *testing.T) {
  124. setupFeeTest(t)
  125. vehicle := seedVehicle(t, 1, "FEE-008")
  126. seedConfig(t, 1, func(c *dao.FeeConfig) { c.IsVIPFree = true })
  127. future := time.Now().Add(24 * time.Hour)
  128. require.NoError(t, global.GVA_DB.Create(&dao.Owner{
  129. OwnerName: "VIP车", IsVip: true, VipExpireTime: future,
  130. }).Error)
  131. require.NoError(t, global.GVA_DB.Model(vehicle).Update("owner_id", 1).Error)
  132. fee, err := CalculateFee(vehicle, 120)
  133. require.NoError(t, err)
  134. require.Zero(t, fee)
  135. }
  136. func TestCalculateFeeVIPDiscount(t *testing.T) {
  137. setupFeeTest(t)
  138. vehicle := seedVehicle(t, 1, "FEE-009")
  139. seedConfig(t, 1, func(c *dao.FeeConfig) { c.VIPDiscount = 0.8 })
  140. future := time.Now().Add(24 * time.Hour)
  141. require.NoError(t, global.GVA_DB.Create(&dao.Owner{
  142. OwnerName: "VIP车", IsVip: true, VipExpireTime: future,
  143. }).Error)
  144. require.NoError(t, global.GVA_DB.Model(vehicle).Update("owner_id", 1).Error)
  145. fee, err := CalculateFee(vehicle, 120)
  146. require.NoError(t, err)
  147. // 原始:5 + ceil(105/30)*3 = 17 → 封顶 30 以内 → ×0.8 = 13.6
  148. require.InDelta(t, 13.6, fee, 0.001)
  149. }