api_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. package centralpayment
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "net/http/httptest"
  8. "testing"
  9. "time"
  10. "github.com/gin-gonic/gin"
  11. "github.com/glebarez/sqlite"
  12. "github.com/stretchr/testify/require"
  13. "gorm.io/gorm"
  14. "wails-app/internal/dao"
  15. "wails-app/internal/global"
  16. ticketService "wails-app/internal/modules/digital-ticket/service"
  17. paymentService "wails-app/internal/modules/payment/service"
  18. utils "wails-app/internal/pkg"
  19. )
  20. func TestPOFSimulatorHTTPFlow(t *testing.T) {
  21. dsn := fmt.Sprintf("file:pof-http-%d?mode=memory&cache=shared", time.Now().UnixNano())
  22. db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
  23. require.NoError(t, err)
  24. sqlDB, err := db.DB()
  25. require.NoError(t, err)
  26. t.Cleanup(func() { _ = sqlDB.Close() })
  27. previousDB := global.GVA_DB
  28. global.GVA_DB = db
  29. t.Cleanup(func() { global.GVA_DB = previousDB })
  30. require.NoError(t, db.AutoMigrate(
  31. &dao.CentralPaymentDevice{}, &dao.CentralPaymentToken{}, &dao.CentralPaymentRequestLog{},
  32. &dao.Owner{}, &dao.Vehicle{}, &dao.VehicleType{}, &dao.FeeConfig{}, &dao.VehicleRecord{}, &dao.DigitalTicket{},
  33. &dao.PaymentMethodConfig{}, &dao.PaymentEntryConfig{}, &dao.PaymentEntryMethod{}, &dao.PaymentRecord{},
  34. &dao.Shortlist{}, &dao.Channel{}, &dao.UHFReader{}, &dao.ParkingLot{}, &dao.MonthlyCard{},
  35. ))
  36. device := dao.CentralPaymentDevice{
  37. DeviceID: "POF-SIM-01", Username: "admin", PasswordHash: utils.BcryptHash("admin"),
  38. Enabled: true, PaymentEntry: "central_payment_machine",
  39. }
  40. require.NoError(t, db.Create(&device).Error)
  41. require.NoError(t, db.Create(&dao.PaymentMethodConfig{
  42. Code: paymentService.PaymentCash, Name: "现金", InputMode: paymentService.InputModeManualAmount,
  43. Enabled: true, IsSystem: true,
  44. }).Error)
  45. require.NoError(t, db.Create(&dao.PaymentEntryConfig{
  46. Code: "central_payment_machine", Name: "中央缴费机", Enabled: true, IsSystem: true,
  47. }).Error)
  48. require.NoError(t, db.Create(&dao.PaymentEntryMethod{
  49. EntryCode: "central_payment_machine", MethodCode: paymentService.PaymentCash,
  50. }).Error)
  51. vehicleType := dao.VehicleType{Name: "临时车", IsSystem: true}
  52. require.NoError(t, db.Create(&vehicleType).Error)
  53. require.NoError(t, db.Create(&dao.FeeConfig{
  54. VehicleTypeID: vehicleType.ID, StartTime: 0, StartFee: 0, UnitTime: 60, UnitFee: 1, DailyMaxFee: 100,
  55. }).Error)
  56. require.NoError(t, db.Create(&dao.Vehicle{PlateNumber: "POF-TEST", VehicleTypeID: vehicleType.ID}).Error)
  57. record := dao.VehicleRecord{
  58. PlateNumber: "POF-TEST", EntryTime: time.Now().Add(-2 * time.Hour), PaymentStatus: "unpaid",
  59. }
  60. require.NoError(t, db.Create(&record).Error)
  61. ticket, err := ticketService.NewTicketService().CreateTx(db, record.PlateNumber, "pof-test", record.ID)
  62. require.NoError(t, err)
  63. router := gin.New()
  64. SetupPublicRouter(router.Group(""))
  65. server := httptest.NewServer(router)
  66. t.Cleanup(server.Close)
  67. login := pofPost(t, server.URL, "/api/login", map[string]string{"UserName": "admin", "Password": "admin"})
  68. token, ok := login["Token"].(string)
  69. require.True(t, ok)
  70. require.NotEmpty(t, token)
  71. var loginLog dao.CentralPaymentRequestLog
  72. require.NoError(t, db.Where("endpoint = ?", "/api/login").First(&loginLog).Error)
  73. require.NotEmpty(t, loginLog.SourceIP)
  74. require.Contains(t, loginLog.RequestBody, `"password_format":"plain-text_expected"`)
  75. require.NotContains(t, loginLog.RequestBody, `"password":"admin"`)
  76. price := pofPost(t, server.URL, "/api/queryprice", map[string]interface{}{
  77. "Token": token,
  78. "Data": map[string]string{"CarNo": record.PlateNumber, "InTime": record.EntryTime.Format("2006-01-02 15:04:05")},
  79. })
  80. priceData := price["Data"].(map[string]interface{})
  81. require.Equal(t, ticket.TicketNo, priceData["OrderId"])
  82. priceValue, ok := priceData["Price"].(string)
  83. require.True(t, ok)
  84. payment := map[string]interface{}{
  85. "Token": token,
  86. "Data": map[string]interface{}{
  87. "OrderId": ticket.TicketNo, "CarNo": record.PlateNumber, "Money": priceValue,
  88. "PayTime": time.Now().Format("2006-01-02 15:04:05"), "PayType": 0, "PayDev": 2,
  89. // DevId is an audit field; authentication relies on the Token.
  90. "DevId": "FC068C0D365F", "ReMark": "HTTP simulator",
  91. },
  92. }
  93. require.EqualValues(t, 1, pofPost(t, server.URL, "/api/paysuccess", payment)["Result"])
  94. status := pofPost(t, server.URL, "/api/paystatus", map[string]interface{}{
  95. "Token": token, "Data": map[string]string{"OrderId": ticket.TicketNo, "CarNo": record.PlateNumber, "DevId": "FC068C0D365F"},
  96. })
  97. statusData := status["Data"].(map[string]interface{})
  98. require.Equal(t, "paid", statusData["PayStatus"])
  99. require.Equal(t, ticketService.StatePaid, statusData["State"])
  100. // A common device retry must be accepted without generating another payment record.
  101. require.EqualValues(t, 1, pofPost(t, server.URL, "/api/paysuccess", payment)["Result"])
  102. require.EqualValues(t, 1, pofPost(t, server.URL, "/api/heartbeat", map[string]string{"Token": token})["Result"])
  103. var paymentCount int64
  104. require.NoError(t, db.Model(&dao.PaymentRecord{}).Where("record_id = ?", record.ID).Count(&paymentCount).Error)
  105. require.EqualValues(t, 1, paymentCount)
  106. var persistedTicket dao.DigitalTicket
  107. require.NoError(t, db.First(&persistedTicket, ticket.ID).Error)
  108. require.Equal(t, ticketService.StatePaid, persistedTicket.State)
  109. }
  110. func pofPost(t *testing.T, baseURL, path string, payload interface{}) map[string]interface{} {
  111. t.Helper()
  112. body, err := json.Marshal(payload)
  113. require.NoError(t, err)
  114. response, err := http.Post(baseURL+path, "application/json", bytes.NewReader(body))
  115. require.NoError(t, err)
  116. defer response.Body.Close()
  117. require.Equal(t, http.StatusOK, response.StatusCode)
  118. var envelope map[string]interface{}
  119. require.NoError(t, json.NewDecoder(response.Body).Decode(&envelope))
  120. require.EqualValues(t, 1, envelope["Result"], "POF failure: %v", envelope["ReMark"])
  121. return envelope
  122. }