|
@@ -0,0 +1,233 @@
|
|
|
|
|
+package service
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "testing"
|
|
|
|
|
+ "time"
|
|
|
|
|
+
|
|
|
|
|
+ "github.com/glebarez/sqlite"
|
|
|
|
|
+ "github.com/stretchr/testify/require"
|
|
|
|
|
+ "gorm.io/gorm"
|
|
|
|
|
+ "wails-app/internal/dao"
|
|
|
|
|
+ "wails-app/internal/global"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+func setupMonthlyServiceTest(t *testing.T) *MonthlyCardService {
|
|
|
|
|
+ t.Helper()
|
|
|
|
|
+ db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NoError(t, db.AutoMigrate(&dao.Vehicle{}, &dao.MonthlyCard{}, &dao.Shortlist{}, &dao.PaymentRecord{}))
|
|
|
|
|
+ global.GVA_DB = db
|
|
|
|
|
+ t.Cleanup(func() { global.GVA_DB = nil })
|
|
|
|
|
+ return NewMonthlyCardService()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func createVehicle(t *testing.T, plate string) *dao.Vehicle {
|
|
|
|
|
+ t.Helper()
|
|
|
|
|
+ vehicle := dao.Vehicle{PlateNumber: plate, VehicleTypeID: 1}
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Create(&vehicle).Error)
|
|
|
|
|
+ return &vehicle
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func countTable(t *testing.T, model interface{}) int64 {
|
|
|
|
|
+ t.Helper()
|
|
|
|
|
+ var count int64
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Model(model).Count(&count).Error)
|
|
|
|
|
+ return count
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestCreateWritesCardWhitelistAndPayment(t *testing.T) {
|
|
|
|
|
+ svc := setupMonthlyServiceTest(t)
|
|
|
|
|
+ vehicle := createVehicle(t, "MC-001")
|
|
|
|
|
+
|
|
|
|
|
+ card, err := svc.Create(vehicle.ID, "month", 300, 7, "测试办理")
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.NotZero(t, card.ID)
|
|
|
|
|
+ require.Equal(t, "paid", card.PaymentStatus)
|
|
|
|
|
+ require.True(t, card.EndDate.After(time.Now().Add(29*24*time.Hour)))
|
|
|
|
|
+
|
|
|
|
|
+ // 三张表全部写入
|
|
|
|
|
+ require.Equal(t, int64(1), countTable(t, &dao.MonthlyCard{}))
|
|
|
|
|
+ require.Equal(t, int64(1), countTable(t, &dao.Shortlist{}))
|
|
|
|
|
+ require.Equal(t, int64(1), countTable(t, &dao.PaymentRecord{}))
|
|
|
|
|
+
|
|
|
|
|
+ // 白名单到期日与月卡一致
|
|
|
|
|
+ var shortlist dao.Shortlist
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.First(&shortlist).Error)
|
|
|
|
|
+ require.Equal(t, "白名单", shortlist.ListType)
|
|
|
|
|
+ require.NotNil(t, shortlist.ExpirationTime)
|
|
|
|
|
+ require.True(t, shortlist.ExpirationTime.Equal(card.EndDate))
|
|
|
|
|
+
|
|
|
|
|
+ // 支付流水
|
|
|
|
|
+ var pay dao.PaymentRecord
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.First(&pay).Error)
|
|
|
|
|
+ require.Equal(t, 300.0, pay.Amount)
|
|
|
|
|
+ require.Equal(t, uint(7), pay.OperatorID)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestCreateRollsBackWhenWhitelistWriteFails(t *testing.T) {
|
|
|
|
|
+ svc := setupMonthlyServiceTest(t)
|
|
|
|
|
+ vehicle := createVehicle(t, "MC-002")
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟白名单写入失败:删除 shortlist 表
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Exec("DROP TABLE shortlist").Error)
|
|
|
|
|
+
|
|
|
|
|
+ _, err := svc.Create(vehicle.ID, "month", 300, 7, "")
|
|
|
|
|
+ require.Error(t, err)
|
|
|
|
|
+ require.Contains(t, err.Error(), "白名单")
|
|
|
|
|
+
|
|
|
|
|
+ // 月卡与流水均回滚
|
|
|
|
|
+ require.Equal(t, int64(0), countTable(t, &dao.MonthlyCard{}))
|
|
|
|
|
+ require.Equal(t, int64(0), countTable(t, &dao.PaymentRecord{}))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestCreateRollsBackWhenPaymentWriteFails(t *testing.T) {
|
|
|
|
|
+ svc := setupMonthlyServiceTest(t)
|
|
|
|
|
+ vehicle := createVehicle(t, "MC-003")
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟支付流水写入失败:删除 payment_record 表
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Exec("DROP TABLE payment_record").Error)
|
|
|
|
|
+
|
|
|
|
|
+ _, err := svc.Create(vehicle.ID, "month", 300, 7, "")
|
|
|
|
|
+ require.Error(t, err)
|
|
|
|
|
+ require.Contains(t, err.Error(), "支付流水")
|
|
|
|
|
+
|
|
|
|
|
+ // 月卡与白名单均回滚
|
|
|
|
|
+ require.Equal(t, int64(0), countTable(t, &dao.MonthlyCard{}))
|
|
|
|
|
+ require.Equal(t, int64(0), countTable(t, &dao.Shortlist{}))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestCreateRejectsDuplicateActiveCard(t *testing.T) {
|
|
|
|
|
+ svc := setupMonthlyServiceTest(t)
|
|
|
|
|
+ vehicle := createVehicle(t, "MC-004")
|
|
|
|
|
+
|
|
|
|
|
+ _, err := svc.Create(vehicle.ID, "month", 300, 7, "")
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ _, err = svc.Create(vehicle.ID, "month", 300, 7, "")
|
|
|
|
|
+ require.Error(t, err)
|
|
|
|
|
+ require.Contains(t, err.Error(), "已有有效月卡")
|
|
|
|
|
+ require.Equal(t, int64(1), countTable(t, &dao.MonthlyCard{}))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestCreateRejectsUnknownVehicle(t *testing.T) {
|
|
|
|
|
+ svc := setupMonthlyServiceTest(t)
|
|
|
|
|
+ _, err := svc.Create(99999, "month", 300, 7, "")
|
|
|
|
|
+ require.Error(t, err)
|
|
|
|
|
+ require.Contains(t, err.Error(), "车辆不存在")
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestRenewExtendsFromEndDateWhenActive(t *testing.T) {
|
|
|
|
|
+ svc := setupMonthlyServiceTest(t)
|
|
|
|
|
+ vehicle := createVehicle(t, "MC-005")
|
|
|
|
|
+
|
|
|
|
|
+ now := time.Now()
|
|
|
|
|
+ card := &dao.MonthlyCard{
|
|
|
|
|
+ VehicleID: vehicle.ID, CardType: "month",
|
|
|
|
|
+ StartDate: now.AddDate(0, -1, 0), EndDate: now.AddDate(0, 0, 10),
|
|
|
|
|
+ Fee: 300, PaymentStatus: "paid",
|
|
|
|
|
+ }
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Create(card).Error)
|
|
|
|
|
+ originalEnd := card.EndDate
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Create(&dao.Shortlist{
|
|
|
|
|
+ VehicleId: int(vehicle.ID), ListType: "白名单", ExpirationTime: &originalEnd,
|
|
|
|
|
+ }).Error)
|
|
|
|
|
+
|
|
|
|
|
+ err := svc.Renew(card.ID, "month", 300, 7)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ var renewed dao.MonthlyCard
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.First(&renewed, card.ID).Error)
|
|
|
|
|
+ // 未过期:从原到期日顺延 30 天
|
|
|
|
|
+ require.True(t, renewed.EndDate.After(now.AddDate(0, 0, 39)), "到期日应约为原到期日+30天")
|
|
|
|
|
+ require.True(t, renewed.EndDate.Before(now.AddDate(0, 0, 41)))
|
|
|
|
|
+ require.Equal(t, 600.0, renewed.Fee)
|
|
|
|
|
+
|
|
|
|
|
+ // 白名单到期日同步
|
|
|
|
|
+ var shortlist dao.Shortlist
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Where("vehicle_id = ?", vehicle.ID).First(&shortlist).Error)
|
|
|
|
|
+ require.True(t, shortlist.ExpirationTime.Equal(renewed.EndDate))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestRenewStartsFromNowWhenCardExpired(t *testing.T) {
|
|
|
|
|
+ svc := setupMonthlyServiceTest(t)
|
|
|
|
|
+ vehicle := createVehicle(t, "MC-006")
|
|
|
|
|
+
|
|
|
|
|
+ now := time.Now()
|
|
|
|
|
+ card := &dao.MonthlyCard{
|
|
|
|
|
+ VehicleID: vehicle.ID, CardType: "month",
|
|
|
|
|
+ StartDate: now.AddDate(0, -3, 0), EndDate: now.AddDate(0, -2, 0), // 已过期 2 个月
|
|
|
|
|
+ Fee: 300, PaymentStatus: "paid",
|
|
|
|
|
+ }
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Create(card).Error)
|
|
|
|
|
+ expiredEnd := card.EndDate
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Create(&dao.Shortlist{
|
|
|
|
|
+ VehicleId: int(vehicle.ID), ListType: "白名单", ExpirationTime: &expiredEnd,
|
|
|
|
|
+ }).Error)
|
|
|
|
|
+
|
|
|
|
|
+ err := svc.Renew(card.ID, "month", 300, 7)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ var renewed dao.MonthlyCard
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.First(&renewed, card.ID).Error)
|
|
|
|
|
+ // 已过期:从今天起算约 30 天,而不是从两个月前的到期日续期
|
|
|
|
|
+ require.True(t, renewed.EndDate.After(now.AddDate(0, 0, 29)), "过期卡应从今天起算")
|
|
|
|
|
+ require.True(t, renewed.EndDate.Before(now.AddDate(0, 0, 31)))
|
|
|
|
|
+
|
|
|
|
|
+ // 白名单到期日同步为新到期日(未过期卡同样适用)
|
|
|
|
|
+ var shortlist dao.Shortlist
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Where("vehicle_id = ?", vehicle.ID).First(&shortlist).Error)
|
|
|
|
|
+ require.True(t, shortlist.ExpirationTime.Equal(renewed.EndDate))
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestRenewRollsBackWhenPaymentWriteFails(t *testing.T) {
|
|
|
|
|
+ svc := setupMonthlyServiceTest(t)
|
|
|
|
|
+ vehicle := createVehicle(t, "MC-007")
|
|
|
|
|
+
|
|
|
|
|
+ now := time.Now()
|
|
|
|
|
+ card := &dao.MonthlyCard{
|
|
|
|
|
+ VehicleID: vehicle.ID, CardType: "month",
|
|
|
|
|
+ StartDate: now, EndDate: now.AddDate(0, 0, 10),
|
|
|
|
|
+ Fee: 300, PaymentStatus: "paid",
|
|
|
|
|
+ }
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Create(card).Error)
|
|
|
|
|
+ originalEnd := card.EndDate
|
|
|
|
|
+
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Exec("DROP TABLE payment_record").Error)
|
|
|
|
|
+
|
|
|
|
|
+ err := svc.Renew(card.ID, "month", 300, 7)
|
|
|
|
|
+ require.Error(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ // 到期日与费用均未变(回滚)
|
|
|
|
|
+ var after dao.MonthlyCard
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.First(&after, card.ID).Error)
|
|
|
|
|
+ require.True(t, after.EndDate.Equal(originalEnd))
|
|
|
|
|
+ require.Equal(t, 300.0, after.Fee)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestRefundMarksRefundedAndRemovesWhitelist(t *testing.T) {
|
|
|
|
|
+ svc := setupMonthlyServiceTest(t)
|
|
|
|
|
+ vehicle := createVehicle(t, "MC-008")
|
|
|
|
|
+
|
|
|
|
|
+ card, err := svc.Create(vehicle.ID, "month", 300, 7, "")
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+ require.Equal(t, int64(1), countTable(t, &dao.Shortlist{}))
|
|
|
|
|
+
|
|
|
|
|
+ err = svc.Refund(card.ID)
|
|
|
|
|
+ require.NoError(t, err)
|
|
|
|
|
+
|
|
|
|
|
+ var refunded dao.MonthlyCard
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.First(&refunded, card.ID).Error)
|
|
|
|
|
+ require.Equal(t, "refunded", refunded.PaymentStatus)
|
|
|
|
|
+
|
|
|
|
|
+ var whitelistCount int64
|
|
|
|
|
+ require.NoError(t, global.GVA_DB.Model(&dao.Shortlist{}).
|
|
|
|
|
+ Where("vehicle_id = ? AND list_type = ?", vehicle.ID, "白名单").Count(&whitelistCount).Error)
|
|
|
|
|
+ require.Equal(t, int64(0), whitelistCount)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func TestRefundRejectsUnknownCard(t *testing.T) {
|
|
|
|
|
+ svc := setupMonthlyServiceTest(t)
|
|
|
|
|
+ err := svc.Refund(99999)
|
|
|
|
|
+ require.Error(t, err)
|
|
|
|
|
+ require.Contains(t, err.Error(), "月卡记录不存在")
|
|
|
|
|
+}
|