notify_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. // Copyright 2021 Tencent Inc. All rights reserved.
  2. package notify
  3. import (
  4. "bytes"
  5. "context"
  6. "crypto/cipher"
  7. "crypto/x509"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "net/http"
  12. "net/http/httptest"
  13. "testing"
  14. "time"
  15. "git.nanodreamtech.com/sg/wechatpay-go/core/auth/validators"
  16. "git.nanodreamtech.com/sg/wechatpay-go/core"
  17. "git.nanodreamtech.com/sg/wechatpay-go/core/auth/verifiers"
  18. "git.nanodreamtech.com/sg/wechatpay-go/utils"
  19. "github.com/stretchr/testify/assert"
  20. "github.com/stretchr/testify/require"
  21. "github.com/agiledragon/gomonkey"
  22. )
  23. func Test_getRequestBody(t *testing.T) {
  24. body := "fake req body"
  25. bodyBuf := &bytes.Buffer{}
  26. bodyBuf.WriteString(body)
  27. req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1", bodyBuf)
  28. bodyBytes, err := getRequestBody(req)
  29. require.NoError(t, err)
  30. assert.Equal(t, body, string(bodyBytes))
  31. // Read Two times
  32. bodyBytes, err = getRequestBody(req)
  33. require.NoError(t, err)
  34. assert.Equal(t, body, string(bodyBytes))
  35. // Read Three times
  36. bodyBytes, err = getRequestBody(req)
  37. require.NoError(t, err)
  38. assert.Equal(t, body, string(bodyBytes))
  39. }
  40. func Test_getRequestBodyReadAllError(t *testing.T) {
  41. patch := gomonkey.ApplyFunc(
  42. ioutil.ReadAll, func(r io.Reader) ([]byte, error) {
  43. return nil, fmt.Errorf("read buf error")
  44. },
  45. )
  46. defer patch.Reset()
  47. body := "fake req body"
  48. bodyBuf := &bytes.Buffer{}
  49. bodyBuf.WriteString(body)
  50. req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1", bodyBuf)
  51. _, err := getRequestBody(req)
  52. require.Error(t, err)
  53. }
  54. type contentType struct {
  55. Mchid *string `json:"mchid"`
  56. Appid *string `json:"appid"`
  57. CreateTime *time.Time `json:"create_time"`
  58. OutContractCode *string `json:"out_contract_code"`
  59. }
  60. func (o contentType) String() string {
  61. ret := ""
  62. if o.Mchid == nil {
  63. ret += "Mchid:<nil>,"
  64. } else {
  65. ret += fmt.Sprintf("Mchid:%v,", *o.Mchid)
  66. }
  67. if o.Appid == nil {
  68. ret += "Appid:<nil>,"
  69. } else {
  70. ret += fmt.Sprintf("Appid:%v,", *o.Appid)
  71. }
  72. if o.CreateTime == nil {
  73. ret += "CreateTime:<nil>,"
  74. } else {
  75. ret += fmt.Sprintf("CreateTime:%v,", *o.CreateTime)
  76. }
  77. if o.OutContractCode == nil {
  78. ret += "OutContractCode:<nil>,"
  79. } else {
  80. ret += fmt.Sprintf("OutContractCode:%v,", *o.OutContractCode)
  81. }
  82. return fmt.Sprintf("contentType{%s}", ret)
  83. }
  84. func TestHandler_ParseNotifyRequest(t *testing.T) {
  85. patch := gomonkey.ApplyFunc(
  86. time.Now, func() time.Time {
  87. return time.Unix(1624523846, 0)
  88. },
  89. )
  90. defer patch.Reset()
  91. const (
  92. mchAPIv3Key = "testMchAPIv3Key0"
  93. wechatPayCertificate = `-----BEGIN CERTIFICATE-----
  94. MIIDVzCCAj+gAwIBAgIJANfOWdH1ItcBMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV
  95. BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
  96. Q29tcGFueSBMdGQwHhcNMjEwNDI3MDg1NTIzWhcNMzEwNDI1MDg1NTIzWjBCMQsw
  97. CQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZh
  98. dWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
  99. 2VCTd91fnUn73Xy9DLvt/V62TVxRTEEstVdeRaZ3B3leO0pldE806mXO4RwdHXag
  100. HQ4vGeZN0yqm++rDsGK+U3AH7kejyD2pXshNP9Cq5YwbptiLGtjcquw4HNxJQUOm
  101. DeJf2vg6byms9RUipiq4SzbJKqJFlUpbuIPDpSpWz10PYmyCNeDGUUK65E5h2B83
  102. 4uxl1zNLYQCrkdBzb8oUxwYeP5a2DNxmjL5lsJML7DGr5znsevnoqGRwTm9fxCGf
  103. y8wus7hwKz6clt3Whmmda7UAdb1c08hEQFVRbF14AR73xbnd8N0obCWJPCbzMCtk
  104. aSef4FdEEgEXJiw0VAJT8wIDAQABo1AwTjAdBgNVHQ4EFgQUT1c7nd/SUO76HSoZ
  105. umNUJv1R5PwwHwYDVR0jBBgwFoAUT1c7nd/SUO76HSoZumNUJv1R5PwwDAYDVR0T
  106. BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAfTjxKRQMzNB/U6ZoCUS+BSNfa2Oh
  107. 0plMN6ZuzwiVVZwg1jywvv5yv04koS7Pd4i9E4gt9ZBUQXlpq+A3oOCEEHNRR6b2
  108. kyazGRM7s0OP5X21WrbpSmKmU6K7hkfx30yYs08LVs/Q8DIhvaj1FCFeJzUCzYn/
  109. fHMq4tsbKO0dKAeydPM/nrUZBmaYQVKMVOORGLFjFKVO7JV6Kq/R86ouhjEPgJOe
  110. 2xulNBUcjicqtZlBdEh/PWCYP2SpGVDclKm8jeo175T3EVAkdKzzmfpxtMmnMlmq
  111. cTJOU9TxuGvNASMtjj7pYIerTx+xgZDXEVBWFW9PjJ0TV06tCRsgSHItgg==
  112. -----END CERTIFICATE-----`
  113. data = "{" +
  114. "\"mchid\":\"1234567890\"," +
  115. "\"appid\":\"054aa7d7a2a54ab5898df65bd96f001c\"," +
  116. "\"create_time\":\"2020-06-30T12:12:00+08:00\"," +
  117. "\"out_contract_code\":\"21640bdbd08e473e828f3206a2741c6e\"" +
  118. "}"
  119. )
  120. headers := map[string]string{
  121. "Content-Type": "application/json",
  122. "Wechatpay-Nonce": "EcZ9Cmy4Xyx1i6RlJQzLcCyEqDa26NBz",
  123. "Wechatpay-Timestamp": "1624523846",
  124. "Wechatpay-Serial": "D7CE59D1F522D701",
  125. "Wechatpay-Signature": "tJHIiIS9eB2hAYstmAmbbD3ZE5LiIm/Ug5tuL4fC0YOFRWIHV39UFIZXC0e9Wl6lBu6sKvkqDkzpqzBsVHyXF" +
  126. "lbYZTOQrVdG4b6LfTnK4mikv9++ixJMd3vTf2yCqvBkh98zs3Ds5zsYQakzbcwhmw4fMJs4nPLws28H0UW9FjDR//rxELLwXvV1VEA1I" +
  127. "BLX70xptjL8hrfUjEE8kkry6yNJTHZRU8CAc7qHli2Ng1V1qb9ARbK8A3ThmFmPQvRGrapI/jS2laKKgYUmfdEdkNO6B2Cke5e8VTxY4" +
  128. "06ArAmQ90GAihDwIcb16TQMnzCMBoutnwZKNiKRACrFmtxw2Q==",
  129. }
  130. body := "{" +
  131. "\"id\":\"3119dfba-e649-5eec-ab1e-3412bc4d2e17\"," +
  132. "\"create_time\":\"2021-06-24T16:37:26+08:00\"," +
  133. "\"resource_type\":\"encrypt-resource\"," +
  134. "\"event_type\":\"PAYSCORE.USER_OPEN_SERVICE\"," +
  135. "\"summary\":\"签约成功\"," +
  136. "\"resource\":{" +
  137. "\"original_type\":\"payscore\"," +
  138. "\"algorithm\":\"AEAD_AES_256_GCM\"," +
  139. "\"ciphertext\":\"YDS3lKPaC4Y52Gf3uhft5qUBlIa8b428AWTtTauHQfQrRw+X1WpiuHIDy0vo1Vd6VEq67aVyqPdDYMkRVSDaZL3iZt" +
  140. "tevRMOoPKMifozg6XPWjIZumks/GpT48lI4NizyeaqLBokNebthah3o1H76qSlO9NkDjp9bzmKLEYYH9TEklFUpsvPqOTOcgSLgh21YJXYR" +
  141. "7dEBXFgRLiNIKRgO5JdXh1hccRUAlyVWxE54PXpnQ==\"," +
  142. "\"associated_data\":\"payscore\"," +
  143. "\"nonce\":\"Kj7QIyUiYx1q\"}" +
  144. "}"
  145. bodyBuf := &bytes.Buffer{}
  146. bodyBuf.WriteString(body)
  147. req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1", bodyBuf)
  148. for key, value := range headers {
  149. req.Header.Set(key, value)
  150. }
  151. cert, err := utils.LoadCertificate(wechatPayCertificate)
  152. require.NoError(t, err)
  153. handler, err := NewRSANotifyHandler(
  154. mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(core.NewCertificateMapWithList([]*x509.Certificate{cert})),
  155. )
  156. assert.Nil(t, err)
  157. content := new(contentType)
  158. notifyReq, err := handler.ParseNotifyRequest(context.Background(), req, content)
  159. require.NoError(t, err)
  160. assert.Equal(t, "3119dfba-e649-5eec-ab1e-3412bc4d2e17", notifyReq.ID)
  161. assert.Equal(t, "2021-06-24T16:37:26+08:00", notifyReq.CreateTime.Format(time.RFC3339))
  162. assert.Equal(t, "encrypt-resource", notifyReq.ResourceType)
  163. assert.Equal(t, "PAYSCORE.USER_OPEN_SERVICE", notifyReq.EventType)
  164. assert.Equal(t, "签约成功", notifyReq.Summary)
  165. assert.Equal(t, "payscore", notifyReq.Resource.AssociatedData)
  166. assert.Equal(t, "AEAD_AES_256_GCM", notifyReq.Resource.Algorithm)
  167. assert.Equal(t, "payscore", notifyReq.Resource.OriginalType)
  168. assert.Equal(t, data, notifyReq.Resource.Plaintext)
  169. assert.Equal(t, "1234567890", *content.Mchid)
  170. assert.Equal(t, "054aa7d7a2a54ab5898df65bd96f001c", *content.Appid)
  171. assert.Equal(t, "21640bdbd08e473e828f3206a2741c6e", *content.OutContractCode)
  172. createTime, _ := time.Parse(time.RFC3339, "2020-06-30T12:12:00+08:00")
  173. assert.Zero(t, content.CreateTime.Sub(createTime))
  174. }
  175. func TestHandler_ParseNotifyRequestValidateError(t *testing.T) {
  176. patch := gomonkey.ApplyFunc(
  177. (*validators.WechatPayNotifyValidator).Validate,
  178. func(v *validators.WechatPayNotifyValidator, ctx context.Context, request *http.Request) error {
  179. return fmt.Errorf("validate error")
  180. },
  181. )
  182. defer patch.Reset()
  183. handler, _ := NewRSANotifyHandler(
  184. "testMchAPIv3Key0", verifiers.NewSHA256WithRSAVerifier(core.NewCertificateMapWithList(nil)),
  185. )
  186. req := httptest.NewRequest(
  187. http.MethodGet, "http://127.0.0.1", ioutil.NopCloser(bytes.NewBuffer([]byte("fake req body"))),
  188. )
  189. content := make(map[string]interface{})
  190. _, err := handler.ParseNotifyRequest(context.Background(), req, content)
  191. assert.Error(t, err)
  192. assert.Contains(t, err.Error(), "validate error")
  193. }
  194. func TestHandler_ParseNotifyRequest_getRequestBodyError(t *testing.T) {
  195. patches := gomonkey.NewPatches()
  196. defer patches.Reset()
  197. patches.ApplyFunc(
  198. (*validators.WechatPayNotifyValidator).Validate,
  199. func(v *validators.WechatPayNotifyValidator, ctx context.Context, request *http.Request) error {
  200. return nil
  201. },
  202. )
  203. patches.ApplyFunc(
  204. getRequestBody, func(request *http.Request) ([]byte, error) {
  205. return nil, fmt.Errorf("getRequestBody error")
  206. },
  207. )
  208. handler, _ := NewRSANotifyHandler(
  209. "testMchAPIv3Key0", verifiers.NewSHA256WithRSAVerifier(core.NewCertificateMapWithList(nil)),
  210. )
  211. req := httptest.NewRequest(
  212. http.MethodGet, "http://127.0.0.1", ioutil.NopCloser(bytes.NewBuffer([]byte("fake req body"))),
  213. )
  214. content := make(map[string]interface{})
  215. _, err := handler.ParseNotifyRequest(context.Background(), req, content)
  216. assert.Error(t, err)
  217. assert.Contains(t, err.Error(), "getRequestBody error")
  218. }
  219. func TestHandler_ParseNotifyRequest_UnmarshalRequestError(t *testing.T) {
  220. patches := gomonkey.NewPatches()
  221. defer patches.Reset()
  222. patches.ApplyFunc(
  223. (*validators.WechatPayNotifyValidator).Validate,
  224. func(v *validators.WechatPayNotifyValidator, ctx context.Context, request *http.Request) error {
  225. return nil
  226. },
  227. )
  228. patches.ApplyFunc(
  229. getRequestBody, func(request *http.Request) ([]byte, error) {
  230. return []byte("invalid json"), nil
  231. },
  232. )
  233. // gonna cause unmarshal error
  234. handler, _ := NewRSANotifyHandler(
  235. "testMchAPIv3Key0", verifiers.NewSHA256WithRSAVerifier(core.NewCertificateMapWithList(nil)),
  236. )
  237. req := httptest.NewRequest(
  238. http.MethodGet, "http://127.0.0.1", ioutil.NopCloser(bytes.NewBuffer([]byte("fake req body"))),
  239. )
  240. content := make(map[string]interface{})
  241. _, err := handler.ParseNotifyRequest(context.Background(), req, content)
  242. assert.Error(t, err)
  243. assert.Contains(t, err.Error(), "parse request body error")
  244. }
  245. func TestHandler_ParseNotifyRequest_DecryptError(t *testing.T) {
  246. patches := gomonkey.NewPatches()
  247. defer patches.Reset()
  248. patches.ApplyFunc(
  249. (*validators.WechatPayNotifyValidator).Validate,
  250. func(v *validators.WechatPayNotifyValidator, ctx context.Context, request *http.Request) error {
  251. return nil
  252. },
  253. )
  254. patches.ApplyFunc(
  255. getRequestBody, func(request *http.Request) ([]byte, error) {
  256. return []byte(`{"resource":{"algorithm":"AEAD_AES_256_GCM"}}`), nil
  257. },
  258. )
  259. patches.ApplyFunc(
  260. doAEADOpen, func(c cipher.AEAD, nonce, ciphertext, additionalData string) (plaintext string, err error) {
  261. return "", fmt.Errorf("decrypt error")
  262. },
  263. )
  264. handler, _ := NewRSANotifyHandler(
  265. "testMchAPIv3Key0", verifiers.NewSHA256WithRSAVerifier(core.NewCertificateMapWithList(nil)),
  266. )
  267. req := httptest.NewRequest(
  268. http.MethodGet, "http://127.0.0.1", ioutil.NopCloser(bytes.NewBuffer([]byte("fake req body"))),
  269. )
  270. content := make(map[string]interface{})
  271. _, err := handler.ParseNotifyRequest(context.Background(), req, content)
  272. assert.Error(t, err)
  273. assert.Contains(t, err.Error(), "decrypt error")
  274. }
  275. func TestHandler_ParseNotifyRequest_UnmarshalContentError(t *testing.T) {
  276. patches := gomonkey.NewPatches()
  277. defer patches.Reset()
  278. patches.ApplyFunc(
  279. (*validators.WechatPayNotifyValidator).Validate,
  280. func(v *validators.WechatPayNotifyValidator, ctx context.Context, request *http.Request) error {
  281. return nil
  282. },
  283. )
  284. patches.ApplyFunc(
  285. getRequestBody, func(request *http.Request) ([]byte, error) {
  286. return []byte(`{"resource":{"algorithm":"AEAD_AES_256_GCM"}}`), nil
  287. },
  288. )
  289. patches.ApplyFunc(
  290. doAEADOpen, func(c cipher.AEAD, nonce, ciphertext, additionalData string) (plaintext string, err error) {
  291. return "invalid content", nil
  292. },
  293. )
  294. // gonna cause unmarshal error
  295. handler, _ := NewRSANotifyHandler(
  296. "testMchAPIv3Key0", verifiers.NewSHA256WithRSAVerifier(core.NewCertificateMapWithList(nil)),
  297. )
  298. req := httptest.NewRequest(
  299. http.MethodGet, "http://127.0.0.1", ioutil.NopCloser(bytes.NewBuffer([]byte("fake req body"))),
  300. )
  301. content := make(map[string]interface{})
  302. _, err := handler.ParseNotifyRequest(context.Background(), req, content)
  303. assert.Error(t, err)
  304. assert.Contains(t, err.Error(), "unmarshal plaintext to content failed")
  305. }
  306. func TestHandler_processBody_InvalidAlgorithm(t *testing.T) {
  307. v := CipherSuite{
  308. signatureType: rsaSignatureType,
  309. validator: validators.WechatPayNotifyValidator{},
  310. aeadAlgorithm: aeadAesGcmAlgorithm,
  311. aead: nil,
  312. }
  313. c := make(map[string]interface{})
  314. _, err := processBody(v, []byte(`{"resource":{"algorithm":"AEAD_SM4_GCM"}}`), c)
  315. assert.Error(t, err)
  316. assert.Contains(t, err.Error(), "is not the configured algorithm")
  317. }