common_test.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. package handlers
  2. import (
  3. "log/slog"
  4. "net/http"
  5. "net/http/httptest"
  6. "strings"
  7. "testing"
  8. "github.com/stretchr/testify/assert"
  9. )
  10. func TestAttachRequestID(t *testing.T) {
  11. t.Run("sets requestId from r query param", func(t *testing.T) {
  12. req := httptest.NewRequest("GET", "/buddylist/addBuddy?r=abc123", nil)
  13. data := attachRequestID(req, BaseResponse{
  14. Response: ResponseBody{
  15. StatusCode: 200,
  16. StatusText: "OK",
  17. },
  18. })
  19. br, ok := data.(BaseResponse)
  20. assert.True(t, ok)
  21. assert.Equal(t, "abc123", br.Response.RequestID)
  22. })
  23. t.Run("preserves explicit requestId", func(t *testing.T) {
  24. req := httptest.NewRequest("GET", "/buddylist/addBuddy?r=abc123", nil)
  25. data := attachRequestID(req, BaseResponse{
  26. Response: ResponseBody{
  27. StatusCode: 200,
  28. StatusText: "OK",
  29. RequestID: "existing",
  30. },
  31. })
  32. br, ok := data.(BaseResponse)
  33. assert.True(t, ok)
  34. assert.Equal(t, "existing", br.Response.RequestID)
  35. })
  36. t.Run("no-op without r param", func(t *testing.T) {
  37. req := httptest.NewRequest("GET", "/buddylist/addBuddy", nil)
  38. data := attachRequestID(req, BaseResponse{
  39. Response: ResponseBody{
  40. StatusCode: 200,
  41. StatusText: "OK",
  42. },
  43. })
  44. br, ok := data.(BaseResponse)
  45. assert.True(t, ok)
  46. assert.Empty(t, br.Response.RequestID)
  47. })
  48. }
  49. func TestSendResponseIncludesRequestID(t *testing.T) {
  50. req := httptest.NewRequest("GET", "/buddylist/addBuddy?r=req-42&f=json", nil)
  51. rr := httptest.NewRecorder()
  52. resp := BaseResponse{
  53. Response: ResponseBody{
  54. StatusCode: 200,
  55. StatusText: "OK",
  56. Data: map[string]string{"resultCode": "success"},
  57. },
  58. }
  59. SendResponse(rr, req, resp, slog.Default())
  60. assert.Equal(t, http.StatusOK, rr.Code)
  61. body := strings.TrimSpace(rr.Body.String())
  62. assert.Equal(t, `{"response":{"statusCode":200,"statusText":"OK","requestId":"req-42","data":{"resultCode":"success"}}}`, body)
  63. }
  64. // A JSONP error must arrive as an executable callback, not as bare JSON in a
  65. // <script> tag, which the Web AIM client reports as "Failed to load script tag".
  66. func TestSendErrorJSONP(t *testing.T) {
  67. t.Run("wraps the envelope in the callback", func(t *testing.T) {
  68. req := httptest.NewRequest("GET", "/im/sendIM?c=_callbacks_._0mq8&r=42", nil)
  69. w := httptest.NewRecorder()
  70. SendError(w, req, http.StatusUnauthorized, "invalid or expired session")
  71. body := w.Body.String()
  72. assert.True(t, strings.HasPrefix(body, "_callbacks_._0mq8("), "body should open with the callback: %s", body)
  73. assert.True(t, strings.HasSuffix(body, ");"), "body should close the call: %s", body)
  74. assert.Contains(t, body, `"statusCode":401`)
  75. assert.Contains(t, body, `"statusText":"invalid or expired session"`)
  76. assert.Contains(t, w.Header().Get("Content-Type"), "javascript")
  77. })
  78. t.Run("uses HTTP 200 so the script executes", func(t *testing.T) {
  79. req := httptest.NewRequest("GET", "/im/sendIM?c=cb&r=42", nil)
  80. w := httptest.NewRecorder()
  81. SendError(w, req, http.StatusUnauthorized, "nope")
  82. assert.Equal(t, http.StatusOK, w.Code)
  83. })
  84. t.Run("echoes requestId so the client can match the reply", func(t *testing.T) {
  85. req := httptest.NewRequest("GET", "/im/sendIM?c=cb&r=42", nil)
  86. w := httptest.NewRecorder()
  87. SendError(w, req, http.StatusBadRequest, "bad")
  88. assert.Contains(t, w.Body.String(), `"requestId":"42"`)
  89. })
  90. t.Run("accepts the callback alias", func(t *testing.T) {
  91. req := httptest.NewRequest("GET", "/im/sendIM?callback=cb", nil)
  92. w := httptest.NewRecorder()
  93. SendError(w, req, http.StatusBadRequest, "bad")
  94. assert.True(t, strings.HasPrefix(w.Body.String(), "cb("))
  95. })
  96. t.Run("rejects an unsafe callback name rather than reflecting it", func(t *testing.T) {
  97. req := httptest.NewRequest("GET", "/im/sendIM?c=alert(1)//", nil)
  98. w := httptest.NewRecorder()
  99. SendError(w, req, http.StatusBadRequest, "bad")
  100. assert.NotContains(t, w.Body.String(), "alert(1)")
  101. assert.Contains(t, w.Header().Get("Content-Type"), "json")
  102. })
  103. }
  104. // Without a callback the response stays plain JSON carrying the HTTP status.
  105. func TestSendErrorJSONFallback(t *testing.T) {
  106. req := httptest.NewRequest("GET", "/im/sendIM?r=42", nil)
  107. w := httptest.NewRecorder()
  108. SendError(w, req, http.StatusNotFound, "not found")
  109. assert.Equal(t, http.StatusNotFound, w.Code)
  110. assert.Contains(t, w.Header().Get("Content-Type"), "json")
  111. assert.Contains(t, w.Body.String(), `"statusCode":404`)
  112. }