package printer import ( "fmt" "strings" "testing" "time" "github.com/gin-gonic/gin" "github.com/glebarez/sqlite" "github.com/stretchr/testify/require" "gorm.io/gorm" "wails-app/internal/dao" "wails-app/internal/global" commonModel "wails-app/internal/model/common" ) type fakeTicketMachinePassage struct { req commonModel.PassageRequest result *commonModel.PassageResult err error } func (f *fakeTicketMachinePassage) HandlePassage(req commonModel.PassageRequest) (*commonModel.PassageResult, error) { f.req = req return f.result, f.err } type fakeTicketMachinePrinter struct { printerID uint lotName string channelCode string plateNo string ticketID uint ticketNo string err error } func (f *fakeTicketMachinePrinter) PrintTicket(printerID uint, lotName, channelCode, plateNo string, ticketID uint, ticketNo string) error { f.printerID = printerID f.lotName = lotName f.channelCode = channelCode f.plateNo = plateNo f.ticketID = ticketID f.ticketNo = ticketNo return f.err } func setupTicketMachineTest(t *testing.T) (*dao.ParkingLot, *dao.Channel) { t.Helper() dsn := fmt.Sprintf("file:ticket-machine-%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.Channel{}, &dao.UHFReader{})) lot := &dao.ParkingLot{LotCode: "LOT-TICKET", LotName: "票机停车场", Capacity: 50, Available: 50} require.NoError(t, db.Create(lot).Error) channel := &dao.Channel{ ChannelCode: "TICKET-IN", ChannelName: "票机入口", Direction: "in", AllowTemporary: true, ParkingLotID: lot.ID, } require.NoError(t, db.Create(channel).Error) return lot, channel } func createTicketMachineDevice(t *testing.T, channelID uint, code string) *dao.UHFReader { t.Helper() device := &dao.UHFReader{ DeviceCode: code, DeviceName: code + "名称", DeviceType: "gate", ConnectType: "tcp", IsActive: true, ChannelID: channelID, } require.NoError(t, global.GVA_DB.Create(device).Error) return device } func TestProcessTicketMachineButtonUsesUnifiedPassageAndReturnedTicket(t *testing.T) { lot, channel := setupTicketMachineTest(t) device := createTicketMachineDevice(t, channel.ID, "GATE-TICKET-01") passage := &fakeTicketMachinePassage{result: &commonModel.PassageResult{ Success: true, Direction: "in", PlateNumber: "TKT-RESULT", SessionID: 41, TicketID: 73, TicketNo: "ticket-no-73", EntryTime: 123456, BusinessCompleted: true, GateOpened: true, GateStatus: "opened", }} printer := &fakeTicketMachinePrinter{} result, err := processTicketMachineButton(buttonReq{ PrinterID: 9, ChannelCode: channel.ChannelCode, }, passage, printer) require.NoError(t, err) require.True(t, result.AnonymousVehicle) require.Equal(t, lot.ID, result.ParkingLotID) require.Equal(t, channel.ChannelCode, result.ChannelCode) require.Equal(t, device.DeviceCode, result.DeviceCode) require.EqualValues(t, 41, result.SessionID) require.EqualValues(t, 73, result.TicketID) require.Equal(t, "ticket-no-73", result.TicketNo) require.Equal(t, "printed", result.PrintStatus) require.Equal(t, device.DeviceCode, passage.req.DeviceCode) require.Equal(t, "in", passage.req.Direction) require.Equal(t, "ticket_machine", passage.req.TriggerSource) require.True(t, strings.HasPrefix(passage.req.PlateNumber, "TKT")) require.Len(t, passage.req.PlateNumber, 19) require.EqualValues(t, 9, printer.printerID) require.Equal(t, lot.LotName, printer.lotName) require.Equal(t, channel.ChannelCode, printer.channelCode) require.Empty(t, printer.plateNo) require.EqualValues(t, 73, printer.ticketID) require.Equal(t, "ticket-no-73", printer.ticketNo) } func TestProcessTicketMachineButtonRequiresDeviceWhenChannelHasMultiple(t *testing.T) { _, channel := setupTicketMachineTest(t) createTicketMachineDevice(t, channel.ID, "GATE-A") createTicketMachineDevice(t, channel.ID, "GATE-B") result, err := processTicketMachineButton(buttonReq{ PrinterID: 1, ChannelCode: channel.ChannelCode, }, &fakeTicketMachinePassage{}, &fakeTicketMachinePrinter{}) require.Nil(t, result) require.EqualError(t, err, "票机通道绑定了多个启用设备,必须指定 device_code") } func TestProcessTicketMachineButtonKeepsBusinessResultWhenPrintFails(t *testing.T) { _, channel := setupTicketMachineTest(t) createTicketMachineDevice(t, channel.ID, "GATE-PRINT") passage := &fakeTicketMachinePassage{result: &commonModel.PassageResult{ SessionID: 8, TicketID: 9, TicketNo: "ticket-9", PlateNumber: "TEST-PLATE", EntryTime: 100, BusinessCompleted: true, GateStatus: "opened", }} printer := &fakeTicketMachinePrinter{err: fmt.Errorf("paper out")} result, err := processTicketMachineButton(buttonReq{ PrinterID: 1, ChannelCode: channel.ChannelCode, PlateNo: "TEST-PLATE", }, passage, printer) require.ErrorContains(t, err, "打印失败") require.EqualValues(t, 8, result.SessionID) require.EqualValues(t, 9, result.TicketID) require.Equal(t, "completed", result.BusinessStatus) require.Equal(t, "failed", result.PrintStatus) } func TestPrinterDebugRoutesAreDisabledByDefault(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() group := router.Group("") SetupPrinterRouter(group) SetupPrinterDebugRoutes(group, false) routes := router.Routes() require.Len(t, routes, 1) require.Equal(t, "POST", routes[0].Method) require.Equal(t, "/ticket-machine/button", routes[0].Path) } func TestPrinterDebugRoutesUseSafeMethodsWhenEnabled(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() SetupPrinterDebugRoutes(router.Group(""), true) methods := map[string]string{} for _, route := range router.Routes() { methods[route.Path] = route.Method } require.Equal(t, "POST", methods["/ticket-machine/test"]) require.Equal(t, "POST", methods["/ticket-machine/debug"]) require.Equal(t, "GET", methods["/ticket-machine/usb-list"]) }