params_test.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package webapi
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "strings"
  6. "testing"
  7. "github.com/stretchr/testify/assert"
  8. )
  9. // formPost builds a POST carrying body as its payload, with contentType set only
  10. // when non-empty.
  11. func formPost(body, contentType string) *http.Request {
  12. r := httptest.NewRequest(http.MethodPost, "/x", strings.NewReader(body))
  13. if contentType != "" {
  14. r.Header.Set("Content-Type", contentType)
  15. }
  16. return r
  17. }
  18. func TestParam(t *testing.T) {
  19. tests := []struct {
  20. name string
  21. req *http.Request
  22. key string
  23. want string
  24. }{
  25. {
  26. name: "from the query string",
  27. req: httptest.NewRequest(http.MethodGet, "/x?aimsid=sid", nil),
  28. key: "aimsid",
  29. want: "sid",
  30. },
  31. {
  32. name: "from a declared form body",
  33. req: formPost("s=mikekelly&pwd=hunter2", "application/x-www-form-urlencoded"),
  34. key: "s",
  35. want: "mikekelly",
  36. },
  37. {
  38. // A body may arrive unannounced, and ParseForm ignores one it cannot
  39. // type.
  40. name: "from an untyped form body",
  41. req: formPost("s=mikekelly&pwd=hunter2", ""),
  42. key: "pwd",
  43. want: "hunter2",
  44. },
  45. {
  46. name: "query wins over body",
  47. req: func() *http.Request {
  48. r := formPost("f=xml", "")
  49. r.URL.RawQuery = "f=json"
  50. return r
  51. }(),
  52. key: "f",
  53. want: "json",
  54. },
  55. {
  56. name: "absent from both",
  57. req: formPost("s=mikekelly", ""),
  58. key: "nope",
  59. want: "",
  60. },
  61. {
  62. name: "body is not read on a GET",
  63. req: httptest.NewRequest(http.MethodGet, "/x", strings.NewReader("s=mikekelly")),
  64. key: "s",
  65. want: "",
  66. },
  67. }
  68. for _, tt := range tests {
  69. t.Run(tt.name, func(t *testing.T) {
  70. assert.Equal(t, tt.want, param(tt.req, tt.key))
  71. })
  72. }
  73. }
  74. func TestParamLeavesBinaryBodyIntact(t *testing.T) {
  75. // expressions/upload POSTs a raw image with its parameters on the query string,
  76. // arriving untyped. A parameter lookup that misses must not hand that body to
  77. // ParseForm, which would read it to EOF.
  78. image := "\xff\xd8\xff\xe0 not form data"
  79. var body string
  80. var missing string
  81. handler := WithBinaryBody(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
  82. missing = param(r, "absent")
  83. read := make([]byte, len(image))
  84. n, _ := r.Body.Read(read)
  85. body = string(read[:n])
  86. }))
  87. req := formPost(image, "")
  88. req.URL.RawQuery = "type=buddyIcon"
  89. handler.ServeHTTP(httptest.NewRecorder(), req)
  90. assert.Empty(t, missing)
  91. assert.Equal(t, image, body, "the image body was consumed by form parsing")
  92. }
  93. func TestParamValues(t *testing.T) {
  94. tests := []struct {
  95. name string
  96. req *http.Request
  97. want []string
  98. }{
  99. {
  100. name: "repeated in the query string",
  101. req: httptest.NewRequest(http.MethodGet, "/x?t=alice&t=bob", nil),
  102. want: []string{"alice", "bob"},
  103. },
  104. {
  105. name: "repeated in an untyped body",
  106. req: formPost("t=alice&t=bob", ""),
  107. want: []string{"alice", "bob"},
  108. },
  109. {
  110. name: "absent",
  111. req: httptest.NewRequest(http.MethodGet, "/x", nil),
  112. want: nil,
  113. },
  114. }
  115. for _, tt := range tests {
  116. t.Run(tt.name, func(t *testing.T) {
  117. assert.Equal(t, tt.want, paramValues(tt.req, "t"))
  118. })
  119. }
  120. }
  121. func TestPresenceTargets(t *testing.T) {
  122. tests := []struct {
  123. name string
  124. url string
  125. want []string
  126. }{
  127. {
  128. // One repetition per value.
  129. name: "repeated parameter",
  130. url: "/presence/get?t=alice&t=bob&t=carol",
  131. want: []string{"alice", "bob", "carol"},
  132. },
  133. {
  134. // Comma-separated in a single parameter.
  135. name: "comma separated",
  136. url: "/presence/get?t=alice,bob,carol",
  137. want: []string{"alice", "bob", "carol"},
  138. },
  139. {
  140. name: "both, with padding and empties discarded",
  141. url: "/presence/get?t=alice,+bob&t=&t=carol",
  142. want: []string{"alice", "bob", "carol"},
  143. },
  144. {
  145. name: "no targets",
  146. url: "/presence/get?bl=1",
  147. want: nil,
  148. },
  149. }
  150. for _, tt := range tests {
  151. t.Run(tt.name, func(t *testing.T) {
  152. got := targetNames(httptest.NewRequest(http.MethodGet, tt.url, nil))
  153. assert.Equal(t, tt.want, got)
  154. })
  155. }
  156. }
  157. func TestRequestIDFromFormBody(t *testing.T) {
  158. // A client may POST its correlation id in the body and read response.requestId
  159. // strictly off the reply, so a query-only read would leave the field empty.
  160. req := formPost("t=bob&message=hi&r=cookie-42", "")
  161. assert.Equal(t, "cookie-42", requestIDFromRequest(req))
  162. rr := httptest.NewRecorder()
  163. SendOK(rr, req, &SendIMData{MsgID: "msg-1", State: "delivered"}, nil)
  164. assert.Contains(t, rr.Body.String(), `"requestId":"cookie-42"`)
  165. }