strict_keys_test.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. package webapi
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "testing"
  6. "github.com/stretchr/testify/assert"
  7. )
  8. // Some clients read these response keys strictly, throwing on an absent key rather
  9. // than treating it as empty, and nothing surfaces that failure: an event parse is
  10. // dropped and a queued request retried forever. Each key below must therefore be
  11. // present even when its value is empty, zero or false — presence is the contract, not
  12. // the value. These tests keep a future `omitempty` from reintroducing the failure.
  13. // renderJSON sends payload through the default JSON path and returns the body.
  14. func renderJSON(t *testing.T, data any) string {
  15. t.Helper()
  16. rr := httptest.NewRecorder()
  17. SendOK(rr, httptest.NewRequest(http.MethodGet, "/x?f=json", nil), data, nil)
  18. return rr.Body.String()
  19. }
  20. func TestStrictKeys_IMEvents(t *testing.T) {
  21. t.Run("im carries imf and autoresponse when false", func(t *testing.T) {
  22. body := renderJSON(t, IMEvent{
  23. Source: UserInfo{AimID: "chattingchuck"},
  24. Message: "hi",
  25. Imf: imfPlainText,
  26. })
  27. assert.Contains(t, body, `"imf":"plain"`)
  28. assert.Contains(t, body, `"autoresponse":false`)
  29. })
  30. t.Run("offlineIM carries imf and autoresponse when false", func(t *testing.T) {
  31. body := renderJSON(t, OfflineIMEvent{
  32. AimID: "chattingchuck",
  33. Message: "hi",
  34. Imf: imfPlainText,
  35. })
  36. assert.Contains(t, body, `"imf":"plain"`)
  37. assert.Contains(t, body, `"autoresponse":false`)
  38. })
  39. }
  40. func TestStrictKeys_BuddyGroupID(t *testing.T) {
  41. // The zero id is the interesting case: omitempty here would drop the key for
  42. // the group the client is most likely to have, and cost it the whole roster.
  43. body := renderJSON(t, BuddyListData{Groups: []BuddyGroup{{
  44. Name: "Buddies", ID: 0, Buddies: []BuddyInfo{},
  45. }}})
  46. assert.Contains(t, body, `"id":0`)
  47. }
  48. func TestStrictKeys_BuddyUserType(t *testing.T) {
  49. // A buddy's userType is read strictly, so an absent key costs the whole roster
  50. // rather than that one entry.
  51. body := renderJSON(t, BuddyListData{Groups: []BuddyGroup{{
  52. Name: "Buddies", Buddies: []BuddyInfo{{AimID: "chattingchuck", DisplayID: "ChattingChuck", State: "offline", UserType: "aim"}},
  53. }}})
  54. assert.Contains(t, body, `"userType":"aim"`)
  55. }
  56. func TestStrictKeys_PresenceUsers(t *testing.T) {
  57. // Each query fills in one field, and a match of none must still render that
  58. // field as an empty array rather than drop it: a client reading data.users or
  59. // data.groups strictly cannot tell an absent key from a failed request.
  60. populatedGroups := []BuddyGroupInfo{{Name: "Buddies", Buddies: []BuddyPresenceInfo{}}}
  61. t.Run("empty user result still renders the array", func(t *testing.T) {
  62. body := renderJSON(t, PresenceData{Users: []BuddyPresenceInfo{}})
  63. assert.Contains(t, body, `"users":[]`)
  64. })
  65. t.Run("empty group result still renders the array", func(t *testing.T) {
  66. body := renderJSON(t, PresenceData{Groups: []BuddyGroupInfo{}})
  67. assert.Contains(t, body, `"groups":[]`)
  68. })
  69. t.Run("buddy list query omits users", func(t *testing.T) {
  70. body := renderJSON(t, PresenceData{Groups: populatedGroups})
  71. assert.NotContains(t, body, `"users"`)
  72. })
  73. t.Run("presence query omits groups", func(t *testing.T) {
  74. body := renderJSON(t, PresenceData{Users: []BuddyPresenceInfo{}})
  75. assert.NotContains(t, body, `"groups"`)
  76. })
  77. }
  78. func TestStrictKeys_FetchEventsArray(t *testing.T) {
  79. // A nil slice renders as null, which throws exactly as an absent key does — and
  80. // a poll that throws is retried every 5s forever.
  81. body := renderJSON(t, &FetchEventsData{Events: []Event{}})
  82. assert.Contains(t, body, `"events":[]`)
  83. assert.NotContains(t, body, `"events":null`)
  84. }
  85. func TestStrictKeys_GetInfoUserData(t *testing.T) {
  86. // The web client reads loginId and displayName off userData unconditionally, so
  87. // both keys must survive a blank name.
  88. body := renderJSON(t, GetInfoData{})
  89. assert.Contains(t, body, `"loginId":""`)
  90. assert.Contains(t, body, `"displayName":""`)
  91. }