package centralpayment import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "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" ticketService "wails-app/internal/modules/digital-ticket/service" paymentService "wails-app/internal/modules/payment/service" utils "wails-app/internal/pkg" ) func TestPOFSimulatorHTTPFlow(t *testing.T) { dsn := fmt.Sprintf("file:pof-http-%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() }) previousDB := global.GVA_DB global.GVA_DB = db t.Cleanup(func() { global.GVA_DB = previousDB }) require.NoError(t, db.AutoMigrate( &dao.CentralPaymentDevice{}, &dao.CentralPaymentToken{}, &dao.CentralPaymentRequestLog{}, &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{}, )) device := dao.CentralPaymentDevice{ DeviceID: "POF-SIM-01", Username: "admin", PasswordHash: utils.BcryptHash("admin"), Enabled: true, PaymentEntry: "central_payment_machine", } require.NoError(t, db.Create(&device).Error) require.NoError(t, db.Create(&dao.PaymentMethodConfig{ Code: paymentService.PaymentCash, Name: "现金", InputMode: paymentService.InputModeManualAmount, Enabled: true, IsSystem: true, }).Error) require.NoError(t, db.Create(&dao.PaymentEntryConfig{ Code: "central_payment_machine", Name: "中央缴费机", Enabled: true, IsSystem: true, }).Error) require.NoError(t, db.Create(&dao.PaymentEntryMethod{ EntryCode: "central_payment_machine", MethodCode: paymentService.PaymentCash, }).Error) vehicleType := dao.VehicleType{Name: "临时车", IsSystem: true} require.NoError(t, db.Create(&vehicleType).Error) require.NoError(t, db.Create(&dao.FeeConfig{ VehicleTypeID: vehicleType.ID, StartTime: 0, StartFee: 0, UnitTime: 60, UnitFee: 1, DailyMaxFee: 100, }).Error) require.NoError(t, db.Create(&dao.Vehicle{PlateNumber: "POF-TEST", VehicleTypeID: vehicleType.ID}).Error) record := dao.VehicleRecord{ PlateNumber: "POF-TEST", EntryTime: time.Now().Add(-2 * time.Hour), PaymentStatus: "unpaid", } require.NoError(t, db.Create(&record).Error) ticket, err := ticketService.NewTicketService().CreateTx(db, record.PlateNumber, "pof-test", record.ID) require.NoError(t, err) router := gin.New() SetupPublicRouter(router.Group("")) server := httptest.NewServer(router) t.Cleanup(server.Close) login := pofPost(t, server.URL, "/api/login", map[string]string{"UserName": "admin", "Password": "admin"}) token, ok := login["Token"].(string) require.True(t, ok) require.NotEmpty(t, token) var loginLog dao.CentralPaymentRequestLog require.NoError(t, db.Where("endpoint = ?", "/api/login").First(&loginLog).Error) require.NotEmpty(t, loginLog.SourceIP) require.Contains(t, loginLog.RequestBody, `"password_format":"plain-text_expected"`) require.NotContains(t, loginLog.RequestBody, `"password":"admin"`) price := pofPost(t, server.URL, "/api/queryprice", map[string]interface{}{ "Token": token, "Data": map[string]string{"CarNo": record.PlateNumber, "InTime": record.EntryTime.Format("2006-01-02 15:04:05")}, }) priceData := price["Data"].(map[string]interface{}) require.Equal(t, ticket.TicketNo, priceData["OrderId"]) priceValue, ok := priceData["Price"].(string) require.True(t, ok) payment := map[string]interface{}{ "Token": token, "Data": map[string]interface{}{ "OrderId": ticket.TicketNo, "CarNo": record.PlateNumber, "Money": priceValue, "PayTime": time.Now().Format("2006-01-02 15:04:05"), "PayType": 0, "PayDev": 2, // DevId is an audit field; authentication relies on the Token. "DevId": "FC068C0D365F", "ReMark": "HTTP simulator", }, } require.EqualValues(t, 1, pofPost(t, server.URL, "/api/paysuccess", payment)["Result"]) status := pofPost(t, server.URL, "/api/paystatus", map[string]interface{}{ "Token": token, "Data": map[string]string{"OrderId": ticket.TicketNo, "CarNo": record.PlateNumber, "DevId": "FC068C0D365F"}, }) statusData := status["Data"].(map[string]interface{}) require.Equal(t, "paid", statusData["PayStatus"]) require.Equal(t, ticketService.StatePaid, statusData["State"]) // A common device retry must be accepted without generating another payment record. require.EqualValues(t, 1, pofPost(t, server.URL, "/api/paysuccess", payment)["Result"]) require.EqualValues(t, 1, pofPost(t, server.URL, "/api/heartbeat", map[string]string{"Token": token})["Result"]) var paymentCount int64 require.NoError(t, db.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error) require.EqualValues(t, 1, paymentCount) var persistedTicket dao.DigitalTicket require.NoError(t, db.First(&persistedTicket, ticket.ID).Error) require.Equal(t, ticketService.StatePaid, persistedTicket.State) } func pofPost(t *testing.T, baseURL, path string, payload interface{}) map[string]interface{} { t.Helper() body, err := json.Marshal(payload) require.NoError(t, err) response, err := http.Post(baseURL+path, "application/json", bytes.NewReader(body)) require.NoError(t, err) defer response.Body.Close() require.Equal(t, http.StatusOK, response.StatusCode) var envelope map[string]interface{} require.NoError(t, json.NewDecoder(response.Body).Decode(&envelope)) require.EqualValues(t, 1, envelope["Result"], "POF failure: %v", envelope["ReMark"]) return envelope }