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) }