edge_cases_test.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package service
  2. import (
  3. "errors"
  4. "sync"
  5. "testing"
  6. "github.com/stretchr/testify/require"
  7. "gorm.io/gorm"
  8. "wails-app/internal/dao"
  9. "wails-app/internal/global"
  10. )
  11. func TestPaymentRejectsNegativeAndInsufficientAmounts(t *testing.T) {
  12. svc := setupPaymentServiceTest(t)
  13. for _, amounts := range [][2]float64{{-1, 0}, {1, -1}, {-1, -1}} {
  14. _, _, err := svc.ValidatePayment(nil, PaymentCash, EntryCounter, amounts[0], amounts[1])
  15. require.Error(t, err)
  16. }
  17. _, _, err := svc.ValidatePayment(nil, PaymentCash, EntryCounter, 10, 0)
  18. require.Error(t, err)
  19. record := &dao.VehicleRecord{PaymentStatus: "unpaid"}
  20. require.NoError(t, global.GVA_DB.Create(record).Error)
  21. _, err = svc.ProcessPayment(record.ID, PaymentCash, -1, 0, 1)
  22. require.Error(t, err)
  23. var paymentCount int64
  24. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  25. require.Zero(t, paymentCount)
  26. }
  27. func TestZeroAmountPaymentIsAllowedForZeroFeeSettlement(t *testing.T) {
  28. svc := setupPaymentServiceTest(t)
  29. _, paidAmount, err := svc.ValidatePayment(nil, PaymentPOS, EntryCounter, 0, 999)
  30. require.NoError(t, err)
  31. require.Zero(t, paidAmount)
  32. record := &dao.VehicleRecord{PaymentStatus: "unpaid"}
  33. require.NoError(t, global.GVA_DB.Create(record).Error)
  34. payment, err := svc.ProcessPayment(record.ID, PaymentCash, 0, 0, 1)
  35. require.NoError(t, err)
  36. require.NotNil(t, payment)
  37. require.Zero(t, payment.Amount)
  38. require.Zero(t, payment.PaidAmount)
  39. require.Zero(t, payment.ChangeAmount)
  40. var persisted dao.VehicleRecord
  41. require.NoError(t, global.GVA_DB.First(&persisted, record.ID).Error)
  42. require.Equal(t, "paid", persisted.PaymentStatus)
  43. }
  44. func TestPaymentRejectsDuplicateSettlement(t *testing.T) {
  45. svc := setupPaymentServiceTest(t)
  46. record := &dao.VehicleRecord{PaymentStatus: "unpaid"}
  47. require.NoError(t, global.GVA_DB.Create(record).Error)
  48. _, err := svc.ProcessPayment(record.ID, PaymentCash, 10, 10, 1)
  49. require.NoError(t, err)
  50. _, err = svc.ProcessPayment(record.ID, PaymentCash, 10, 10, 1)
  51. require.ErrorIs(t, err, ErrPaymentAlreadyProcessed)
  52. var paymentCount int64
  53. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  54. require.EqualValues(t, 1, paymentCount)
  55. }
  56. func TestPaymentConcurrentSettlementHasSingleWinner(t *testing.T) {
  57. svc := setupPaymentServiceTest(t)
  58. record := &dao.VehicleRecord{PaymentStatus: "unpaid"}
  59. require.NoError(t, global.GVA_DB.Create(record).Error)
  60. const attempts = 8
  61. start := make(chan struct{})
  62. results := make(chan error, attempts)
  63. var wg sync.WaitGroup
  64. for i := 0; i < attempts; i++ {
  65. wg.Add(1)
  66. go func() {
  67. defer wg.Done()
  68. <-start
  69. _, err := svc.ProcessPayment(record.ID, PaymentCash, 0, 0, 1)
  70. results <- err
  71. }()
  72. }
  73. close(start)
  74. wg.Wait()
  75. close(results)
  76. successes := 0
  77. for err := range results {
  78. if err == nil {
  79. successes++
  80. }
  81. }
  82. require.Equal(t, 1, successes)
  83. var paymentCount int64
  84. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  85. require.EqualValues(t, 1, paymentCount)
  86. }
  87. func TestPaymentTransactionRollbackLeavesNoPartialState(t *testing.T) {
  88. svc := setupPaymentServiceTest(t)
  89. record := &dao.VehicleRecord{PaymentStatus: "unpaid"}
  90. require.NoError(t, global.GVA_DB.Create(record).Error)
  91. rollbackErr := errors.New("force rollback")
  92. err := global.GVA_DB.Transaction(func(tx *gorm.DB) error {
  93. if _, err := svc.ProcessPaymentTxAtEntry(tx, record.ID, EntryCounter, PaymentCash, 10, 10, 1); err != nil {
  94. return err
  95. }
  96. return rollbackErr
  97. })
  98. require.ErrorIs(t, err, rollbackErr)
  99. var persisted dao.VehicleRecord
  100. require.NoError(t, global.GVA_DB.First(&persisted, record.ID).Error)
  101. require.Equal(t, "unpaid", persisted.PaymentStatus)
  102. var paymentCount int64
  103. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  104. require.Zero(t, paymentCount)
  105. }
  106. func TestPaymentMissingVehicleRollsBackCreatedRecord(t *testing.T) {
  107. svc := setupPaymentServiceTest(t)
  108. _, err := svc.ProcessPayment(999999, PaymentCash, 0, 0, 1)
  109. require.ErrorIs(t, err, ErrVehicleRecordNotFound)
  110. var paymentCount int64
  111. require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Count(&paymentCount).Error)
  112. require.Zero(t, paymentCount)
  113. }