| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- package vehicle
- import (
- "fmt"
- "testing"
- "time"
- "github.com/glebarez/sqlite"
- "github.com/stretchr/testify/require"
- "gorm.io/gorm"
- "wails-app/internal/dao"
- "wails-app/internal/global"
- "wails-app/internal/model/vehicle/request"
- ticketService "wails-app/internal/modules/digital-ticket/service"
- paymentService "wails-app/internal/modules/payment/service"
- )
- func setupVehiclePaymentTest(t *testing.T) *VehicleService {
- t.Helper()
- dsn := fmt.Sprintf("file:vehicle-payment-%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)
- sqlDB.SetMaxOpenConns(8)
- t.Cleanup(func() {
- _ = sqlDB.Close()
- if global.GVA_DB == db {
- global.GVA_DB = nil
- }
- })
- global.GVA_DB = db
- require.NoError(t, db.AutoMigrate(
- &dao.Owner{}, &dao.Vehicle{}, &dao.VehicleType{}, &dao.FeeConfig{}, &dao.VehicleRecord{}, &dao.DigitalTicket{},
- &dao.PaymentMethodConfig{}, &dao.PaymentEntryConfig{}, &dao.PaymentEntryMethod{}, &dao.PaymentRecord{}, &dao.Shortlist{},
- &dao.Channel{}, &dao.UHFReader{}, &dao.ParkingLot{}, &dao.MonthlyCard{},
- ))
- require.NoError(t, db.Create(&dao.ParkingLot{
- LotCode: "TEST-LOT", LotName: "测试停车场", Capacity: 100, Available: 100,
- AllowMonthlyWhenFull: true, AllowVIPWhenFull: true,
- }).Error)
- require.NoError(t, db.Create(&dao.PaymentMethodConfig{
- Code: paymentService.PaymentCash, Name: "cash", InputMode: paymentService.InputModeManualAmount,
- Scenes: []string{paymentService.EntryCounter}, Enabled: true, IsSystem: true,
- }).Error)
- require.NoError(t, db.Create(&dao.PaymentEntryConfig{
- Code: paymentService.EntryCounter, Name: "counter", Enabled: true, IsSystem: true,
- }).Error)
- require.NoError(t, db.Create(&dao.PaymentEntryMethod{
- EntryCode: paymentService.EntryCounter, MethodCode: paymentService.PaymentCash,
- }).Error)
- return &VehicleService{}
- }
- func createVehiclePaymentFixture(t *testing.T, withTicket bool) (*dao.VehicleRecord, *dao.Vehicle) {
- t.Helper()
- vehicleType := &dao.VehicleType{Name: "temporary", IsSystem: true}
- require.NoError(t, global.GVA_DB.Create(vehicleType).Error)
- require.NoError(t, global.GVA_DB.Create(&dao.FeeConfig{
- VehicleTypeID: vehicleType.ID, StartTime: 0, StartFee: 0, UnitTime: 60, UnitFee: 1, DailyMaxFee: 100,
- }).Error)
- vehicle := &dao.Vehicle{PlateNumber: "PAY-EDGE", VehicleTypeID: vehicleType.ID}
- require.NoError(t, global.GVA_DB.Create(vehicle).Error)
- record := &dao.VehicleRecord{
- PlateNumber: vehicle.PlateNumber, EntryTime: time.Now().Add(-2 * time.Hour), PaymentStatus: "unpaid",
- }
- require.NoError(t, global.GVA_DB.Create(record).Error)
- if withTicket {
- _, err := ticketService.NewTicketService().CreateTx(global.GVA_DB, vehicle.PlateNumber, "test", record.ID)
- require.NoError(t, err)
- }
- return record, vehicle
- }
- func TestExitConfirmRollsBackPaymentWhenTicketUpdateFails(t *testing.T) {
- svc := setupVehiclePaymentTest(t)
- record, vehicle := createVehiclePaymentFixture(t, false)
- _, err := svc.ExitConfirm(request.ExitConfirmRequest{
- PlateNumber: vehicle.PlateNumber, PaymentEntry: paymentService.EntryCounter,
- PaymentMethod: paymentService.PaymentCash, PaidAmount: 10,
- }, 1)
- require.Error(t, err)
- var persistedRecord dao.VehicleRecord
- require.NoError(t, global.GVA_DB.First(&persistedRecord, record.ID).Error)
- require.Nil(t, persistedRecord.ExitTime)
- require.Equal(t, "unpaid", persistedRecord.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)
- var persistedVehicle dao.Vehicle
- require.NoError(t, global.GVA_DB.First(&persistedVehicle, vehicle.ID).Error)
- require.Zero(t, persistedVehicle.TotalFee)
- require.Zero(t, persistedVehicle.TotalStayTime)
- }
- func TestExitConfirmRejectsDuplicatePayment(t *testing.T) {
- svc := setupVehiclePaymentTest(t)
- record, vehicle := createVehiclePaymentFixture(t, true)
- req := request.ExitConfirmRequest{
- PlateNumber: vehicle.PlateNumber, PaymentEntry: paymentService.EntryCounter,
- PaymentMethod: paymentService.PaymentCash, PaidAmount: 10,
- }
- _, err := svc.ExitConfirm(req, 1)
- require.NoError(t, err)
- _, err = svc.ExitConfirm(req, 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.EqualValues(t, 1, paymentCount)
- var ticket dao.DigitalTicket
- require.NoError(t, global.GVA_DB.Where("vehicle_record_id = ?", record.ID).First(&ticket).Error)
- require.Equal(t, ticketService.StateExited, ticket.State)
- }
- func TestPayDigitalTicketUpdatesPaymentAndVehicleRecordTogether(t *testing.T) {
- svc := setupVehiclePaymentTest(t)
- record, _ := createVehiclePaymentFixture(t, true)
- var ticket dao.DigitalTicket
- require.NoError(t, global.GVA_DB.Where("vehicle_record_id = ?", record.ID).First(&ticket).Error)
- require.NoError(t, svc.PayDigitalTicket(
- ticket.TicketNo, paymentService.EntryCounter, paymentService.PaymentCash, "ORDER-UNIFIED-1", 10, 7,
- ))
- var persistedRecord dao.VehicleRecord
- require.NoError(t, global.GVA_DB.First(&persistedRecord, record.ID).Error)
- require.Equal(t, "paid", persistedRecord.PaymentStatus)
- require.Equal(t, paymentService.EntryCounter, persistedRecord.PaymentEntry)
- require.Equal(t, paymentService.PaymentCash, persistedRecord.PaymentMethod)
- require.Positive(t, persistedRecord.Fee)
- require.Nil(t, persistedRecord.ExitTime)
- var payment dao.PaymentRecord
- require.NoError(t, global.GVA_DB.Where("record_id = ?", record.ID).First(&payment).Error)
- require.EqualValues(t, 7, payment.OperatorID)
- require.InDelta(t, persistedRecord.Fee, payment.Amount, 0.001)
- require.NoError(t, global.GVA_DB.First(&ticket, ticket.ID).Error)
- require.Equal(t, ticketService.StatePaid, ticket.State)
- require.Equal(t, "ORDER-UNIFIED-1", ticket.PaymentOrderNo)
- require.InDelta(t, payment.Amount, ticket.Fee, 0.001)
- }
- func TestExitConfirmConsumesPrepaidTicketWithoutSecondPayment(t *testing.T) {
- svc := setupVehiclePaymentTest(t)
- record, vehicle := createVehiclePaymentFixture(t, true)
- var ticket dao.DigitalTicket
- require.NoError(t, global.GVA_DB.Where("vehicle_record_id = ?", record.ID).First(&ticket).Error)
- require.NoError(t, svc.PayDigitalTicket(
- ticket.TicketNo, paymentService.EntryCounter, paymentService.PaymentCash, "ORDER-UNIFIED-2", 10, 7,
- ))
- result, err := svc.ExitConfirm(request.ExitConfirmRequest{
- PlateNumber: vehicle.PlateNumber,
- // A device may still send its normal free-release request. A prepaid
- // ticket must consume the recorded payment instead of charging again.
- PaymentEntry: paymentService.EntryCounter, PaymentMethod: paymentService.PaymentFree,
- }, 9)
- require.NoError(t, err)
- require.Equal(t, paymentService.PaymentCash, result.PaymentMethod)
- require.Equal(t, paymentService.EntryCounter, result.PaymentEntry)
- 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)
- var persistedRecord dao.VehicleRecord
- require.NoError(t, global.GVA_DB.First(&persistedRecord, record.ID).Error)
- require.NotNil(t, persistedRecord.ExitTime)
- require.Equal(t, "paid", persistedRecord.PaymentStatus)
- require.Equal(t, paymentService.PaymentCash, persistedRecord.PaymentMethod)
- require.NoError(t, global.GVA_DB.First(&ticket, ticket.ID).Error)
- require.Equal(t, ticketService.StateExited, ticket.State)
- }
|