| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- package service
- import (
- "fmt"
- "testing"
- "time"
- "github.com/glebarez/sqlite"
- "github.com/stretchr/testify/require"
- "gorm.io/gorm"
- "wails-app/internal/dao"
- "wails-app/internal/global"
- )
- func setupFeeTest(t *testing.T) {
- t.Helper()
- dsn := fmt.Sprintf("file:fee-%d?mode=memory&cache=shared", time.Now().UnixNano())
- db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
- require.NoError(t, err)
- sqlDB, err := db.DB()
- require.NoError(t, err)
- t.Cleanup(func() {
- _ = sqlDB.Close()
- if global.GVA_DB == db {
- global.GVA_DB = nil
- }
- })
- global.GVA_DB = db
- require.NoError(t, db.AutoMigrate(&dao.Vehicle{}, &dao.VehicleType{}, &dao.Owner{}, &dao.FeeConfig{}))
- }
- func seedVehicle(t *testing.T, typeID uint, plate string) *dao.Vehicle {
- t.Helper()
- vehicle := dao.Vehicle{PlateNumber: plate, VehicleTypeID: typeID}
- require.NoError(t, global.GVA_DB.Create(&vehicle).Error)
- return &vehicle
- }
- // seedConfig 创建收费配置:免费 15 分钟,起步 5 元,每 30 分钟 3 元。
- func seedConfig(t *testing.T, typeID uint, mut func(*dao.FeeConfig)) {
- t.Helper()
- config := dao.FeeConfig{
- VehicleTypeID: typeID, StartTime: 15, StartFee: 5,
- UnitTime: 30, UnitFee: 3, DailyMaxFee: 30, VIPDiscount: 1.0,
- }
- if mut != nil {
- mut(&config)
- }
- require.NoError(t, global.GVA_DB.Create(&config).Error)
- }
- func TestCalculateFeeRejectsMissingConfig(t *testing.T) {
- setupFeeTest(t)
- vehicle := seedVehicle(t, 99, "FEE-001")
- fee, err := CalculateFee(vehicle, 60)
- require.ErrorIs(t, err, ErrFeeConfigNotFound)
- require.Zero(t, fee)
- }
- func TestCalculateFeeRejectsInvalidUnitTime(t *testing.T) {
- setupFeeTest(t)
- vehicle := seedVehicle(t, 99, "FEE-002")
- seedConfig(t, 99, func(c *dao.FeeConfig) { c.UnitTime = 0 })
- fee, err := CalculateFee(vehicle, 60)
- require.ErrorIs(t, err, ErrFeeConfigInvalid)
- require.Zero(t, fee)
- }
- func TestCalculateFeeFreePeriodAndNormalBilling(t *testing.T) {
- setupFeeTest(t)
- vehicle := seedVehicle(t, 1, "FEE-003")
- seedConfig(t, 1, nil)
- // 免费时长内
- fee, err := CalculateFee(vehicle, 15)
- require.NoError(t, err)
- require.Zero(t, fee)
- // 30 分钟:5 + ceil(15/30)*3 = 8
- fee, err = CalculateFee(vehicle, 30)
- require.NoError(t, err)
- require.Equal(t, 8.0, fee)
- // 45 分钟:5 + ceil(30/30)*3 = 8(免费 15 后正好 1 个单位)
- fee, err = CalculateFee(vehicle, 45)
- require.NoError(t, err)
- require.Equal(t, 8.0, fee)
- // 46 分钟:5 + ceil(31/30)*3 = 11(不足一个单位按一个单位)
- fee, err = CalculateFee(vehicle, 46)
- require.NoError(t, err)
- require.Equal(t, 11.0, fee)
- }
- func TestCalculateFeeDailyCapWithinOneDay(t *testing.T) {
- setupFeeTest(t)
- vehicle := seedVehicle(t, 1, "FEE-004")
- seedConfig(t, 1, nil)
- // 10 小时:5 + ceil(585/30)*3 = 65 → 封顶 30
- fee, err := CalculateFee(vehicle, 600)
- require.NoError(t, err)
- require.Equal(t, 30.0, fee)
- }
- func TestCalculateFeeMultiDayCapAccumulates(t *testing.T) {
- setupFeeTest(t)
- vehicle := seedVehicle(t, 1, "FEE-005")
- seedConfig(t, 1, nil)
- // 26 小时(跨 2 天):原始 5 + ceil(1545/30)*3 = 161 → 封顶 2×30 = 60
- fee, err := CalculateFee(vehicle, 1560)
- require.NoError(t, err)
- require.Equal(t, 60.0, fee)
- // 3 天整:封顶 3×30 = 90
- fee, err = CalculateFee(vehicle, 3*1440)
- require.NoError(t, err)
- require.Equal(t, 90.0, fee)
- }
- func TestCalculateFeeNoCapMultiDayChargesFull(t *testing.T) {
- setupFeeTest(t)
- vehicle := seedVehicle(t, 1, "FEE-006")
- seedConfig(t, 1, func(c *dao.FeeConfig) { c.DailyMaxFee = 0 })
- // 2 天:5 + ceil(2865/30)*3 = 5 + 288 = 293(无封顶)
- fee, err := CalculateFee(vehicle, 2*1440)
- require.NoError(t, err)
- require.Equal(t, 293.0, fee)
- }
- func TestCalculateFeeZeroConfigMeansExplicitFree(t *testing.T) {
- setupFeeTest(t)
- vehicle := seedVehicle(t, 1, "FEE-007")
- seedConfig(t, 1, func(c *dao.FeeConfig) {
- c.StartTime, c.StartFee, c.UnitFee, c.DailyMaxFee = 0, 0, 0, 0
- })
- fee, err := CalculateFee(vehicle, 600)
- require.NoError(t, err)
- require.Zero(t, fee)
- }
- func TestCalculateFeeVIPFree(t *testing.T) {
- setupFeeTest(t)
- vehicle := seedVehicle(t, 1, "FEE-008")
- seedConfig(t, 1, func(c *dao.FeeConfig) { c.IsVIPFree = true })
- future := time.Now().Add(24 * time.Hour)
- require.NoError(t, global.GVA_DB.Create(&dao.Owner{
- OwnerName: "VIP车", IsVip: true, VipExpireTime: future,
- }).Error)
- require.NoError(t, global.GVA_DB.Model(vehicle).Update("owner_id", 1).Error)
- fee, err := CalculateFee(vehicle, 120)
- require.NoError(t, err)
- require.Zero(t, fee)
- }
- func TestCalculateFeeVIPDiscount(t *testing.T) {
- setupFeeTest(t)
- vehicle := seedVehicle(t, 1, "FEE-009")
- seedConfig(t, 1, func(c *dao.FeeConfig) { c.VIPDiscount = 0.8 })
- future := time.Now().Add(24 * time.Hour)
- require.NoError(t, global.GVA_DB.Create(&dao.Owner{
- OwnerName: "VIP车", IsVip: true, VipExpireTime: future,
- }).Error)
- require.NoError(t, global.GVA_DB.Model(vehicle).Update("owner_id", 1).Error)
- fee, err := CalculateFee(vehicle, 120)
- require.NoError(t, err)
- // 原始:5 + ceil(105/30)*3 = 17 → 封顶 30 以内 → ×0.8 = 13.6
- require.InDelta(t, 13.6, fee, 0.001)
- }
|