service_test.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package service
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/glebarez/sqlite"
  6. "github.com/stretchr/testify/require"
  7. "gorm.io/gorm"
  8. "wails-app/internal/dao"
  9. "wails-app/internal/global"
  10. )
  11. func setupMonthlyServiceTest(t *testing.T) *MonthlyCardService {
  12. t.Helper()
  13. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  14. require.NoError(t, err)
  15. require.NoError(t, db.AutoMigrate(&dao.Vehicle{}, &dao.MonthlyCard{}, &dao.Shortlist{}, &dao.PaymentRecord{}))
  16. global.GVA_DB = db
  17. t.Cleanup(func() { global.GVA_DB = nil })
  18. return NewMonthlyCardService()
  19. }
  20. func createVehicle(t *testing.T, plate string) *dao.Vehicle {
  21. t.Helper()
  22. vehicle := dao.Vehicle{PlateNumber: plate, VehicleTypeID: 1}
  23. require.NoError(t, global.GVA_DB.Create(&vehicle).Error)
  24. return &vehicle
  25. }
  26. func countTable(t *testing.T, model interface{}) int64 {
  27. t.Helper()
  28. var count int64
  29. require.NoError(t, global.GVA_DB.Model(model).Count(&count).Error)
  30. return count
  31. }
  32. func requireSameDate(t *testing.T, expected, actual time.Time) {
  33. t.Helper()
  34. require.Equal(t, expected.Format("2006-01-02"), actual.Format("2006-01-02"))
  35. }
  36. func TestCreateWritesCardWhitelistAndPayment(t *testing.T) {
  37. svc := setupMonthlyServiceTest(t)
  38. vehicle := createVehicle(t, "MC-001")
  39. card, err := svc.Create(vehicle.ID, "month", 300, 7, "测试办理")
  40. require.NoError(t, err)
  41. require.NotZero(t, card.ID)
  42. require.Equal(t, "paid", card.PaymentStatus)
  43. require.True(t, card.EndDate.After(time.Now().Add(29*24*time.Hour)))
  44. // 三张表全部写入
  45. require.Equal(t, int64(1), countTable(t, &dao.MonthlyCard{}))
  46. require.Equal(t, int64(1), countTable(t, &dao.Shortlist{}))
  47. require.Equal(t, int64(1), countTable(t, &dao.PaymentRecord{}))
  48. // 白名单到期日与月卡一致
  49. var shortlist dao.Shortlist
  50. require.NoError(t, global.GVA_DB.First(&shortlist).Error)
  51. require.Equal(t, "白名单", shortlist.ListType)
  52. require.NotNil(t, shortlist.ExpirationTime)
  53. require.True(t, shortlist.ExpirationTime.Equal(card.EndDate))
  54. // 支付流水
  55. var pay dao.PaymentRecord
  56. require.NoError(t, global.GVA_DB.First(&pay).Error)
  57. require.Equal(t, 300.0, pay.Amount)
  58. require.Equal(t, uint(7), pay.OperatorID)
  59. }
  60. func TestCreateUsesEffectiveDate(t *testing.T) {
  61. svc := setupMonthlyServiceTest(t)
  62. vehicle := createVehicle(t, "MC-DATE-001")
  63. effectiveDate := time.Now().AddDate(0, 0, 5)
  64. card, err := svc.Create(vehicle.ID, "month", 300, 7, "", effectiveDate)
  65. require.NoError(t, err)
  66. requireSameDate(t, normalizeDate(effectiveDate), card.StartDate)
  67. requireSameDate(t, normalizeDate(effectiveDate).AddDate(0, 0, 30), card.EndDate)
  68. }
  69. func TestCreateRejectsPastEffectiveDate(t *testing.T) {
  70. svc := setupMonthlyServiceTest(t)
  71. vehicle := createVehicle(t, "MC-DATE-002")
  72. _, err := svc.Create(vehicle.ID, "month", 300, 7, "", time.Now().AddDate(0, 0, -1))
  73. require.ErrorContains(t, err, "生效日期不能早于今天")
  74. }
  75. func TestCreateRollsBackWhenWhitelistWriteFails(t *testing.T) {
  76. svc := setupMonthlyServiceTest(t)
  77. vehicle := createVehicle(t, "MC-002")
  78. // 模拟白名单写入失败:删除 shortlist 表
  79. require.NoError(t, global.GVA_DB.Exec("DROP TABLE shortlist").Error)
  80. _, err := svc.Create(vehicle.ID, "month", 300, 7, "")
  81. require.Error(t, err)
  82. require.Contains(t, err.Error(), "白名单")
  83. // 月卡与流水均回滚
  84. require.Equal(t, int64(0), countTable(t, &dao.MonthlyCard{}))
  85. require.Equal(t, int64(0), countTable(t, &dao.PaymentRecord{}))
  86. }
  87. func TestCreateRollsBackWhenPaymentWriteFails(t *testing.T) {
  88. svc := setupMonthlyServiceTest(t)
  89. vehicle := createVehicle(t, "MC-003")
  90. // 模拟支付流水写入失败:删除 payment_record 表
  91. require.NoError(t, global.GVA_DB.Exec("DROP TABLE payment_record").Error)
  92. _, err := svc.Create(vehicle.ID, "month", 300, 7, "")
  93. require.Error(t, err)
  94. require.Contains(t, err.Error(), "支付流水")
  95. // 月卡与白名单均回滚
  96. require.Equal(t, int64(0), countTable(t, &dao.MonthlyCard{}))
  97. require.Equal(t, int64(0), countTable(t, &dao.Shortlist{}))
  98. }
  99. func TestCreateRejectsDuplicateActiveCard(t *testing.T) {
  100. svc := setupMonthlyServiceTest(t)
  101. vehicle := createVehicle(t, "MC-004")
  102. _, err := svc.Create(vehicle.ID, "month", 300, 7, "")
  103. require.NoError(t, err)
  104. _, err = svc.Create(vehicle.ID, "month", 300, 7, "")
  105. require.Error(t, err)
  106. require.Contains(t, err.Error(), "已有有效月卡")
  107. require.Equal(t, int64(1), countTable(t, &dao.MonthlyCard{}))
  108. }
  109. func TestCreateRejectsUnknownVehicle(t *testing.T) {
  110. svc := setupMonthlyServiceTest(t)
  111. _, err := svc.Create(99999, "month", 300, 7, "")
  112. require.Error(t, err)
  113. require.Contains(t, err.Error(), "车辆不存在")
  114. }
  115. func TestRenewExtendsFromEndDateWhenActive(t *testing.T) {
  116. svc := setupMonthlyServiceTest(t)
  117. vehicle := createVehicle(t, "MC-005")
  118. now := time.Now()
  119. card := &dao.MonthlyCard{
  120. VehicleID: vehicle.ID, CardType: "month",
  121. StartDate: now.AddDate(0, -1, 0), EndDate: now.AddDate(0, 0, 10),
  122. Fee: 300, PaymentStatus: "paid",
  123. }
  124. require.NoError(t, global.GVA_DB.Create(card).Error)
  125. originalEnd := card.EndDate
  126. require.NoError(t, global.GVA_DB.Create(&dao.Shortlist{
  127. VehicleId: int(vehicle.ID), ListType: "白名单", ExpirationTime: &originalEnd,
  128. }).Error)
  129. err := svc.Renew(card.ID, "month", 300, 7)
  130. require.NoError(t, err)
  131. var renewed dao.MonthlyCard
  132. require.NoError(t, global.GVA_DB.First(&renewed, card.ID).Error)
  133. // 未过期:从原到期日顺延 30 天
  134. require.True(t, renewed.EndDate.After(now.AddDate(0, 0, 39)), "到期日应约为原到期日+30天")
  135. require.True(t, renewed.EndDate.Before(now.AddDate(0, 0, 41)))
  136. require.Equal(t, 600.0, renewed.Fee)
  137. // 白名单到期日同步
  138. var shortlist dao.Shortlist
  139. require.NoError(t, global.GVA_DB.Where("vehicle_id = ?", vehicle.ID).First(&shortlist).Error)
  140. require.True(t, shortlist.ExpirationTime.Equal(renewed.EndDate))
  141. }
  142. func TestRenewUsesExplicitEffectiveDate(t *testing.T) {
  143. svc := setupMonthlyServiceTest(t)
  144. vehicle := createVehicle(t, "MC-DATE-003")
  145. now := time.Now()
  146. card := &dao.MonthlyCard{VehicleID: vehicle.ID, CardType: "month", StartDate: now.AddDate(0, -1, 0), EndDate: now.AddDate(0, 0, 2), Fee: 300, PaymentStatus: "paid"}
  147. require.NoError(t, global.GVA_DB.Create(card).Error)
  148. effectiveDate := time.Now().AddDate(0, 0, 3)
  149. require.NoError(t, svc.Renew(card.ID, "month", 300, 7, effectiveDate))
  150. var renewed dao.MonthlyCard
  151. require.NoError(t, global.GVA_DB.First(&renewed, card.ID).Error)
  152. requireSameDate(t, normalizeDate(effectiveDate), renewed.StartDate)
  153. requireSameDate(t, normalizeDate(effectiveDate).AddDate(0, 0, 30), renewed.EndDate)
  154. }
  155. func TestRenewStartsFromNowWhenCardExpired(t *testing.T) {
  156. svc := setupMonthlyServiceTest(t)
  157. vehicle := createVehicle(t, "MC-006")
  158. now := time.Now()
  159. card := &dao.MonthlyCard{
  160. VehicleID: vehicle.ID, CardType: "month",
  161. StartDate: now.AddDate(0, -3, 0), EndDate: now.AddDate(0, -2, 0), // 已过期 2 个月
  162. Fee: 300, PaymentStatus: "paid",
  163. }
  164. require.NoError(t, global.GVA_DB.Create(card).Error)
  165. expiredEnd := card.EndDate
  166. require.NoError(t, global.GVA_DB.Create(&dao.Shortlist{
  167. VehicleId: int(vehicle.ID), ListType: "白名单", ExpirationTime: &expiredEnd,
  168. }).Error)
  169. err := svc.Renew(card.ID, "month", 300, 7)
  170. require.NoError(t, err)
  171. var renewed dao.MonthlyCard
  172. require.NoError(t, global.GVA_DB.First(&renewed, card.ID).Error)
  173. // 已过期:从今天起算约 30 天,而不是从两个月前的到期日续期
  174. require.True(t, renewed.EndDate.After(now.AddDate(0, 0, 29)), "过期卡应从今天起算")
  175. require.True(t, renewed.EndDate.Before(now.AddDate(0, 0, 31)))
  176. // 白名单到期日同步为新到期日(未过期卡同样适用)
  177. var shortlist dao.Shortlist
  178. require.NoError(t, global.GVA_DB.Where("vehicle_id = ?", vehicle.ID).First(&shortlist).Error)
  179. require.True(t, shortlist.ExpirationTime.Equal(renewed.EndDate))
  180. }
  181. func TestRenewRollsBackWhenPaymentWriteFails(t *testing.T) {
  182. svc := setupMonthlyServiceTest(t)
  183. vehicle := createVehicle(t, "MC-007")
  184. now := time.Now()
  185. card := &dao.MonthlyCard{
  186. VehicleID: vehicle.ID, CardType: "month",
  187. StartDate: now, EndDate: now.AddDate(0, 0, 10),
  188. Fee: 300, PaymentStatus: "paid",
  189. }
  190. require.NoError(t, global.GVA_DB.Create(card).Error)
  191. originalEnd := card.EndDate
  192. require.NoError(t, global.GVA_DB.Exec("DROP TABLE payment_record").Error)
  193. err := svc.Renew(card.ID, "month", 300, 7)
  194. require.Error(t, err)
  195. // 到期日与费用均未变(回滚)
  196. var after dao.MonthlyCard
  197. require.NoError(t, global.GVA_DB.First(&after, card.ID).Error)
  198. require.True(t, after.EndDate.Equal(originalEnd))
  199. require.Equal(t, 300.0, after.Fee)
  200. }
  201. func TestRefundMarksRefundedAndRemovesWhitelist(t *testing.T) {
  202. svc := setupMonthlyServiceTest(t)
  203. vehicle := createVehicle(t, "MC-008")
  204. card, err := svc.Create(vehicle.ID, "month", 300, 7, "")
  205. require.NoError(t, err)
  206. require.Equal(t, int64(1), countTable(t, &dao.Shortlist{}))
  207. err = svc.Refund(card.ID)
  208. require.NoError(t, err)
  209. var refunded dao.MonthlyCard
  210. require.NoError(t, global.GVA_DB.First(&refunded, card.ID).Error)
  211. require.Equal(t, "refunded", refunded.PaymentStatus)
  212. var whitelistCount int64
  213. require.NoError(t, global.GVA_DB.Model(&dao.Shortlist{}).
  214. Where("vehicle_id = ? AND list_type = ?", vehicle.ID, "白名单").Count(&whitelistCount).Error)
  215. require.Equal(t, int64(0), whitelistCount)
  216. }
  217. func TestRefundRejectsUnknownCard(t *testing.T) {
  218. svc := setupMonthlyServiceTest(t)
  219. err := svc.Refund(99999)
  220. require.Error(t, err)
  221. require.Contains(t, err.Error(), "月卡记录不存在")
  222. }