package service import ( "errors" "sync" "testing" "github.com/stretchr/testify/require" "gorm.io/gorm" "wails-app/internal/dao" "wails-app/internal/global" ) func TestPaymentRejectsNegativeAndInsufficientAmounts(t *testing.T) { svc := setupPaymentServiceTest(t) for _, amounts := range [][2]float64{{-1, 0}, {1, -1}, {-1, -1}} { _, _, err := svc.ValidatePayment(nil, PaymentCash, EntryCounter, amounts[0], amounts[1]) require.Error(t, err) } _, _, err := svc.ValidatePayment(nil, PaymentCash, EntryCounter, 10, 0) require.Error(t, err) record := &dao.VehicleRecord{PaymentStatus: "unpaid"} require.NoError(t, global.GVA_DB.Create(record).Error) _, err = svc.ProcessPayment(record.ID, PaymentCash, -1, 0, 1) require.Error(t, err) var paymentCount int64 require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error) require.Zero(t, paymentCount) } func TestZeroAmountPaymentIsAllowedForZeroFeeSettlement(t *testing.T) { svc := setupPaymentServiceTest(t) _, paidAmount, err := svc.ValidatePayment(nil, PaymentPOS, EntryCounter, 0, 999) require.NoError(t, err) require.Zero(t, paidAmount) record := &dao.VehicleRecord{PaymentStatus: "unpaid"} require.NoError(t, global.GVA_DB.Create(record).Error) payment, err := svc.ProcessPayment(record.ID, PaymentCash, 0, 0, 1) require.NoError(t, err) require.NotNil(t, payment) require.Zero(t, payment.Amount) require.Zero(t, payment.PaidAmount) require.Zero(t, payment.ChangeAmount) var persisted dao.VehicleRecord require.NoError(t, global.GVA_DB.First(&persisted, record.ID).Error) require.Equal(t, "paid", persisted.PaymentStatus) } func TestPaymentRejectsDuplicateSettlement(t *testing.T) { svc := setupPaymentServiceTest(t) record := &dao.VehicleRecord{PaymentStatus: "unpaid"} require.NoError(t, global.GVA_DB.Create(record).Error) _, err := svc.ProcessPayment(record.ID, PaymentCash, 10, 10, 1) require.NoError(t, err) _, err = svc.ProcessPayment(record.ID, PaymentCash, 10, 10, 1) require.ErrorIs(t, err, ErrPaymentAlreadyProcessed) var paymentCount int64 require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error) require.EqualValues(t, 1, paymentCount) } func TestPaymentConcurrentSettlementHasSingleWinner(t *testing.T) { svc := setupPaymentServiceTest(t) record := &dao.VehicleRecord{PaymentStatus: "unpaid"} require.NoError(t, global.GVA_DB.Create(record).Error) const attempts = 8 start := make(chan struct{}) results := make(chan error, attempts) var wg sync.WaitGroup for i := 0; i < attempts; i++ { wg.Add(1) go func() { defer wg.Done() <-start _, err := svc.ProcessPayment(record.ID, PaymentCash, 0, 0, 1) results <- err }() } close(start) wg.Wait() close(results) successes := 0 for err := range results { if err == nil { successes++ } } require.Equal(t, 1, successes) var paymentCount int64 require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error) require.EqualValues(t, 1, paymentCount) } func TestPaymentTransactionRollbackLeavesNoPartialState(t *testing.T) { svc := setupPaymentServiceTest(t) record := &dao.VehicleRecord{PaymentStatus: "unpaid"} require.NoError(t, global.GVA_DB.Create(record).Error) rollbackErr := errors.New("force rollback") err := global.GVA_DB.Transaction(func(tx *gorm.DB) error { if _, err := svc.ProcessPaymentTxAtEntry(tx, record.ID, EntryCounter, PaymentCash, 10, 10, 1); err != nil { return err } return rollbackErr }) require.ErrorIs(t, err, rollbackErr) var persisted dao.VehicleRecord require.NoError(t, global.GVA_DB.First(&persisted, record.ID).Error) require.Equal(t, "unpaid", persisted.PaymentStatus) var paymentCount int64 require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error) require.Zero(t, paymentCount) } func TestPaymentMissingVehicleRollsBackCreatedRecord(t *testing.T) { svc := setupPaymentServiceTest(t) _, err := svc.ProcessPayment(999999, PaymentCash, 0, 0, 1) require.ErrorIs(t, err, ErrVehicleRecordNotFound) var paymentCount int64 require.NoError(t, global.GVA_DB.Model(&dao.PaymentRecord{}).Count(&paymentCount).Error) require.Zero(t, paymentCount) }