| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- package service
- 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/modules/parking-session/model/request"
- )
- func setupParkingSessionTest(t *testing.T) *ParkingSessionService {
- t.Helper()
- dsn := fmt.Sprintf("file:parking-session-%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)
- t.Cleanup(func() {
- _ = sqlDB.Close()
- if global.GVA_DB == db {
- global.GVA_DB = nil
- }
- })
- global.GVA_DB = db
- require.NoError(t, db.AutoMigrate(&dao.ParkingLot{}, &dao.VehicleRecord{}))
- require.NoError(t, db.Create(&dao.ParkingLot{
- LotCode: "SESSION-LOT", LotName: "会话测试停车场", Capacity: 10, Available: 10,
- }).Error)
- return NewParkingSessionService()
- }
- func TestParkingSessionOpenResolveAndClose(t *testing.T) {
- svc := setupParkingSessionTest(t)
- var session *dao.VehicleRecord
- require.NoError(t, global.GVA_DB.Transaction(func(tx *gorm.DB) error {
- var err error
- session, err = svc.OpenSessionTx(tx, request.OpenSessionRequest{
- PlateNumber: "SESSION-001", RFIDTag: "RFID-001", ParkingLotID: 1,
- EntryChannelID: 11, EntryChannelCode: "ENTRY-A", EntryChannelName: "东入口通道",
- EntryDeviceCode: "GATE-ENTRY-A", EntryDeviceName: "东入口读卡器",
- })
- return err
- }))
- resolved, err := svc.ResolveActiveSession(request.ResolveActiveSessionRequest{
- PlateNumber: "SESSION-001", RFIDTag: "RFID-001",
- })
- require.NoError(t, err)
- require.Equal(t, session.ID, resolved.ID)
- require.NoError(t, global.GVA_DB.Transaction(func(tx *gorm.DB) error {
- if _, err := svc.MarkPaidTx(tx, session.ID, "counter", "cash", 12); err != nil {
- return err
- }
- _, err := svc.CloseSessionTx(tx, request.CloseSessionRequest{
- SessionID: session.ID, StayTime: 60, Fee: 12, PaymentEntry: "counter", PaymentMethod: "cash",
- ExitChannelID: 22, ExitChannelCode: "EXIT-B", ExitChannelName: "西出口通道",
- ExitDeviceCode: "GATE-EXIT-B", ExitDeviceName: "西出口读卡器",
- })
- return err
- }))
- var persisted dao.VehicleRecord
- require.NoError(t, global.GVA_DB.First(&persisted, session.ID).Error)
- require.EqualValues(t, 11, persisted.EntryChannelID)
- require.Equal(t, "ENTRY-A", persisted.EntryChannelCode)
- require.Equal(t, "东入口通道", persisted.EntryChannelName)
- require.Equal(t, "GATE-ENTRY-A", persisted.EntryDeviceCode)
- require.Equal(t, "东入口读卡器", persisted.EntryDeviceName)
- require.EqualValues(t, 22, persisted.ExitChannelID)
- require.Equal(t, "EXIT-B", persisted.ExitChannelCode)
- require.Equal(t, "西出口通道", persisted.ExitChannelName)
- require.Equal(t, "GATE-EXIT-B", persisted.ExitDeviceCode)
- require.Equal(t, "西出口读卡器", persisted.ExitDeviceName)
- _, err = svc.ResolveActiveSession(request.ResolveActiveSessionRequest{SessionID: session.ID})
- require.ErrorIs(t, err, ErrSessionAlreadyClosed)
- }
- func TestParkingSessionRejectsDuplicateAndConflictingIdentity(t *testing.T) {
- svc := setupParkingSessionTest(t)
- first, err := svc.OpenSessionTx(global.GVA_DB, request.OpenSessionRequest{PlateNumber: "SESSION-A", RFIDTag: "RFID-A"})
- require.NoError(t, err)
- _, err = svc.OpenSessionTx(global.GVA_DB, request.OpenSessionRequest{PlateNumber: "SESSION-A"})
- require.ErrorIs(t, err, ErrSessionAlreadyOpen)
- second, err := svc.OpenSessionTx(global.GVA_DB, request.OpenSessionRequest{PlateNumber: "SESSION-B", RFIDTag: "RFID-B"})
- require.NoError(t, err)
- require.NotEqual(t, first.ID, second.ID)
- _, err = svc.ResolveActiveSession(request.ResolveActiveSessionRequest{PlateNumber: "SESSION-A", RFIDTag: "RFID-B"})
- require.ErrorIs(t, err, ErrSessionIdentityConflict)
- }
- func TestParkingSessionFullAdmissionPolicies(t *testing.T) {
- tests := []struct {
- name string
- profile request.EntryAccessProfile
- policy string
- allow bool
- wantErr bool
- }{
- {name: "普通车辆满位拒绝", wantErr: true},
- {name: "临时车未配置时拒绝", profile: request.EntryAccessProfile{IsTemporary: true}, policy: "allow_temporary_when_full", wantErr: true},
- {name: "临时车配置后允许", profile: request.EntryAccessProfile{IsTemporary: true}, policy: "allow_temporary_when_full", allow: true},
- {name: "月租车配置后允许", profile: request.EntryAccessProfile{HasValidMonthlyCard: true}, policy: "allow_monthly_when_full", allow: true},
- {name: "VIP车配置后允许", profile: request.EntryAccessProfile{IsVIP: true}, policy: "allow_vip_when_full", allow: true},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- svc := setupParkingSessionTest(t)
- updates := map[string]interface{}{
- "capacity": 0, "occupied": 0, "available": 0,
- "allow_temporary_when_full": false,
- "allow_monthly_when_full": false,
- "allow_vip_when_full": false,
- }
- if tt.policy != "" {
- updates[tt.policy] = tt.allow
- }
- require.NoError(t, global.GVA_DB.Model(&dao.ParkingLot{}).Where("id = ?", 1).Updates(updates).Error)
- session, err := svc.OpenSessionTx(global.GVA_DB, request.OpenSessionRequest{
- PlateNumber: "FULL-" + tt.name, ParkingLotID: 1, AccessProfile: tt.profile,
- })
- if tt.wantErr {
- require.ErrorIs(t, err, ErrParkingLotFull)
- require.Nil(t, session)
- } else {
- require.NoError(t, err)
- require.NotNil(t, session)
- }
- var lot dao.ParkingLot
- require.NoError(t, global.GVA_DB.First(&lot, 1).Error)
- if tt.wantErr {
- require.Zero(t, lot.Occupied)
- } else {
- require.Equal(t, 1, lot.Occupied)
- }
- require.Zero(t, lot.Available)
- })
- }
- }
- func TestParkingSessionOverflowExitKeepsAvailabilityAccurate(t *testing.T) {
- svc := setupParkingSessionTest(t)
- require.NoError(t, global.GVA_DB.Model(&dao.ParkingLot{}).Where("id = ?", 1).Updates(map[string]interface{}{
- "capacity": 1, "occupied": 0, "available": 1, "allow_vip_when_full": true,
- }).Error)
- regular, err := svc.OpenSessionTx(global.GVA_DB, request.OpenSessionRequest{PlateNumber: "REGULAR", ParkingLotID: 1})
- require.NoError(t, err)
- overflow, err := svc.OpenSessionTx(global.GVA_DB, request.OpenSessionRequest{
- PlateNumber: "VIP-OVERFLOW", ParkingLotID: 1, AccessProfile: request.EntryAccessProfile{IsVIP: true},
- })
- require.NoError(t, err)
- closeSession := func(session *dao.VehicleRecord) {
- require.NoError(t, global.GVA_DB.Transaction(func(tx *gorm.DB) error {
- if _, err := svc.MarkPaidTx(tx, session.ID, "counter", "free", 0); err != nil {
- return err
- }
- _, err := svc.CloseSessionTx(tx, request.CloseSessionRequest{SessionID: session.ID})
- return err
- }))
- }
- closeSession(overflow)
- var lot dao.ParkingLot
- require.NoError(t, global.GVA_DB.First(&lot, 1).Error)
- require.Equal(t, 1, lot.Occupied)
- require.Zero(t, lot.Available)
- closeSession(regular)
- require.NoError(t, global.GVA_DB.First(&lot, 1).Error)
- require.Zero(t, lot.Occupied)
- require.Equal(t, 1, lot.Available)
- }
|