preference_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. "github.com/stretchr/testify/mock"
  10. "github.com/mk6i/open-oscar-server/state"
  11. "github.com/mk6i/open-oscar-server/wire"
  12. )
  13. // buddyPrefsFeedbag builds a feedbag reply carrying a single buddy-prefs item
  14. // with the given prefs pre-set.
  15. func buddyPrefsFeedbag(prefs map[uint16]bool) wire.SNACMessage {
  16. var list wire.TLVList
  17. for num, val := range prefs {
  18. list = wire.SetBuddyPref(list, num, val)
  19. }
  20. return wire.SNACMessage{
  21. Body: wire.SNAC_0x13_0x06_FeedbagReply{
  22. Items: []wire.FeedbagItem{
  23. {ClassID: wire.FeedbagClassIdBuddyPrefs, ItemID: 1, TLVLBlock: wire.TLVLBlock{TLVList: list}},
  24. },
  25. },
  26. }
  27. }
  28. func TestPreferenceHandler_SetPreferences(t *testing.T) {
  29. fs := &MockFeedbagService{}
  30. oscarInstance := state.NewSession().AddInstance()
  31. sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
  32. // Existing feedbag already has displayLogin (0x00) enabled; it must survive.
  33. fs.On("Query", mock.Anything, oscarInstance, mock.Anything).
  34. Return(buddyPrefsFeedbag(map[uint16]bool{0x00: true}), nil)
  35. var upserted []wire.FeedbagItem
  36. fs.On("UpsertItem", mock.Anything, oscarInstance, mock.Anything, mock.Anything).
  37. Run(func(args mock.Arguments) { upserted = args.Get(3).([]wire.FeedbagItem) }).
  38. Return(nil, nil)
  39. handler := &PreferenceHandler{
  40. SessionManager: sessionMgr,
  41. FeedbagService: fs,
  42. Logger: slog.Default(),
  43. }
  44. req, _ := http.NewRequest("GET", "/preference/set?aimsid="+aimsid+"&playIMSound=0&discloseTyping=1", nil)
  45. rr := httptest.NewRecorder()
  46. handler.SetPreferences(rr, req)
  47. assert.Equal(t, http.StatusOK, rr.Code)
  48. if assert.Len(t, upserted, 1) {
  49. item := upserted[0]
  50. assert.Equal(t, wire.FeedbagClassIdBuddyPrefs, item.ClassID)
  51. assertPref := func(num uint16, wantValid, wantValue bool) {
  52. valid, value := wire.BuddyPref(item.TLVList, num)
  53. assert.Equalf(t, wantValid, valid, "pref 0x%02x valid", num)
  54. assert.Equalf(t, wantValue, value, "pref 0x%02x value", num)
  55. }
  56. assertPref(0x00, true, true) // preserved
  57. assertPref(0x15, true, false) // playIMSound off
  58. assertPref(0x16, true, true) // discloseTyping on
  59. }
  60. fs.AssertExpectations(t)
  61. }
  62. func TestPreferenceHandler_GetPreferences_Selected(t *testing.T) {
  63. fs := &MockFeedbagService{}
  64. oscarInstance := state.NewSession().AddInstance()
  65. sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
  66. // playIMSound (0x15) explicitly disabled in the feedbag.
  67. fs.On("Query", mock.Anything, oscarInstance, mock.Anything).
  68. Return(buddyPrefsFeedbag(map[uint16]bool{0x15: false}), nil)
  69. handler := &PreferenceHandler{
  70. SessionManager: sessionMgr,
  71. FeedbagService: fs,
  72. Logger: slog.Default(),
  73. }
  74. // Request two prefs: playIMSound (stored=false) and acceptIcons (unset -> default true).
  75. req, _ := http.NewRequest("GET", "/preference/get?aimsid="+aimsid+"&playIMSound&acceptIcons", nil)
  76. rr := httptest.NewRecorder()
  77. handler.GetPreferences(rr, req)
  78. assert.Equal(t, http.StatusOK, rr.Code)
  79. body := rr.Body.String()
  80. assert.Contains(t, body, `"playIMSound":0`)
  81. assert.Contains(t, body, `"acceptIcons":1`)
  82. // Only the two requested prefs should be present.
  83. assert.NotContains(t, body, `"displayLogin"`)
  84. }
  85. func TestPreferenceHandler_GetPreferences_All(t *testing.T) {
  86. fs := &MockFeedbagService{}
  87. oscarInstance := state.NewSession().AddInstance()
  88. sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
  89. // Empty feedbag -> every pref resolves to its spec default.
  90. fs.On("Query", mock.Anything, oscarInstance, mock.Anything).
  91. Return(wire.SNACMessage{Body: wire.SNAC_0x13_0x06_FeedbagReply{}}, nil)
  92. handler := &PreferenceHandler{
  93. SessionManager: sessionMgr,
  94. FeedbagService: fs,
  95. Logger: slog.Default(),
  96. }
  97. req, _ := http.NewRequest("GET", "/preference/get?aimsid="+aimsid, nil)
  98. rr := httptest.NewRecorder()
  99. handler.GetPreferences(rr, req)
  100. assert.Equal(t, http.StatusOK, rr.Code)
  101. body := rr.Body.String()
  102. assert.Contains(t, body, `"displayLogin":1`) // default true
  103. assert.Contains(t, body, `"acceptCustomBart":0`) // default false
  104. }
  105. func TestPreferenceHandler_GetPreferences_AMF(t *testing.T) {
  106. fs := &MockFeedbagService{}
  107. oscarInstance := state.NewSession().AddInstance()
  108. sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
  109. fs.On("Query", mock.Anything, oscarInstance, mock.Anything).
  110. Return(buddyPrefsFeedbag(map[uint16]bool{0x15: false}), nil)
  111. handler := &PreferenceHandler{
  112. SessionManager: sessionMgr,
  113. FeedbagService: fs,
  114. Logger: slog.Default(),
  115. }
  116. // Single-pref AMF request returns a numeric value (not wrapped in jsonData).
  117. req, _ := http.NewRequest("GET", "/preference/get?aimsid="+aimsid+"&f=amf&playIMSound", nil)
  118. rr := httptest.NewRecorder()
  119. handler.GetPreferences(rr, req)
  120. assert.Equal(t, http.StatusOK, rr.Code)
  121. body := rr.Body.String()
  122. // AMF-encoded response: the value is a numeric 0 (AMF integer marker 0x04
  123. // followed by 0x00), not the string "0".
  124. assert.Contains(t, body, "playIMSound\x04\x00")
  125. assert.NotContains(t, body, `playIMSound":"0"`)
  126. }
  127. func TestValidBuddyPrefs_OnlyValidBits(t *testing.T) {
  128. // Only playIMSound (set false) and viewIMsInBubbles (set true) are valid;
  129. // everything else must be omitted rather than defaulted.
  130. var list wire.TLVList
  131. list = wire.SetBuddyPref(list, wire.FeedbagBuddyPrefsPlayIMSound, false)
  132. list = wire.SetBuddyPref(list, wire.FeedbagBuddyPrefsViewIMsInBubbles, true)
  133. got := validBuddyPrefs(list)
  134. assert.Equal(t, map[string]interface{}{
  135. "playIMSound": 0,
  136. "viewIMsInBubbles": 1,
  137. }, got)
  138. }
  139. func TestValidBuddyPrefs_EmptyWhenNothingSet(t *testing.T) {
  140. assert.Empty(t, validBuddyPrefs(wire.TLVList{}))
  141. }
  142. func TestPreferenceHandler_SetPreferences_NoOSCARSession(t *testing.T) {
  143. fs := &MockFeedbagService{}
  144. sessionMgr, aimsid := createTestSessionManager("webonly") // nil OSCARSession
  145. handler := &PreferenceHandler{
  146. SessionManager: sessionMgr,
  147. FeedbagService: fs,
  148. Logger: slog.Default(),
  149. }
  150. req, _ := http.NewRequest("GET", "/preference/set?aimsid="+aimsid+"&playIMSound=1", nil)
  151. rr := httptest.NewRecorder()
  152. handler.SetPreferences(rr, req)
  153. assert.Equal(t, http.StatusBadRequest, rr.Code)
  154. fs.AssertNotCalled(t, "Query", mock.Anything, mock.Anything, mock.Anything)
  155. _ = strings.TrimSpace(rr.Body.String())
  156. }